MySQL count() 函数我们并不陌生,用来统计每张表的函数。但如果你的表越来越大,并且是 InnoDB 引擎的话,会发现计算的速度会越来越慢。在这篇文章里,会先介绍 count() 实现的原理及原因,然后是 count 不同用法的性能分析,最后给出需要频繁改变并需要统计表行数的解决方案。

Count() 的实现

InnoDB 和 MyISAM 是 MySQL 常用的数据引擎,由于两者实现的不同,导致 count() 操作计算的效率也不同。

对于 MyISAM 来说,它把每个表的总行数都存在了磁盘上,因此使用 count(*) 计算时,效率很高直接返回结果。但如果加入了 where 条件,依然会进行搜索,所以效率是不高的。

对于 InnoDB 来说,在进行 count(*) 运算时,会把数据从引擎中一行行读出来,然后累计计数,自然表大了之后,效率就变低了。

那么,为什么 InnoDB 不能像 MyISAM 在表中记录呢?原因就在于 InnoDB 比 MyISAM 多了支持事务的特性,同时也需要一定的取舍。由于 MVCC 的控制,使得 MySQL 具有并发的能力,也就是说对于同一时刻,InnoDB 返回的表的行数是不一定的,事务看到的行数与开启后的一致性视图有关,换句话说,每个事务能看到的数据版本是不一样的,只能一行行拿出来进行判断。

像下面的事务,假设表 t 有 10000 条数据:

Session A Session B Session C
select count(*) from t;
insert into t ();
begin;
insert into t();
select count(*) from t; select count(*) from t; select count(*) from t;
10000; 结果是 10002 结果是 10001

对于 Session A 来说,Session B 未提交不可见,Session C 提交了,但是在 Session A 启动后提交的,也不可见。所以是 10000.

而对于 Session B 而言,Session C 在启动之前提交,自己又插入了一条,所以结果是 10002.

其实 InnoDB 在进行 count(*) 操作时,还是做了优化的,在进行 count(*) 操作时,由于普通索引会保存主键的 id 值,所以会找到最小的那颗普通索引树进行查找,而不是去遍历主键索引树。

在保证逻辑正确的前提下,减少扫描的数据量,是数据库系统设计的通用法则。

另外在使用 show table status 时,也可以查询出行数,而且速度很快,但需要注意的是,该命令是通过索引统计的值来采样估算的。官方文档说误差可以有 40%-50%.

但如果我们真的需要实时的获取的某个表的行数,应该怎么办呢?

手动保存表的数量

用缓存系统来保存计数

对于进行更新的表,可能会想到用缓存系统来支持。比如 Redis 里来保存某个表总行数。

每次插入数据库时,Redis 计数加一,相反则减一,这样看起来读写操作都很快,但会存在一些问题。

缓存系统会丢失更新:

对于 Redis 在内存中的数据,需要定期的同步到磁盘中,但对于 Redis 异常重启,就没有办法了。比如在 Redis 中插入后,Redis 重启,数据没有持久化到硬盘。这时可以在重启 Redis 后,从数据库执行下 count(*) 操作,然后更新到 Redis 中。一次全表扫描还是可行的。

逻辑不精确:

假设一个页面中,需要显示一张表的行数,以及每一条数据。在实现时,可以先从 Redis 取数量,然后从数据库里取记录。

但可能会出现这样的情况:

  1. 数据库查到 100 行结果里有最新插入的记录,而 Redis 计数里少 1.
  2. 数据库查到 100 行结果没有最新的记录,但 Redis 计数却多了 1.
Session A Session B
插入一条数据; T1
读 Redis 计数; T2
从数据库中查记录;
Redis 计数加 1; T3

对于 Session B 来说,在 T2 时刻,会发现 Redis 的数量比数据库少 1 条。

Session A Session B
Redis 计数加 1; T1
读 Redis 计数; T2
从数据库中查记录;
插入一条数据; T3

对于 Session B 来说,在 T2 时刻,会发现 Redis 的数量比数据库多 1 条。

其实产生问题的原因就是因为 Redis 和数据库查记录没有在同一个事务中。

用数据库保存

由于 InnoDB 引擎的支持,MySQL 本身是支持事务的,所以将 Redis 的插入操作换成在数据库的更新操作,就可以利用在RR级别下的事务特性,进而保证数据的精确性。

而且还有一点,由于 redo log 的支持,在 MySQL 发生异常时,是可以保证 crash-safe。

不同 count 用法的执行效率

count() 本身是一个聚合函数,对于返回的结果集,一行行地判断。如果参数不是 NULL 的话,会一直累加,最后返回结果。

所以 count(*), count(id), count(1) 表示都是返回满足条件的结果集总行数。

而 count(字段),则表示满足条件的数据行里,不为 NULL 的字段。

对于 count(id) 来说,InnoDB 会遍历整张表,把每行 id 取出来,给 server 层。Server 判断 id 是否为空,然后累加。

对于 count(1) 来说,InnoDB 会遍历整张表,但不取值。Server 层会自己放入 1,然后累加。

所以对于 count(1) 的执行会比 count(*) 要快,少了解析数据行以及拷贝字段值的操作。

对于 count(字段) 来说,如果字段定义时是 not null, 会一行行读出,并判断不能为 null,然后累加。如果定义时可以为 null,执行时,需要将值去除,判断不是 null 才累加。

count(*) 除外,专门做了优化,不取值,直接按行累加,并且会找到最小的索引树进行计算。

