SQLite 索引:最左前缀不是限制,是排序的结果

复合索引 (a, b) 只有先按 a 排才能按 b 排,所以「跳过 a 只用 b」自然用不上。理解成排序而非规则,就不会再背前缀口诀。

「复合索引 (a, b) 能用于 WHERE a = ? 和 WHERE a = ? AND b = ?,不能用于 WHERE b = ?」这条口诀人人都背过。真正的理由很简单:索引就是一串排好序的行,排序方式决定了能查什么。

索引是排序,不是集合

CREATE INDEX idx ON t (a, b) 建立的顺序是:先按 a 升序,a 相同再按 b 升序。落到磁盘上大致是:

a=1, b=1
a=1, b=5
a=1, b=9
a=2, b=3
a=2, b=7
a=3, b=2

查 a = 2:落在连续一段,二分即可。 查 a = 2 AND b = 7:先定位 a = 2 那段,段内已按 b 有序,再二分。

查 b = 7:7 散布在每一段里,没有任何连续区间包含全部答案。只能全表扫。

这不是数据库的规矩,是排序的必然。

等式与范围的分界

复合索引里,第一个范围条件会终止后续列的使用:

-- 用上 (a, b)
WHERE a = 1 AND b = 2

-- a 用上,b 只用上「有序」而不能再做精确定位
WHERE a = 1 AND b > 2

-- b 完全用不上索引定位
WHERE a > 1 AND b = 2

第三条是最容易写错的形式。因为 a > 1 命中多段,段内的 b 各自独立有序,全局不有序。

怎么排索引里列的顺序

规则 原因
等值列在前,范围列在后 范围会截断后续列
选择性高的列在前 先筛掉更多行
覆盖查询的列可以追加到最后 避免回表

第三条是复合索引最实用的用法:

CREATE INDEX idx_user_time ON posts (user_id, created_at, title);

查询 SELECT created_at, title FROM posts WHERE user_id = ? ORDER BY created_at DESC 能只读索引得到结果,不必回表。列再多一个,索引就胖一分;多到把行本身装进去,不如直接建表。

用 EXPLAIN 验证而不是猜

EXPLAIN QUERY PLAN
SELECT * FROM posts WHERE user_id = 7 ORDER BY created_at DESC;

看到 SEARCH posts USING INDEX idx_user_time 才是真的用了索引;出现 SCAN 就是全表扫描。SQLite 里 ANALYZE 会更新统计信息,帮助优化器选对索引。

索引顺序就是排序顺序。先想「我要按什么顺序取数」,列顺序自然就出来了。

← 返回文章列表

评论

…