跳到正文

SQLite

共 4 篇 · 全部标签

索引原理与失效场景:把数据库文件剖开看树,再用 40 条 EXPLAIN QUERY PLAN 原文钉死最左前缀

实测:10 万行的表占 909 页、树高 3 层,索引 i_c 占 259 页、树高 2 层,每个索引的字节数都精确等于"页数 × 4096"(+1060864 字节 = 259 页,一页不多);索引的内部页存的是**完整 key + rowid**,表的内部页只存 rowid;(a,b,c) 上 `WHERE b=500` 的计划是 `SCAN`、`a=500 AND c=500` 只用到 `(a=?)`、`a>500 AND b=500` 只用到 `(a>?)`;`lower(name)=…` 16.2 ms,建表达式索引后 0.168 ms(96 倍);`a=5 OR pad='zz'` 走 `MULTI-INDEX OR`(0.135 ms)而 `a=5 OR b=5` 退回 `SCAN`(3.35 ms);TEXT 亲和列拿整数比照样走索引,BLOB 亲和列拿整数比会**静默少行**(4 行只查到 2 行);同一谓词下 50% 命中范围取未索引列 178 ms、走覆盖索引 1.08 ms(165 倍);首列只有 5 个值时 SQLite 不做跳扫,但扫窄索引比扫表快 3.7 倍,与页账本算出来的 3.5 倍对上;4 个索引让 20000 行插入慢 1.34 倍。

查询:JOIN、子查询与聚合——四种 JOIN 的行数、NULL 把 NOT IN 变成空集、同一语义 13 种写法的实测

实测:`dept_id <> 10` 少掉 1 行 NULL;子查询里一个 NULL 让 `NOT IN` 从 7 行变 0 行;`count(*)=7` 而 `count(dept_id)=6`,`avg` 的分母是 6 不是 7;LEFT JOIN 条件写在 ON 里 1364 行、写在 WHERE 里 1008 行;10 万行上同一语义的 5 种写法差 3.8 倍(IN 10.3 ms vs 标量相关子查询 39.6 ms);给 orders 加两个索引后,一条 LEFT JOIN 从 9.7 ms 变成 57.8 ms(慢 6.0 倍),补一个覆盖索引才翻回 3.3 ms;`OFFSET 50000` 52.8 ms vs 键集分页 0.113 ms。

窗口函数与 CTE:默认帧让累计和翻倍、递归 CTE 会挂死、把 1000 次循环压成一条 SQL 实测快 170 倍

实测:`sum() over (order by v)` 的默认 RANGE 帧把并列行全算进来,10 万行里 99900 行的累计值与 ROWS 帧不同;`last_value` 默认帧右端就是当前行;`row_number`/`rank`/`dense_rank` 取每组前 3 实测给 3000 / 3008 / 3000 行,`max` 连接只给 1006 行;`row_number()` 进 WHERE 报 `misuse of window function row_number()`;`generate_series` 报 `no such table: generate_series`;递归 CTE 不带截断条件遇环跑满 40 s 未返回被 timeout 杀掉,而 `select v from 无上界递归 limit 5` 正常返回 1..5;1000 次 N+1 点查中位 11282 ms vs 一条 GROUP BY 66.3 ms(中位 170 倍);给每笔订单写 Top3 标签,应用层逐行 UPDATE 1204 ms vs `UPDATE FROM`+窗口 552 ms(2.18 倍)。

关系模型与 SQL 入门:把表、主键、外键放到机器上量一遍

实测:VARCHAR(10) 存进 20 字符照样成功;TEXT 主键允许两个 NULL,INTEGER PRIMARY KEY 就是 rowid;外键默认 OFF,孤儿数据留着而 integrity_check 仍然返回 ok;10 万行订单反范式化多花 1.83 倍字节、改名要动 11643 行而不是 116 行;逐条 autocommit 比单事务慢 717–1053 倍。