MySQL count(*)语句

本文将介绍 MySQL 中 count(*) 语句的执行。

一、count(*) 的遍历计算

  • 假如没有过滤条件:

    • 在 MyISAM 中,表的总行数被存在磁盘中某个位置,因此执行 count(*) 时可以直接返回这个数

    • 在 InnoDB 中,执行 count(*) 时,需要将数据一行行读出累积计数

      由于 MVCC,即使在同一时刻,不同事务的相同查询的结果也可能是不同的,因此这个数字需要遍历确定

  • 假如有过滤条件:

    MyISAM 和 InnoDB 都需要遍历确定数量

在遍历计算的情况下,一个简单的 count(*) 语句可能会花费较多的性能和时间。

二、show table status

在 MySQL 中,show table status 的输出结果中的 TABLE_ROWS 字段会显示表的行数,这个数据是通过采样估算的,官方文档中说明误差可能达到 40% ~ 50%,因此,这条命令中显示的行数不能直接使用。

三、解决方案

1. 缓存保存计数

可以在缓存中保存 count(*) 的计数,并在增、删时维护。

这个方法可能会遇到数据不一致的问题:

  • 会话 A 插入数据
  • 会话 B 读取 “旧计数”
  • 会话 B 读取数据
  • 会话 A 更新计数

会话 B 读取到的数据和计数并不一致

2. 数据库中保存计数

可以在数据库中单独放置一张计数表,在表中保存 count(*) 的计数,并在增、删时维护。

这种方法可以解决数据不一致问题:

  • 会话 A 更新计数
  • 会话 B 读取计数
  • 会话 B 读取数据
  • 会话 A 插入数据
  • 会话 A 提交

由于 MVCC,在 “可重复读” 下,会话 B 不会读取到会话 A 中的改动,避免了数据不一致的情况

四、count(字段)、count(主键 ID)、count(1)、count(*)

  • count(主键 ID)count(1)count(*) 的结果相同,都是满足条件的总行数;

    count(字段) 的结果是满足条件且字段不为 null 的总行数

  • count(主键 ID) 会遍历整张表,对于每一行,判断主键不为空,累加计数;

    count(1) 会遍历整张表,对于每一行,判断 1 不为空,累加计数;

    count(*) 会遍历整张表,对于每一行,累加计数

  • 按效率的排序:

    count(字段) < count(主键 ID) < count(1) ≈ count(*)

参考

  • MySQL 技术内幕
  • MySQL 实战 45 讲
  • MySQL 是怎样运行的:从根儿上理解 MySQL