功能介绍
Adaptive Ordering Index(自适应排序索引)用于降低
ORDER BY 或 GROUP BY 配合 LIMIT 查询因统计信息偏差而选错排序索引的风险。当
prefer_ordering_index 开启时,优化器可能优先使用能够直接提供目标顺序的现有索引,以避免 filesort,并期望扫描少量记录后便可满足 LIMIT。例如:SELECT *FROM messagesWHERE room_id = 100ORDER BY create_timeLIMIT 10;
如果表上存在以
create_time 开头的索引,优化器可能按时间顺序扫描该索引。但当 room_id = 100 对应的数据在索引中非常稀疏时,查询需要跳过大量不符合条件的记录,实际代价可能远高于“先按过滤条件访问,再排序”的计划。Adaptive Ordering Index 会在执行期间检查排序索引的实际效果。如果实际扫描进度明显差于优化器预期,并且结果尚未发送给客户端,系统会自动放弃本轮执行,重新优化并执行查询;第二轮将不再优先选择该排序索引。整个过程对应用透明,应用只会收到最终结果。
该功能具有以下特点:
不创建新索引:功能只在已有索引访问计划和其他执行计划之间自适应选择,不会创建、修改或删除物理索引。
运行时纠偏:使用真实扫描行数和已产出行数校验优化器估算,减少统计信息不准确或数据分布不均造成的慢查询。
语句级生效:自适应状态仅用于当前语句,不会持久化为索引或长期学习结果。
查询块隔离:子查询、派生表等查询块独立记录自适应状态;某个查询块触发重优化,不会错误地影响其他查询块。
支持正向和反向索引扫描:可覆盖
ORDER BY ... ASC 和 ORDER BY ... DESC 的排序索引访问。支持普通查询和预处理语句:普通
SELECT、多语句请求中的 SELECT 和 prepared statement 均可透明重执行。支持版本
适用于 TDSQL Boundless V21.6.4.0 及以上版本。
适用场景
建议用于以下查询:
包含
ORDER BY 或 GROUP BY,并带有非零 LIMIT;已开启
prefer_ordering_index,优化器可能为避免排序而选择排序索引;过滤列不是排序索引的前导列,或者过滤条件与排序键的分布相关性较强;
数据倾斜、统计信息误差或多表 fanout 估算误差可能导致排序索引扫描量被低估。
使用限制
自适应重执行要求查询可以安全地再次执行,因此以下场景不启用:
非
SELECT 语句或没有有效 LIMIT 的查询;存储过程中的语句;
带副作用或不可安全重执行的查询,例如锁定读、用户变量读写、存储函数、UDF 或其他非确定性结构;
活跃读写事务中的查询;只读事务不受此项限制;
并行查询计划;
通过
FORCE INDEX、FORCE INDEX FOR ORDER BY 或 FORCE INDEX FOR GROUP BY 强制选择索引的查询;首轮结果已经刷新到客户端之后才发现计划低效的查询。
该功能的前提是优化器在首轮实际选择了排序索引。开启功能并不代表每条查询都会重执行,也不会改变未选择排序索引的查询。
工作原理
Adaptive Ordering Index 采用“首轮执行采样 + 必要时重新优化”的两阶段机制。
首轮选择排序索引
优化器首先按照正常流程生成计划。当
prefer_ordering_index=on 时,优化器会比较:使用现有索引直接提供
ORDER BY/GROUP BY 顺序并尽早满足 LIMIT;使用过滤效率更高的访问路径,再执行排序。
对于多表查询,开启
limit_aware_join_order 后,优化器还会评估将带有排序索引的表作为驱动表是否能通过 LIMIT 提前结束,从而调整连接顺序成本。只有首轮最终采用了非强制的排序索引计划,自适应检查才会建立运行时上下文。
计算运行时检查点
优化器记录以下估算值:
L:查询的 LIMIT 行数;E:预计为得到 L 行结果需要扫描的索引行数;T:参数 adaptive_ordering_checkpoint_rows 指定的基础检查阈值。运行时的预期已产出行数为:
如果 E <= T:expected_seen_rows = L如果 E > T:expected_seen_rows = max(L / E × T, 1)
实际触发检查的扫描行数为:
checkpoint_rows = max(T, E / L)
该计算同时考虑固定采样规模和优化器估算的平均“每产出一行需要扫描多少行”,避免检查点过早或过晚。
执行期间校验
驱动表扫描期间,系统累计:
actual_examined_rows:实际扫描的记录数;actual_seen_rows:已经通过上层算子、可计入 LIMIT 的记录数。当扫描量达到
checkpoint_rows 时:如果
actual_seen_rows >= expected_seen_rows,说明排序索引效果符合预期,关闭后续检查并继续执行;如果
actual_seen_rows < expected_seen_rows,说明排序索引的过滤效率明显低于预期,尝试触发自适应重执行。在 TDStore 条件下推场景中,
cond_pushdown_checkpoint 会把检查点传递给存储层,并回传被存储层过滤的行数,使 SQL 层能够正确统计真实扫描量,避免因过滤发生在存储层而漏判。透明重新优化与执行
触发自适应重执行时,系统会:
1. 确认当前结果尚未刷新到客户端;
2. 回退当前语句的协议输出缓冲区;
3. 仅为触发问题的查询块记录“跳过排序索引偏好”标记;
4. 重新优化并执行整条
SELECT;5. 第二轮同时跳过
prefer_ordering_index 和对应的 limit_aware_join_order 优惠,避免通过另一条优化路径再次选回同一类计划;6. 向客户端返回第二轮的最终结果。
每次成功触发重执行,状态计数器
Adaptive_ordering_used 增加 1。第二轮不会再次触发同类重执行,因此一条语句不会因该机制无限重试。参数配置
参数总览
参数 | 默认值 | 范围或选项 | 作用 |
adaptive_plans_switch.ordering_index | on | on / off | 是否启用排序索引的运行时检查和自适应重执行。 |
adaptive_plans_switch.warnings | off | on / off | 重执行时是否向客户端产生 NOTE,说明对应查询块已禁用 prefer_ordering_index/limit_aware_join_order。 |
adaptive_plans_switch.cond_pushdown_checkpoint | on | on / off | 是否为 TDStore 条件下推扫描启用存储层检查点和过滤行统计。通常建议保持开启。 |
adaptive_ordering_checkpoint_rows | 50000 | 1~18446744073709551615 | 基础扫描检查阈值。值越小,越早检查并更容易纠正坏计划;值越大,误判风险和重执行概率越低,但发现坏计划更晚。 |
prefer_ordering_index_fanout_safety_factor | 0.1 | 0.0~1.0 | 多表 limit_aware_join_order 的 fanout 安全系数。值越小,成本优惠越保守。单表排序索引选择不受该参数影响。 |
上述参数支持动态配置。通常使用
SET SESSION 对当前连接生效;也可以通过全局变量或实例启动参数为新连接设置默认值。它们不写入 binlog,并支持通过 SET_VAR 优化器 Hint 进行语句级设置。说明:
adaptive_plans_switch 的默认值虽然已包含 ordering_index=on,但 prefer_ordering_index 默认关闭,因此按照编译默认配置,Adaptive Ordering Index 不会实际介入执行。需要同时开启 prefer_ordering_index。