帮你快速理解、总结文档立即下载

Ordering Index

最近更新时间:2026-08-20 15:23:31
我的收藏

功能概述

Ordering Index(有序索引)不是一种新的索引类型,而是指优化器利用普通索引天然有序的特性,按查询需要的顺序读取数据。
当索引顺序与 ORDER BYGROUP BY 的顺序要求兼容时,数据库可以获得以下收益:
Skip Sort:避免 filesort,减少排序的 CPU、内存和临时空间开销;
流式聚集:相同分组的数据连续到达,完成一组即可输出一组;
LIMIT 提前终止:按目标顺序取得足够结果后停止扫描;
LIMIT-aware Join Order:多表查询中,将有序读取和提前终止的收益纳入连接顺序选择。
该能力适合分页、Top-N、时间线、排行榜,以及按维度分组的聚集查询。
说明:
“使用 Ordering Index”既可能表示当前过滤索引本身已经满足顺序,也可能表示优化器从过滤性更强的索引切换到能够提供顺序的索引。

支持版本

适用于 TDSQL Boundless V21.6.4.0 及以上版本。

索引如何提供顺序

以联合索引 INDEX(a, b, c) 为例,索引先按 a 排序,再在相同 a 内按 bc 排序。因此,下列查询可以直接利用索引顺序:
SELECT *
FROM t
ORDER BY a, b, c;
如果前导列被等值条件固定,也可以从后续列开始利用顺序:
SELECT *
FROM t
WHERE a = 10
ORDER BY b, c;
此时 a 只有一个值,INDEX(a, b, c) 对当前结果集提供的有效顺序等价于 (b, c)
以下查询通常不能利用该索引完成全局排序:
SELECT *
FROM t
ORDER BY b, c;
不同 a 值内部的 (b, c) 分别有序,但合并后不具备全局 (b, c) 顺序。

排序方向

索引可以正向或反向扫描。升序索引可以通过反向扫描满足:
SELECT *
FROM t
WHERE a = 10
ORDER BY b DESC
LIMIT 20;
传统 EXPLAIN 可能显示 Backward index scan
如果 ORDER BY 包含混合方向,建议使索引定义与排序方向一致:
CREATE INDEX idx_score_time
ON ranking(category ASC, score DESC, created_at ASC);
对应查询:
SELECT *
FROM ranking
WHERE category = 1
ORDER BY score DESC, created_at ASC
LIMIT 20;
方向不匹配、存储引擎不支持反向读取,或混合方向的多范围扫描无法保持顺序时,优化器仍会使用 filesort

范围条件的影响

联合索引前导列上的范围条件可能破坏后续列的全局顺序:
-- 索引为 INDEX(a, b)
SELECT *
FROM t
WHERE a BETWEEN 1 AND 10
ORDER BY b;
每个 a 值内部的 b 有序,但多个 a 范围合并后通常不能保证全局 b 有序,因此仍可能需要 filesortIN 形成多个范围时也需按执行计划确认。

利用 Ordering Index 实现 Skip Sort

如果访问路径输出的记录已经满足最终顺序,执行阶段不再创建额外排序步骤,这就是 Skip Sort。
有序路径:
索引范围扫描或索引扫描
-> 条件过滤
-> 返回结果
普通排序路径:
范围扫描或全表扫描
-> 条件过滤
-> 临时结果
-> filesort
-> 返回结果
Skip Sort 不要求索引覆盖所有返回列。非覆盖索引也可以提供顺序,但可能需要逐行回表。优化器会综合比较:
当前过滤访问路径的读取成本;
Ordering Index 的扫描和回表成本;
过滤条件选择率;
排序成本;
LIMIT 大小;
多表连接的 fanout。

单表 Top-N 示例

CREATE TABLE events (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
created_at DATETIME NOT NULL,
payload VARCHAR(200),
KEY idx_user_time(user_id, created_at DESC)
);

