sqlite中相关子查询默认走nested-loop,性能随外层行数线性下降;应优先改用join或临时表预计算,辅以索引和pragma缓存调优。

相关子查询在 SQLite 3 中默认走 Nested-Loop,数据量稍大就明显变慢——这不是你 SQL 写得不对,而是 SQLite 的执行器天然不擅长处理这类结构。
为什么相关子查询会慢
SQLite 对 WHERE x IN (SELECT ... FROM t WHERE t.id = outer.id) 这类语句,通常不会自动去关联化(un-nesting),而是对外层每一行都执行一次内层查询。如果外层有 1000 行,内层就执行 1000 次,哪怕加了索引也救不了这种结构性开销。
常见错误现象包括:EXPLAIN 显示多层 SCAN 或 SEARCH 嵌套;实际执行时间随外层行数线性增长;sqlite3_stmt_busy() 长时间返回 true。
- SQLite 3.35+ 开始支持部分去关联化,但仅限简单场景(如单表、无聚合、无 GROUP BY)
- 带
ORDER BY/LIMIT的相关子查询几乎必然退化为嵌套循环 - 使用
EXISTS代替IN有时能触发更早的短路判断,但不改变底层执行模型
用 JOIN 替代相关子查询(最直接有效)
把 SELECT a.* FROM accounts a WHERE EXISTS (SELECT 1 FROM logs l WHERE l.account_id = a.id AND l.time > '2024-01-01') 改成 JOIN,让优化器有机会选择哈希或排序合并策略。
实操建议:
- 先确认内层表(
logs)在关联字段(account_id)上有索引:CREATE INDEX idx_logs_account_id ON logs(account_id); - 改写为:
SELECT DISTINCT a.* FROM accounts a JOIN logs l ON l.account_id = a.id WHERE l.time > '2024-01-01'; - 若只需判断存在性,用
SELECT a.* FROM accounts a JOIN (SELECT DISTINCT account_id FROM logs WHERE time > '2024-01-01') l ON l.account_id = a.id;减少中间结果集 - 注意:JOIN 可能重复返回外层行,需用
DISTINCT或GROUP BY控制,但比子查询的 N×M 开销小得多
用临时表预计算中间结果
当 JOIN 也不够用(比如内层逻辑复杂、含聚合或多个条件组合),先把子查询结果物化到 CREATE TEMP TABLE,再与主表关联。
适用场景:
- 子查询本身已带
GROUP BY/MAX()/COUNT()等聚合 - 同一子查询被多个外层查询反复引用
- 子查询过滤后结果集远小于原表(例如日志表中只取最近 1 天活跃用户)
示例:
CREATE TEMP TABLE recent_active AS
SELECT account_id, MAX(time) AS last_time
FROM logs
WHERE time > datetime('now', '-1 day')
GROUP BY account_id;
<p>SELECT a.* FROM accounts a
JOIN recent_active r ON r.account_id = a.id
WHERE r.last_time > '2024-07-01';</p>
关键点:recent_active 表自动带隐式索引(主键或唯一约束可显式加),且只扫描一次原始表。
PRAGMA 设置辅助加速(有限但有用)
某些 PRAGMA 能缓解相关子查询的 IO 压力,但不能改变执行计划本质:
-
PRAGMA cache_size = 8000;—— 增大页缓存,减少重复读取同一数据页的磁盘开销(尤其在嵌套循环中反复访问内表时) -
PRAGMA journal_mode = WAL;—— 允许并发读写,避免子查询期间被写事务阻塞 -
PRAGMA synchronous = NORMAL;—— 降低 fsync 频率,适合非关键业务场景(慎用) - 不要设
PRAGMA auto_vacuum = 1;—— 它对查询性能无帮助,反而增加写开销
这些设置必须在 sqlite3_open() 后立即执行,且只对当前连接生效。
真正卡住性能的往往不是单条 SQL 写法,而是没意识到 SQLite 对相关子查询缺乏深度优化能力。优先考虑结构重写(JOIN / 临时表),再辅以缓存和模式调优——别指望加个索引就能让 WHERE ... IN (SELECT ...) 突然变快。











