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

    文章

    10456

    浏览量

    206607
  • SQL
    SQL
    +关注

    关注

    1

    文章

    738

    浏览量

    43468
  • 数据库
    +关注

    关注

    7

    文章

    3592

    浏览量

    63385

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

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

收藏 人收藏

    评论

    相关推荐

    通过Modbus读写数据库中的数据

    本文是将数据库数据转为Modbus服务端/从站,实现数据库内的数据也可以走Modbus协议通过网口或串口读写的案例,下图是通过智能网关的参数软件(在附件中)配置的参数: 上图中的配置
    发表于 03-14 13:44

    Linux服务器CPU飙升的原因

    首先在Linux系统中检查CPU使用率。可以通过在命令行中输入top或htop命令来查看当前系统中各个进程的CPU使用率。如果CPU
    发表于 02-28 11:00 397次阅读
    Linux服务器<b class='flag-5'>CPU</b>飙升的原因

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

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

    Java程序CPU使用率高的原因

    Java程序是一种高级编程语言,由于其跨平台的特性和强大的功能,被广泛应用于服务器端、企业级应用和大数据处理等场景。然而,在某些情况下,我们可能会发现Java程序的CPU使用率异常高,这会导致系统
    的头像 发表于 12-05 11:20 2803次阅读

    元件数据库

    软件可以识别设备的元件数据库就好了,我们公司的机器数据都是用物料编码建立的
    发表于 11-16 14:39

    如何在HarmonyOS对数据库进行备份,恢复与加密

    数据库备份与恢复 场景介绍 当应用在处理一项重要的操作,显然是不能被打断的。例如:写入多个表关联的事务。此时,每个表的写入都是单独的,但是表与表之间的事务关联性不能被分割。 如果操作的过程中
    发表于 11-07 08:57

    Java11和Java17使用率达48%和45%

    2018 年 9 月发布的 Java 11 和 2020 年 9 月发布的 Java 17 是使用最广泛的 Java 版本,使用率分别为 48% 和 45%。其次是 2014 年 3 月发布
    的头像 发表于 11-01 12:30 313次阅读

    codewarrior怎样知道各种内存的使用率

    codewarrior怎样知道rom,ram,eeprom的使用率
    发表于 11-01 07:02

    STM32怎么获取CPU使用率

    CPU使用率信息都是怎么读取的
    发表于 10-23 07:20

    怎样查看堆栈使用率

    如何查看堆(stack)的使用率
    发表于 10-20 07:01

    关于PLC设备对接ORACLE数据库上传查询数据

    智能网关IGT-DSER方便实现PLC与数据库之间的数据通讯,既可以读取PLC的数据上报到数据库,也可以从数据库查询
    发表于 10-12 15:34

    什么是CPU使用率?如何测量CPU使用率

    CPU 使用率CPU 在计算机上执行各种任务和进程所花费的时间量的度量。
    的头像 发表于 08-06 17:07 3079次阅读

    数据库数据模型设计(2)#数据库

    数据库
    未来加油dz
    发布于 :2023年07月18日 17:54:39

    基于BOLT IOT的CPU使用率监控器

    电子发烧友网站提供《基于BOLT IOT的CPU使用率监控器.zip》资料免费下载
    发表于 07-03 10:23 0次下载
    基于BOLT IOT的<b class='flag-5'>CPU</b><b class='flag-5'>使用率</b>监控器

    LVGL cpu使用率过高无法显示视频怎么处理?

    seqence 代码。PXP 和 VGLite 已启用。 `LV_USE_PERF_MONITOR` 启用查看 cpu 使用情况,但 cpu 使用率始终为 99%,即使只有两个标签和两个按钮。 有两种
    发表于 05-10 07:31