MySQL的査询优化器会通过两个API来了解存储引擎的索引值的分布信息,以决定如何使用索引。第一个API是 records_in_range(),通过向存储引擎传入两个边界值获取在这个范围大概有多少条记录。对于某些存储引擎,该接口返回精确值,例如MyISAM;但对于另一些存储引擎则是一个估算值,例如 InnoDB。
第二个API是info(),该接口返回各种类型的数据,包括索引的基数(每个键值有多少条记录)。
如果存储引擎向优化器提供的扫描行数信息是不准确的数据,或者执行计划本身太复杂以致无法准确地获取各个阶段匹配的行数,那么优化器会使用索引统计信息来估算扫描行数。 MySQL优化器使用的是基于成本的模型,而衡量成本的主要指标就是一个查询需要扫描多少行。如果表没有统计信息,或者统计信息不准确,优化器就很有可能做出错误的决定。可以通过运行ANALYZE TABLE来重新生成统计信息解决这个问题。
每种存储引擎实现索引统计信息的方式不同,所以需要进行ANALYZE TABLE的频率也因不同的引擎而不同,每次运行的成本也不同:
- Memory引擎根本不存储索引统计信息。
- MyISAM将索引统计信息存储在磁盘中, ANALYZE TABLE需要进行一次全索引扫描来计算索引基数。在整个过程中需要锁表。
- 直到 My SQL5.5版本,InnoDB也不在磁盘存储索引统计信息,而是通过随机的索引访问进行评估并将其存储在内存中。
可以使用 SHOW INDEX FR0M命令来查看索引的基数( Cardinality)。例如
mysql> SHOW INDEX FROM people
image.png
这个命令输出了很多关于索引的信息,在MySQL手册中对上面每个字段的含义都有详细的解释。这里需要特别提及的是索引列的基数(Cardinality),其显示了存储引擎估算索引列有多少个不同的取值。在 MySQL5.0和更新的版本中,还可以通过
FORMATION_SCHEMA. STATISTICS表很方便地查询到这些信息。例如基于 INFORMATION_SCHEMA的表,可以编写一个查询给出当前选择性比较低的索引。需要注意的是,如果服务器上的库表非常多,则从这里获取元数据的速度可能会非常慢,而且会给 MySQL带来额外的压力。
InnodB的统计信息值得深入研究。 InnoDB引擎通过抽样的方式来计算统计信息,首先随机地读取少量的索引页面,然后以此为样本计算索引的统计信息。在老的 InnodB版本中,样本页面数是8,新版本的 InnoDB可以通过参数 innodb_stats_sample_pages来设置样本页的数量。设置更大的值,理论上来说可以帮助生成更准确的索引信息,特别是对于某些超大的数据表来说,但具体设置多大合适依赖于具体的环境。
InnoDB会在表首次打开,或者执行 ANALYZE TABLE,抑或表的大小发生非常大的变化(大小变化超过十六分之一或者新插入了20亿行都会触发)的时候计算索引的统计信息。
InnoDB在打开某些INF0RMATION_SCHEMA表,或者使用 SHOW TABLE STATUS和SHOW INDEX,抑或在MySQL客户端开启自动补全功能的时候都会触发索引统计信息的更新。如果服务器上有大量的数据,这可能就是个很严重的问题,尤其是当IO比较慢的时候。客户端或者监控程序触发索引信息采样更新时可能会导致大量的锁,并给服务器带来很多的额外压力,这会让用户因为启动时间漫长而沮丧。只要SHOW INDEX查看索引统计信息,就一定会触发统计信息的更新。可以关闭 innodb_stats_on_metadata参数来避免上面提到的问题。
如果想要更稳定的执行计划,并在系统重启后更快地生成这些统计信息,那么可以使用系统表来持久化这些索引统计信息。甚至还可以在不同的机器间迁移索引统计信息,这样新环境启动时就无须再收集这些数据。在官方的5.6版本都已经加入这个特,通过innodb_analyze_is_persistent参数控制。
一旦关闭索引统计信息的自动更新,那么就需要周期性地使用ANALYZE TABLE来手动更新。否则,索引统计信息就会永远不变。如果数据分布发生大的变,可能会出现一些很糟糕的执行计划。
网友评论