0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何解决数据库CPU使用率100%报警频繁的问题呢

工程师邓生 来源:OSCHINA 社区 作者:京东云开发者 2022-09-01 12:12 次阅读

1 前言

近期随着数据量的增长,数据库 CPU 使用率 100% 报警频繁起来。第一个想到的就是慢 Sql,我们对未合理运用索引的表加入索引后,问题依然没有得到解决,深入排查时,发现在 order by id asc limit n 时,即使 where 条件已经包含了覆盖索引,优化器还是选择了错误的索引导致。通过查询大量资料,问题得到了解决。这里将解决问题的思路以及排查过程分享出来,如果有错误欢迎指正。

2 正文

2.1 环境介绍

690987da-292c-11ed-ba43-dac502259ad0.png

2.2 发现问题

22 日开始,收到以下图 1 报警变得频繁起来,由于数据库中会有大数据推数动作,数据库 CPU 偶尔报警并没有引起对该问题的重视,直到通过图 2 对整日监控数据分析时,才发现问题的严重性,从 0 点开始,数据库 CPU 频繁被打满。

691d470c-292c-11ed-ba43-dac502259ad0.png

图 1:报警图

692f59f6-292c-11ed-ba43-dac502259ad0.png

图 2:整日 CPU 监控图

2.3 排查问题

发现问题后,开始排查慢 Sql,发现很多查询未添加合适的索引,经过一轮修复后,问题依然没有得到解决,在深入排查时发现了一个奇怪现象,SQL 代码如下(表名已经替换),比较简单的一个单表查询语句。

poYBAGMQMVOAOpvtAABHrDwPvaU298.jpg

看似比较简单的查询,但执行时长平均在 90s 以上,并且调用频次较高。如图 3 所示。
6942671c-292c-11ed-ba43-dac502259ad0.png

图 3:慢 Sql 平均执行时长 开始检查表信息,可以看到表数据量在 2100w 左右。

69682524-292c-11ed-ba43-dac502259ad0.png

图 4:数据表情况 排查索引情况,主键为 id,并且有 business_day 与 full_ps_code 的联合索引。

pYYBAGMQMXKAJKg_AAC5df4wQwY672.jpg

通过 Explain 查看执行计划时发现,possible_keys 中包含上面的联合索引,而 Key 却选择了 Primary 主键索引,扫描行数 Rows 为 1700w,几乎等于全表扫描。 697ffca8-292c-11ed-ba43-dac502259ad0.png 图 5:执行计划情况

2.4 解决问题

第一次,我们分析是,由于 Where 条件中包含了 ID,查询分析器认为主键索引扫描行数会少,同时根据主键排序,使用主键索引会更加合理,我们试着添加以下索引,想要让查询分析器命中我们新加的索引。

ADD INDEX `idx_test`(`business_day`, `full_ps_code`, `id`) USING BTREE;
再次通过 Explain 语句进行分析,发现执行计划完全没变,还是走的主键索引。

poYBAGMQNrmAAXD5AABV73g3LmY369.jpg

69999f8c-292c-11ed-ba43-dac502259ad0.png

图 6:执行计划情况 第二次,我们通过强制指定索引方式 force index (idx_test) 方式,再次分析执行情况,得到图 7 的结果,同样的查询条件同样的结果,查询时长由 90s->0.49s 左右。问题得到解决

69b4bd44-292c-11ed-ba43-dac502259ad0.png

图 7:强制指定索引后执行计划情况

69d0761a-292c-11ed-ba43-dac502259ad0.png

第三次,我们怀疑是 where 条件中有 ID 导致直接走的主键索引,where 条件中去掉 id,Sql 调整如下,然后进行分析。依然没有命中索引,扫描 rows 变成 111342,查询时间 96s

pYYBAGMQNt-AKsWhAABGpuLdlpQ556.jpg

69ebc4f6-292c-11ed-ba43-dac502259ad0.png

69fe5170-292c-11ed-ba43-dac502259ad0.png

第四次,我们把 order by 去掉,SQL 调整如下,然后进行分析。命中了 idx_business_day_full_ps_code 之前建立的联合索引。扫描行数变成 154900,查询时长变为 0.062s,但是发现结果与预想的不一致,发生了乱序

pYYBAGMQNv6Adm2NAABQ1mQ72mw386.jpg

6a19bab4-292c-11ed-ba43-dac502259ad0.png


6a30d938-292c-11ed-ba43-dac502259ad0.png

第五次,经过前几次的分析可以确定,order by 导致查询分析器选择了主键索引,我们在 Order by 中增加排序字段,将 Sql 调整如下,同样可以命中我们之前的联合索引,查询时长为 0.034s,由于先按照主键排序,结果是一致的。相比第四种方法多了一份 filesort,问题得解决。

