likes
comments
collection
share

日常工作中如何优化 COUNT(*) 语句

作者站长头像
站长
· 阅读数 33

大家好,我是小黑,8、9双月忙翻了,荒废了分享的大事,最近缓和了,加紧补上。

背景

如果大家平时有关注线上系统的数据库慢查询日志,相信一些 COUNT() 语句是最常出现的,今天和大家分享下怎么优化 COUNT() 语句。

COUNT()函数的基本原理

先简单介绍下 COUNT() 的基本原理。

COUNT() 本质上是一个MySQL的内置函数,入参是数据库表的某个字段名,另外还有 COUNT(1) 和 COUNT(*) 两种特殊用法。

如果传的是数据库表的某个字段,COUNT() 的作用就是统计库表中该字段非空的行数。如果是 COUNT(1) 和 COUNT(*),MySQL 则会直接统计数据库表中的行数。

另外,不同的存储引擎在细节上还会不同。

像 MyISAM 引擎,直接记录了数据库表的行数,如果执行 COUNT(1),COUNT() 的话可以很快的得到结果(当然不能带上 WHERE 条件)。而 InnoDB 为了支持事物,并没有存储数据库表的行数,所以执行 COUNT(1)/COUNT(),往往需要全表扫描。

日常工作中,更多是使用 InnoDB 引擎,也正是因为 InnoDB 引擎没有记录数据库表的行数,而且往往也是带上了WHERE条件的,如果数据量一大的话,扫描的行数就大,COUNT(1)/COUNT(*) 语句自然就慢了。

COUNT(*),COUNT(1),COUNT(主键 id),COUNT(col)的性能区别

那这么多 COUNT() 的用法,应该怎么选呢?(下面仅针对 InnoDB 引擎)

  • COUNT(col) 主要是用来统计某一列非 NULL 的行数,会去遍历字段,并判断是否为空
  • COUNT(主键id) 和 COUNT(col) 本质是一样的,也会去遍历字段,但是主键 id 都是 NOT NULL 的,所以判空这一步 MySQL 做了优化
  • COUNT(1) 和 COUNT(*) 是一样的,也会去遍历,但是仅仅只是从存储引擎层取出来,性能要优异些

所以,性能上:count(col) < count(主键 id) < count(1) = count(*)

对精度要求不高的场景

那么,该如何优化呢?这个要分场景,如果对精度要求不高的场景。比如百度上显示查询结果条目的场景,用户并不需要知道具体有多少行,知道个大概就行。 日常工作中如何优化 COUNT(*) 语句

这类场景,就可以使用 MySQL 的 explanin 命令。这个命令的作用是统计出sql执行的大概计划,其中的 rows 字段(从右往左数第三个)就是数据库表行数的估算值(MySQL自身会使用这个值选择优化方案)。

从实际情况看,如果写入操作都是有序的,很少做随机的删除操作,这个值的估算还是比较准确的,当然为了保险起见,可以专门起个定时任务,定时对数据库表做:analyze table t,让MySQL重新采样统计基数。

-- 使 用explanin 查看 city 表的行数
mysql> EXPLAIN SELECT * FROM city;

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+

| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+

|  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  600 |   100.00 | NULL  |

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+

1 row in set, 1 warning (0.01 sec)

对精度要求高的场景

那如果对精度要求高呢? ​

这类场可以基于 redis 实现,如果插入/删除数据就同步修改下 redis,这个方案的缺点是如果事物回滚,redis 的数据和 MySQL 的行数就不一致了,还需要弄个定时任务去兜底同步数据。 ​

还可以就用 MySQL 实现,设计一张计数表统计数量。如果插入/删除数据,就在同一个事物内更新计数表,缺点是有幻读的问题。 ​

总结

最后总结一下。 ​

  1. 性能上,count(col) < count(主键 id) < count(1) = count(*),日常工作中推荐使用 COUNT(*),它是 SQL92 定义的标准统计行数的语法。

  1. 如果要优化 COUNT(*),首先要确认场景,精确、简单、快速,三者只能得其二
    • 要性能,要成本,不要求精度:show table status
    • 要性能,要精度,成本啥的无所谓:设计一个计数的统计机制(要注意下并发情况下的一致性和事物回滚的情况)
    • 要成本,要精读,不要求性能:那就啥都别说了,老老实实用COUNT(*)

感谢阅读,thanks。 日常工作中如何优化 COUNT(*) 语句

Ref

  1. 极客时间,14 | count(*)这么慢,我该怎么办?

time.geekbang.org/column/arti…

  1. MySQL的COUNT语句,竟然都能被面试官虐的这么惨!?

mp.weixin.qq.com/s/IOHvtel2K…

转载自:https://juejin.cn/post/7016885499306442789
评论
请登录