MySQL中无过滤条件的count详解

count(*)

实现

1、MyISAM:将表的总行数存放在磁盘上,针对无过滤条件的查询可以直接返回

如果有过滤条件的count(*),MyISAM也不能很快返回

2、InnoDB:从存储引擎一行行地读出数据,然后累加计数

由于MVCC,在同一时刻,InnoDB应该返回多少行是不确定

样例

假设表t有10000条记录

session A session B session C
BEGIN;
SELECT COUNT(*) FROM t;(返回10000)
INSERT INTO t;(插入一行)
BEGIN;
INSERT INTO t(插入一行);
SELECT COUNT(*) FROM t;(返回10000) SELECT COUNT(*) FROM t;(返回10002) SELECT COUNT(*) FROM T;(返回10001)

最后时刻三个会话同时查询t的总行数,拿到的结果却是不同的

InnoDB默认事务隔离级别是RR,通过MVCC实现

  • 每个事务都需要判断每一行记录是否对自己可见

优化

1、InnoDB是索引组织表

  • 聚簇索引树:叶子节点是数据
  • 二级索引树:叶子节点是主键值

2、二级索引树占用的空间比聚簇索引树小很多

3、优化器会在保证逻辑正确的前提下,遍历最小的索引树,尽量减少扫描的数据量

  • 针对无过滤条件的count操作,无论遍历哪一颗索引树,效果都是一样的
  • 优化器会为count(*)选择最优的索引树

show table status

mysql> SHOW TABLE STATUS\G;
*************************** 1. row ***************************
 Name: t
 Engine: InnoDB
 Version: 10
 Row_format: Dynamic
 Rows: 100256
 Avg_row_length: 47
 Data_length: 4734976
Max_data_length: 0
 Index_length: 5275648
 Data_free: 0
 Auto_increment: NULL
 Create_time: 2019-02-01 17:49:07
 Update_time: NULL
 Check_time: NULL
 Collation: utf8_general_ci
 Checksum: NULL
 Create_options:
 Comment:

SHOW TABLE STATUS同样通过采样来估算(非常不精确),误差能到40%~50%

维护计数

缓存

方案

  • 用Redis来保存表的总行数(无过滤条件)
  • 这个表每插入一行,Redis计数+1,每删除一行,Redis计数-1

缺点

丢失更新

1、Redis可能会丢失更新

2、解决方案:Redis异常重启后,到数据库执行一次count(*)

  • 异常重启并不常见,这时全表扫描的成本是可以接受的

逻辑不精确 – 致命

1、场景:显示操作记录的总数和最近操作的100条记录

2、Redis和MySQL是两个不同的存储系统,不支持分布式事务,因此无法拿到精确的一致性视图

时序A

session B在T3时刻,查到的100行结果里面有最新插入的记录,但Redis还没有+1,逻辑不一致

时刻 session A session B
T1
T2 插入一行数据R;
T3 读取Redis计数;
查询最近100条记录;
T4 Redis计数+1;

时序B

session B在T3时刻,查到的100行结果里面没有最新插入的记录,但Redis已经+1,逻辑不一致

时刻 session A session B
T1
T2 Redis计数+1;
T3 读取Redis计数;
查询最近100条记录;
T4 插入一行数据R;

数据库

  • 把计数值放到数据库单独的一张计数表C中
  • 利用InnoDB的crash-safe的特性,解决了崩溃丢失的问题
  • 利用InnoDB的支持事务的特性,解决了一致性视图的问题
  • session B在T3时刻,session A的事务还未提交,表C的计数值+1对自己不可见,逻辑一致
时刻 session A session B
T1
T2 BEGIN;
表C中的计数值+1;
T3 BEGIN;
读表C计数值;
查询最新100条记录;
COMMIT;
T4 插入一行数据R;
COMMIT;

count的性能

语义

1、count()是一个聚合函数,对于返回的结果集,一行一行地进行判断

如果count函数的参数值不是NULL,累计值+1,否则不加,最后返回累计值

2、count(字段F)

  • 字段F有可能为NULL
  • 表示返回满足条件的结果集里字段F不为NULL的总数

3、count(主键ID)、count(1)、count(*)

  • 不可能为NULL
  • 表示返回满足条件的结果集的总数

4、Server层要什么字段,InnoDB引擎就返回什么字段

  • count(*)例外,不返回整行,只返回空行

性能对比

count(字段F)

1、如果字段F定义为不允许为NULL,一行行地从记录里读出这个字段,判断通过后按行累加

  • 通过表结构判断该字段是不可能为NULL

2、如果字段F定义为允许NULL,一行行地从记录里读出这个字段,判断通过后按行累加

  • 通过表结构判断该字段是有可能为NULL
  • 判断该字段值是否实际为NULL

3、如果字段F上没有二级索引,只能遍历整张表(聚簇索引)

