相信每一个人在写代码时都有遇到过要获取MYSQL表里数据行数的状况,多数人获取数据表行数时都用COUNT(*)
,但同时也流传了很多其余方式,好比说COUNT(1)、COUNT(主键)、COUNT(字段)。到底哪一种方式MYSQL执行起来更快也是众说纷纭,其实以前我也不知道到底哪一个执行起来快,到底谁说的对(笑哭)。好在最近在认真学习极客时间的MySQL专栏,其中专门有一节是对这个问题的讨论,看完后也是解除了长久以来的疑惑。并发
文章中都是针对MySQL的InnoDB引擎展开讨论的,MyISAM引擎是把一个表的总行数记录在了磁盘里,查询时效率很高(若是加了where条件也不能直接从磁盘返回)。而InnoDB因为多版本并发控制(MVCC)的缘由,即便时同一时刻的查询InnoDB表应该"返回多少行"也是不肯定的,好比假设表t中有10000行数据:分布式
时刻 | 会话A | 会话B | 会话C |
---|---|---|---|
T1 | begin; | ||
T2 | select count(*) from t; | ||
T3 | insert into t (插入一行); | ||
T4 | begin; | ||
T5 | insert into t (插入一行); | ||
T6 | select count(*) from t; (返回10000) | select count(*) from t; (返回10002); | select count(*) from t; (返回10001) |
会话A在T1开启事务拿到一致性视图,可重复读级别下在事务中任什么时候刻读到数据都同样,其余事务的更新对会话A没影响因此count(*)
的结果是10000,会话B在T4开启事务拿到一致性视图,T4以前会话C已经新插入了一条语句并提交(单独执行一条更新语句,InnoDB会本身启动一个事务,语句执行完立刻提交)。会话B在T5插入一条新数据,在T6查询时count(*)
的结果是10002(T4 begin时会话C insert语句已经提交,因此在会话B的事务中能看到这个更新)。因为会话B在T6时事务尚未提交,会话C看不到会话B的更新,因此会话C在T6时count(*)
的结果是10001。函数
COUNT是一个聚合函数,它的功能是对返回的结果集中每一行进行判断,若是COUNT函数的参数不是NULL则累加1,不然不累加,最后返回累计值。接下来看一下每一个COUNT版本的执行效率:学习
COUNT(*)
MySQL专门作了优化,会找到表中最小的索引树,InnoDB普通索引树比主键索引小不少,对于COUNT(*)
遍历哪一个树是同样的,count(*)
时MySQL不取记录值,count(*)
也确定不为NULL,Server层中直接按行累加。因此这个版本COUNT的从低到高分别为:优化
COUNT(字段)
< COUNT(主键)
< COUNT(1)
≈ COUNT(*)
code
因此建议你尽可能使用count(*)
来获取记录行数。索引
另外要注意,不少人为了销量会把表的行数记录到Redis中,但这样不能保证Redis里的计数和MySQL表里的数据保持精确一致,这是两个不一样的存储系统不支持分布式事务因此就没法拿到精确的一致性视图,若是为了效率把表行数单独存储那么最好存放在一个单独的MySQL表里,这样没法拿到一致性视图的问题就能解决了。事务
关于MySQL更详细的分析,推荐关注MySQL实战45讲get