EXPLAIN
SELECT id, created_at, payload
FROM events
WHERE user_id = 100
ORDER BY created_at DESC
LIMIT 20;
理想计划特征:
keyidx_user_time
Extra 中没有 Using filesort
反向读取升序索引时可能显示 Backward index scan
是否使用 Ordering Index 由成本决定。数据规模很小,或其他索引过滤后只需排序少量数据时,选择 filesort 也可能是更优计划。

利用 Ordering Index 实现流式聚集

工作原理

对于 GROUP BY,只要输入数据按分组键兼容的顺序排列,相同分组的记录就会连续到达。执行器可以:
1. 初始化当前分组的聚集状态;
2. 持续累加当前组记录;
3. 发现分组键变化时输出上一组;
4. 重置状态并开始下一组。
该方式通常只需维护当前分组及聚集函数状态,不需要同时保存全部分组,因而可以降低聚集内存和首批结果延迟。

使用示例

CREATE TABLE metrics (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
category INT NOT NULL,
value DECIMAL(18,2) NOT NULL,
KEY idx_tenant_category(tenant_id, category)
);

EXPLAIN
SELECT category, COUNT(*), SUM(value)
FROM metrics
WHERE tenant_id = 100
GROUP BY category;
tenant_id 被等值条件固定后,索引可以按 category 连续输出数据。理想计划特征:
keyidx_tenant_category
Extra 中没有 Using temporary
Extra 中没有 Using filesort

prefer_ordering_index

功能说明

prefer_ordering_index 控制优化器是否更积极地搜索能够满足 ORDER BYGROUP BY 顺序的替代索引。
开启后,如果当前过滤路径不能提供所需顺序,优化器会评估其他候选索引,主要考虑:
是否完整提供目标顺序;
正向还是反向扫描;
是否为覆盖索引;
Ordering Index 的扫描、回表成本;
当前访问路径的读取成本;
LIMIT 大小;
GROUP BY 平均每组记录数;
后续连接表的 fanout。
该开关允许优化器选择成本合理的有序路径,并不强制使用某个索引。

配置方式

当前会话开启:
SET SESSION optimizer_switch = 'prefer_ordering_index=on';
单语句开启:
SELECT /*+ SET_VAR(optimizer_switch='prefer_ordering_index=on') */
id, created_at, payload
FROM events
WHERE user_id = 100
ORDER BY created_at DESC
LIMIT 20;
关闭:
SET SESSION optimizer_switch = 'prefer_ordering_index=off';
不同版本或部署模板的默认值可能不同,应以实例的 @@global.optimizer_switch@@session.optimizer_switch 为准。

关闭后仍可能 Skip Sort

prefer_ordering_index=off 不等于禁用所有索引排序优化。以下场景仍可能 Skip Sort:
当前因过滤条件选择的索引本身已经提供顺序;
当前访问方式为表扫描或全索引扫描,优化器找到成本合适的有序索引;
使用了 FORCE INDEX FOR ORDER BYFORCE INDEX FOR GROUP BY
其他优化规则已经确定有序访问路径。
该开关主要影响是否主动从已有访问路径切换到另一个 Ordering Index。

适用场景与风险

典型场景:
SELECT ...
FROM t
WHERE filter_col = ?
ORDER BY order_col DESC
LIMIT 10;
LIMIT、符合条件的记录在 Ordering Index 中分布较均匀,且待排序数据量较大时,收益通常更明显。
Ordering index 优先保证顺序,不一定具有最强过滤能力。例如:
SELECT *
FROM messages
WHERE receiver_id = 100
AND status = 1
ORDER BY created_at DESC
LIMIT 20;
只使用 INDEX(created_at) 可能扫描大量无效记录。更合适的索引通常是:
CREATE INDEX idx_receiver_status_time
ON messages(receiver_id, status, created_at DESC);
如果版本启用了 Adaptive Ordering Index,系统可以在实际扫描明显超出估算时重新优化,作为运行时保护。可通过下列状态观察是否触发:
SHOW SESSION STATUS LIKE 'Adaptive_ordering_used';
自适应保护不能替代合理的联合索引和准确的统计信息。

