SQLite 核心实战:后端工程师视角

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)
- 需要分布式、高可用
相关推荐
dusk_star1 小时前
go语言--笔记--接口
java·笔记·golang
IT_陈寒1 小时前
被Vite的HMR坑惨了,原来这样配置才能用对!
前端·人工智能·后端
凌览1 小时前
为什么我不推荐一人公司用PostgreSQL
前端·后端·node.js
我是一颗柠檬1 小时前
【Java后端技术亮点】Feed流三级缓存设计,从10秒到100毫秒的优化实战
java·开发语言·后端·缓存
JaguarJack1 小时前
PHP 应用 security.txt 漏洞披露实践
后端·php
wuhen_n1 小时前
阿里云百炼平台 API 接入教程(附 Node.js + TypeScript 实战)
前端·人工智能·阿里云·ai编程
程序员三明治1 小时前
【AI】RAG 数据分块(Chunk)策略与实践
java·人工智能·后端·ai·大模型·llm·rag
会编程的土豆1 小时前
前端和后端是怎么配合工作的(Go后端视角)
前端·golang·状态模式
Mr.huang1 小时前
面向驾驭工程的 MCP-Agent 研发运维闭环自动化模型研究
ai编程·devops