告别索引选择困境:TiDB优化器如何智能挑选最优索引
你是否曾遇到过这样的情况:明明为表创建了索引,查询却依旧缓慢?TiDB作为分布式关系型数据库,其查询性能很大程度上依赖于优化器对索引的选择。本文将深入解析TiDB优化器的索引选择原理,帮助你理解优化器如何在复杂场景下做出最优决策,避免因统计信息过时导致的性能问题。读完本文,你将掌握索引选择的核心逻辑,学会如何利用TiDB的规则优化查询性能。
索引选择的挑战:统计信息的"双刃剑"
TiDB的查询优化器传统上依赖统计信息来选择访问路径,但统计信息可能因数据更新而过时或不准确,导致选择错误的索引。例如,当表数据发生显著变化而未及时更新统计信息时,优化器可能会基于旧的统计数据选择低效索引,造成查询延迟飙升。
为解决这一问题,TiDB引入了基于规则的索引选择机制,包括启发式规则(Heuristics)和天际线剪枝(Skyline Pruning)。这些机制不依赖统计信息,而是通过预定义的逻辑规则来筛选和排序候选索引,从而提高索引选择的稳健性。相关设计细节可参考规则化索引选择设计文档。
索引选择三剑客:Always-Good、Skyline与Maybe-Good
Always-Good启发式规则:确定性最优选择
Always-Good启发式规则旨在识别那些在任何情况下都是最优的索引选择,无需依赖统计信息。其核心逻辑包括:
-
唯一索引优先:如果查询条件完全匹配唯一索引(包括主键)且只需单次扫描,则直接选择该索引。例如,
SELECT * FROM t WHERE id = 100会直接使用主键索引。 -
双重扫描的唯一索引比较:当唯一索引需要索引扫描+表扫描(双重扫描)时,收集所有此类索引并选择扫描行数最少的作为候选。
-
索引精炼:如果普通索引只需单次扫描,且其访问条件是某个双重扫描唯一索引的子集,则优先选择该普通索引。
以下是一个实际案例:
CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE INDEX idx_b(b), UNIQUE INDEX idx_b_c(b, c));
-- 规则1适用:直接选择主键索引
SELECT * FROM t WHERE a = 2 OR a = 5;
-- 规则3适用:精炼选择idx_b_c而非idx_b
SELECT b, c FROM t WHERE b = 5 AND c > 10;
天际线剪枝:淘汰明显劣势的索引
天际线剪枝(Skyline Pruning)通过多维度比较来淘汰明显劣势的索引。TiDB主要从三个维度评估索引:
- 扫描类型:单次扫描(仅索引扫描)优于双重扫描(索引+表扫描)
- 访问条件覆盖度:索引的访问条件覆盖越多查询条件越好
- 物理属性匹配:索引顺序是否匹配查询的排序需求
当比较两个索引时,如果索引X在所有维度上都不劣于索引Y,且至少有一个维度优于Y,则Y会被剪枝淘汰。例如:
CREATE TABLE t(a INT, b INT, c INT, INDEX idx_b(b), INDEX idx_b_c(b, c));
-- idx_b_c优于idx_b,因为其访问条件覆盖更多查询条件
SELECT * FROM t WHERE b > 5 and c > 5;
详细的剪枝算法实现可参考天际线剪枝设计文档。
Maybe-Good启发式规则:处理边界情况
Maybe-Good启发式规则用于处理那些无法通过前两种规则确定最优解的边界情况,例如全表扫描与范围扫描的选择困境:
CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, INDEX idx_b(b));
-- 优化器可能错误选择全表扫描而非范围扫描
SELECT * FROM t WHERE b = 2 ORDER BY a LIMIT 10;
为此,TiDB提供了tidb_opt_prefer_range_scan系统变量,允许用户控制是否优先范围扫描。该变量支持会话级和全局级配置,满足不同场景需求。
索引选择的实现架构
TiDB的索引选择逻辑主要实现在以下代码路径中:
- Always-Good启发式规则:在
(*DataSource).DeriveStats方法中实现,位于planner/core/datasource.go - 天际线剪枝:在
(*DataSource).findBestTask方法中实现,位于planner/core/datasource.go - Maybe-Good启发式规则:通过系统变量控制,相关代码在planner/core/optimizer.go
这些组件协同工作,形成完整的索引选择流水线:首先生成所有可能的访问路径,然后应用Always-Good规则筛选,接着通过天际线剪枝淘汰劣势路径,最后结合统计信息和Maybe-Good规则做出最终选择。
实践建议:如何辅助优化器做出更好选择
尽管TiDB优化器已具备强大的索引选择能力,但合理的使用策略仍能进一步提升性能:
-
及时更新统计信息:定期执行
ANALYZE TABLE确保统计信息准确性,尤其在大批量数据变更后。 -
合理设置系统变量:根据 workload 特点调整
tidb_opt_prefer_range_scan等参数。例如,对于频繁使用LIMIT的查询,可开启范围扫描优先。 -
创建合适的复合索引:遵循"最左前缀匹配"原则,将选择性高的字段放在索引前面。例如,
WHERE b = 5 AND c > 10应创建(b, c)而非(c, b)索引。 -
利用索引提示:在复杂查询中,可使用
USE INDEX明确指定索引,如SELECT * FROM t USE INDEX(idx_b_c) WHERE b = 5 AND c > 10。
通过理解TiDB优化器的索引选择原理,你可以更好地设计表结构和查询语句,充分发挥TiDB的性能潜力。TiDB的索引选择机制持续进化,更多优化将在未来版本中推出,敬请关注TiDB路线图获取最新动态。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