LIMIT-aware Join Order

功能说明

传统连接顺序优化主要比较各表的过滤、连接和读取成本。对于多表 ORDER BY ... LIMIT,如果排序列位于非驱动表,通常需要完成较多连接后再排序。
LIMIT-aware Join Order 会评估另一类计划:
1. 让能按 ORDER BY 顺序读取的表作为驱动表;
2. 通过 Ordering Index 按目标顺序扫描;
3. 逐行连接后续表;
4. 产生足够结果后提前停止。
这样可能同时减少驱动表扫描量、后续连接次数和最终排序开销。

配置方式

开启连接顺序成本优化:
SET SESSION optimizer_switch = 'limit_aware_join_order=on';
生产使用通常建议与 Ordering Index 偏好一并开启,使单表访问路径选择和多表连接顺序选择协同工作:
SET SESSION optimizer_switch =
'prefer_ordering_index=on,limit_aware_join_order=on';
单语句开启时,应在同一个 optimizer_switch 字符串中合并两个子选项:
SELECT /*+ SET_VAR(
optimizer_switch='prefer_ordering_index=on,limit_aware_join_order=on'
) */
o.id, o.updated_at, d.tag
FROM order_detail AS d
JOIN orders AS o ON o.org_id = d.org_id
WHERE d.tag = 1
AND o.region_id = 1
ORDER BY o.updated_at DESC
LIMIT 5;

多表 Top-N 示例

建议索引:
CREATE INDEX idx_orders_region_time
ON orders(region_id, updated_at);
查询:
EXPLAIN
SELECT o.id, o.updated_at, d.tag
FROM order_detail AS d
JOIN orders AS o ON o.org_id = d.org_id
WHERE d.tag = 1
AND o.region_id = 1
ORDER BY o.updated_at DESC
LIMIT 5;
region_id 被等值固定后,orders 可以通过 idx_orders_region_time 反向扫描 updated_at
优化前可能表现为:
order_detail 为驱动表;
连接后出现 Using temporary; Using filesort
优化后可能表现为:
orders 成为第一张非 const 表;
使用 idx_orders_region_time
显示 Backward index scan
不再显示 Using filesort

成本估算

优化器根据后续连接的平均 fanout,估算为了产生 LIMIT 条最终结果需要从驱动表读取多少行:
> 预计驱动表读取行数 = max(LIMIT / 有效后缀 fanout, 1)
再按预计读取量占驱动表候选行数的比例折算完整计划成本。

生效条件与限制

典型生效条件包括:
多表查询包含有效的 ORDER BYLIMIT
ORDER BY 可由驱动表的可用 refrange 索引满足;
排序项是可索引字段,而不是普通索引无法直接满足的表达式;
驱动表有序输出能被后续连接算子保持;
预计只需读取驱动表的一部分数据即可产生足够结果;
调整后的完整计划成本优于其他连接顺序。
以下场景通常不应用该优化:
没有 LIMIT 或没有 ORDER BY
LIMIT 很大,提前停止收益不足;
排序字段来自多张表,不能由一个驱动表提供最终顺序;
包含聚集、HAVING、窗口函数或 DISTINCT
ORDER BY 使用普通索引不能满足的表达式;
后续 join buffer、BKA/MRR 等执行方式不能保持驱动表顺序;
所选 range 访问无法按需要方向稳定输出。
STRAIGHT_JOIN 或固定连接顺序的 Hint 会限制优化器调整 join order。希望该能力自动选择驱动表时,不应固定一个与 Ordering Index 方案冲突的连接顺序。

索引设计指南

WHERE + ORDER BY + LIMIT

推荐的联合索引顺序通常是:
等值过滤列 -> 排序列 -> 其他用于覆盖的列
例如:
SELECT id, created_at, title
FROM articles
WHERE tenant_id = 10
AND status = 1
ORDER BY created_at DESC
LIMIT 20;
推荐索引:
CREATE INDEX idx_article_tenant_status_time
ON articles(tenant_id, status, created_at DESC);

