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. 在emacs 里使用gdb

    在emacs 里使用gdb M-x gdb就在emacs里启动了gdb 在gdb窗口里shell-mode的命令都适用 启动gdb后,再启动minor mode:M-x gud-tooltip-mod ...

  2. 10. Go语言—for循环

    一.for循环 for 初始化语句;条件判断;变量修改 for i := 0; i < 100; i++{ fmt.Printf('i=%d\n',i) } // C 的 while 一样 fo ...

  3. C++编程思想 - 对象的创建和使用

    前言 用户定义的数据类型(data type)或类(class),是C++区别于传统过程型语言的地方. 通常将创建好的类库存放在库(library)中. 本篇会使用几个C++类库(class libr ...

  4. angular的Hash 模式和 HTML 5 模式

    去除地址 # ,将{ provide: LocationStrategy, useClass: HashLocationStrategy }改为 { provide: LocationStrategy ...

  5. Linux学习笔记-第10天 特殊的交换分区

    关键词,分区.mkswap swapon .uquota,RAID,/etc/fstab 此章开始,难度有些提升.不过还好自己有点基础.

  6. 公式推导【IoUNet//ECCV2018】

    Jiang B, Luo R, Mao J, Xiao T, Jiang Y. Acquisition of localization confidence for accurate object d ...

  7. Jupyter Notebook使用

    不论你是刚开始学 Python,还是正在啃数据分析的骨头,对你来说,不断在各种命令行窗口和编辑器里切来切去,或者不断打开各种窗口查看 matplotlib 的输出之类的繁琐操作,一定是家常便饭了.哎呀 ...

  8. matplotlib动态绘图

    目录 package Process 解决中文乱码问题 simple_plot() scatter_plot() three_dimension_scatter() Jupyter notebook ...

  9. springboot2 中Druid和ibatis(baomidou) 遇到org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.iflytek.pandaai.service.multi.mapper.TanancyMapper

    调用mapper中任何方法都会出现类似的错误 org.apache.ibatis.binding.BindingException: Invalid bound statement (not foun ...

  10. git操作记录(如何回退到某个历史版本,如何提交部分文件等方法)

    当前项目使用git管理代码,在使用的过程中会遇到一些问题,这里记录下 1.合并代码后 在合并组员的代码后会出现下面的界面,开始的时候都会关闭,重新打开,乐此不疲到忍无可忍 解决方法: 出现这种界面是要 ...