poYBAGMQNxyALd7yAABV3WGs9aw362.jpg

6a475fb4-292c-11ed-ba43-dac502259ad0.png
6a53257e-292c-11ed-ba43-dac502259ad0.png

第六次,我们考虑是不是 Limit 导致的问题,我们将 Limit 500 调整到 1000,Sql 调整如下,奇迹发生了,命中了联合索引,查询时长为 0.316s,结果一致,只不过多返回来 500 条数据。问题得到了解决。经过多次实验 Limit 大于 695 时就会命中联合索引,查询条件下的数据量是 79963,696/79963 大概占比是 0.0087,猜测当获取数据比超过 0.0087 时,会选择联合索引,未找到源代码验证此结论。

pYYBAGMQNziAI8UWAABXKmVwUbE843.jpg

6a7287ac-292c-11ed-ba43-dac502259ad0.png

6a87f5f6-292c-11ed-ba43-dac502259ad0.png

经过我们的验证,其中第 2、5、6 三种方法都可以解决性能问题。为了不影响线上,我们立即修改代码,并选择了 force index 的方式,上线观察一段时间后,数据库 CPU 恢复正常,问题得到了解决。

6a9a7802-292c-11ed-ba43-dac502259ad0.png

3 事后分析

上线后问题得到了解决,同时也留给我了很多疑问。

为什么明明 where 条件中包含了联合索引,却未能命中,反而选择了性能较慢的主键索引?

为什么在 order by 中增加了一个索引其他字段,就可以命中联合索引了呢?

为什么我仅仅是将 limit 限制条件由原来的 500 调大后,也能命中联合索引呢?

这一切的答案都来自 MySQL 的查询优化器。

3.1 查询优化器

查询优化器是专门负责优化查询语句的优化器模块,通过计算分析收集的各种系统统计信息,为查询给出最优的执行计划 —— 最优的数据检索方式。 优化器决定如何执行查询的方式是基于一种称为基于代价的优化的方法。5.7 在代价类型上分为 IO、CPU、Memory。内存的代价收集了,但是并没有参与最终的代价计算。Mysql 中引入了两个系统表,mysql.server_cost 和 mysql.engine_cost,server_cost 对应 CPU 的代价,engine_cost 代表 IO 的代价。 server_cost(CPU 代价)

row_evaluate_cost (default 0.2) 计算符合条件的行的代价,行数越多,此项代价越大

memory_temptable_create_cost (default 2.0) 内存临时表的创建代价

memory_temptable_row_cost (default 0.2) 内存临时表的行代价

key_compare_cost (default 0.1) 键比较的代价,例如排序

disk_temptable_create_cost (default 40.0) 内部 myisam 或 innodb 临时表的创建代价

disk_temptable_row_cost (default 1.0) 内部 myisam 或 innodb 临时表的行代价

由上可以看出创建临时表的代价是很高的,尤其是内部的 myisam 或 innodb 临时表。 engine_cost(IO 代价)

io_block_read_cost (default 1.0) 从磁盘读数据的代价,对 innodb 来说,表示从磁盘读一个 page 的代价

memory_block_read_cost (default 1.0) 从内存读数据的代价,对 innodb 来说,表示从 buffer pool 读一个 page 的代价

这些信息都可以在数据库中配置,当数据库中未配置时,从 MySql 源代码(5.7)中可以看到以上默认值情况

6ab9aaf6-292c-11ed-ba43-dac502259ad0.png

3.2 代价配置

poYBAGMQN2KAGZiXAABiuFnB6hE024.jpg

3.3 代价计算

代价是如何算出来的呢,通过读 MySql 的源代码,可以找到最终的答案 3.3.1 全表扫描(table_scan_cost) 以下代码摘自 MySql Server(5.7 分支),全表扫描时,IO 与 CPU 的代价计算方式。

poYBAGMQN4OAI0MjAAE_FZ2z7ls735.jpg
pYYBAGMQN4uAHQ0zAADlMtj10YI285.jpg

根据源代码分析,当表中包含 100 行数据时,全表扫描的成本为 23.1,计算逻辑如下

poYBAGMQN52AMqygAABZDU0N6c8455.jpg

验证结果如下图

6ad746ba-292c-11ed-ba43-dac502259ad0.png

3.3.2 索引扫描(index_scan_cost) 以下代码摘自 MySql Server(5.7 分支),当出现索引扫描时,是如何进行计算的,核心代码如下

pYYBAGMQN8iAKhoGAABfHEGQbbs544.jpg

io 代价计算核心代码

//核心代码
const double io_cost= index_only_read_time(index, rows) *
table->cost_model()->page_read_cost_index(index, 1.0);

// index_only_read_time(index, rows)
// 估算index占page个数

//page_read_cost_index(index, 1.0)
//根据buffer pool大小和索引大小来估算page in memory和in disk的比例,计算读一个page的代价

