开始前,会这些就够了
- 知道表的行和列
- 看得懂简单 WHERE 条件
假设订单按“店铺编号、下单时间”建立联合索引。查某家店今天的订单很顺手,只查今天所有店铺的订单却未必同样省事。明明查询都用到了索引中的列,差别在哪里?先暂时放下树的层数,看看这些值是怎样排在一起的。
第二列只在第一列相同时继续排序
可以先把索引键想成排好序的二元组:先比较店铺编号,店铺相同再比较时间。这样某家店的记录聚在一段,这家店某个时间范围又是其中的一小段。但“所有店铺中时间为二”的记录,会散在每家店自己的那一段里。
这只是理解键顺序的模型。真实 MySQL InnoDB 的索引还有页、记录和主键信息,字符串比较也受排序规则影响。这里使用整数,让你只观察列的先后关系;不借这个小列表声称磁盘读取次数或实际查询速度。
打印它们的位置,而不是背口诀
程序把四条记录按“店铺、时间、订单号”排序,再找出店铺等于一与时间等于一的记录下标。前一种条件定位到连续位置,后一种条件跨了两个店铺。这解释了为什么固定前面的列,往往更容易把后面的条件变成一段紧凑查找。
SQL 中 AND 条件的书写顺序不是索引列顺序。WHERE time = 2 AND shop = 1,不会仅因为把时间写在前面就失去按 shop、time 查找的能力。数据库看的是条件和索引结构,不能把“最左”理解为 WHERE 语句中最靠左的文字。
rows = [(2, 1, 201), (1, 2, 102), (2, 2, 202), (1, 1, 101)]
ordered = sorted(rows)
print(ordered)
print("shop=1", [i for i, row in enumerate(ordered) if row[0] == 1])
print("time=1", [i for i, row in enumerate(ordered) if row[1] == 1])
怎样运行:Python 3.10+;保存为 prefix_demo.py,运行 python prefix_demo.py。
运行后对照结果
[(1, 1, 101), (1, 2, 102), (2, 1, 201), (2, 2, 202)]
shop=1 [0, 1]
time=1 [0, 2]
少了第一列,不等于索引永远没用
最左前缀解释的是常见的定位方式,不是所有执行计划的禁令。MySQL 8.4 在满足要求时可以使用 Skip Scan:枚举前面不同的值,再分别查后面的范围。也可能扫描整个索引,或因为索引已经包含所需列而避免再访问完整行。它们和一次直接定位不是同一种成本。
同样,“遇到范围之后的列完全无用”也太绝对。某个条件可能不能继续缩紧主要查找区间,却仍能用于过滤,或作为覆盖查询所需的数据。判断时要分别问:少读了哪些索引记录,少读了哪些完整行,还有没有额外排序。
为实际查询挑顺序
如果主要查询都是“某店铺最近二十单”,shop、time 的顺序值得考虑;如果主要查“全站最近二十单”,需求已经变了。不能只说某列区分度高,就要求它在所有联合索引里排第一,还要看等值、范围、排序与返回列怎样配合。
用固定数据库版本和接近真实分布的练习数据比较 EXPLAIN,再验证返回结果和实际耗时。小表选择全表扫描可能很合理,强行用索引不等于更快。还应记录各店铺的数据量是否悬殊,以及查询一次返回多少行;同一个条件命中十行和命中九成记录,优化器面对的选择并不一样。这里学到的顺序直觉用于提出候选方案,最后仍要让执行计划和测量给出证据。
记住这三点
- 联合索引先按第一列排列,再在相同值里比较后续列。
- WHERE 中条件的文字顺序不决定索引列顺序。
- 索引定位、扫描、过滤、覆盖和排序是不同作用。
别把例子用过头
- Python 排序不是 B+ 树实现,不能据此推断真实 I
/O次数。 MySQL8.4 的 Skip Scan 有适用条件,不能保证缺前缀时一定出现。- 本篇讨论联合索引的列前缀,不是字符串前缀索引的截取长度。
自己试一小步
把列表改为先按时间再按店铺排序,哪一种查询的位置会变得连续?
- 每条记录仍按“店铺、时间、订单号”保存,只改变排序时先比较哪一列。查询条件使用的列位置保持不变。
- 增加一个同时限制店铺和时间的查询。
- 给两个不同查询场景分别提出候选索引,并说明你要查看什么证据。
做到这些,就算完成
- 没有把条件书写顺序当作索引顺序。
- 清楚区分“可以使用”与“优化器实际选择”。
- 没有用四行玩具数据宣称真实性能提升。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- MySQL 8.4: Multiple-Column Indexes (新标签页)↗
支持联合索引按列前缀定位及与多个单列索引的区别。
- MySQL 8.4: Range Optimization (新标签页)↗
支持范围访问、Skip Scan 的多个子范围、适用限制及执行计划标记。
找一份资料继续练
先选一项,按下面的范围继续学;不必从头读完整门课程。
TiDB 执行计划入门:数据库准备怎样查数据 (新标签页)↗
只读“解读 EXPLAIN 的返回结果”,给一次查询标出所选索引与估计行数,不把出现索引名称就当成扫描很少。
查看学习准备与访问说明 →