SQLite 是一个进程内的、无服务器的、零配置的、支持事务的嵌入式关系型数据库。它不是一个 C/S 架构的数据库,而是以库的形式链接到应用程序中,数据库就是磁盘上的一个普通文件。
本篇笔记主要包含以下内容
- SQLite 特点
- 安装
- 数据类型与类型亲和性
- 命令行工具
- 数据库与表操作
- 约束
- 增删改查
- 索引
- 视图与触发器
- 事务与并发
- PRAGMA 常用配置
- 备份与恢复
- 性能优化
- 编程语言中使用
- SQLite 的局限
- FAQ
- 参考资料
SQLite 特点
- 无服务器:不需要单独的数据库服务进程,直接读写数据库文件。
- 零配置:无需安装、启动、配置,没有用户和权限体系。
- 单文件:整个数据库(表、索引、视图、触发器)都存放在一个跨平台的文件中。
- 事务安全:支持 ACID,即使断电或进程崩溃也能保证数据一致性。
- 轻量:编译后体积小,运行时占用内存少,适合嵌入式设备、移动端、桌面应用。
- 公共领域(Public Domain):可自由用于商业项目。
- 跨平台:数据库文件格式在 32/64 位、大小端机器之间通用。
SQLite 适合读多写少、单机、数据量适中的场景,例如移动 App、桌面软件、小型网站、测试环境、数据分析和缓存。不适合高并发写入、超大规模数据、需要网络访问和多用户权限管理的场景。
安装
Linux 下直接通过包管理器安装:
# Debian/Ubuntu |
macOS 自带 sqlite3,Windows 可从官网下载预编译的 sqlite-tools 包,得到 sqlite3.exe。
编译安装(可选):
wget https://www.sqlite.org/2024/sqlite-autoconf-3450000.tar.gz |
查看版本:
sqlite3 --version |
数据类型与类型亲和性
SQLite 采用动态类型,值本身有类型,列没有强制的类型。共有 5 种存储类型(Storage Class):
| 存储类型 | 说明 |
|---|---|
| NULL | 空值 |
| INTEGER | 有符号整数,按大小存为 1/2/3/4/6/8 字节 |
| REAL | 8 字节 IEEE 浮点数 |
| TEXT | 字符串,默认 UTF-8/UTF-16 编码 |
| BLOB | 二进制数据,原样存储 |
声明列类型时,SQLite 会根据类型名称推断类型亲和性(Type Affinity):
| 亲和性 | 匹配的声明类型 | 行为 |
|---|---|---|
| INTEGER | 含 INT 的类型,如 INTEGER、BIGINT |
能无损转为整数时存为 INTEGER |
| TEXT | 含 CHAR、CLOB、TEXT |
数值会转为文本存储 |
| BLOB | 含 BLOB 或未声明类型 |
原样存储,不做转换 |
| REAL | 含 REAL、FLOA、DOUB |
能转为浮点时存为 REAL |
| NUMERIC | 其他类型,如 NUMERIC、DECIMAL、BOOLEAN、DATE |
优先整数,其次浮点,否则文本 |
几个常见约定:
BOOLEAN没有独立类型,通常用INTEGER存0/1。DATETIME/DATE通常存为TEXT(ISO8601 格式)或 Unix 时间戳(INTEGER)。INTEGER PRIMARY KEY是rowid的别名,自增且高效。AUTOINCREMENT会额外维护sqlite_sequence表,保证 ID 不复用,一般不需要。
从 3.37 开始支持 STRICT 表,强制列类型检查:
CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL) STRICT; |
命令行工具
sqlite3 mydb.db # 打开/创建数据库 |
常用点命令(以 . 开头,不是 SQL):
| 命令 | 说明 |
|---|---|
.help |
查看帮助 |
.databases |
列出已连接的数据库 |
.tables |
列出表 |
.schema [表名] |
查看建表语句 |
.indexes [表名] |
列出索引 |
.headers on |
显示列名 |
.mode column / .mode table / .mode csv / .mode json |
输出格式 |
.width 10 20 |
设置列宽 |
.nullvalue NULL |
设置 NULL 的显示方式 |
.output file.txt / .once file.txt |
输出到文件 |
.read script.sql |
执行 SQL 文件 |
.import data.csv tablename |
导入 CSV |
.dump [表名] |
导出 SQL |
.backup backup.db |
在线备份 |
.restore backup.db |
恢复 |
.timer on |
显示执行耗时 |
.quit / .exit |
退出 |
示例:
sqlite3 app.db <<'EOF' |
数据库与表操作
创建、删除表:
CREATE TABLE IF NOT EXISTS users ( |
修改表结构(SQLite 的 ALTER TABLE 能力有限):
ALTER TABLE users ADD COLUMN phone TEXT; |
SQLite 没有 ALTER COLUMN,修改列类型/约束需要「新建表 → 复制数据 → 删除旧表 → 重命名」:
BEGIN; |
多数据库与附加:
ATTACH DATABASE 'other.db' AS other; |
临时表只在当前连接有效:
CREATE TEMP TABLE tmp_data (id INTEGER, v TEXT); |
约束
| 约束 | 说明 |
|---|---|
PRIMARY KEY |
主键,INTEGER PRIMARY KEY 即 rowid |
NOT NULL |
非空 |
UNIQUE |
唯一,可多列组合 |
CHECK |
条件校验 |
DEFAULT |
默认值 |
FOREIGN KEY |
外键,默认不生效,需 PRAGMA foreign_keys = ON |
CREATE TABLE orders ( |
注意:
- 外键检查是连接级别的,每次连接都要执行
PRAGMA foreign_keys = ON;。 UNIQUE允许多个NULL(NULL 不等于 NULL)。- 冲突处理:
INSERT OR REPLACE、INSERT OR IGNORE、ON CONFLICT DO UPDATE(UPSERT)。
增删改查
插入:
INSERT INTO users (name, email, age) VALUES ('Tom', 'tom@example.com', 20); |
查询:
SELECT id, name, age FROM users WHERE age >= 18 ORDER BY age DESC LIMIT 10 OFFSET 20; |
更新与删除:
UPDATE users SET age = age + 1 WHERE id = 1; |
常用内置函数:
SELECT length('hello'); -- 5 |
JSON(3.38+ 内置):
SELECT json_extract('{"a":{"b":1}}', '$.a.b'); -- 1 |
窗口函数与 CTE:
SELECT name, age, row_number() OVER (ORDER BY age DESC) AS rn FROM users; |
索引
CREATE INDEX idx_users_name ON users(name); |
使用建议:
- 在
WHERE、JOIN、ORDER BY常用列上建索引。 - 组合索引遵循最左前缀原则,
(age, name)可服务age或age, name,不能单独服务name。 - 索引会加快查询,但会增加写入成本和存储空间,不要滥建。
- 用
EXPLAIN QUERY PLAN查看是否走索引:
EXPLAIN QUERY PLAN SELECT * FROM users WHERE name = 'Tom'; |
输出中的 SCAN 表示全表扫描,SEARCH ... USING INDEX 表示使用索引。若查询字段全部包含在索引中(覆盖索引),可以避免回表。
视图与触发器
视图:
CREATE VIEW v_adult AS SELECT id, name, age FROM users WHERE age >= 18; |
触发器:
CREATE TRIGGER trg_users_updated |
触发器支持 BEFORE/AFTER/INSTEAD OF、INSERT/UPDATE/DELETE,行级触发。可用 NEW 和 OLD 引用新旧行。触发器里的逻辑会隐式包含在触发语句的事务中,注意不要在触发器中做重操作。
事务与并发
BEGIN; -- 默认 DEFERRED |
三种事务模式:
DEFERRED:默认,第一次读写时才加锁,可能在提交时才发现冲突。IMMEDIATE:立即获取 RESERVED 锁,其他写事务会等待,适合写操作。EXCLUSIVE:立即获取排他锁,WAL 模式下与 IMMEDIATE 类似。
保存点:
SAVEPOINT sp1; |
并发模型:
- 传统 rollback journal 模式下,写事务会阻塞读,读也会阻塞写。
- WAL(Write-Ahead Logging)模式下,读写互不阻塞,但同一时刻只允许一个写者。
- 多个连接同时写会得到
database is locked,应设置busy_timeout或使用IMMEDIATE事务减少冲突。
PRAGMA journal_mode = WAL; |
PRAGMA 常用配置
PRAGMA 是 SQLite 特有的配置/查询语句:
PRAGMA database_list; -- 当前连接的数据库 |
常用可写配置:
PRAGMA journal_mode = WAL; -- 推荐 |
注意:journal_mode、page_size、auto_vacuum 等部分 PRAGMA 会持久化在数据库文件中,其余多为连接级临时设置。
备份与恢复
命令行备份(在线、安全):
sqlite3 app.db ".backup backup.db" |
SQL 方式(3.27+):
VACUUM INTO 'backup.db'; |
直接复制文件在数据库未使用 WAL 且没有写入时是安全的;启用 WAL 后还有 -wal、-shm 文件,不要只复制主文件。推荐使用 .backup 或 VACUUM INTO。
恢复:把备份文件覆盖回去即可,或用 .restore:
sqlite3 app.db ".restore backup.db" |
性能优化
- 使用 WAL 模式:
PRAGMA journal_mode = WAL; - 批量写入使用事务包裹,避免每条语句一次 fsync:
BEGIN; |
- 批量导入时临时降低安全性:
PRAGMA synchronous = OFF; |
- 定期执行
ANALYZE更新统计信息,帮助查询优化器选择索引:
ANALYZE; |
- 用
VACUUM整理碎片、回收空间(需要额外磁盘空间,执行期间会锁库):
VACUUM; |
- 使用 预编译语句(prepared statement) 复用执行计划。
- 只查询需要的列,避免
SELECT *。 - 合理使用索引与覆盖索引,用
EXPLAIN QUERY PLAN验证。 - 开启
PRAGMA mmap_size提升读性能(视内存而定)。 - 频繁写入场景考虑
synchronous = NORMAL,但要接受极端情况下丢失最后几个事务的风险。
编程语言中使用
Python 标准库自带 sqlite3:
import sqlite3 |
要点:
- 始终使用
?占位符传参,不要拼接 SQL,避免注入。 executemany()适合批量插入。with conn只管理事务,不关闭连接。- SQLite 默认类型转换较宽松,需要精确控制时可注册适配器/转换器。
- 多线程共享连接需
check_same_thread=False并自行加锁,通常更推荐每个线程独立连接。
Node.js(better-sqlite3,同步 API,性能好):
const Database = require("better-sqlite3"); |
Go 使用 modernc.org/sqlite(纯 Go)或 mattn/go-sqlite3(CGO),通过 database/sql 操作,注意设置 _pragma=busy_timeout(5000)、_pragma=journal_mode(WAL) 等连接参数。
SQLite 的局限
- 单写者模型,写并发能力有限,不适合高并发写入。
- 没有网络协议,不能像 MySQL 那样被多台机器直接访问(需要借助应用层或 LiteFS/rqlite 等方案)。
- 没有用户、权限、角色管理,文件权限即安全边界。
- 没有存储过程,触发器和视图能力相对简单。
ALTER TABLE能力有限,复杂的表结构变更需要重建表。- 不适合超大规模数据(一般建议单库几十 GB 以内,视场景而定)。
- 大量小事务写入性能受 fsync 限制,应合并事务。
- 锁是数据库级别的,长事务会阻塞其他写入。
FAQ
Q:database is locked 怎么解决?
A:通常是多个连接同时写。设置 PRAGMA busy_timeout = 5000;,写事务用 BEGIN IMMEDIATE,缩短事务时间,或启用 WAL 模式。避免在事务中执行耗时操作。
Q:主键用 INTEGER PRIMARY KEY 还是 AUTOINCREMENT?
A:优先 INTEGER PRIMARY KEY,它复用 rowid,性能更好。AUTOINCREMENT 只保证 ID 单调不复用,代价是额外的表和写入开销,仅在业务确实要求 ID 不复用时使用。
Q:为什么外键不生效?
A:SQLite 默认关闭外键约束,需要在每个连接上执行 PRAGMA foreign_keys = ON;。
Q:删除数据后文件为什么不变小?
A:删除只是把页标记为空闲,文件不会自动收缩。执行 VACUUM 重建数据库,或开启 auto_vacuum 增量回收。
Q:时间应该怎么存?
A:推荐存 UTC 的 ISO8601 文本(strftime('%Y-%m-%dT%H:%M:%SZ','now'))或 Unix 时间戳整数,展示时再按本地时区转换。不要存本地时间,避免时区和夏令时问题。
Q:WAL 模式需要注意什么?
A:会额外产生 -wal 和 -shm 文件;备份不能只复制主文件;网络文件系统(NFS/SMB)上使用 WAL 可能不可靠,应放在本地磁盘。
Q:如何做数据库版本迁移?
A:利用 PRAGMA user_version 记录版本号,应用启动时比较并依次执行迁移脚本,整个过程放在事务中。
参考资料
- 官方文档:https://www.sqlite.org/docs.html
- SQL 语法:https://www.sqlite.org/lang.html
- PRAGMA 列表:https://www.sqlite.org/pragma.html
- 何时使用 SQLite:https://www.sqlite.org/whentouse.html
- Python sqlite3:https://docs.python.org/3/library/sqlite3.html