COUNT全表记录

在MySQL中,相同的SQL不同的存储引擎执行计划不同:

对于MyISAM引擎,由于使用表锁进行并发控制,同一时间点多个并发线程执行相同查询获得的结果相同,且MyISAM存储引擎专门存储表总记录数,因此使用COUNT(*)查询全表记录时能直接返回。

而对于InnoDB存储引擎,由于使用MVCC和行锁进行并发控制,同一时间点多个并发线程执行相同查询获得的结果存在差异(每个回话的READVIEW不同),且没有专门存储表总记录数,因此每次查询都需要扫描全表或扫描某个索引的全部记录。

For transactional storage engines such as InnoDB, storing an exact row count is problematic. Multiple transactions may be occurring at the same time, each of which may affect the count.

InnoDB does not keep an internal count of rows in a table because concurrent transactions might “see” different numbers of rows at the same time. Consequently, SELECT COUNT(*) statements only count rows visible to the current transaction.

Prior to MySQL 5.7.18, InnoDB processes SELECT COUNT(*) statements by scanning the clustered index. As of MySQL 5.7.18, InnoDB processes SELECT COUNT(*) statements by traversing the smallest available secondary index unless an index or optimizer hint directs the optimizer to use a different index. If a secondary index is not present, the clustered index is scanned.

Processing SELECT COUNT(*) statements takes some time if index records are not entirely in the buffer pool. For a faster count, create a counter table and let your application update it according to the inserts and deletes it does. However, this method may not scale well in situations where thousands of concurrent transactions are initiating updates to the same counter table. If an approximate row count is sufficient, use SHOW TABLE STATUS.

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.

For MyISAM tables, COUNT(*) is optimized to return very quickly if the SELECT retrieves from one table, no other columns are retrieved, and there is no WHERE clause.

This optimization only applies to MyISAM tables, because an exact row count is stored for this storage engine and can be accessed very quickly. COUNT(1) is only subject to the same optimization if the first column is defined as NOT NULL。

现有测试表TB101:

CREATE TABLE `tb101` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`C1` int(11) NOT NULL,
`C2` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM AUTO_INCREMENT=140001 DEFAULT CHARSET=utf8

对于没有WHERE条件的COUNT(*)/COUNT(1)/COUNT(ID)/COUNT(C1)的执行计划为:

mysql> EXPLAIN SELECT COUNT(*) FROM TB101 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: NULL
partitions: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
filtered: NULL
Extra: Select tables optimized away
1 row in set, 1 warning (0.00 sec)

对于没有WHERE条件的COUNT(C2)的执行计划为:

mysql> EXPLAIN SELECT COUNT(C2) FROM TB101 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: TB101
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 140000
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)

可以发现,对于MyISAM存储引擎,在没有WHERE条件下,如果C1列为NOT NULL,则可以将COUNT(C1)与COUNT(*)和COUNT(1)做相同的处理。

针对上面的测试,对于InnoDB存储引擎,在没有WHERE条件下:

1、ID列为NOT NULL主键,COUNT(ID)和COUNT(1)或COUNT(*)的执行计划相同,返回结果相同。

2、C1列为NOT NULL,COUNT(C1)和COUNT(1)或COUNT(*)的执行结果相同,但执行计划不同。

COUNT(expr)异同

1、COUNT(1)和COUNT(*)等价,两者在执行计划和执行效率上完全相同。

个人推荐使用COUNT(1)替换COUNT(*),原因是简单直观,
他人推荐使用COUNT(1),原因是符合SQL92标准,阿里巴巴Java开发手册推荐。

2、COUNT(*)和COUNT(C1)不一定等价,两者执行计划和执行结果会存在差异。

COUNT(*):执行返回满足WHERE条件的行数,不考虑NULL值问题
COUNT(C1): 执行返回满足WHERE条件且C1不等于NULL的行数,不统计C1等于NULL的行。 换种理解思路:
对于MyISAM引擎表和InnoDB引擎表,无论是显式主键还是因此ROWID,都要求非空唯一,每行记录都肯定存在一个不为NULL的列(列组),因此计算COUNT(*)时不需要考虑NULL值问题。

