一篇文章说清楚MySQL的count

date
Aug 18, 2023
slug
understand-the-performance-of-mysql-count-function
status
Published
tags
Tech
summary
很多人都知道count(*)count(1)count(col_name)它们在性能上有什么区别
type
Post

前言

相信在此之前,很多人都只是记忆,没去理解,只知道count(*)count(1)包括了所有行,在统计结果的时候,不会忽略列值为NULLcount(列名)只统计列名那一列,在统计结果的时候,会忽略列值为NULL的记录。

count是怎么样执行的?

当你需要统计表中有多少数据的时候,会经常使用如下语句
由于聚集索引和非聚集索引中的记录是一一对应的,而非聚集索引记录中包含的列(索引列+主键id)是少于聚集索引(所有列)记录的,所以同样数量的非聚集索引记录比聚集索引记录占用更少的存储空间。如果我们使用非聚集索引执行上述查询,即统计一下非聚集索引uk_key2中共有多少条记录,是比直接统计聚集索引中的记录数节省很多I/O成本。所以优化器会决定使用非聚集索引uk_key2执行上述查询。
注意:这里已经验证过了,uk_key2比其他索引成本更低。 详情可见MySQL查询为什么选择使用这个索引?——基于MySQL 8.0.22索引成本计算
在执行上述查询时,server层会维护一个名叫count的变量,然后:
  • server层向InnoDB要第一条记录。
  • InnoDB找到uk_key2的第一条二级索引记录,并返回给server层(注意:由于此时只是统计记录数量,所以并不需要回表)
  • 由于count函数的参数是*,MySQL会将*当作常数0处理。由于0并不是NULL,server层给count变量加1
  • server层向InnoDB要下一条记录
  • InnoDB通过二级索引记录的next_record属性找到下一条二级索引记录,并返回给server层
  • server层继续给count变量加1
  • 重复上述过程,直到InnoDB向server层返回没记录可查的消息
  • server层将最终的count变量的值发送到客户端。
 
对于count(非索引列)来说,优化器选择全表扫描,说明只能在聚集索引的叶子结点顺序扫描。
请确认你理解了全表扫描,它是顺序扫描聚集索引的所有叶子结点并判断。
而对于其他二级索引列,count(二级索引列),优化器只能选择包含我们指定的列的索引去执行查询,只能去指定非聚集索引的B+树扫描 ,可能导致优化器选择的索引扫描代价并不是最小。
综上所述:   对于count()、count(常数)、count(主键)形式的count函数来说,优化器可以选择扫描成本最小的索引执行查询,从而提升效率,它们的执行过程是一样的,只不过在判断表达式是否为NULL时选择不同的判断方式,这个判断为NULL的过程的代价可以忽略不计,所以我们可以认为count()、count(常数)、count(主键)所需要的代价是相同的。
而对于count(非索引列)来说,优化器选择全表扫描,说明只能在聚集索引的叶子结点顺序扫描。
count(二级索引列)只能选择包含我们指定的列的索引去执行查询,可能导致优化器选择的索引执行的代价并不是最小。
其实上述这些区别就是因为非聚集索引记录比聚集索引记录占用更少的存储空间,减少更多I/O成本,所以优化器才有了不同索引的选择,仅此而已。
 

© Devpan 2023 - 2024