即使我们为表添加了索引,查询语句最终也不一定会使用索引,因为 InnoDB 是否使用索引、使用哪个索引是由优化器决定的。
SQL 优化器会分析所有可能的执行计划,并根据成本(代价)预估进行选择,倾向于选择成本最低的查询方式。这种优化器称为 CBO(基于成本的优化器)。
那么所谓的代价或成本主要与以下几个因素有关:
- 基数性:
索引的基数性通常指区分度,表示索引中不同取值的数量。基数性越高,索引的区分度越好,优化器越倾向于使用该索引。 - 选择性:
选择性反映索引过滤数据的能力。高选择性意味着索引能过滤掉更多的数据行,优化器更倾向于选择此类索引。该因素是决定扫描行数的关键,选择性越高的索引扫描行数越少。 - 索引覆盖:
如果查询所需的所有列都包含在索引中,即可完全通过索引获取数据,优化器会倾向于使用该“覆盖索引”,避免回表操作。 - ORDER BY:
当查询中包含ORDER BY子句时,如果排序字段上有索引,优化器为了减少文件排序(file sort),会更愿意使用该索引,因为索引本身具有顺序性。 - 索引类型:
不同类型的索引(如 B-TREE、HASH、FULLTEXT 等)适用于不同查询场景,优化器会根据查询类型选择最合适的索引。 - JOIN 类型和顺序:
对于包含 JOIN 的查询,优化器会综合考虑使用哪些索引以及 JOIN 的执行顺序。 - 索引的大小和深度:
较小、深度较浅的索引通常更快,因为占用磁盘空间少,能更快加载到内存中。 - 访问类型:
不同的访问方式,如范围查询、点查找或全表扫描,也会影响索引选择。例如某些索引结构更适用于范围查询。 - 内存使用:
对于大型表,优化器会评估执行计划的内存占用情况,尽量避免过度消耗内存。 - 系统资源限制:
优化器还会结合系统实际资源限制,如内存和磁盘 I/O 能力,进行执行计划选择。 - 查询缓存:
如果启用了查询缓存,且相同查询的结果已被缓存,优化器会直接使用缓存结果,而不再重新选择索引。
这里面比较重要的因素就是索引的基数性(区分度)、索引的选择性(扫描行数)、是否有索引覆盖等这几个。
由于索引选择是由以上多种因素共同决定的,因此最终选错索引可能由以下原因导致:
- 不准确的统计信息
InnoDB 存储引擎依赖统计信息(如基数性、选择性等)来决定使用哪个索引。如果这些统计信息过时或不准确,优化器可能做出错误决策。 - 复杂的查询逻辑
对于包含多表 JOIN、子查询、函数等复杂查询,优化器可能难以准确判断哪个索引最为有效。 - 系统和配置因素
MySQL 的配置设置和系统资源限制(如内存大小)也会影响优化器的决策。
那么,如果发现 MySQL 选择了一个错误的索引,一般来说有以下几种解决方式:
- 更新统计信息
定期运行ANALYZE TABLE命令来更新表的统计信息。这可以帮助优化器更准确地评估各个索引的有效性。 - 使用强制索引(FORCE INDEX)
如果我们确定某个索引比优化器选择的更有效,可以在查询中使用FORCE INDEX来强制使用特定索引。
如:SELECT * FROM clay_test_table FORCE INDEX (idx_name) WHERE name = 'CLAY'; - 但是,
FORCE INDEX应该谨慎使用,因为强制使用特定的索引可能会导致性能下降,特别是当表的数据分布发生变化时。在使用之前,应该确保理解该索引为什么是最好的选择,并且定期评估其效果。 - 优化查询
简化查询逻辑,尽量避免复杂的连接和子查询,这有助于优化器做出更好的决策。 - 调整索引
我们可以为 WHERE 条件中的过滤条件创建更合适的索引,并尽可能考虑创建复合索引来提高查询效率,尤其是对于多列的过滤和排序。 - 调整 MySQL 配置
根据系统的资源和需求调整 MySQL 的配置参数,比如缓冲池大小(innodb_buffer_pool_size)。