热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

MySql的count(*)统计结果很慢?为什么

起因:最近在学习mysql的数据库,发现在innodb表中大数据量下count(*)的统计结果实在是太慢,所以想找个办法替代这种查询,下面分享一下我查找的过程。

  起因:最近在学习mysql的数据库,发现在innodb表中大数据量下count(*)的统计结果实在是太慢,所以想找个办法替代这种查询,下面分享一下我查找的过程。

         实践:在给出具体的结论之前,我们先看看下面的现象。

一.     创建数据库

创建数据库的表语句如下:


create database IF NOT EXISTS MY_TEST default charset utf8  COLLATE utf8_general_ci;


二.     创建User表

创建User表的语句如下,UserId为主键id,在Id和k上分别建立索引,索引的名字分为了“index_id”和“index_k”。


create table USER (

                  UserId bigint(20) unsigned not null auto_increment,

                  Id     int(10) unsigned not null default 0,

                  k       int(10) unsigned not null default 0,

                  UserName varchar(120) not null default '',

                  PRIMARY KEY  (UserId),

                  KEY index_Id (Id),

                  KEY index_k (k)

)Engine=InnoDB  DEFAULT CHARSET=UTF8;


查看User上的索引,查询结果如下:


mysql> show index from user;

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

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

| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardi

nality | Sub_part | Packed | Null | Index_type | Comment |

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

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

| user  |          0 | PRIMARY  |            1 | UserId      | A         |     4

041613 |     NULL | NULL   |      | BTREE      |         |

| user  |          1 | index_Id |            1 | Id          | A         |     4

041613 |     NULL | NULL   |      | BTREE      |         |

| user  |          1 | index_k  |            1 | k           | A         |     4

041613 |     NULL | NULL   |      | BTREE      |         |

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

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

3 rows in set (1.30 sec)


从上表中我们可以看到user表上有3个索引,分别为主键Primary索引、、二级索引index_Id和index_k。

三.     在User表上比较查询统计

1.       直接count(*)统计


mysql> explain select count(*) from user;

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

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

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

|  1 | SIMPLE      | user  | index | NULL          | index_Id | 4       | NULL | 4041613 | Using index |

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

1 row in set (0.04 sec)

 

mysql> select count(*) from user;

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

| count(*) |

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

|  4058181 |

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

1 row in set (2.50 sec)


在这里使用select  count(*) 的时候,默认走的索引是index_Id。虽然user表上有主键索引,但是分析引擎计算的结果需要走“index_Id”索引(有的时候走主键索引),“4041613”这个数字说明分析引擎认为能够提取到正确的结果之前需要扫描“4041613”行索引。Rows参数表明该查询所使用的索引的索引长度,index_Id的索引长度为“4”;

2.       主键字段做条件查询count(*)

这里我们查询所有Userid大于0的记录,我们来看看查询情况。


mysql> explain select count(*) from user where UserId>0;

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

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

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

|  1 | SIMPLE      | user  | range | PRIMARY       | PRIMARY | 8       | NULL | 2020806 | Using where; Using index |

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

1 row in set (0.13 sec)

 

mysql> select count(*) from user where UserId>0;

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

| count(*) |

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

|  4058181 |

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

1 row in set (15.39 sec)


当我们加上主键条件的时候,我们可以看到本次查询走的索引是“Primary”主键索引,我们可以看到此次请求需要15.39秒,比第一个查询2.50秒慢了很多。主键索引的长度为“8”。

3.       二级索引做条件查询count(*)

这里我们查询所有Id大于0的记录,我们来看下查询结果:


mysql> explain select count(*) from user where id>0;

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

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

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

|  1 | SIMPLE      | user  | range | index_Id      | index_Id | 4       | NULL | 1734104 | Using where; Using index |

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

1 row in set (0.16 sec)

 

mysql> select count(*) from user where id>0;

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

| count(*) |

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

|  4058181 |

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

1 row in set (2.94 sec)


(1)和(3)的查询时间都差不多,基本在2.5秒左右,因为这两个查询走的都是“index_Id”索引。但是(2)虽然走的主键索引,但是很慢,竟然用掉了15秒,index_Id的索引长度为4,主键索引的长度为8,是不是因为索引的长度影响了索引的查询效率??先别下结论,我们再看下下面的例子。

四.     创建AnotherUser表

建立另外一张表,这张表与上一张表的区别是主键字段UserId和Id字段类型的调换过来了,主键UserId为int(10),Id类型为bigint(20)。建表语句如下:


create table ANOTHERUSER (

                  UserId  int(10) unsigned not null auto_increment,

                  Id     bigint(20) unsigned not null default 0,

                  k       int(10) unsigned not null default 0,

                  UserName varchar(120) not null default '',

                  PRIMARY KEY  (UserId),

                  KEY index_Id (Id),

                  KEY index_k (k)

         )Engine=InnoDB  DEFAULT CHARSET=UTF8;


 

五.     在AnotherUser表上比较查询统计

anotherUser表与User表的字段个数完全相同,唯一不同点在于两个表的主键id的类型不同,另外名称为“Id”的字段的类型也不同,两个表的数据记录基本相同。

1.       直接count(*)统计


mysql> explain select count(*) from anotherUser;

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

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

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

|  1 | SIMPLE      | anotherUser | index | NULL          | PRIMARY | 4       | NULL | 4056379 | Using index |

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

1 row in set (0.80 sec)

 

mysql> select count(*) from anotheruser;

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

| count(*) |

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

|  4056400 |

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

1 row in set (13.75 sec)