一个有趣的扩展,如果C1为NOT NULL,那么COUNT(C1)与COUNT(1)的返回结果相同,那么MySQL会对此进行优化么?

现有测试表结果如下:

CREATE TABLE `tb01` (
`ID` int() NOT NULL AUTO_INCREMENT,
`C1` int() NOT NULL,
`C2` int() NOT NULL,
PRIMARY KEY (`ID`),
KEY `IDX_C2` (`C2`)
) ENGINE=InnoDB AUTO_INCREMENT= DEFAULT CHARSET=utf8

查看COUNT(*)和COUNT(C1)的执行计划:

mysql> EXPLAIN SELECT COUNT(*) FROM TB01 WHERE C2<100 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: TB01
partitions: NULL
type: range
possible_keys: IDX_C2
key: IDX_C2
key_len: 4
ref: NULL
rows: 99
filtered: 100.00
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec) mysql> EXPLAIN SELECT COUNT(C1) FROM TB01 WHERE C2<100 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: TB01
partitions: NULL
type: range
possible_keys: IDX_C2
key: IDX_C2
key_len: 4
ref: NULL
rows: 99
filtered: 100.00
Extra: Using index condition
1 row in set, 1 warning (0.00 sec)

从上面执行计划可以发现,在处理COUNT(*)时,仅需要使用IDX_C2即可完成查询,因此Extra为Using index,而在处理COUNT(C1)时,需要使用IDX_C2进行过滤后再执行回表查询,因此Extra为Using index condition。

针对上面的测试,MyISAM存储引擎和InnoDB存储引擎的测试结果相同。

COUNT(DISTINC ...)操作

MySQL官网解释为:

COUNT(DISTINCT expr,[expr...])

Returns a count of the number of rows with different non-NULL expr values.

In MySQL, you can obtain the number of distinct expression combinations that do not contain NULL by giving a list of expressions. In standard SQL, you would have to do a concatenation of all expressions inside COUNT(DISTINCT ...).

在MySQL中允许执行:

SELECT COUNT(DISTINCT ID,C1) FROM TB02;

但不允许执行:

SELECT COUNT(ID,C1) FROM TB02;

总结

1、对于InnoDB和MyISAM存储引擎,COUNT(1)和COUNT(*)在任何场景下都等价,执行性能和执行计划相同。

2、在查询全表记录(没有WHERE条件)时,对于MyISAM存储引擎,存储引擎存储表总记录数,无需扫描数据因此查询可以很快返回,对于InnoDB存储引擎,需要扫描全表或某个索引的全部记录因此查询可能比较耗时。

3、对于MyISAM存储引擎,在没有WHERE条件情况下,如果列C1为NOT NULL,那么COUNT(C1)和COUNT(*)执行操作相同。

4、对于InnoDB存储引擎,如果列C1为主键,那么COUNT(C1)和COUNT(*)执行计划和执行效率相同,如果C1为NOT NULL,那么COUNT(C1)和COUNT(*)执行计划和执行效率不一定相同,只有在查询使用C1列上索引时才可能相同。

参考链接:

https://dev.mysql.com/doc/refman/8.0/en/group-by-functions.html#function_count

https://dev.mysql.com/doc/refman/5.7/en/create-index.html

https://mp.weixin.qq.com/s/IOHvtel2KLNi-Ol4UBivbQ