cpu 代价计算核心代码

pYYBAGMQN_OAAgS5AABfhflMzRE733.jpg


3.3.3 其他方式 计算代价的方式有很多,其他方式请参考 MySql 原代码。https://github.com/mysql/mysql-server.git

3.4 深度解析

通过查看 optimizer_trace,可以了解查询优化器是如何选择的索引。

pYYBAGMQOAqAd6a5AAClSllMXjo610.jpg

通过分析 rows_estimation 节点,可以看到通过全表扫描(table_scan)的话的代价是 8.29e6,同时也可以看到该查询可以选择到主键索引与联合索引,如下图。

6af7ba26-292c-11ed-ba43-dac502259ad0.png

上图中全表扫描的代价是 8.29e6,我们转换成普通计数法为 8290000,如果使用主键索引成本是 3530000,联合索引 185881,最小的应该是 185881 联合索引,也可以看到第一步通过成本分析确实选择了我们的联合索引。

6b1a2052-292c-11ed-ba43-dac502259ad0.png

6b2c6622-292c-11ed-ba43-dac502259ad0.png

6b3f857c-292c-11ed-ba43-dac502259ad0.png

但是为什么还是选择了主键索引呢? 通过往下看,在 reconsidering_access_paths_for_index_ordering 节点下, 发现由于 Order by 导致重新选择了索引,在下图中可以看到主键索引可用(usable=true),我们的联合索引为 not_applicable (不适用),意味着排序只能使用主键索引。

6b5f31c4-292c-11ed-ba43-dac502259ad0.png

接下来通过 index_order_summary 可以看出,执行计划最终被调整,由原来的联合索引改成了主键索引,就是说这个选择无视了之前的基于索引成本的选择。

6b8978bc-292c-11ed-ba43-dac502259ad0.png

为什么会有这样的一个选项呢,主要原因如下:

The short explanation is that the optimizer thinks — or should I say hopes — that scanning the whole table (which is already sorted by the id field) will find the limited rows quick enough, and that this will avoid a sort operation. So by trying to avoid a sort, the optimizer ends-up losing time scanning the table.

从这段解释可以看出主要原因是由于我们使用了 order by id asc 这种基于 id 的排序写法,优化器认为排序是个昂贵的操作,所以为了避免排序,并且它认为 limit n 的 n 如果很小的话即使使用全表扫描也能很快执行完,所以它选择了全表扫描,也就避免了 id 的排序。

5 总结

查询优化器会基于代价来选择最优的执行计划,但由于 order by id limit n 的存在,MySql 可能会重新选择一个错误的索引,忽略原有的基于代价选择出来的索引,转而选择全表扫描的主键索引。这个问题在国内外有大量的用户反馈,BUG 地址https://bugs.mysql.com/bug.php?id=97001。官方称在 5.7.33 以后版本可以关闭 prefer_ordering_index 来解决。如下图所示。

6ba69b36-292c-11ed-ba43-dac502259ad0.png

另外在我们日常慢 Sql 调优时,可以通过以下两种方式,了解更多查询优化器选择过程。

poYBAGMQOFyAZk2CAACQfGRgS0c916.jpg

当你也出现了本篇文章碰到的问题时,可以采用以下的方法来解决

使用 force index,强制指定索引。

order by 中增加一个联合索引的 key。

扩大 limit 返回的范围(不推荐,随着数据量的增大,可能还会走回主键索引)

order by (id+0) asc 欺骗查询优化器,让其选择联合索引。

MySQL 5.7.33 版本以上,可以关闭 prefer_ordering_index 解决。



审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10872

    浏览量

    212014
  • SQL
    SQL
    +关注

    关注

    1

    文章

    766

    浏览量

    44162
  • 数据库
    +关注

    关注

    7

    文章

    3816

    浏览量

    64455