从上面的查询我们可以看到,count(*)在没有加任何条件的时候此次查询走的是主键索引,这个表的主键索引长度是4。优化器认为在提取到正确结果集之前大概需要扫描“4056379”行索引。

2.       主键字段做条件查询count(*)

我们来看下用主键UserId作为查询条件的情况,下面是查询的代码:


mysql> explain select count(*) from anotherUser where UserId>0;

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

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

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

|  1 | SIMPLE      | anotherUser | range | PRIMARY       | PRIMARY | 4       | NULL | 2028189 | Using where; Using index |

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

1 row in set (0.04 sec)

 

mysql> select count(*) from anotherUser where UserId>0;

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

| count(*) |

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

|  4056400 |

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

1 row in set (13.82 sec)


我们可以看到,虽然这里的主键索引的长度为4,但是查询时间基本还是在15秒左右。由此可以看出,index_Id索引查询时间比主键查询时间短并不是索引长度造成的。

3.       二级索引做条件查询count(*)

我们来测试下走Id索引,anotherUser的表Id字段是bigInt(20)。查询结果如下:


mysql> explain select count(*) from  anotherUser where Id>0;

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

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

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

|  1 | SIMPLE      | anotherUser | range | index_Id      | index_Id | 8       | NULL | 1862640 | Using where; Using index |

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

1 row in set (0.09 sec)

 

mysql> select count(*) from  anotherUser where Id>0;

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

| count(*) |

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

|  4056400 |

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

1 row in set (2.87 sec)


                  走二级索引index_Id只需要2.5秒左右,该索引的长度是8(比主键索引长度4大),但是此次统计仍然要比使用主键来统计要快的多。这更加说明了两者的查询结果不同不是由两者的索引长度造成的。

六.     结论

1.       没有任何条件的查询不一定走的是主键索引,mysql优化器会使用认为是最小代价的索引

2.       在count(*)的时候,采用主键索引比二级索引要慢,而且慢的原因不是因为两者的索引的长度不同

3.       Count(*)在没有查询条件的情况下,对innodb引擎的mysql会进行全表扫描,而myasm引擎的mysql无需进行全表扫描,因为myasm的引擎记录了每个表的多少记录。但是当有查询条件的时候,两者的查询效率一致。

4.       经过后来查询大量的资料,主键索引count(*)的时候之所以慢

l  InnoDB引擎

[1]     数据文件和索引文件存储在一个文件中,主键索引默认直接指向数据存储位置。

[2]     二级索引存储指定字段的索引,实际的指向位置是主键索引。当我们通过二级索引统计数据的时候,无需扫描数据文件;而通过主键索引统计数据时,由于主键索引与数据文件存放在一起,所以每次都会扫描数据文件,所以主键索引统计没有二级索引效率高。

[3]     由于主键索引直接指向实际数据,所以当我们通过主键id查询数据时要比通过二级索引查询数据要快。

l  MyAsm引擎

[1]     该引擎把每个表都分为几部分存储,比如用户表,包含user.frm,user.MYD和user.MYI。

[2]     User.frm负责存储表结构

[3]     User.MYD负责存储实际的数据记录,所有的用户记录都存储在这个文件中

[4]     User.MYI负责存储用户表的所有索引,这里也包括主键索引。

5.       MyAsm引擎不支持事务处理,没有仔细深入研究。两种引擎各有自己的使用场景,每个引擎的特点也不尽相同,感兴趣的你可以再仔细深入研究。

 

 

本人也是mysql菜鸟,以上是本人的个人观点,如果有误,请指正,多谢!


推荐阅读
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 数据库(外键及其约束理解)(https:www.cnblogs.comchenxiaoheip6909318.html)My ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • yum安装_Redis —yum安装全过程
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Redis—yum安装全过程相关的知识,希望对你有一定的参考价值。访问https://redi ... [详细]
  • 解决VS写C#项目导入MySQL数据源报错“You have a usable connection already”问题的正确方法
    本文介绍了在VS写C#项目导入MySQL数据源时出现报错“You have a usable connection already”的问题,并给出了正确的解决方法。详细描述了问题的出现情况和报错信息,并提供了解决该问题的步骤和注意事项。 ... [详细]
  • 本文介绍了关于apache、phpmyadmin、mysql、php、emacs、path等知识点,以及如何搭建php环境。文章提供了详细的安装步骤和所需软件列表,希望能帮助读者解决与LAMP相关的技术问题。 ... [详细]
  • 本文介绍了通过mysql命令查看mysql的安装路径的方法,提供了相应的sql语句,并希望对读者有参考价值。 ... [详细]
  • 本文详细介绍了MySQL表分区的创建、增加和删除方法,包括查看分区数据量和全库数据量的方法。欢迎大家阅读并给予点评。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • mysql-cluster集群sql节点高可用keepalived的故障处理过程
    本文描述了mysql-cluster集群sql节点高可用keepalived的故障处理过程,包括故障发生时间、故障描述、故障分析等内容。根据keepalived的日志分析,发现bogus VRRP packet received on eth0 !!!等错误信息,进而导致vip地址失效,使得mysql-cluster的api无法访问。针对这个问题,本文提供了相应的解决方案。 ... [详细]
  • 众筹商城与传统商城的区别及php众筹网站的程序源码
    本文介绍了众筹商城与传统商城的区别,包括所售产品和玩法不同以及运营方式不同。同时还提到了php众筹网站的程序源码和方维众筹的安装和环境问题。 ... [详细]
  • ubuntu用sqoop将数据从hive导入mysql时,命令: ... [详细]
author-avatar
壹诺彡_壹生
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有