SQLite 连接

SQLite 是嵌入式数据库,不需要独立的服务进程。一个文件就是一个数据库,部署简单到极致。虽然不适合高并发场景,但在特定场景下是绝佳选择。

驱动安装

go get -u gorm.io/driver/sqlite

底层使用 github.com/mattn/go-sqlite3,这是一个 CGO 库,编译时需要 GCC 环境。

基本连接

package main

import (
    "fmt"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func main() {
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("连接失败")
    }
    fmt.Println("连接成功")
}

test.db 是数据库文件路径。文件不存在会自动创建。

内存模式

SQLite 支持纯内存数据库,适合测试和临时缓存:

db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})

内存数据库速度极快,但进程退出后数据丢失。单元测试中广泛使用。

共享缓存

多个连接共享同一个内存数据库:

db1, _ := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
db2, _ := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})

不加 cache=shared,每个连接是独立的内存数据库。

SQLite 特定配置

import "gorm.io/driver/sqlite"

sqliteConfig := sqlite.Config{
    DriverName: "sqlite3",
    DSN:        "test.db",
}

db, err := gorm.Open(&sqliteConfig, &gorm.Config{})

DriverName

默认是 sqlite3,除非注册了自定义驱动,一般不需要改。

DSN

数据源名称,可以是文件路径或内存标识。

适用场景

SQLite 不是万能的,但在这些场景下表现出色:

桌面应用

Electron、Wails、Fyne 等桌面框架开发的本地应用,SQLite 是首选数据存储方案。用户数据存在本地,无需网络。

移动应用

Android、iOS 都内置 SQLite 支持。Go 编译到移动端时,SQLite 是天然的本地数据库选择。

嵌入式设备

树莓派、路由器等资源受限设备,跑不动 MySQL,但 SQLite 完全没问题。

开发测试

本地开发时用 SQLite 替代 MySQL,省去安装和配置的麻烦。单元测试用内存数据库,速度快且隔离。

小型项目

访问量不大的内部工具、个人项目,SQLite 足够用。一个文件搞定一切,备份就是复制文件。

原型开发

快速验证想法时,先用 SQLite 跑起来,后期再迁移到正式数据库。

不适用场景

这些场景应该避免使用 SQLite:

高并发写入

SQLite 写操作会锁整个数据库,并发写入性能差。如果每秒写入超过几十次,考虑 PostgreSQL 或 MySQL。

网络访问

SQLite 是本地文件,不能通过网络访问。多服务器共享数据必须用独立数据库服务。

大数据量

单文件数据库,数据量大了备份和迁移都麻烦。建议单库不超过几百 GB。

复杂查询

SQLite 不支持存储过程、触发器等高级特性,复杂业务逻辑需要应用层实现。

并发控制

SQLite 的并发模型比较特殊:

  • 多个读操作可以并发
  • 写操作会阻塞所有其他操作
  • 写操作之间串行执行

GORM 默认配置下,SQLite 会启用 WAL 模式提升并发:

db.Exec("PRAGMA journal_mode=WAL")

WAL 模式下,读写可以并发,但写操作仍然串行。

连接池

SQLite 的连接池配置和 MySQL 不同:

sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(1)

对于文件数据库,MaxOpenConns 设为 1 可以避免 "database is locked" 错误。因为 SQLite 的锁是文件级别的,多个连接同时写入会冲突。

内存数据库可以开多个连接:

sqlDB.SetMaxOpenConns(10)

数据类型映射

SQLite 是动态类型系统,字段类型比较灵活:

Go 类型SQLite 类型
int, uintINTEGER
float64REAL
stringTEXT
boolINTEGER (0/1)
time.TimeTEXT (ISO8601)
[]byteBLOB

GORM 会自动处理类型转换。

外键约束

SQLite 默认不启用外键约束,需要手动开启:

db.Exec("PRAGMA foreign_keys=ON")

或者每次连接时执行:

db, _ := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
db.Exec("PRAGMA foreign_keys=ON")

常用 PRAGMA

PRAGMA 是 SQLite 的配置指令,几个常用的:

db.Exec("PRAGMA journal_mode=WAL")
db.Exec("PRAGMA synchronous=NORMAL")
db.Exec("PRAGMA cache_size=-64000")
db.Exec("PRAGMA busy_timeout=5000")

journal_mode

日志模式。WAL 性能最好,推荐生产使用。

synchronous

同步级别。NORMAL 在性能和安全间平衡,FULL 最安全但慢。

cache_size

缓存大小。负数表示 KB,-64000 是 64MB。

busy_timeout

锁等待超时毫秒数。避免 "database is locked" 错误。

迁移到其他数据库

项目初期用 SQLite 快速开发,后期迁移到 PostgreSQL 或 MySQL:

import (
    "gorm.io/driver/sqlite"
    "gorm.io/driver/postgres"
)

var db *gorm.DB

func InitDB() {
    if os.Getenv("ENV") == "production" {
        dsn := "host=..."
        db, _ = gorm.Open(postgres.Open(dsn), &gorm.Config{})
    } else {
        db, _ = gorm.Open(sqlite.Open("dev.db"), &gorm.Config{})
    }
}

GORM 的抽象层让迁移变得简单,大部分代码不用改。

常见错误

database is locked

并发写入冲突。解决方案:

  • 减少并发连接数
  • 启用 WAL 模式
  • 增加 busy_timeout

no such table

表不存在。检查是否执行了自动迁移:

db.AutoMigrate(&User{})

CGO 编译失败

缺少 GCC 环境。Windows 上安装 MinGW 或 TDM-GCC,Linux 上安装 build-essential

unable to open database file

文件路径不存在或无权限。确保目录存在且有写权限。

小结

SQLite 简单、轻量、零配置,是开发测试和小型项目的利器。理解它的并发限制和适用场景,才能用好它。下一章看企业级的 SQL Server。