MySQL自动索引选择机制与优化方法(4/16)

MySQL自动索引选择

MySQL会在某些情况下选择错误索引导致查询性能下降。例如不断地删除历史数据和新增数据的场景。

因为索引选择是由MySQL的优化器决定的,优化器的目标是找到最优的执行方案,最小化执行代价。

在数据库里面,扫描行数是影响执行代价的因素之一。扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。除此之外,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。

优化器索引选择逻辑

**首先考虑索引基数:**索引基数决定了要不要使用索引,如果索引基数太小,就会弃用索引。当索引基数比较大的时候,要不要走这个索引,还得看扫描行数,回表,子查询等等,最后优化器根据预估的成本决定是否走这个索引。

索引的基数(Cardinality)通常被认为是索引的"区分度"。基数指的是索引值的唯一性的度量,即索引列中不同值的数量。基数高意味着列中的值更加多样化,索引能够更好地区分数据行。相反,基数低则意味着列中的值有很多重复,索引在区分数据行方面的效果就不佳。

一个索引上不同的值越多,这个索引的区分度就越好。例如一个字段是布尔型值只有0,1,另一个字段是枚举型有10个枚举值。分别在两个字段上建索引,布尔型字段索引会把数据分成两部分,枚举型会把数据分成十份,根据索引查找的时候,布尔型选择了一个排除了一半,枚举型选一个会排除9/10,所以枚举型区分度更好。

可以使用 show index 方法,看到一个索引的基数。

但这个基数并不准确,是通过采样进行统计的。

在 MySQL 中,有两种存储索引统计的方式,可以通过设置参数 innodb_stats_persistent 的值来选择:

  • 设置为 on 的时候(默认),表示统计信息会持久化存储。这时,默认的 N 是 20,M 是 10。
  • 设置为 off 的时候,表示统计信息只存储在内存中。这时,默认的 N 是 8,M 是 16。

采样统计的时候,InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。

而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。当M为20时,变更行数超过1/20会重新进行索引统计。

**然后再看扫描行数,**在MySQL中,预计扫描行数(rows)是优化器在执行查询之前估算的一个值,用来表示执行特定查询语句可能需要检查的数据行数。这个估算对于优化器选择最佳的执行计划至关重要,因为它影响了查询的性能,从而决定查询会走哪个索引。以下是确定预计扫描行数的几个关键因素:

  1. 数据分布

    优化器会考虑数据的分布情况。如果数据分布不均匀,优化器可能无法准确估算扫描行数,因为它依赖于均匀分布的假设。

  2. 表的大小

    表的总行数也会影响扫描行数的估算。如果表很大,优化器可能会倾向于高估扫描行数,因为它假设数据分布较为均匀。

  3. 查询条件

    查询条件的复杂性也会影响扫描行数的估算。简单的范围查询(如a between 1 and 100)通常比复杂的条件(如多列查询和复杂的JOIN操作)更容易估算。

  4. 历史执行信息

    MySQL可以存储历史执行信息,用于优化器的决策。如果之前的执行表明实际扫描行数与统计信息有显著差异,优化器可能会调整其估算。

  5. 参数设置

    MySQL中的一些参数,如innodb_stats_oninnodb_stats_persistent,会影响统计信息的更新和存储方式,从而间接影响扫描行数的估算。

  6. 优化器的启发式方法

    优化器还会使用一些启发式方法来估算扫描行数,这些方法基于经验规则和历史数据。

当优化器估算出扫描行数后,它会根据这个估算值来选择使用全表扫描、索引扫描、索引回表等不同的执行策略。如果预计扫描行数较少,优化器可能倾向于使用索引;如果预计扫描行数较多,优化器可能选择全表扫描或其他策略。

**需要注意的是,这个估算过程并不总是准确的,**特别是在数据分布不均匀或者统计信息过时的情况下。因此,数据库管理员有时需要手动干预,比如使用ANALYZE TABLE命令来更新统计信息,或者使用FORCE INDEX来强制优化器使用特定的索引,甚至删除无必要的索引。

优化策略

因为整个估算的过程是不精确的,可能会导致优化器在索引选择时没有使用索引,或者选择了错误的索引。从而使得SQL执行效率差,尤其是在建立了索引的情况下。

此时可以通过EXPLAIN命令分析SQL执行中,是否在索引选择上没有按照预期。

