让排序直接用索引顺序
排序本身不慢,慢的是为排序把大量行收集起来再排。如果能按索引已有的顺序取数,排序步骤就可以完全去掉。条件是把列的顺序、方向、以及过滤条件这三者对齐:等值过滤的列放在索引前面,排序的列紧跟其后,排序方向与索引方向一致。做不到对齐时,就要想办法减少参与排序的行数。
基本信息
| 适用平台 | MySQL 8.0 / PostgreSQL 12 及以上 |
|---|---|
| 对齐要点 | 过滤列在前导、排序列紧随其后、方向一致 |
| 方向处理 | 反向扫描是允许的,混合方向才需要额外处理 |
| 表达式排序 | 排序键是表达式时普通索引无法直接利用 |
| 结果判读 | 计划里出现排序提示说明排序在内存或磁盘上单独完成 |
典型使用场景
- 列表页按时间倒序分页:希望直接取索引尾部
- 按用户维度排序取最近记录:判断能否消除排序
- 排查排序落盘:大结果集排序溢出到磁盘
- 表达式排序优化:考虑改为函数索引或调整存储
- 评估是否需要为排序单独建索引
可消除与不可消除的排序
可消除与不可消除的排序
-- 建立与排序匹配的复合索引
ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);
-- 可消除排序:等值列在前导,排序列紧随其后
EXPLAIN SELECT * FROM orders
WHERE user_id = 100
ORDER BY created_at DESC
LIMIT 20;
-- 需要额外排序:排序键与索引顺序不匹配
EXPLAIN SELECT * FROM orders
WHERE user_id = 100
ORDER BY total DESC
LIMIT 20; -- 计划里会出现排序提示或独立排序节点
-- 排序无法消除时,先把参与排序的行数压下来
-- 用条件缩小结果集,或在子查询里先筛选再排序
-- 表达式排序需要函数索引或把计算列落库
-- ORDER BY LOWER(name) 这类写法无法直接利用普通索引能否消除排序
| 查询形态 | 能否利用索引顺序 | 说明 |
|---|---|---|
| 等值过滤加排序列 | 可以 | 索引前导等值,后面接排序列 |
| 范围过滤加排序列 | 多数情况不行 | 范围之后的有序性被打断 |
| 多列排序方向不一致 | 不行 | 混合方向需要额外处理 |
| 按表达式排序 | 不行 | 需要函数索引或计算列 |
| 排序键与索引顺序颠倒 | 不行 | 列顺序不对齐则无法复用 |
出现排序步骤不等于性能问题:结果集只有几十行时,内存里排一次远比强行走索引再回表更快。判断依据应该是实际耗时与扫描行数,而不是计划里有没有排序字样。
为了消除排序而改排序键风险很大:排序顺序本身就是业务语义,改了会返回错误顺序的数据。索引调整应该服务于原有排序,而不是反过来改变查询行为。