先说现象:已经有两个学员反馈因为从5.7升级到8.0,导致count(*)语句的查询性能严重下降。
历史:这个bug 最初在2020就有社区bug report了,由于官方没有标记成严重bug,直到最近还有国内DBA踩到这个坑的, 觉得有必要再喊一声。
顺便介绍一下相关的知识点。
1. 关于count(*) 的一些背景知识:
a) MyISAM引擎是直接把表的总数存起来的,所以count(*) 很快;
b) InnoDB引擎没有【其实也是因为MVCC的要求,不能】, 因此需要真实地一行行算,得到结果;
c) InnoDB是聚集索引,主键索引上是所有字段,非主键索引上有主键id,这就意味着:
c.1) 主键索引一般都比非主键索引大,碰到宽表或者有大字段的表,主键索引大很多;
c.2) 因为带着主键id,非主键索引的叶子节点,不可能是空,这意味着count(*) 扫非主键索引 跟扫主键索引的结果是一样的;
c.3) 因此正常来说,如果是一个纯count(*) from t 的语句,会选最小的那个索引来遍历,得到结果并返回。【合理而且机智】
2. 8.0.17做了什么
a) 以前count(*) 这种操作,是要从InnoDB引擎里一行行读出来,到Server层累加的;
b) 8.0.17给InnoDB增加了一个接口,直接调用InnoBase::records()让InnoDB自己算完返回【设计合理,出发点很好】
c) 但是实现这个records接口的时候,为了用上并行查询,强行使用主键索引。【这不是不小心,是故意的,注释写了,等并行查询支持到普通索引,会考虑改回来】
但是,并行带来多少收益先不说,强行换成扫主键,这个损耗巨大。
对于不是很大的表,比如十万级别的,如果有一个int字段的索引,一个count(*)扫描的数据在百M以内,对于现在的硬件还是可以接受,在从库上还是有用处的,有些运营类的需求,在主库上也能跑。
改成扫主键,基本就是劝退了。
一开始社区那还强行回复是not a bug。好在后来听劝,8.0.37修复了,但是当作low priority,release note一笔带过。
以这个bug的触发概率,标个大红字都不为过,应该让更多DBA知道。
总之结论就是,刚好用来落在[17~36]之间版本的,安排下升级。
往期文章推荐:
MySQL进阶课程
原文作者:丁奇3306;如有侵权,请联系管理员删除。