如果确实在执行过程中,没有使用索引,或者使用了错误的索引,可以使用以下方法尝试解决:

  • 使用analyze table命令可以重新统计索引信息,解决由于统计信息不准确导致的问题。

  • 通过force index语法可以强制MySQL使用特定的索引。

    --使用force index时,要考虑SQL的迁移成本。
    --使用force index时,确保性能是最优的,因为强制使用某个索引可能会降低查询性能

    --在select语句中使用强制索引
    SELECT * FROM table_name FORCE INDEX (index_name) WHERE condition;

    --在update语句中使用强制索引
    UPDATE table_name FORCE INDEX (index_name) SET column1 = value1, column2 = value2 WHERE condition;

    --在delete语句中使用强制索引
    DELETE FROM table_name FORCE INDEX (index_name) WHERE condition;

  • 修改SQL语句的结构,引导优化器使用期望的索引。

  • 在某些情况下,可以通过增加或删除索引来解决优化器选错索引的问题。

引导方法:

  1. 调整查询条件的顺序

    • 优化器在选择索引时会考虑最左前缀原则,即索引中靠前的列在查询条件中出现时,优化器更倾向于选择这个索引。
    • 例如,如果有一个查询条件是WHERE a = ? AND b = ?,并且有一个组合索引(a, b),那么直接使用这个查询条件就能够让优化器选择组合索引。如果查询条件是WHERE b = ? AND a = ?,则可能不会选择这个组合索引。
  2. 改变排序规则

    • 如果查询包含ORDER BY子句,优化器可能会选择一个能够满足排序要求的索引,以减少额外的排序操作。
    • 例如,如果有一个索引(a, b),查询条件是WHERE a = ?,并且排序要求是ORDER BY b,那么优化器可能会选择使用这个索引,因为它已经包含了排序所需的列。
  3. 使用覆盖索引

    • 覆盖索引是指一个查询可以完全通过索引来得到结果,而不需要访问数据行。
    • 如果可能,设计一个覆盖索引来包含查询所需的所有列,这样优化器更可能选择这个索引,因为它可以减少磁盘I/O操作。
  4. 避免在索引列上使用函数或计算

    • 优化器可能无法使用索引来加速对列的函数或计算操作。
    • 例如,如果有一个索引在列a上,查询条件应该是WHERE a = ?而不是WHERE FUNCTION(a) = ?
  5. 使用索引合并(Index Merge)优化

    • 当查询条件涉及多个索引时,MySQL可以使用索引合并优化来结合这些索引的结果。
    • 通过调整查询条件,可以影响优化器是否使用索引合并。

在使用引导方法时,也要注意修改所需要花费的时间成本。

作者与版本更新计划

感谢您的阅读与支持!本文是《MySQL实战与优化》专栏中的一篇精选文章,该专栏共包含16篇文章,旨在为您提供实战中可直接应用的宝贵知识。

关注公众号【数舟】,获取作者最新动态,公众号后台回复【mysql2024】,即可免费领取这份包含16篇文章的完整的PDF专栏!

目前版本为v1.0,更新时间2024年4月10日。后续此文档更新与版本发布会同步到知识星球【数舟】中。

知识整理与创作不易,感谢大家理解与支持!

加入知识星球,您将获得更多独家内容、专栏更新以及与行业内专家和同行的互动交流机会。我们在知识星球等您,一起探索MySQL的深层次世界!

星球内目前包含300+精品文章,内容涵盖大数据、MySQL、运维、Python、调优、经验分享、数据分析等方向内容,会根据大家的学习需求更新更多方向的内容。

🔗 立即扫描下方二维码,加入知识星球,与行业精英共同成长,开启您的专属学习之旅!

相关推荐
Rookie也要加油22 分钟前
01_SQLite
数据库·sqlite
liuxin3344556627 分钟前
教育技术革新:SpringBoot在线教育系统开发
数据库·spring boot·后端
看山还是山,看水还是。1 小时前
MySQL 管理
数据库·笔记·mysql·adb
fishmemory7sec1 小时前
Koa2项目实战2(路由管理、项目结构优化)
数据库·mongodb·koa
momo小菜pa1 小时前
【MySQL 09】表的内外连接
数据库·mysql
Jasonakeke2 小时前
【重学 MySQL】四十九、阿里 MySQL 命名规范及 MySQL8 DDL 的原子化
数据库·mysql
程序猿小D2 小时前
第二百六十九节 JPA教程 - JPA查询OrderBy两个属性示例
java·开发语言·数据库·windows·jpa
小宇成长录2 小时前
Mysql:数据库和表增删查改基本语句
数据库·mysql·数据库备份
团儿.3 小时前
解锁MySQL高可用新境界:深入探索MHA架构的无限魅力与实战部署
数据库·mysql·架构·mysql之mha架构
程序猿小D3 小时前
第二百六十七节 JPA教程 - JPA查询AND条件示例
java·开发语言·前端·数据库·windows·python·jpa