4、由于InnoDB必须返回字段F,因此优化器能做出的优化决策将减少

  • 例如不能选择最优的索引来遍历

count(主键ID)

  • InnoDB会遍历整张表(聚簇索引),把每一行的id值取出来,返回给Server层
  • Server层拿到id后,判断为不可能为NULL,然后按行累加
  • 优化器可能会选择最优的索引来遍历

count(1)

  1. InnoDB引擎会遍历整张表(聚簇索引),但不取值
  2. Server层对于返回的每一行,放一个数字1进去,判断是不可能为NULL,按行累加
  3. count(1)比count(主键ID)快,因为count(主键ID)会涉及到两部分操作
  • 解析数据行
  • 拷贝字段值

count(*)

  1. count(*)不会把所有值都取出来,而是专门做了优化,不取值,因为『*』肯定不为NULL,按行累加
  2. 不取值:InnoDB返回一个空行,告诉Server层不是NULL,可以计数

效率排序

  1. count(字段F) < count(主键ID) < count(1) ≈ count(*)
  2. 尽量使用count(*)

样例

mysql> SHOW CREATE TABLE prop_action_batch_reward\G;
*************************** 1. row ***************************
 Table: prop_action_batch_reward
Create Table: CREATE TABLE `prop_action_batch_reward` (
 `id` bigint(20) NOT NULL,
 `source` int(11) DEFAULT NULL,
 `serial_id` bigint(20) NOT NULL,
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `user_ids` mediumtext,
 `serial_index` tinyint(4) DEFAULT '0',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uniq_serial_id_source_index` (`serial_id`,`source`,`serial_index`),
 KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

count(字段F)

无索引

user_ids上无索引,而InnoDB又必须返回user_ids字段,只能遍历聚簇索引

mysql> EXPLAIN SELECT COUNT(user_ids) FROM prop_action_batch_reward;
+----+-------------+--------------------------+------+---------------+------+---------+------+----------+-------+
| id | select_type | table   | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------------------+------+---------------+------+---------+------+----------+-------+
| 1 | SIMPLE | prop_action_batch_reward | ALL | NULL  | NULL | NULL | NULL | 16435876 | NULL |
+----+-------------+--------------------------+------+---------------+------+---------+------+----------+-------+

mysql> SELECT COUNT(user_ids) FROM prop_action_batch_reward;
+-----------------+
| count(user_ids) |
+-----------------+
| 17689788 |
+-----------------+
1 row in set (10.93 sec)

有索引

1、serial_id上有索引,可以遍历uniq_serial_id_source_index

2、但由于InnoDB必须返回serial_id字段,因此不会遍历逻辑结果等价的更优选择idx_create_time

  • 如果选择idx_create_time,并且返回serial_id字段,这意味着必须回表
mysql> EXPLAIN SELECT COUNT(serial_id) FROM prop_action_batch_reward;
+----+-------------+--------------------------+-------+---------------+-----------------------------+---------+------+----------+-------------+
| id | select_type | table   | type | possible_keys | key    | key_len | ref | rows | Extra |
+----+-------------+--------------------------+-------+---------------+-----------------------------+---------+------+----------+-------------+
| 1 | SIMPLE | prop_action_batch_reward | index | NULL  | uniq_serial_id_source_index | 15 | NULL | 16434890 | Using index |
+----+-------------+--------------------------+-------+---------------+-----------------------------+---------+------+----------+-------------+

mysql> SELECT COUNT(serial_id) FROM prop_action_batch_reward;
+------------------+
| count(serial_id) |
+------------------+
|  17705069 |
+------------------+
1 row in set (5.04 sec)

count(主键ID)

优化器选择了最优的索引idx_create_time来遍历,而非聚簇索引

mysql> EXPLAIN SELECT COUNT(id) FROM prop_action_batch_reward;
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref | rows | Extra |
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+
| 1 | SIMPLE | prop_action_batch_reward | index | NULL  | idx_create_time | 5 | NULL | 16436797 | Using index |
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+

mysql> SELECT COUNT(id) FROM prop_action_batch_reward;
+-----------+
| count(id) |
+-----------+
| 17705383 |
+-----------+
1 row in set (4.54 sec)

count(1)

mysql> EXPLAIN SELECT COUNT(1) FROM prop_action_batch_reward;
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref | rows | Extra |
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+
| 1 | SIMPLE | prop_action_batch_reward | index | NULL  | idx_create_time | 5 | NULL | 16437220 | Using index |
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+

mysql> SELECT COUNT(1) FROM prop_action_batch_reward;
+----------+
| count(1) |
+----------+
| 17705808 |
+----------+
1 row in set (4.12 sec)

count(*)

mysql> EXPLAIN SELECT COUNT(*) FROM prop_action_batch_reward;
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref | rows | Extra |
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+
| 1 | SIMPLE | prop_action_batch_reward | index | NULL  | idx_create_time | 5 | NULL | 16437518 | Using index |
+----+-------------+--------------------------+-------+---------------+-----------------+---------+------+----------+-------------+

mysql> SELECT COUNT(*) FROM prop_action_batch_reward;
+----------+
| count(*) |
+----------+
| 17706074 |
+----------+
1 row in set (4.06 sec)

参考资料

《MySQL实战45讲》

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • mySQL count多个表的数据实例详解

    一.实现需求 最近在做成就系统,成就中有很多维度都和数量有关,比如用户粉丝数达到多少50个,授予 名扬四海 称号,用户点赞达到 100 次,授予 点赞圣手 称号等等. 粉丝数在 user_relation 表 点赞数在 user_praise 表 按照以往的做法,如果需要查询用户的这些数据,又因为数据是在不同的表中,所以往往会分开多条 SQL 查询,比如: SELECT count(*) mysh FROM user_relation WHERE other_uid =123456; SELEC

  • MySQL中聚合函数count的使用和性能优化技巧

    本文的环境是Windows 10,MySQL版本是5.7.12-log 一. 基本使用 count的基本作用是有两个: 统计某个列的数据的数量: 统计结果集的行数: 用来获取满足条件的数据的数量.但是其中有一些与使用中印象不同的情况,比如当count作用一列.多列.以及使用*来表达整行产生的效果是不同的. 示例表如下: CREATE TABLE `NewTable` ( `id` int(11) NULL DEFAULT NULL , `name` varchar(30) NULL DEFAUL

  • mysql count详解及函数实例代码

    mysql count详解 count函数是用来统计表中或数组中记录的一个函数,下面我来介绍在mysql中count函数用法. count(*) 它返回检索行的数目, 不论其是否包含 NULL值. SELECT 从一个表中检索,而不检索其它的列,并且没有 WHERE子句时, COUNT(*)被优化到最快的返回速度. 例如: mysql> SELECT COUNT(*) FROM student; COUNT(DISTINCT 字段)这个优化仅适用于 MyISAM表, 原因是这些表类型会储存一个函

  • mysql count提高方法总结

    mysql想必很多程序猿都是不陌生的.很多人在纠结count的用法,怎么样才能查询效果最好.今天来谈谈小编的一些看法,仅供参考. 1.咱们先准备建一个表,并准备好测试数据方便后续步骤测试 以InnoDB引擎表为例 建表语句如下 CREATE TABLE test.test ( a VARCHAR(50) NOT NULL COMMENT 'ddfdf', b VARCHAR(15) NOT NULL COMMENT 'fds', c VARCHAR(20) NOT NULL COMMENT 'a

  • mysql中count(), group by, order by使用详解

    最近做IM的时候遇到一个问题,同时用到了这三个关键字.就是查询一个人的离线消息详情,我们服务端返回给客户端显示的这个详情包括了三个内容,第一个要求列出离线这段时间哪些人或者群给你发了消息,第二个这其中的某个人或者群发了多少条离线消息,第三个拿出最新的一条显示出来.很明显,group by分组哪些人或者群给你发了离线消息,count()得到离线消息数量,order by时间来排序拿出最新的消息. select count(1) as cnt, msg_data from t_im_chat_off

  • MySQL中distinct和count(*)的使用方法比较

    首先对于MySQL的DISTINCT的关键字的一些用法: 1.在count 不重复的记录的时候能用到,比如SELECT COUNT( DISTINCT id ) FROM tablename:就是计算talbebname表中id不同的记录有多少条. 2,在需要返回记录不同的id的具体值的时候可以用,比如SELECT DISTINCT id FROM tablename:返回talbebname表中不同的id的具体的值. 3.上面的情况2对于需要返回mysql表中2列以上的结果时会有歧义,比如SE

  • MySQL中count(*)、count(1)和count(col)的区别汇总

    前言 count函数是用来统计表中或数组中记录的一个函数,count(*) 它返回检索行的数目, 不论其是否包含 NULL值.最近感觉大家都在讨论count的区别,那么我也写下吧:欢迎留言讨论,话不多说了,来一起看看详细的介绍吧. 1.表结构: dba_jingjing@3306>[rds_test]>CREATE TABLE `test_count` ( -> `c1` varchar(10) DEFAULT NULL, -> `c2` varchar(10) DEFAULT N

  • MySQL中无过滤条件的count详解

    count(*) 实现 1.MyISAM:将表的总行数存放在磁盘上,针对无过滤条件的查询可以直接返回 如果有过滤条件的count(*),MyISAM也不能很快返回 2.InnoDB:从存储引擎一行行地读出数据,然后累加计数 由于MVCC,在同一时刻,InnoDB应该返回多少行是不确定 样例 假设表t有10000条记录 session A session B session C BEGIN; SELECT COUNT(*) FROM t;(返回10000) INSERT INTO t;(插入一行)

  • 对numpy中的数组条件筛选功能详解

    在程序设计中,时常会遇到数据的唯一化.相同.相异信息的提取等工作,在格式化的向量存储矩阵中南,numpy能够提供比较不错的快速处理功能. 1,唯一化的实现: In [63]: data = np.array(['int','float','int','boolean','double','boolean']) In [64]: data Out[64]: array(['int', 'float', 'int', 'boolean', 'double', 'boolean'], dtype='|

  • 对python字典过滤条件的实例详解

    如下所示: d = { 'a': '0.0000', 'b': '1.2' } d_tmp = dict((key, value) for key, value in d.items() if float(value) > 0) print(d_tmp) output: {'b': '1.2'} 以上这篇对python字典过滤条件的实例详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • mysql中decimal数据类型小数位填充问题详解

    前言 在开发过程中,我们往往会用到decimal数据类型.因为decimal是MySQL中存在的精准数据类型. MySQL中的数据类型有:float,double等非精准数据类型和decimal这种精准. 区别:float,double等非精准类型,在DB中保存的是近似值. Decimal则以字符串的形式保存精确的原始数值. decimal介绍: decimal(a,b) 其中:a指定指定小数点左边和右边可以存储的十进制数字的最大个数,最大精度38.b指定小数点右边可以存储的十进制数字的最大个数

  • MySQL中replace into与replace区别详解

    目录 0.故事的背景 1.replace into 的使用方法 2.有唯一索引时—replace into & 与replace 效果 3.没有唯一索引时—replace into 与 replace 1).replace函数的具体情况 2).replace into 函数的具体情况 4.replace的用法 本篇为抛砖引玉篇,之前没关注过replace into 与replace 的区别.经过多个场景测试,居然没找到在插入数据的时候两者有什么本质的区别?如果了解详情的伙伴们,请告知留言告知一二

  • 关于Mysql中ON与Where区别问题详解

    今天工作的时候编写了一个SQL,大家一起来看一下 SELECT * FROM user user LEFT JOIN user_message msg ON user.id = msg.user_id AND user.name = '张三' 我们有一张user表 我们还有一张user_message表 我们执行上面的SQL后,查询结果如下 并不如我们所想的,只出现张三的结果,这是为什么呢? 经过我的整理发现查询是如下原因 关联的时候ON条件是在合并两张表形成临时表前进行的条件筛选,所以在使用

  • MySQL中的行级锁定示例详解

    前言 锁是在执行多线程时用于强行限定资源访问的同步机制,数据库锁根据锁的粒度可分为行级锁,表级锁和页级锁 行级锁 行级锁是mysql中粒度最细的一种锁机制,表示只对当前所操作的行进行加锁,行级锁发生冲突的概率很低,其粒度最小,但是加锁的代价最大.行级锁分为共享锁和排他锁. 特点: 开销大,加锁慢,会出现死锁:锁定粒度最小,发生锁冲突的概率最大,并发性也高: 实现原理: InnoDB行锁是通过给索引项加锁来实现的,这一点mysql和oracle不同,后者是通过在数据库中对相应的数据行加锁来实现的,

  • mysql中关键词exists的用法实例详解

    目录 前言 语法解释 语法 说明 使用案例 环境准备 常用查询 exists与in的效率比较 循环嵌套查询执行原理 循环优化策略 exists和in查询原理的区别 结论 总结 前言 在日常开发中,用mysql进行查询的时候,有一个比较少见的关键词exists,我们今天来学习了解一下这个exists这个sql关键词的用法,这样在工作中遇到一些特定的业务场景就可以有更加多样化的解决方案 语法解释 语法 SELECT column1 FROM t1 WHERE [conditions] and EXI

  • MySQL中replace into语句的用法详解

    在向表中插入数据的时候,经常遇到这样的情况: 1.首先判断数据是否存在: 2.如果不存在,则插入: 3.如果存在,则更新.   在 SQL Server 中可以这样写: 复制代码 代码如下: if not exists (select 1 from table where id = 1) insert into table(id, update_time) values(1, getdate()) else update table set update_time = getdate() whe

  • MySQL中的唯一性约束与NULL详解

    前言 之前做的一个需求,简化描述下就是接受其他组的 MQ 的消息,然后在数据库里插入一条记录.为了防止他们重复发消息,插入多条重复记录,所以在表中的几个列上加了个唯一性索引. CREATE UNIQUE INDEX IDX_UN_LOAN_PLAN_APP ON testTable (A, B, C); 这时 A,B,C 三列都是不允许 NULL 值的,唯一性约束也是 work 的. 后来由于需求的变化,修改了以前的唯一性约束,又多加了一列.(至于为什么加就不赘述了). ALTER TABLE

随机推荐