Go语言操作 SQLite

  • A+
所属分类:编程茶楼

最近把博客的数据库从 MySQL 迁移到了 SQLite,主要迁移原因有:

  • 使博客系统更加轻量,一键部署
  • 方便备份,一个普通文件(不能直接拷贝文件)
  • 方便部署,MySQL有点重,我不需要那么复杂
  • SQLite 官方说其能支撑中小型网站数据库,我信了

代码改动其实很少,但是迁移过程遇到不少的坑,这篇文章简单记一下一些注意事项。

注:公司最近一位同事也恰好也用到了这个 SQLite 数据库,我发现他也有隐藏 BUG,只是因为没有压测,没有发现。

本文环境:

共享缓存模式

设置 cacheshared 模式:官方文档

据官方文档说这样会大大减少并发连接时内存的占用。

v := url.Values{}
v.Set(`cache`, `shared`)
v.Set(`mode`, `rwc`)
u := url.URL{
	Scheme:   `file`,
	Opaque:   url.PathEscape(cfg.Database.SQLite.Path),
	RawQuery: v.Encode(),
}
db, err = sql.Open(`sqlite3`, u.String())

 

 

最大连接数

db.SetMaxOpenConns(1)

 

 

SQLite3 支持多线程模式,但似乎那个模式只是为了在多个线程之间共享同一个连接,并不是为了支持并发。

如果不开启这个选项,可能会报错:database table is locked.

最大一个连接?会不会影响性能?会。但是为了数据安全不得已这样做。

实测我博客的 QPS 能达到几千,所以还是决定用它替换了 MySQL。

不区分大小写

SQLite3 的 text 类型默认 COLLATE 是 BINARY,即区分大小写。

如果要不区分大小写,应该在字段后面加上 COLLATE NOCASE

CREATE TABLE IF NOT EXISTS tags (
    `id` INTEGER PRIMARY KEY AUTOINCREMENT,
    `name` TEXT NOT NULL UNIQUE COLLATE NOCASE,
    `alias` INTEGER NOT NULL
);

 

 

记得关闭查询结果

rows, err := db.Query(query, ...)

defer rows.Close()

 

 

否则可能导致死锁。

获得原始 sqlite3.SQLiteConn

我要获取原始 sqlite3.SQLiteConn 的原因是为了备份。

官方的例子说不能直接拿到原始连接(忘记在哪里说的了)。

我是用以下方式来获取原始连接的,还算比较好使(官方的不太优雅):

db, err := sql.Open(`sqlite3`, `a.db`)
if err != nil {
	return err
}
defer db.Close()

conn, err := db.Conn(ctx)
if err != nil {
	return err
}
defer conn.Close()

err = conn.Raw(func(dc interface{}) error {
	rawConn := dc.(*sqlite3.SQLiteConn)
	
})

 

 

值得注意的是:Raw() 回调中的 dc 只能在回调中被使用,不能在回调函数退出后继续使用。

数据库备份

虽然 SQLite3 就一个数据文件,但是不能直接复制文件的方式来达到备份的目的。因为有其它连接可能正在更新文件。

以下代码片段来自我博客实现,可以参考一下。

func (s *Service) backupSQLite3(ctx context.Context) (string, error) { tmpFile, err := ioutil.TempFile(``, `taoblog-*`) if err != nil { return ``, err } tmpFile.Close() dstDB, err := sql.Open(`sqlite3`, tmpFile.Name()) if err != nil { return ``, err } defer dstDB.Close() dstConn, err := dstDB.Conn(ctx) if err != nil { return ``, err } defer dstConn.Close() if err := dstConn.Raw(func(dstDC interface{}) error { rawDstConn := dstDC.(*sqlite3.SQLiteConn) srcConn, err := s.db.Conn(ctx) if err != nil { return err } defer srcConn.Close() if err := srcConn.Raw(func(srcDC interface{}) error { rawSrcConn := srcDC.(*sqlite3.SQLiteConn) backup, err := rawDstConn.Backup(`main`, rawSrcConn, `main`) if err != nil { return err } _, _ = backup.Step(-1) if err := backup.Close(); err != nil { return err } return nil }); err != nil { return err } return nil }); err != nil { return ``, err } zap.L().Info(`backuped to file`, zap.String(`path`, tmpFile.Name())) return tmpFile.Name(), nil }

 

 

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin