广告
返回顶部
首页 > 资讯 > 数据库 >故障分析 | MySQL 优化案例 - select count(*)
  • 286
分享到

故障分析 | MySQL 优化案例 - select count(*)

故障分析|MySQL优化案例-selectcount(*) 2015-08-04 19:08:13 286人浏览 才女
摘要

作者:xuty 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。 本文关键字:count、sql、二级索引 一、故事背景 项目组联系我说是有一张 500w 左右的表做select

故障分析 | MySQL 优化案例 - select count(*)

作者:xuty 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


本文关键字:count、sql、二级索引

一、故事背景

项目组联系我说是有一张 500w 左右的表做select count(*)速度特别慢。

二、原 SQL 分析

Server version: 5.7.24-log Mysql CommUnity Server (GPL)

SQL 如下,仅仅就是统计 api_runtime_log 这张表的行数,一条简单的不能再简单的 SQL:

select count(*) from api_runtime_log;

我们先去运行一下这条 SQL,可以看到确实运行很慢,要 40 多秒左右,确实很不正常~

mysql> select count(*) from api_runtime_log;
+----------+
| count(*) |
+----------+
|  5718952 |
+----------+
1 row in set (42.95 sec)

我们再去看下表结构,看上去貌似也挺正常的~存在主键,表引擎也是 InnoDB,字符集也没问题。

CREATE TABLE `api_runtime_log_copy` (
  `BelongXiaQuCode` varchar(50) DEFAULT NULL,
  `OperateUserName` varchar(50) DEFAULT NULL,
  `OperateDate` datetime DEFAULT NULL,
  `Row_ID` int(11) DEFAULT NULL,
  `YearFlag` varchar(4) DEFAULT NULL,
  `RowGuid` varchar(50) NOT NULL,
   ......
  `apiid` varchar(50) DEFAULT NULL,
  `apiname` varchar(50) DEFAULT NULL,
  `apiguid` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`RowGuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

三、执行计划

通过执行计划,我们看下是否可以找到什么问题点。

mysql> explain select count(*) from api_runtime_log G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: api_runtime_log
   partitions: NULL
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 152
          ref: NULL
         rows: 5718952
     filtered: 100.00
      Extra: Using index

可以看到,查询走的是 PRIMARY,也就是主键索引。貌似也没有什么问题,走索引了呀!那么是不是真的就没问题呢?

四、原理

为了找到答案,通过 Google 查找 mysql 下select count(*)的原理,找到了答案。这边省略过程,直接上结果。

简单介绍下原理:

  • 聚簇索引:每一个 InnoDB 存储引擎下的表都有一个特殊的索引用来保存每一行的数据,称为聚簇索引(通常都为主键),聚簇索引实际保存了 B-Tree 索引和行数据,所以大小实际上约等于为表数据量
  • 二级索引:除了聚集索引,表上其他的索引都是二级索引,索引中仅仅存储了对应索引列及主键列

在 InnoDB 存储引擎中,count(*)函数是先从内存中读取数据到内存缓冲区,然后进行扫描获得行记录数。这里 InnoDB 会优先走二级索引;如果同时存在多个二级索引,会选择key_len 最小的二级索引;如果不存在二级索引,那么会走主键索引;如果连主键都不存在,那么就走全表扫描!这里我们由于走的是主键索引,所以 MySQL 需要先把整个主键索引读取到内存缓冲区,这是个从磁盘读写到内存的过程,而且主键索引基本等于整个表数据量(10GB+),所以非常耗时!

那么如何解决呢?

答案就是:建二级索引。

因为二级索引只包含对应的索引列及主键列,所以体积非常小。在select  count(*)的查询过程中,只需要将二级索引读取到内存缓冲区,只有几十 MB 的数据量,所以速度会非常快。举个形象的比喻,我们想知道一本书的页数:

  • 走聚集索引:从第一页翻到最后一页,知道总页数;

  • 走二级索引:通过目录直接知道总页数。

五、验证

创建二级索引后,再次执行 SQL 及查看执行计划。


mysql> create index idx_rowguid on api_runtime_log(rowguid);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> select count(*) from api_runtime_log;
+----------+
| count(*) |
+----------+
|  5718952 |
+----------+
1 row in set (0.89 sec)

mysql> explain select count(*) from api_runtime_log G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: api_runtime_log
   partitions: NULL
         type: index
possible_keys: NULL
          key: idx_rowguid
      key_len: 152
          ref: NULL
         rows: 5718952
     filtered: 100.00
        Extra: Using index
1 row in set, 1 warning (0.00 sec)

可以看到添加二级索引后,确实速度明显变快,而且执行计划也变成了走二级索引。至此这个问题其实已经解决了,就是由于表上缺少二级索引导致

六、深入测试

为了进一步验证上述的推论,所以就做了如下的测试。

测试过程如下:

  1. 通过 sysbench 创建了一张 500W 的测试表 sbtest1,表上仅仅包含一个主键索引,表大小为 1125MB;
  2. 调整部分 MySQL 参数,重启 MySQL,保证目前 innodb buffer pool(内存缓冲区) 中为空,不缓存任何数据;
  3. 执行select count(*),理论上走主键索引,查看当前内存缓冲区中缓存的数据量(理论上会缓存整个聚簇索引);
  4. 在测试表 sbtest1 上添加二级索引,索引大小为 55MB;
  5. 再次重启 MySQL,保证内存缓冲区为空;
  6. 再次执行select count(*),理论上走二级索引;
  7. 再次查看内存缓冲区中缓存的数据量(理论上只会缓存二级索引)。

测试结果如下:

1. 聚簇索引

查询当前内存缓冲区状态,结果为空证明不缓存测试表数据。

mysql> select * from sys.innodb_buffer_stats_by_table where object_schema = "test";
Empty set (1.92 sec)

mysql>  select count(*) from test.sbtest1;
+----------+
| count(*) |
+----------+
|  5188434 |
+----------+
1 row in set (5.52 sec)

再次查看内存缓冲区,发现缓存了 sbtest1 表上 1G 多的数据,基本等于整个表数据量。

mysql> select * from sys.innodb_buffer_stats_by_table where object_schema = "test" G;
*************************** 1. row ***************************
object_schema: test
  object_name: sbtest1
    allocated: 1.08 GiB
         data: 1.01 GiB
        pages: 71081
 pages_hashed: 0
    pages_old: 28119
  rows_cached: 5189798

最后我们再来看下执行计划,确实走的是主键索引,放在最后执行是为了避免影响缓冲区。

mysql> explain  select count(*) from test.sbtest1 G;                                          
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: sbtest1
   partitions: NULL
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 5117616
     filtered: 100.00
        Extra: Using index                                   

2. 二级索引

创建二级索引 idx_id,查看 sbtest1 表上主键索引与二级索引的数据量。

mysql> create index idx_id on sbtest1(id);
Query OK, 0 rows affected (12.97 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SELECT sum(stat_value) pages ,index_name ,
(round((sum(stat_value) * @@innodb_page_size)/1024/1024)) as MB 
  FROM mysql.innodb_index_stats 
  WHERE table_name = "sbtest1" 
  AND database_name = "test" 
  AND stat_description = "Number of pages in the index" 
  GROUP BY index_name;
+-------+------------+------+
| pages | index_name | MB   |
+-------+------------+------+
| 72000 | PRIMARY    | 1125 |
|  3492 | idx_id     |   55 |
+-------+------------+------+

重启 MySQL,再次查看缓冲区同样为空,证明没有缓存测试表上的数据。

mysql> select * from sys.innodb_buffer_stats_by_table where object_schema = "test";
Empty set (1.49 sec)

mysql> select count(*) from test.sbtest1;
+----------+
| count(*) |
+----------+
|  5188434 |
+----------+
1 row in set (2.92 sec)

再次查看内存缓冲区,发现仅仅缓存了 sbtest1 表上的 50M 数据,约等于二级索引的数据量。

mysql> select * from sys.innodb_buffer_stats_by_table where object_schema = "test" G;
*************************** 1. row ***************************
object_schema: test
  object_name: sbtest1
    allocated: 49.48 MiB
         data: 46.41 MiB
        pages: 3167
 pages_hashed: 0
    pages_old: 1575
rows_cached: 2599872

最后确认下执行计划,确实走的是二级索引。

mysql> explain select count(*) from test.sbtest1 G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: sbtest1
   partitions: NULL
         type: index
possible_keys: NULL
          key: idx_id
      key_len: 4
          ref: NULL
         rows: 5117616
     filtered: 100.00
        Extra: Using index

七、案例总结

从上述这个测试结果可以看出,和之前的推论基本吻合。如果select count(*)走的是主键索引,那么会缓存整个表数据,大量查询时间会花费在读取表数据到缓冲区。

如果存在二级索引,那么只需要读取索引页到缓冲区即可,速度自然快。

另:项目上由于磁盘性能层次不齐,所以当遇上这种情况时,性能较差的磁盘更会放大这个问题;一张超级大表,统计行数时如果走了主键索引,后果可想而知~

八、优化建议

此次测试过程中我们仅仅模拟是百万数据量,此时我们通过二级索引统计表行数,只需要读取几十 M 的数据量,就可以得到结果。 那么当我们的表数据量是上千万,甚至上亿时呢。此时即便是最小的二级索引也是 几百 M、过 G 的数据量,如果继续通过二级索引来统计行数,那么速度就不会如此迅速了。

这个时候可以通过避免直接select count(*) from table来解决,方法较多,例如:

  1. 使用 MySQL 触发器 + 统计表实时计算表数据量;
  2. 使用 MyISAM 替换 InnoDB,因为 MyISAM 自带计数器,坏处就不多说了;
  3. 通过 ETL 导入表数据到其他更高效的异构环境中进行计算;
  4. 升级到 MySQL 8 中,使用并行查询,加快检索速度。

当然,什么时候 InnoDB 存储引擎可以直接实现计数器的功能就好了!

您可能感兴趣的文档:

--结束END--

本文标题: 故障分析 | MySQL 优化案例 - select count(*)

本文链接: https://www.lsjlt.com/news/7028.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 故障分析 | MySQL 优化案例 - select count(*)
    作者:xuty 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。 本文关键字:count、SQL、二级索引 一、故事背景 项目组联系我说是有一张 500w 左右的表做select ...
    99+
    2015-08-04
    故障分析 | MySQL 优化案例 - select count(*)
  • MySQL select count(*)计数很慢优化方案
    目录前言1. MyISAM存储引擎计数为什么这么快?2. 能不能手动实现统计总行数3. InnoDB引擎能否实现快速计数4. 四种计数方式的性能差别前言 在日常开发工作中,我经常会遇到需要统计总数的场景,比如:统计订单总...
    99+
    2022-08-11
    MySQLselectcount(*)计数慢优化 MySQL优化
  • 故障分析 | MySQL 优化案例 - 字符集转换
    作者:xuty 本文来源:原创投稿 *爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。 本文关键字:SQL 优化、字符集 一、背景 Server version: 5.7.24-log MySQL C...
    99+
    2021-10-14
    故障分析 | MySQL 优化案例 - 字符集转换
  • MySQL性能优化的案例分析
    这篇文章主要介绍MySQL性能优化的案例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!前言MySQL索引底层数据结构与算法MySQL性能优化原理-前篇实践(1)--MySQL性能...
    99+
    2022-10-18
  • 故障分析 | MySQL 派生表优化
    一、问题 SQL 原 SQL 如下: select name,count(name) from bm_id a left JOIN (select TaskName from up_pro_accept_v3_bdc...
    99+
    2014-08-06
    故障分析 | MySQL 派生表优化
  • MySQL原理与优化的案例分析
    这篇文章主要介绍MySQL原理与优化的案例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!MySQL是目前互联网公司应用最广泛的数据库软件(DBMS),没有之一。小至初创公司,大至...
    99+
    2022-10-18
  • MySQL慢日志优化的案例分析过程
    这期内容当中小编将会给大家带来有关MySQL慢日志优化的案例分析过程,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。最近在分析一个问题的时候,...
    99+
    2022-10-18
  • python代码优化案例分析
    第一版举例:def displayNumType(num):     print num,"is",     if type(num)==type(0):         print 'an interger'     elif type(...
    99+
    2023-01-31
    案例分析 代码 python
  • MySQL5.7中select半连接优化的示例分析
    这篇文章主要为大家展示了“MySQL5.7中select半连接优化的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“MySQL5.7中selec...
    99+
    2022-10-18
  • Mysql优化之Zabbix分区优化的示例分析
    这篇文章主要介绍了Mysql优化之Zabbix分区优化的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。使用zabbix最大的瓶颈在于...
    99+
    2022-10-18
  • mysql之调优概论的案例分析
    小编给大家分享一下mysql之调优概论的案例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一  简介咱们先不说cp...
    99+
    2022-10-18
  • MySQL索引优化实例分析
    目录1.数据准备2.实例一3.mysql如何选择合适的索引?4.常见 SQL 深入优化4.1.Order by与Group by优化4.2.分页查询优化4.3.join关联查询优化4.3.1.数据准备4.3.2.MySQ...
    99+
    2022-07-29
    MySQL索引优化 MySQL索引
  • mysql hint优化的示例分析
    这篇文章将为大家详细讲解有关mysql hint优化的示例分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。SELECT t.oldcontract...
    99+
    2022-10-18
  • MySQL大表优化的示例分析
    小编给大家分享一下MySQL大表优化的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!背景阿里云RDS FOR MySQL(MySQL5.7版本)数据库业务表每月新增数据量超过千万,...
    99+
    2022-10-18
  • MySQL查询优化的示例分析
    小编给大家分享一下MySQL查询优化的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!一、优化的思路和原则有哪些1、 优化更需要优化的查询 2、 定位优化对象的性能瓶颈 3、 明确优...
    99+
    2022-10-18
  • Mysql优化策略的示例分析
    这篇文章主要介绍Mysql优化策略的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!总的来说:1、数据库设计和表创建时就要考虑性能2、sql的编写需要注意优化3、分区、分表、分...
    99+
    2022-10-18
  • mysql中limit优化的示例分析
    小编给大家分享一下mysql中limit优化的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!    ...
    99+
    2022-10-18
  • my.ini与mysql优化的示例分析
    小编给大家分享一下my.ini与mysql优化的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!mysql优化了所以先说说...
    99+
    2022-10-19
  • MySQL中table_cache优化的示例分析
    这篇文章主要介绍MySQL中table_cache优化的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!table_cache指定表高速缓存的大小。每当MySQL访问一个表时,...
    99+
    2022-10-19
  • SEO网站优化方案的示例分析
    小编给大家分享一下SEO网站优化方案的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、数据分析大数据时代已经来临,通过数据分析可以找出用户需求。数据分析...
    99+
    2023-06-10
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作