MySQL---优化器优化的真与假

优化器,顾名思义是对将要做的事情进行优化以达到提升的效果。MySQL里的优化器是找到一个最优的执行方案,并用最小的代价去执行语句。

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

扫描行数的确定

MySQL在真正开始执行语句之前,并不能精确地知道满足这个条件的记录有多少条,而只能根据统计信息来估算记录数。

统计信息是索引的“区分度”。一个索引上不同的值越多,索引的区分度就越好。而一个索引上不同的值的个数,通常称之为“基数”(cardinality)。因此,这个基数越大,索引的区分度越好。

通过show index方法,看到一个索引的基数。 show index from table;

基数的确定

简单的可以把整张表取出来一行行统计,虽然可以得到精确的结果,但是代价太大,因此MySQL使用采样统计来确定基数。

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

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

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

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

由于是采样统计,所以基数的值并不是十分准确。

索引错误选择

因为基数的值并不是准确的,因此有时候优化器会进行错误的选择。

当一张表具有大量数据时,表内字段id设有主键索引,card_id设有普通索引。对于SQL select * from person where card_id = 1001;,执行普通索引是一个更佳的选择,但是有时候优化器会选择主键索引,这是什么原因?

这是因为普通索引每次都会再通过主键索引进行扫描,也就是之前说的回表。由于基数值的不准确,优化器会认为多次的回表效率低于主键索引,所以导致了错误的选择。

解决办法:

使用 analyze table 表名 重新统计索引信息。

如何避免索引选择错误?

大多数时候优化器都能找到正确的索引,但是碰到与预期时间相差很大的SQL语句该怎么办?

  1. 使用 force index 强行选择索引。但是当索引修改后,就需要时刻注意对SQL语句的维护,这也是很多程序员不喜欢此方法的原因。
  2. 优化SQL语句,引导MySQL使用我们期望的索引。比如SQLselect * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1,把order by b limit 1改成 order by b,a limit 1 ,语义的逻辑是相同的,修改前优化器认为使用索引b可以避免排序,将其认定为代价最小的情况。修改后意味着使用这两个索引都需要排序,优化器会选择两者扫描行数最少的索引。但是注意的是优化SQL语句不要改变其本意。
  3. 新建一个更合适的索引或删掉误用的索引。有时优化器错误选择的索引其实根本没有必要存在,删掉了这个索引后优化器就重新选择到了正确的索引。

猜你喜欢

转载自blog.csdn.net/MAKEJAVAMAN/article/details/118496460