SQLite 命令速查表 - SQLite 数据库常用命令大全
嵌入式数据库的选择其实很窄:需要零运维、单文件可搬、备份就是拷贝的时候,SQLite 几乎总是正确答案,代价是同一时刻只允许一个写者。这张表按连接与基础、表操作、数据操作、查询进阶、索引与约束、事务与锁、导入导出与工具、实用函数与 PRAGMA 分组,命令从建库建表一路排到事务、WAL 模式与 CSV 导入导出。判断依据是并发模型:如果写入来自多个进程或多个线程,PRAGMA journal_mode=WAL 加上合理的 busy_timeout 就是必选项;如果只是单机脚本,那么真正该做的是把类型和约束在表定义里定死,让错误在插入时就失败。什么时候翻它:给命令行工具加本地持久化、做数据迁移、跑一次性分析、或者把 CSV 快速灌进库里再查。与 Postgres 或 MySQL 的分界很明确——没有服务进程也就没有网络层,瓶颈通常出现在写入而非查询。
典型使用场景
面向需要轻量本地存储的开发者:桌面与移动应用的内嵌数据、脚本与自动化任务、测试夹具、浏览器本地数据、服务端的中小规模只读配置。SQLite 零配置、无需服务端进程,一个文件就是一个库,拷走即迁移。本表覆盖三层内容:DML 与查询进阶(UPSERT、GROUP BY + HAVING、JOIN、聚合函数);索引与约束(唯一索引、外键、CHECK)——这是决定数据量上来后性能好坏的唯一手段;以及运维向的 `PRAGMA journal_mode=WAL`、`.dump` 备份、`VACUUM` 回收空间和 `integrity_check`。
数据库连接与基础 6
sqlite3 file.db.open file.db.databases.tables.quit.help表操作 6
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);DROP TABLE IF EXISTS users;ALTER TABLE users ADD COLUMN email TEXT;CREATE TABLE backup AS SELECT * FROM users;CREATE TEMP TABLE temp_data (id INT);.schema users数据操作 7
INSERT INTO users (name, age) VALUES ('Tom', 25);INSERT INTO users (name, age) VALUES ('Tom', 25), ('Jerry', 30);SELECT * FROM users;UPDATE users SET age = 26 WHERE name = 'Tom';DELETE FROM users WHERE name = 'Tom';REPLACE INTO users (id, name, age) VALUES (1, 'Tom', 26);INSERT INTO users (id, name) VALUES (1, 'Tom') ON CONFLICT(id) DO UPDATE SET name = excluded.name;查询进阶 8
SELECT * FROM users WHERE age > 18;SELECT * FROM users ORDER BY age DESC, name ASC;SELECT status, COUNT(*) FROM users GROUP BY status;SELECT status, COUNT(*) FROM users GROUP BY status HAVING COUNT(*) > 5;SELECT * FROM users LIMIT 10 OFFSET 20;SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id;SELECT DISTINCT city FROM users;SELECT COUNT(*), AVG(age), MAX(age), MIN(age) FROM users;索引与约束 6
CREATE INDEX idx_users_name ON users(name);CREATE UNIQUE INDEX idx_users_email ON users(email);DROP INDEX IF EXISTS idx_users_name;CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL, age INT DEFAULT 0);CREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INT REFERENCES users(id));CREATE TABLE products (id INT, price REAL CHECK(price > 0));事务与锁 7
BEGIN TRANSACTION;COMMIT;ROLLBACK;SAVEPOINT sp1;ROLLBACK TO sp1;RELEASE SAVEPOINT sp1;PRAGMA journal_mode=WAL;导入导出与工具 6
.import --csv data.csv my_table.output dump.sql.dump.read dump.sqlVACUUM;ANALYZE;实用函数与 PRAGMA 6
PRAGMA table_info(users);PRAGMA index_list(users);PRAGMA integrity_check;SELECT datetime('now');SELECT strftime('%Y-%m-%d', 'now');SELECT length('hello'), typeof(42), randomblob(16);参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
.open <file> | 打开或创建数据库文件 | sqlite3 file.db |
.tables / .databases | 列出当前库的表 / 已打开的连接 | .tables |
.schema <table> | 查看建表语句 | .schema users |
CREATE TABLE ... PRIMARY KEY | 定义主键与列级约束 | CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL, age INT DEFAULT 0); |
REFERENCES users(id) | 外键约束 | CREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INT REFERENCES users(id)); |
CHECK(price > 0) | 限制列的取值范围 | CREATE TABLE products (id INT, price REAL CHECK(price > 0)); |
ON CONFLICT DO UPDATE | UPSERT:冲突更新、不冲突插入 | INSERT INTO users (id, name) VALUES (1, 'Tom') ON CONFLICT(id) DO UPDATE SET name = excluded.name; |
REPLACE INTO | 按主键或唯一约束插入或整行替换 | REPLACE INTO users (id, name, age) VALUES (1, 'Tom', 26); |
GROUP BY + HAVING | 分组统计后再过滤分组 | SELECT status, COUNT(*) FROM users GROUP BY status HAVING COUNT(*) > 5; |
LIMIT / OFFSET | 分页:限制条数并跳过前若干条 | SELECT * FROM users LIMIT 10 OFFSET 20; |
JOIN ... ON | 关联查询(INNER JOIN) | SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id; |
CREATE [UNIQUE] INDEX | 建索引 / 唯一索引以加速与去重 | CREATE UNIQUE INDEX idx_users_email ON users(email); |
PRAGMA journal_mode=WAL | 切 WAL 模式提升并发读写性能 | PRAGMA journal_mode=WAL; |
BEGIN / COMMIT / ROLLBACK | 事务边界控制 | BEGIN TRANSACTION; |
SAVEPOINT / ROLLBACK TO | 保存点与部分回滚 | ROLLBACK TO sp1; |
.import --csv | 把 CSV 导入为表 | .import --csv data.csv my_table |
.dump / .read | 导出整库为 SQL / 执行 SQL 文件恢复 | .dump |
VACUUM / ANALYZE | 回收空闲空间 / 更新统计信息 | VACUUM; |
PRAGMA integrity_check | 检查数据库文件完整性 | PRAGMA integrity_check; |
易错点与避坑指南
现象数据库文件越用越大,删掉大量数据后体积纹丝不动
原因删除只是把页标记为空闲,不会自动把文件缩回去。
处置执行 `VACUUM;` 回收空闲空间;该操作会重写整库,注意预留磁盘余量。
现象查询在数据量上来后越来越慢,执行计划里没有用上索引
原因统计信息陈旧,优化器按旧数据量做决策;或者查询条件与索引列不匹配。
处置先跑 `ANALYZE;` 更新统计信息,再用 `PRAGMA index_list(users)` 确认目标列上确实有索引。
现象写入时报 database is locked,或并发读多时频繁超时
原因默认回滚日志模式下读写互斥,长事务还会一直占着锁。
处置执行 `PRAGMA journal_mode=WAL;` 切换到 WAL;并把批量操作包进事务,用 `COMMIT` 尽早释放。
现象批量导入做了一半失败,数据处于半成品状态
原因多条语句没有事务包裹,前半段已提交无法回退。
处置用 `BEGIN TRANSACTION;` 开头,出错走 `ROLLBACK;`,成功走 `COMMIT;`;需要更细的粒度时用 `SAVEPOINT sp1;` 和 `ROLLBACK TO sp1;`。
现象按某个从别的 SQL 迁过来的语法写查询,直接报错
原因SQLite 支持大部分 SQL-92 标准,但不支持 RIGHT JOIN 和 FULL JOIN。
处置改写为 `LEFT JOIN`,或用子查询/`UNION` 的组合达到同样结果。
现象恢复备份时报语法错误,文件只恢复了一半
原因`.read` 逐条执行导出文件,文件里含有不支持的语句或编码不一致。
处置先 `PRAGMA integrity_check;` 确认库本身完好,再用 `sqlite3 new.db < dump.sql` 整文件重放,观察是否有报错行。
现象命令和 DDL 一起执行时看起来没生效
原因`.tables`、`.schema`、`.import`、`.output` 这类是 sqlite3 命令行专属的点命令,不能放进 SQL 脚本或代码字符串里交给数据库引擎执行。
处置在 sqlite3 交互 shell 中执行点命令,或在脚本文件里改用等价的 SQL 语句。
排障路径
1确认文件完整性
PRAGMA integrity_check;输出 ok 表示文件结构完好;出现错误行则应先用 .dump 抢救数据再修复。
2查看表结构与实际建表语句
.schema users建表语句里能看到约束与索引定义是否如设计预期。
3查看列信息与索引清单
PRAGMA table_info(users); PRAGMA index_list(users);前者确认列类型与是否可空,后者确认查询条件列上是否已有索引。
4统计分组结果定位异常数据
SELECT status, COUNT(*) FROM users GROUP BY status HAVING COUNT(*) > 5;输出可直接看出哪些分组数据异常集中,HAVING 在此过滤的是分组而非行。
5把整库导出为 SQL 做备份
.output dump.sql && .dump先 .output 重定向再 .dump,生成的 dump.sql 即为可重放的备份文本。
6切到 WAL 并确认生效
PRAGMA journal_mode=WAL; PRAGMA integrity_check;第一条应返回 wal,随后跑一次完整性检查确认切换后库仍可正常读写。
命令示例
先备份再改结构,出问题整库回退
.output dump.sql
.dump
.open file.db
.read dump.sql.output 把后续输出重定向到文件,紧接着 .dump 就把整个库写进 dump.sql。改表、加索引前先做这一步:SQLite 没有系统级快照,误删列只能靠这个文件恢复。恢复时对空库执行 .read 即可。
输出
dump.sql // 数据库被导出为可重放的 SQL 文本
批量改数据放进事务,出错回滚
BEGIN TRANSACTION;
UPDATE users SET age = age + 1 WHERE age < 18;
SELECT COUNT(*) FROM users WHERE age = 18;
COMMIT;UPDATE/DELETE 少了 WHERE 就是全表覆盖。放进事务里先看 SELECT 结果再 COMMIT,确认影响行数符合预期;不符合就 ROLLBACK,数据库回到原状。
分组统计并过滤掉小样本
SELECT status, COUNT(*) FROM users GROUP BY status HAVING COUNT(*) > 1;WHERE 在分组前过滤行,HAVING 在分组后过滤组。查「有两条以上记录的状态」必须用 HAVING——写在 WHERE 里只会把不满足的行剔掉,COUNT 结果仍是 1。
输出
status | COUNT(*) active | 2
UPSERT:同一段脚本重复跑也不会产生重复行
INSERT INTO users (id, name) VALUES (1, 'Tom') ON CONFLICT(id) DO UPDATE SET name = excluded.name;对比 REPLACE INTO:REPLACE 是「先删后插」,外键级联删除会连带删掉关联行;ON CONFLICT DO UPDATE 是原地更新,只动指定的列。写迁移脚本或导入脚本时用 UPSERT 比 REPLACE 安全得多。
排查「查询很慢」:看列、看索引、看执行计划
PRAGMA table_info(users);
PRAGMA index_list(users);
ANALYZE;table_info 能确认列类型和默认值——很多「插不进去」其实是类型或约束问题;index_list 看索引是否真的建上;ANALYZE 更新统计信息让优化器选对索引。三条跑完,慢查询的原因基本就锁定在缺索引或缺统计信息上。
输出
cid | name | type | notnull | dflt_value | pk 0 | id | INTEGER | 0 | NULL | 1 1 | name | TEXT | 1 | NULL | 0 2 | age | INT | 0 | 0 | 0
保存点:长事务里只回滚其中一段
BEGIN TRANSACTION;
SAVEPOINT sp1;
DELETE FROM users WHERE name = 'Bob';
ROLLBACK TO sp1;
RELEASE SAVEPOINT sp1;
COMMIT;DELETE 之后 SELECT COUNT(*) 仍是 3:ROLLBACK TO sp1 只撤销了保存点之后的操作,前面的工作全部保留。这是分批处理大量数据时「失败只丢一批」的最小单位。
常见坑与注意事项
- UPDATE 或 DELETE 漏写 WHERE 会覆盖/删除全表数据。SQLite 没有系统快照,事后只能靠 .dump 备份恢复;执行前务必先用同条件 SELECT 数一遍。
- REPLACE INTO 基于主键冲突时会先删后插,外键级联删除可能连带清掉关联表数据,且不可恢复;优先用 ON CONFLICT DO UPDATE。
- SQLite 默认不开启外键约束(PRAGMA foreign_keys 为 0),建了 REFERENCES 也不会拦截脏数据;迁移脚本要在同一个连接里显式开启并复检。
- 不开启 WAL 时并发写会互相阻塞并报 database is locked,长事务尤其明显;生产环境先 PRAGMA journal_mode=WAL,但注意它会生成 -wal/-shm 伴随文件,备份时不能只拷 .db。
- .read dump.sql 往已有数据的库执行会直接追加并产生重复行,不报错;恢复必须针对空库进行。
- .import --csv 依赖文件列顺序与表结构一致,CSV 列顺序一变数据就会静默错位;导入后先 SELECT 抽查几行。
- UPDATE ... SET age = 25 赋值类型与列声明不一致时,SQLite 的动态类型可能把数字存成文本;用 PRAGMA table_info 核对类型,别只看值「看起来对不对」。
- VACUUM 只能在没有活跃事务时执行,且会重写整个数据库文件,大库上执行会长时间锁库并需要约两倍磁盘空间;线上执行前先停写。
提示
- SQLite 是零配置数据库,无需安装服务端进程
- 使用 .dump 备份数据库,VACUUM 回收空间
- PRAGMA journal_mode=WAL 可显著提升并发性能
- SQLite 支持大部分 SQL-92 标准,但不支持 RIGHT/FULL JOIN
- .import 命令支持 CSV 文件直接导入为表
- 启用 WAL 后读写不互斥,但仍是单写者:多进程高频写入需靠 busy_timeout 等待,而不是靠重试。
- 大表加索引前先用 EXPLAIN QUERY PLAN 看走没走上索引,凭直觉加索引常常只增大库不提速。
- 整库备份用 .backup 或 .dump 而非直接复制 .db 文件,WAL 模式下直接拷贝可能拿到不一致的快照。
常见问题
REPLACE INTO 和 UPSERT 该怎么选?
REPLACE INTO `users` 是「先删后插」:命中主键或唯一约束时删除旧行再插入新行,触发删除与插入行为,外键级联也会跟着走,只适合确定要整行覆盖的场景。UPSERT 更精细:`INSERT INTO users (id, name) VALUES (1, 'Tom') ON CONFLICT(id) DO UPDATE SET name = excluded.name`,冲突时只更新指定列(用 excluded 引用新值),不冲突时正常插入,其余字段保持不变。需要保留原记录的部分字段就用 UPSERT。
怎么用 sqlite3 命令行做备份和恢复?
备份用 `.output dump.sql` 把输出重定向到文件,紧接着执行 `.dump`,整个库就导出成 SQL 文本了;恢复时用 `.read dump.sql` 执行它。想搬一批 CSV 进来用 `.import --csv data.csv my_table`,同样先 `.output` 再导也能导出成表。数据膨胀后跑一次 `VACUUM;` 回收空闲空间减小文件体积,`ANALYZE;` 更新统计信息帮助优化器选索引。怀疑文件损坏先跑 `PRAGMA integrity_check;`。
什么时候该给字段建索引,建错了会有什么代价?
给高频过滤、排序、关联的列建索引:`CREATE INDEX idx_users_name ON users(name)`;需要唯一性就用 `CREATE UNIQUE INDEX idx_users_email ON email`,它同时充当数据约束。代价是写入变慢——每次 INSERT/UPDATE 都要维护索引结构,还会占用磁盘空间,所以别给几乎不查询的列加。用 `PRAGMA index_list(users);` 确认当前有哪些索引,不需要的用 `DROP INDEX IF EXISTS idx_users_name;` 清掉。`CREATE TABLE backup AS SELECT * FROM users` 生成的新表不继承索引,需要重建。
WHERE 和 HAVING 有什么区别,怎么搭配 GROUP BY?
WHERE 在分组之前过滤原始行,只能用普通列,不能用聚合函数;HAVING 在分组聚合之后过滤分组结果,必须配合 GROUP BY 使用。典型统计写法:`SELECT status, COUNT(*) FROM users GROUP BY status HAVING COUNT(*) > 5`,只保留人数超过 5 的分组。如果两个条件都有,顺序是先 WHERE 缩小数据集、再 GROUP BY 聚合、最后 HAVING 筛分组,效率最高。多字段排序用 `ORDER BY age DESC, name ASC`。
多人同时写 SQLite 报 database is locked,怎么调优?
先把日志模式换成 WAL:`PRAGMA journal_mode=WAL;`,它允许读写并发,配合 `PRAGMA journal_mode=WAL` 提示里说的性能提升,是解决锁冲突的第一步。写操作务必包在事务里,`BEGIN TRANSACTION;` 后连续执行多条 INSERT 再 `COMMIT;`,可以避免每条语句单独加锁。需要局部回滚时用 `SAVEPOINT sp1;` 打点,出问题 `ROLLBACK TO sp1;`,不需要了 `RELEASE SAVEPOINT sp1;`,整体放弃用 `ROLLBACK;`。临时中间表用 `CREATE TEMP TABLE`,会话结束自动清理,不参与主库锁。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。