总结

MySQL count() 函数的执行效率和底层的数据引擎有关。MyISAM 不加 where 条件,查询会很快,但不支持事务。InnoDB 支持事务,由于 MVCC 的实现,导致每次查询都需要一行行的扫描,效率不高。

解决方法可以通过设计外部缓存如 Redis,保存记录。但存在异常重启和数据不准确的情况。可以通过在 InnoDB 中新建一张表,保存记录这样的解决方案。

最后,InnoDB 对 count(*) 做了独立的优化,而其他的 count 操作,则需要额外的操作。

MySQL 统计行数的 count的更多相关文章

  1. (转载)MySQL 统计数据行数 Select Count

    (转载)http://www.5idev.com/p-php_mysql_select_count.shtml 统计数据行数 SELECT COUNT() FROM 语法用于从数据表中统计数据行数. ...

  2. SQL Server遍历所有表统计行数

    DECLARE CountTableRecords CURSOR READ_ONLY FOR SELECT sst.name, Schema_name(sst.schema_id) FROM sys. ...

  3. Python,针对指定文件类型,过滤空行和注释,统计行数

    参考网络上代码编辑而成,无技术含量,可自行定制: 目前亲测有效,若有待完善之处,还望指出! 强调:将此统计py脚本放置项目的根目录下执行即可. 1.遍历文件,递归遍历文件夹中的所有 def getFi ...

  4. 《c程序设计语言》读书笔记--统计 行数、单词数、字符数

    #include <stdio.h> int main() { int lin = 0,wor = 0,cha = 0; int flag = 0; int c; while((c = g ...

  5. shell 统计行数

    语法:wc [选项] 文件… 说明:该命令统计给定文件中的字节数.字数.行数.如果没有给出文件名,则从标准输入读取.wc同时也给出所有指定文件的总统计数.字是由空格字符区分开的最大字符串. 该命令各选 ...

  6. linux、WINDOWS命令行下查找和统计行数

    linux : 例子: netstat -an | grep TIME_WAIT | wc -l |  管道符 grep 查找命令 wc 统计命令 windows: 例子: netstat -an | ...

  7. wc 统计行数 字数

    Linux统计文件行数 2011-07-17 17:32 by 依水间, 168255 阅读, 4 评论, 收藏, 编辑 语法:wc [选项] 文件… 说明:该命令统计给定文件中的字节数.字数.行数. ...

  8. C++->10.3.2-3,使用文件流类录入数据,并统计行数

    题目:建立一个文本文件,从键盘录入一篇短文存放在该文件中短文由若干行构成,每行不超过80个字符,并统计行数. /* #include<iostream.h>#include<stdl ...

  9. oracle查询表统计行数与注释

    SELECT TABLE_NAME,NUM_ROWS,(select COMMENTS from user_tab_comments WHERE TABLE_NAME=C.TABLE_NAME) FR ...

随机推荐

  1. SHELL用法四(for语句)

    1.SHELL编程For语句案例实战 1)For语句固定的语法格式: For i in (表达式) do 动作1 done 2)For语句案例操作一: #!/bin/bash do echo “$NU ...

  2. firefox上网慢

    由于Chromium浏览器,打字的时候经常会跳字母,所以就换了firefox浏览器,但是FF上网的时候特别慢,而且大部分时间是花费在解析域名上.因此到网上找了许多资料,最终解决方法如下. 1.安装dn ...

  3. Nginx笔记总结十一:Nginx重写规则指南

    依赖PCRE库,需要安装pcre,最多循环10次,超过后返回500错误, 1.       rewrite模块指令 break:完成当前设置的重写规则,停止执行其他的重写规则 if:  if () { ...

  4. CentOS7下MySQL5.7的安装-RPM方式

    Installing MySQL on Linux Using RPM Packages 下载安装包 mysql下载地址:https://dev.mysql.com/downloads/mysql/ ...

  5. <JZOJ5937&luogu3944>斩杀计划&肮脏的牧师

    第一次写桶排相关题.... #include<cstdio> #include<iostream> #define rint register int template < ...

  6. Docker学习笔记_04 Rancher的部署安装(编排选用K8S)

    原文地址:http://dbase.cc/2018/01/12/docker/04_rancher的部署安装/ 为什么要使用Rancher Rancher是一个开源的企业级容器管理平台.通过Ranch ...

  7. 携程酒店DevOps测试实践

    作者简介 王幸福,携程酒店研发部高级测试经理,负责无线自动化测试相关工作.在测试框架和平台研发.移动测试.DevOps等领域有着丰富的经验. 如今很多大型互联网公司.创新型企业都在积极地进行DevOp ...

  8. Rails (栈)

    题目链接:https://vjudge.net/problem/UVA-514 题目大意: 有A,B,C三个火车停靠点,火车最初停在A站,给你一个序列,问你能不能通过中转站C到达B站,火车从A站进入到 ...

  9. MySQL5.7报错[ERROR] Unix socket lock file is empty /tmp/mysql.sock.lock的解决方法

    发现MySQL服务器因系统磁盘写满导致服务停了,清理磁盘后启动服务时无法正常启动,查看localhost.err日志发现如下报错: [ERROR] Unix socket lock file is e ...

  10. 使用Handler的步骤

    使用Handler的大致流程: 1.首先创建一个Handler对象,可以直接使用Handler无参构造函数创建Handler对象,也可以继承Hander类,重写HandleMessage方法来创建Ha ...