SQLite = 零配置、单文件、功能完整的嵌入式关系数据库,具备完整的 ACID 事务支持已成为单机 Agent 记忆持久化的首选方案。
一、本质理解
传统数据库(MySQL/PostgreSQL):
客户端 → 网络连接 → 数据库服务 → 磁盘存储
SQLite:
应用进程 → 直接读写 → 数据库文件(.db/.sqlite)
差别:SQLite 没有服务端进程,应用直接操作文件
用后端视角类比:
|--------|--------------|----------------|
| 概念 | 传统数据库 | SQLite |
| 架构 | C/S 架构,需要服务端 | 嵌入式,无服务端 |
| 部署 | 安装、配置、启动服务 | 复制一个文件即可 |
| 连接 | TCP 连接池 | 文件句柄 |
| 并发 | 多连接并发读写 | 单写多读 |
| 适用 | 高并发 Web 服务 | 移动端、桌面应用、小规模服务 |
二、适用场景
✅ 适合 SQLite
|-----------|---------------------------|
| 场景 | 原因 |
| 移动应用 | Android/iOS 内置,无需额外部署 |
| 桌面应用 | Electron、本地工具,数据本地化 |
| 小型 Web 服务 | 日访问量 < 1千,单机部署 |
| 开发测试 | 零配置,快速启动 |
| 嵌入式设备 | 资源占用低(几 MB 内存) |
| 分析型查询 | OLAP 场景,读多写少 |
| RAG 向量存储 | 本地向量库,Milvus/Qdrant 的轻量替代 |
❌ 不适合 SQLite
|--------|-------------------|
| 场景 | 原因 |
| 高并发写入 | 单写锁,并发写入排队 |
| 分布式系统 | 无主从复制、分片 |
| 大数据量 | 单文件限制,建议 < 100MB |
| 高可用要求 | 无内置主备切换 |
三、核心特性
3.1 零配置
# 传统数据库
apt install mysql-server
systemctl start mysql
mysql -u root -p
CREATE DATABASE mydb;
# SQLite
# 直接创建文件,完事!
sqlite3 mydb.db
3.2 单文件存储
mydb.db ← 所有数据、索引、元数据都在这一个文件里
备份 = 复制文件
迁移 = 复制文件
回滚 = 替换文件
3.3 动态类型
-- MySQL/PostgreSQL:列类型严格
CREATE TABLE users (
id INT,
name VARCHAR(100) -- 只能存字符串
);
-- SQLite:列类型宽松
CREATE TABLE users (
id INTEGER,
name TEXT
);
-- 实际上可以存任意类型(不推荐,但可以)
INSERT INTO users VALUES (1, 123); -- name 存数字
INSERT INTO users VALUES (2, 3.14); -- name 存浮点
INSERT INTO users VALUES (3, 'hello'); -- name 存字符串
SQLite 5 种存储类型:
|---------|-------------------|---------------|
| 类型 | 说明 | 示例 |
| NULL | 空值 | NULL |
| INTEGER | 整数(1/2/3/4/6/8字节) | 1, 100, -42 |
| REAL | 浮点数(8字节) | 3.14, -0.5 |
| TEXT | 字符串(UTF-8/UTF-16) | 'hello', '中文' |
| BLOB | 二进制数据 | 图片、序列化对象 |
3.4 并发模型:单写多读
┌─────────────────────────────────────────────────────────────┐
│ SQLite 并发模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 读操作(可并发): │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │读取 │ │读取 │ │读取 │ ← 多个连接同时读,互不阻塞 │
│ └─────┘ └─────┘ └─────┘ │
│ │
│ 写操作(独占锁): │
│ ┌─────────────┐ │
│ │ 写入(锁定) │ ← 写入时阻塞所有读和其他写 │
│ └─────────────┘ │
│ │
│ WAL 模式(推荐): │
│ ┌─────┐ ┌─────┐ │
│ │读取 │ │读取 │ ← 写入时不阻塞读(读写分离) │
│ └─────┘ └─────┘ │
│ ┌─────────────┐ │
│ │ 写入 WAL │ ← 写入写入 WAL 文件,定期合并 │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
四、实战:Go 操作 SQLite
4.1 安装驱动
# 纯 Go 实现(推荐,无需 CGO)
go install github.com/glebarez/go-sqlite@latest
# 或者官方驱动(需要 CGO)
# go install github.com/mattn/go-sqlite3@latest
4.2 基础 CRUD
// main.go
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/glebarez/go-sqlite" // 纯 Go 驱动
)
// User 用户表
type User struct {
ID int64
Name string
Age int
}
func main() {
// 1. 打开数据库(文件不存在会自动创建)
db, err := sql.Open("sqlite", "test.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 2. 建表
createTable := `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
);
`
_, err = db.Exec(createTable)
if err != nil {
log.Fatal(err)
}
fmt.Println("建表成功")
// 3. 插入数据
result, err := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)", "张三", 25)
if err != nil {
log.Fatal(err)
}
id, _ := result.LastInsertId()
fmt.Printf("插入成功,ID: %d\n", id)
// 4. 查询单条
var user User
err = db.QueryRow("SELECT id, name, age FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name, &user.Age)
if err != nil {
log.Fatal(err)
}
fmt.Printf("查询结果: %+v\n", user)
// 5. 查询多条
rows, err := db.Query("SELECT id, name, age FROM users WHERE age > ?", 20)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
fmt.Println("年龄 > 20 的用户:")
for rows.Next() {
var u User
rows.Scan(&u.ID, &u.Name, &u.Age)
fmt.Printf(" %+v\n", u)
}
// 6. 更新
result, err = db.Exec("UPDATE users SET age = ? WHERE name = ?", 26, "张三")
if err != nil {
log.Fatal(err)
}
affected, _ := result.RowsAffected()
fmt.Printf("更新了 %d 行\n", affected)
// 7. 删除
result, err = db.Exec("DELETE FROM users WHERE id = ?", id)
if err != nil {
log.Fatal(err)
}
affected, _ = result.RowsAffected()
fmt.Printf("删除了 %d 行\n", affected)
}
4.3 事务处理
// 事务:批量插入
func batchInsert(db *sql.DB, users []User) error {
// 开始事务
tx, err := db.Begin()
if err != nil {
return err
}
// 确保事务要么提交要么回滚
defer func() {
if err != nil {
tx.Rollback()
}
}()
// 准备语句(预编译,提高性能)
stmt, err := tx.Prepare("INSERT INTO users (name, age) VALUES (?, ?)")
if err != nil {
return err
}
defer stmt.Close()
// 批量插入
for _, u := range users {
_, err = stmt.Exec(u.Name, u.Age)
if err != nil {
return err // 发生错误会触发 defer 中的 Rollback
}
}
// 提交事务
return tx.Commit()
}
// 使用示例
users := []User{
{Name: "李四", Age: 30},
{Name: "王五", Age: 28},
{Name: "赵六", Age: 35},
}
err := batchInsert(db, users)
4.4 开启 WAL 模式
// 开启 WAL 模式(写前日志,提高并发)
func enableWAL(db *sql.DB) error {
// 设置日志模式为 WAL
_, err := db.Exec("PRAGMA journal_mode=WAL;")
if err != nil {
return err
}
// 其他推荐配置
pragmas := []string{
"PRAGMA synchronous=NORMAL;", // 同步模式(性能与安全平衡)
"PRAGMA cache_size=10000;", // 缓存页数(约 40MB)
"PRAGMA busy_timeout=5000;", // 锁等待超时(毫秒)
}
for _, p := range pragmas {
_, err = db.Exec(p)
if err != nil {
return err
}
}
return nil
}
// 连接时开启
db, _ := sql.Open("sqlite", "test.db")
enableWAL(db)
五、业务场景示例
场景 1:RAG 向量存储
// internal/vector/sqlite_store.go
package vector
import (
"database/sql"
"fmt"
"math"
)
// SQLiteStore SQLite 向量存储
type SQLiteStore struct {
db *sql.DB
}
// 创建向量表
func (s *SQLiteStore) CreateTable(collection string, dimension int) error {
// 创建主表
createTable := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (
id TEXT PRIMARY KEY,
content TEXT,
vector BLOB, -- 存储序列化的向量
metadata TEXT -- JSON 格式元数据
);
`, collection)
_, err := s.db.Exec(createTable)
return err
}
// 存储向量
func (s *SQLiteStore) Upsert(collection, id, content string, vector []float32, metadata map[string]interface{}) error {
// 序列化向量
vectorBytes := serializeVector(vector)
metadataJSON := serializeJSON(metadata)
_, err := s.db.Exec(
fmt.Sprintf("INSERT OR REPLACE INTO %s (id, content, vector, metadata) VALUES (?, ?, ?, ?)", collection),
id, content, vectorBytes, metadataJSON,
)
return err
}
// 向量搜索(暴力搜索,适合小规模数据)
func (s *SQLiteStore) Search(collection string, queryVector []float32, topK int) ([]SearchResult, error) {
rows, err := s.db.Query(fmt.Sprintf("SELECT id, content, vector, metadata FROM %s", collection))
if err != nil {
return nil, err
}
defer rows.Close()
var results []SearchResult
for rows.Next() {
var id, content string
var vectorBytes []byte
var metadataJSON string
rows.Scan(&id, &content, &vectorBytes, &metadataJSON)
// 反序列化向量
docVector := deserializeVector(vectorBytes)
// 计算余弦相似度
score := cosineSimilarity(queryVector, docVector)
results = append(results, SearchResult{
ID: id,
Content: content,
Score: score,
Metadata: deserializeJSON(metadataJSON),
})
}
// 按相似度排序,取 Top-K
sortResults(results)
if len(results) > topK {
results = results[:topK]
}
return results, nil
}
// 余弦相似度
func cosineSimilarity(a, b []float32) float32 {
var dot, normA, normB float32
for i := range a {
dot += a[i] * b[i]
normA += a[i] * a[i]
normB += b[i] * b[i]
}
return dot / (float32(math.Sqrt(float64(normA))) * float32(math.Sqrt(float64(normB))))
}
场景 2:本地缓存
// internal/cache/sqlite_cache.go
package cache
import (
"database/sql"
"time"
)
// SQLiteCache 本地缓存
type SQLiteCache struct {
db *sql.DB
}
type CacheItem struct {
Key string
Value []byte
ExpiredAt time.Time
}
func NewSQLiteCache(path string) (*SQLiteCache, error) {
db, err := sql.Open("sqlite", path)
if err != nil {
return nil, err
}
// 建表
db.Exec(`
CREATE TABLE IF NOT EXISTS cache (
key TEXT PRIMARY KEY,
value BLOB,
expired_at INTEGER
);
CREATE INDEX IF NOT EXISTS idx_expired ON cache(expired_at);
`)
return &SQLiteCache{db: db}, nil
}
// Set 设置缓存
func (c *SQLiteCache) Set(key string, value []byte, ttl time.Duration) error {
expiredAt := time.Now().Add(ttl).Unix()
_, err := c.db.Exec(
"INSERT OR REPLACE INTO cache (key, value, expired_at) VALUES (?, ?, ?)",
key, value, expiredAt,
)
return err
}
// Get 获取缓存
func (c *SQLiteCache) Get(key string) ([]byte, bool) {
var value []byte
var expiredAt int64
err := c.db.QueryRow(
"SELECT value, expired_at FROM cache WHERE key = ?",
key,
).Scan(&value, &expiredAt)
if err == sql.ErrNoRows {
return nil, false
}
if err != nil {
return nil, false
}
// 检查是否过期
if time.Now().Unix() > expiredAt {
c.Delete(key)
return nil, false
}
return value, true
}
// Delete 删除缓存
func (c *SQLiteCache) Delete(key string) error {
_, err := c.db.Exec("DELETE FROM cache WHERE key = ?", key)
return err
}
// CleanExpired 清理过期缓存
func (c *SQLiteCache) CleanExpired() error {
_, err := c.db.Exec("DELETE FROM cache WHERE expired_at < ?", time.Now().Unix())
return err
}
场景 3:会话存储
// internal/session/sqlite_store.go
package session
import (
"database/sql"
"encoding/json"
"time"
)
type Session struct {
ID string
UserID string
Data map[string]interface{}
CreatedAt time.Time
ExpiresAt time.Time
}
func (s *SQLiteStore) Save(session *Session) error {
dataJSON, _ := json.Marshal(session.Data)
_, err := s.db.Exec(`
INSERT OR REPLACE INTO sessions (id, user_id, data, created_at, expires_at)
VALUES (?, ?, ?, ?, ?)
`, session.ID, session.UserID, dataJSON, session.CreatedAt, session.ExpiresAt)
return err
}
func (s *SQLiteStore) Get(id string) (*Session, error) {
var sess Session
var dataJSON []byte
err := s.db.QueryRow(`
SELECT id, user_id, data, created_at, expires_at
FROM sessions WHERE id = ? AND expires_at > ?
`, id, time.Now()).Scan(&sess.ID, &sess.UserID, &dataJSON, &sess.CreatedAt, &sess.ExpiresAt)
if err != nil {
return nil, err
}
json.Unmarshal(dataJSON, &sess.Data)
return &sess, nil
}
六、SQLite 高级特性
6.1 FTS5 全文搜索
-- 创建全文搜索虚拟表
CREATE VIRTUAL TABLE docs USING fts5(
title,
content,
tokenize = 'unicode61' -- 支持中文分词
);
-- 插入数据
INSERT INTO docs (title, content) VALUES
('差标管理', '技术部高级工程师差旅标准:机票经济舱2000元'),
('机票退改', '国航退票规则:起飞前24小时收取10%手续费');
-- 全文搜索
SELECT * FROM docs WHERE docs MATCH '差标 技术部' ORDER BY rank;
-- 搜索结果高亮
SELECT highlight(docs, 1, '<b>', '</b>') FROM docs WHERE docs MATCH '差标';
Go 使用 FTS5:
// 全文搜索
func (s *SQLiteStore) SearchFullText(query string) ([]Doc, error) {
rows, err := s.db.Query(`
SELECT title, content, rank
FROM docs
WHERE docs MATCH ?
ORDER BY rank
LIMIT 10
`, query)
// ...
}
6.2 JSON 支持
-- SQLite 3.38+ 内置 JSON 函数
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
data TEXT -- 存储 JSON 字符串
);
INSERT INTO orders (data) VALUES (
'{"user": "张三", "items": [{"name": "机票", "price": 1200}]}'
);
-- 提取 JSON 字段
SELECT json_extract(data, '$.user') FROM orders;
-- 提取嵌套字段
SELECT json_extract(data, '$.items[0].price') FROM orders;
-- JSON 路径查询
SELECT * FROM orders WHERE json_extract(data, '$.user') = '张三';
6.3 窗口函数
-- 排名计算
SELECT
name,
score,
RANK() OVER (ORDER BY score DESC) as rank,
ROW_NUMBER() OVER (ORDER BY score DESC) as row_num
FROM students;
-- 分组统计
SELECT
department,
name,
salary,
AVG(salary) OVER (PARTITION BY department) as dept_avg
FROM employees;
-- 移动平均
SELECT
date,
amount,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) as moving_avg
FROM sales;
七、性能优化
7.1 索引优化
-- 创建索引
CREATE INDEX idx_users_name ON users(name);
CREATE INDEX idx_users_age ON users(age);
-- 复合索引
CREATE INDEX idx_users_name_age ON users(name, age);
-- 查看执行计划
EXPLAIN QUERY PLAN SELECT * FROM users WHERE name = '张三';
-- 分析统计信息
ANALYZE;
7.2 批量操作优化
// ❌ 差:逐条插入
for _, item := range items {
db.Exec("INSERT INTO users (name) VALUES (?)", item.Name)
}
// ✅ 好:事务 + 预编译
tx, _ := db.Begin()
stmt, _ := tx.Prepare("INSERT INTO users (name) VALUES (?)")
for _, item := range items {
stmt.Exec(item.Name)
}
stmt.Close()
tx.Commit()
7.3 连接池配置
db, _ := sql.Open("sqlite", "test.db")
// 设置连接池
db.SetMaxOpenConns(1) // SQLite 写操作只能单连接,建议设为 1
db.SetMaxIdleConns(1) // 保持 1 个空闲连接
db.SetConnMaxLifetime(0) // 连接不超时(文件句柄)
7.4 性能对比
|----------|---------|---------|--------|
| 操作 | 无索引 | 有索引 | 提升 |
| 10万条查询 | 500ms | 5ms | 100x |
| 批量插入(事务) | 10s | 500ms | 20x |
| FTS全文搜索 | 200ms | 10ms | 20x |
八、SQLite vs 其他数据库
|---------|------------|-----------|----------------|
| 特性 | SQLite | MySQL | PostgreSQL |
| 部署 | 零配置 | 需安装配置 | 需安装配置 |
| 并发写入 | 单写 | 多写 | 多写 |
| 存储限制 | 1TB | 无限制 | 无限制 |
| 全文搜索 | FTS5 | 全文索引 | tsvector |
| JSON 支持 | ✅ | ✅ | ✅(更好) |
| 向量扩展 | 需自己实现 | 无 | pgvector |
| 适用场景 | 小型/嵌入式 | 中大型Web | 复杂查询/分析 |
九、避坑指南
9.1 并发写入问题
// 问题:多个协程同时写入会报错 "database is locked"
// 解决方案 1:使用互斥锁
var dbMutex sync.Mutex
func safeWrite(db *sql.DB, query string, args ...interface{}) error {
dbMutex.Lock()
defer dbMutex.Unlock()
_, err := db.Exec(query, args...)
return err
}
// 解决方案 2:开启 WAL 模式 + busy_timeout
db.Exec("PRAGMA journal_mode=WAL;")
db.Exec("PRAGMA busy_timeout=5000;")
9.2 数据类型问题
// 问题:SQLite 动态类型,可能存入意外类型
// 解决方案:应用层校验
func InsertUser(db *sql.DB, name string, age int) error {
if name == "" {
return errors.New("name 不能为空")
}
if age < 0 || age > 150 {
return errors.New("age 范围错误")
}
_, err := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)", name, age)
return err
}
9.3 文件锁问题
// 问题:多个进程访问同一个 db 文件
// 解决方案:使用连接池,保持长连接
db, _ := sql.Open("sqlite", "file:test.db?cache=shared&mode=rwc")
// 或者使用 NFS 友好模式
db.Exec("PRAGMA locking_mode=EXCLUSIVE;")
9.4 内存管理
// 问题:大数据集查询内存占用高
// 解决方案:使用 LIMIT 分页
rows, _ := db.Query("SELECT * FROM large_table LIMIT ? OFFSET ?", 100, 0)
// 或者使用游标方式
rows, _ := db.Query("SELECT id FROM large_table")
for rows.Next() {
var id int
rows.Scan(&id)
// 按 ID 分批处理
}
十、总结
一句话理解 SQLite
SQLite = 零配置的单文件嵌入式数据库,适合小型应用、移动端、本地缓存
什么时候用 SQLite
✅ 用:
- 移动应用、桌面应用
- 小型 Web 服务(日活 < 1万)
- 本地缓存、会话存储
- 开发测试环境
- RAG 向量存储(小规模)
❌ 不用:
- 高并发写入(QPS > 1000)
- 大数据量(> 100GB)
- 需要分布式、高可用