MySQL Execution Plan--COUNT相关测试的更多相关文章

  1. Oracle SQL explain/execution Plan

    From http://blog.csdn.net/wujiandao/article/details/6621073 1. Four ways to get execution plan(anyti ...

  2. sql server 执行计划(execution plan)介绍

    大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...

  3. MySQL优化之COUNT(*)效率

    MySQL优化之COUNT(*)效率 刚给一个朋友解决他写的Discuz!插件的问题,说到MySQL的COUNT(*)的效率,发现越说越说不清楚,干脆写下来,分享给大家. COUNT(*)与COUNT ...

  4. 用count(*)还是count(列名) || Mysql中的count()与sum()区别

    Mysql中的count()与sum()区别   首先创建个表说明问题 CREATE TABLE `result` (   `name` varchar(20) default NULL,   `su ...

  5. mysql中的count(primary_key)、count(1)、count(*)的区别

    表结构如下: mysql> show create table user\G; *************************** 1. row ********************** ...

  6. mysql提示Column count doesn't match value count at row 1错误

    mysql提示Column count doesn't match value count at row 1错误,后来发现是由于写的SQL语句里列的数目和后面的值的数目不一致, 比如insert in ...

  7. mysql错误:Column count doesn't match value count at row 1

    mysql错误:Column count doesn't match value count at row 1 mysql错误:Column count doesn't match value cou ...

  8. Execution Plan 执行计划介绍

    后面的练习中需要下载 Demo 数据库, 有很多不同的版本, 可以根据个人需要下载.  下载地址 -http://msftdbprodsamples.codeplex.com/ 1. 什么是执行计划 ...

  9. 【mysql】mysql统计查询count的效率优化问题

    mysql统计查询count的效率优化问题 涉及到一个问题 就是 mysql的二级索引的问题,聚簇索引和非聚簇索引 引申地址:https://www.cnblogs.com/sxdcgaq8080/p ...

随机推荐

  1. Jmeter设置默认中文启动

    安装好jmeter,启动的页面是全英文,对于不熟悉jmeter的人来说,理解起来还是很困难的.如何设置成中文页面呢? 方法一:打开jmeter后,设置Options—choose language—C ...

  2. 简述ECMAScript6新增特性

    1.变量 var 可以重复声明.只有函数级的作用域.存在变量提升 let 不能重复声明.有块级作用域.没有变量提升.变量 const 不能重复声明.具有块级作用域.常量 2.箭头函数 a.为了方便而存 ...

  3. 非阻塞式I/O

    套接字的默认状态是阻塞的.这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待相应的操作完成.可能阻塞的套接字调用可分为以下4类 (1)输入操作,包括read,readv,recv ...

  4. Django的下载与创建。

    一.下载 (1)下载命令. 在cmd中输入下载命令: pip3 install django==1.11.11 1.11.11是该版本号. (2)pycharm中下载 直接在pycharm中下载set ...

  5. rabbitmq 添加用户

    参考博客 https://www.rabbitmq.com/access-control.html rabbitmqctl add_user admin admin rabbitmqctl set_p ...

  6. InvalidProgramException

    InvalidProgramException 这tmd是个什么错,我现在都想不起这个exception是怎么触发的了. 后来google了一下,发现是.net 2.0的编译器的bug,和内存或堆栈使 ...

  7. [LeetCode] 84. Largest Rectangle in Histogram 直方图中最大的矩形

    Given n non-negative integers representing the histogram's bar height where the width of each bar is ...

  8. HikariCP连接池

    1.HikariCP连接池是什么? HikariCP是数据库连接池,而且是号称史上最快的,而且目前来看确实是这样的,SpringBoot2.0也已经采用HikariCP作为默认连接池配置. githu ...

  9. 【目录】洛谷|CODEVS题解汇总

    [动规]爱与愁的心痛 [动规]编辑距离 [动规]采药 [动规]创意吃鱼法 [动规]过河卒 [动规]开心的金明 [动规]旅行 [动规]骑士游历 [动规]数字三角形 [动规]最长连号 [动规]装箱问题 [ ...

  10. Python连载34-信息队列

    一.生产者消费者模型 1.一个模型.可以用来搭建消息队列:queue是一个用来存放变量的数据结构,特点是:先进先出 import threading import time import queue ...