6月1日 00:42

如何优化 SQLite 性能?索引、事务和 PRAGMA 怎么用?

优化 SQLite 性能,先别急着改一堆 PRAGMA。大多数慢查询来自三个地方:没有合适索引、事务拆得太碎、一次取了太多无用数据。SQLite 很快,但它不是魔法;把查询路径、写入批次和文件维护做好,通常比盲目调参数更稳定。

sql
EXPLAIN QUERY PLAN SELECT id, title FROM posts WHERE user_id = ? AND status = 'published' ORDER BY created_at DESC LIMIT 20; CREATE INDEX idx_posts_user_status_time ON posts(user_id, status, created_at DESC); BEGIN; INSERT INTO logs(message, created_at) VALUES (?, datetime('now')); INSERT INTO logs(message, created_at) VALUES (?, datetime('now')); COMMIT; PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA cache_size = -20000; PRAGMA temp_store = MEMORY; ANALYZE; PRAGMA optimize;

第一步是看执行计划。EXPLAIN QUERY PLAN 能告诉你查询是在 SCAN 全表,还是在 SEARCH 索引。看到全表扫描不一定立刻加索引,小表扫描可能比走索引还便宜;但如果这是高频查询,且过滤列、排序列很稳定,就应该设计组合索引。组合索引的顺序要贴近查询条件,常见写法是等值过滤在前,范围和排序在后。

第二步是控制写入成本。SQLite 每次事务提交都涉及日志和同步,如果一万条数据拆成一万次提交,慢是正常的。批量导入时用一个事务包起来,性能差距会非常明显。事务也不能太长,尤其不要在事务里做网络请求或复杂计算,否则其他写入会被锁住。

第三步才是配置。WAL 适合改善读写并发,synchronous=NORMAL 常用于在可靠性和速度之间折中,cache_size 可以用负数按 KiB 设置缓存上限。ANALYZE 会更新统计信息,让查询规划器更懂数据分布;较新的 SQLite 也推荐周期性执行 PRAGMA optimize;VACUUM 能回收大量删除后的空洞,但它会重建数据库文件,最好放在低峰期。

追问

索引是不是越多越好?

不是。索引能加速读取,但每次插入、更新、删除都要维护索引,索引太多会拖慢写入并占用更多磁盘。取舍上,只给高频查询、明确过滤或排序的字段建索引,临时低频报表不一定值得。踩坑点是给每个单列都建索引,却忽略真实查询使用的是多列条件,结果优化器仍然选不到理想路径。

为什么批量插入一定要用事务?

因为 SQLite 的提交成本比单条 INSERT 本身更贵,事务能把多次写入合并成一次提交。比如导入几千行日志,如果每行自动提交一次,会反复刷日志和申请锁;放进 BEGIN/COMMIT 后,写入会顺很多。边界是单个事务也别无限大,移动端或内存紧张环境可以按几千条一批提交。踩坑点是异常时忘记 ROLLBACK,连接留在事务状态里,后续操作都变得奇怪。

EXPLAIN QUERY PLAN 看到全表扫描就一定有问题吗?

不一定。表很小、过滤条件选择性很差、或者查询本来就要读大部分数据时,全表扫描可能是合理的。判断时要结合数据量、调用频率和响应时间,而不是只看 SCAN 两个字。真正要警惕的是大表高频查询反复扫描,或者排序时出现临时 B-tree,这通常说明索引没覆盖过滤和排序。优化前后都要重新看计划,避免凭感觉调。

WAL、cache_size、temp_store 这些 PRAGMA 怎么选?

WAL 适合有并发读写的应用,纯一次性脚本不一定需要。cache_size 可以提升热点页命中率,但设置过大可能挤压应用内存,尤其是多进程同时打开数据库时。temp_store=MEMORY 能减少临时排序和中间表落盘,但大排序会吃内存。取舍原则是先用默认值跑基准,再针对瓶颈改一项测一项,不要把网上配置整段复制进生产。

VACUUM 和 ANALYZE 什么时候执行?

ANALYZE 适合在大批量导入、删除或新建索引后执行,它帮助查询规划器更新统计信息。VACUUM 适合大量删除后回收文件体积,或者需要重建数据库文件时执行,但它成本更高,会占用时间和磁盘空间。边界是频繁 VACUUM 不会让普通查询神奇变快,反而可能影响正常业务。比较稳的做法是在低峰维护窗口执行,并在执行前确认有足够磁盘空间和备份。

标签:Sqlite