原文标题:记录一次数据库CPU被打满的排查过程

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    体验领礼啦!体验自建数据库迁移到阿里云数据库RDS,领取桌面置物架!

    本方案介绍如何将自建数据库平滑迁移至云数据库RDS,解决您随着业务增长可能会面临的数据库运维难题。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业
    的头像 发表于 01-03 17:12 60次阅读
    体验领礼啦!体验自建<b class='flag-5'>数据库</b>迁移到阿里云<b class='flag-5'>数据库</b>RDS,领取桌面置物架!

    数据库加密办法

    企业对于数据的重视程度不言而喻,也衍生出了数据=资产的概念。但是数据泄漏的事件频繁发生,为了保护数据资产,企业有必要对
    的头像 发表于 12-24 09:47 84次阅读

    数据库数据恢复—Mysql数据库表记录丢失的数据恢复流程

    Mysql数据库故障: Mysql数据库表记录丢失。 Mysql数据库故障表现: 1、Mysql数据库表中无任何数据或只有部分
    的头像 发表于 12-16 11:05 171次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—Mysql<b class='flag-5'>数据库</b>表记录丢失的<b class='flag-5'>数据</b>恢复流程

    数据库事件触发的设置和应用

    数据库无论对于生产管理还是很多的实际应用都非常重要。小编这次聊一下数据库事件触发的应用。示例使用了postgresql和Python。
    的头像 发表于 12-13 15:14 147次阅读

    数据库数据恢复—MYSQL数据库ibdata1文件损坏的数据恢复案例

    mysql数据库故障: mysql数据库文件ibdata1、MYI、MYD损坏。 故障表现:1、数据库无法进行查询等操作;2、使用mysqlcheck和myisamchk无法修复数据库
    的头像 发表于 12-09 11:05 168次阅读

    数据库数据恢复—通过拼接数据库碎片恢复SQLserver数据库

    一个运行在存储上的SQLServer数据库,有1000多个文件,大小几十TB。数据库每10天生成一个NDF文件,每个NDF几百GB大小。数据库包含两个LDF文件。 存储损坏,数据库
    的头像 发表于 10-31 13:21 247次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—通过拼接<b class='flag-5'>数据库</b>碎片恢复SQLserver<b class='flag-5'>数据库</b>

    Oracle数据恢复—异常断电后Oracle数据库报错的数据恢复案例

    Oracle数据库故障: 机房异常断电后,Oracle数据库报错:“system01.dbf需要更多的恢复来保持一致性,数据库无法打开”。数据
    的头像 发表于 09-30 13:31 317次阅读
    Oracle<b class='flag-5'>数据</b>恢复—异常断电后Oracle<b class='flag-5'>数据库</b>启<b class='flag-5'>库</b>报错的<b class='flag-5'>数据</b>恢复案例

    数据库数据恢复—Oracle数据库文件system01.dbf损坏的数据恢复案例

    打开oracle数据库报错“system01.dbf需要更多的恢复来保持一致性,数据库无法打开”。
    的头像 发表于 09-21 14:25 383次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—Oracle<b class='flag-5'>数据库</b>文件system01.dbf损坏的<b class='flag-5'>数据</b>恢复案例

    数据库数据恢复—SQL Server数据库出现823错误的数据恢复案例

    SQL Server数据库故障: SQL Server附加数据库出现错误823,附加数据库失败。数据库没有备份,无法通过备份恢复数据库
    的头像 发表于 09-20 11:46 362次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—SQL Server<b class='flag-5'>数据库</b>出现823错误的<b class='flag-5'>数据</b>恢复案例

    JAVA应用CPU跳点自动DUMP工具

    问题。如果CPU使用率过高,可能表示系统存在资源瓶颈,需要进行优化或升级。 CPU监控的难点 现有的监控平台提供了多种方式来获取容器和JVM的CPU
    的头像 发表于 08-05 17:48 537次阅读

    数据库数据恢复—SQL Server数据库所在分区空间不足报错的数据恢复案例

    SQL Server数据库数据恢复环境: 某品牌服务器存储中有两组raid5磁盘阵列。操作系统层面跑着SQL Server数据库,SQL Server数据库存放在D盘分区中。
    的头像 发表于 07-10 13:54 516次阅读

    恒讯科技全面解析:如何有效降低服务器CPU用率

    降低服务器CPU用率是一个涉及监控、诊断和优化的全面过程。以下是一些有效的方法: 1、监控CPU使用率: 使用工具如top, htop, vmstat, 或 iostat实时监控
    的头像 发表于 05-10 17:24 745次阅读

    数据库数据恢复—raid5阵列上层Sql Server数据库数据恢复案例

    数据库数据恢复环境: 5块硬盘组建一组RAID5阵列,划分LUN供windows系统服务器使用。windows系统服务器内运行了Sql Server数据库,存储空间在操作系统层面划分了三个逻辑分区
    的头像 发表于 05-08 11:43 522次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—raid5阵列上层Sql Server<b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复案例

    数据库数据恢复】Oracle数据库ASM实例无法挂载的数据恢复案例

    oracle数据库ASM磁盘组掉线,ASM实例不能挂载。数据库管理员尝试修复数据库,但是没有成功。
    的头像 发表于 02-01 17:39 531次阅读
    【<b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复】Oracle<b class='flag-5'>数据库</b>ASM实例无法挂载的<b class='flag-5'>数据</b>恢复案例

    如何在Linux系统中检查CPU使用率

    首先在Linux系统中检查CPU使用率。可以通过在命令行中输入top或htop命令来查看当前系统中各个进程的CPU使用率。如果CPU
    发表于 01-06 10:42 1331次阅读
    如何在Linux系统中检查<b class='flag-5'>CPU</b><b class='flag-5'>使用率</b>