WHERE + GROUP BY

推荐顺序通常是:
等值过滤列 -> 分组列 -> 其他用于过滤或覆盖的列
例如:
SELECT category, COUNT(*), SUM(amount)
FROM sales
WHERE tenant_id = 10
GROUP BY category;
推荐索引:
CREATE INDEX idx_sales_tenant_category
ON sales(tenant_id, category);

覆盖索引

将高频返回列纳入索引可以减少回表,但需要权衡索引空间、写放大、DML 性能和缓存利用率。不建议仅为消除一次小规模排序而创建过宽索引。

分页方式

即使 Skip Sort,大偏移分页仍需跳过大量索引项:
ORDER BY created_at DESC
LIMIT 100000, 20;
建议使用游标式翻页:
SELECT id, created_at, payload
FROM events
WHERE tenant_id = 10
AND (created_at, id) < (?, ?)
ORDER BY created_at DESC, id DESC
LIMIT 20;
配套索引:
CREATE INDEX idx_events_tenant_time_id
ON events(tenant_id, created_at DESC, id DESC);
将主键等唯一列加入排序,可以在排序列重复时保证分页结果稳定。

如何确认优化是否生效

查看 EXPLAIN

检查项
说明
table 顺序
多表查询中,排序列所在表是否成为第一张非 const 表
key
是否选择能够提供目标顺序的索引
type
可能是 refrangeindex,不能只凭访问类型判断
Using filesort
消失通常表示已 Skip Sort
Using temporary
在分组查询中消失通常表示避免临时表聚集
Backward index scan
表示通过反向扫描提供顺序
Using index
表示覆盖索引,不等同于使用索引排序
Using index for group-by
表示 Loose Index Scan,不是普通流式聚集的必要标志
不要只看 key。即使选择了排序列相关索引,如果 Extra 仍有 Using filesort,说明该访问路径没有完整满足最终顺序。

查看 Optimizer Trace

SET SESSION optimizer_trace = 'enabled=on';
SET SESSION optimizer_trace_max_mem_size = 1048576;

EXPLAIN
SELECT ...;

SELECT JSON_PRETTY(TRACE)
FROM information_schema.optimizer_trace;

SET SESSION optimizer_trace = 'enabled=off';
Ordering Index 选择重点关注:
ordering_index_selection
contextorder_bygroup_bytable_scan
indexes_considered
directionused_key_partsis_covering
index_scan_costchosen
selected_indexselected_direction
常见未选原因包括:
index cannot provide required order
index scan cost higher than read_time
no limit and not covering
covering index preferred over non-covering
ref_key covering with better selectivity
LIMIT-aware Join Order 重点关注:
ordering_limit_aware_cost
ordering_keyselect_limit
suffix_fanoutfanout_safety_factor
driving_tab_early_stop_ratio
new_cost_for_plan
如果没有 ordering_index_selection,可能是当前路径已经天然有序,无需搜索替代索引;也可能是查询不满足优化条件。

能力边界总结

能力
主要场景
主要收益
核心条件
Ordering Index Skip Sort
ORDER BY
避免 filesort
索引完整提供目标顺序
Ordering Index 流式聚集
GROUP BY
避免排序/临时聚集,降低内存和首行延迟
输入按分组键连续
prefer_ordering_index
单表或既定 join order 下的排序、分组
在过滤路径与有序路径之间做成本选择
开关开启且有序路径成本更优
LIMIT-aware Join Order
多表 ORDER BY ... LIMIT
调整驱动表,按序连接并提前停止
顺序可保持、LIMIT 收益明显
Adaptive Ordering Index
Ordering Index 估算偏差
运行时避开扫描量过大的偏好路径
版本支持且自适应能力开启
Ordering Index 的核心价值不是尽可能以索引代替排序,而是在过滤、读取、回表、连接、排序和提前终止之间选择总体成本更低的执行路径。