记一次MySQL CPU被打满的SQL优化案例分析

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 记一次MySQL CPU被打满的SQL优化案例分析

背景介绍

系统中有个公告模块,当用户登录后,根据用户所属机构查询公告列表,同时公告列表中需要展示出该用户对公告的阅读状态及阅读时间。公告(bulletin)、公告接收者(bulletin_receiver)、公告阅读者(bulletin_reader)定义及关联关系如下:

应用使用的数据库连接池是druid,数据库是阿里云RDS MySQL 5.6(16c64g)

问题梳理

经过梳理,事件时间线大概是这样的:

14:45 GetConnectionTimeoutException

14:47 钉钉报警

14:59 CommunicationsException

问题分析

应用分析

GetConnectionTimeoutException

arthas vmtool

我们分析问题,通常会碰到日志记录不够排查问题的情况,此时可以通过arthas相关命令进行分析。比如我们想看一下druid连接池统计信息。

CommunicationsException

出现该异常几个典型场景是:

  • 数据库连接空闲时间超过了MySQL服务器配置的wait_timeout
  • MySQL server versions like 5.6.25 and earlier or 5.7.5 and earlier,客户端连接属性useSSL默认是false;MySQL server versions like 5.6.25+ or 5.7.5+,客户端连接属性useSSL默认是true。默认useSSL=true的MySQL server版本,客户端连接属性还需要配置其他额外的连接属性,如果没有配置会抛出_CommunicationsException异常。_
  • 客户端发送请求后,服务端比较忙,一直没有回复客户端的请求导致超时。

从上面时间线看,当出现该异常的时候,MySQL CPU已经被打满,无法及时处理客户端请求,导致客户端请求超时。

系统负载

业务指标

  • 业务入口请求量比较平缓,没有波动
  • 系统GC情况正常… …

数据库分析

数据库侧没有打开performance_schema,主要排查操作:

  • show processlist:查看数据库会话信息
  • select * from information_schema.innodb_trx
  • 查询慢SQL:没有慢SQL

可疑SQL:

SELECT A.ID AS ID,
   A.BULLETIN_TYPE AS BULLETIN_TYPE,
   A.BULLETIN_CONTENT AS BULLETIN_CONTENT,
   A.STATUS_CODE AS REMARKS,
   B.READ_STATUS AS STATUS_CODE,
   B.READ_TIME AS GMT_MODIFIED
FROM(
    SELECT C.*
      FROM BULLETIN C
     WHERE C.ID IN(
                SELECT D.BULLETIN_ID
                  FROM BULLETIN_RECEIVER AS D
                 WHERE D.RECV_CODE IN('45346600', '45302600')
                 GROUP BY D.BULLETIN_ID)
 AND C.STATUS_CODE= 'RELEASE'
 AND C.BULLETIN_TYPE= 2
 AND C.VALID_DATE_END> '2022-11-16 00:00:00'
 AND C.VALID_DATE_BEGIN<= '2022-11-17 00:00:00') AS A
LEFT JOIN BULLETIN_READER AS B ON A.ID= B.BULLETIN_ID
 AND B.READER_ID= 1990234
WHERE B.IS_DELETED IS NULL OR B.IS_DELETED= '0'
LIMIT 0,10

explain 执行计划:

似乎没有啥问题,而且SQL执行的还可以。

一时没有好的办法快速解决,扩容了两台从库用来分担读的流量,从库上线后一切还算正常,于是恢复了部分数据,哪知过了两天CPU再次被打满… …

问题分析到这里,似乎已经不是资源的问题了… …

解决办法

SQL优化

话说SQL里尽量不要使用IN,可以使用EXISTS或JOIN替代,于是我们改了下SQL。

EXISTS

SELECT A.ID AS ID,
   A.BULLETIN_TYPE AS BULLETIN_TYPE,
   A.BULLETIN_CONTENT AS BULLETIN_CONTENT,
   A.STATUS_CODE AS REMARKS,
   B.READ_STATUS AS STATUS_CODE,
   B.READ_TIME AS GMT_MODIFIED
FROM(
    SELECT C.*
      FROM BULLETIN C
     WHERE EXISTS (
                SELECT 1
                  FROM BULLETIN_RECEIVER AS D
                 WHERE D.BULLETIN_ID=C.ID AND D.RECV_CODE IN('45346600', '45302600')
                 )
 AND C.STATUS_CODE= 'RELEASE'
 AND C.BULLETIN_TYPE= 2
 AND C.VALID_DATE_END> '2022-11-16 00:00:00'
 AND C.VALID_DATE_BEGIN<= '2022-11-17 00:00:00') AS A
LEFT JOIN BULLETIN_READER AS B ON A.ID= B.BULLETIN_ID
 AND B.READER_ID= 1990234
WHERE B.IS_DELETED IS NULL OR B.IS_DELETED= '0'
LIMIT 0,10

JOIN

除了将IN改为JOIN,同时将select中的公告内容字段去掉,因为多数场景下公告列表不需要内容字段。

SELECT A.ID AS ID,
   A.BULLETIN_TYPE AS BULLETIN_TYPE,
   A.STATUS_CODE AS REMARKS,
   B.READ_STATUS AS STATUS_CODE,
   B.READ_TIME AS GMT_MODIFIED
FROM(
    SELECT C.ID AS ID,C.BULLETIN_TYPE AS BULLETIN_TYPE,C.STATUS_CODE AS STATUS_CODE
      FROM BULLETIN C
      JOIN (
              SELECT D.BULLETIN_ID AS ID
                FROM POR_BULLETIN_RECEIVER AS D
               WHERE D.RECV_CODE IN('45346600', '45302600') 
               GROUP BY D.BULLETIN_ID
              ) AS E ON C.ID=E.ID
 AND C.STATUS_CODE= 'RELEASE'
 AND C.BULLETIN_TYPE= 2
 AND C.VALID_DATE_END> '2022-11-16 00:00:00'
 AND C.VALID_DATE_BEGIN<= '2022-11-17 00:00:00') AS A
LEFT JOIN BULLETIN_READER AS B ON A.ID= B.BULLETIN_ID
 AND B.READER_ID= 1990234
WHERE B.IS_DELETED IS NULL OR B.IS_DELETED= '0'
LIMIT 0,10

验证

无论执行计划怎么样,还是实际验证下比较放心。

结论

IN、EXISTS性能几乎无差别,每次请求平均耗时56ms,JOIN每次请求平均耗时1ms。

服务拆分

公告模块目前与核心模块耦合有点紧,当公告模块出现问题的时候会直接影响到核心模块,需要将公告模块进行拆分。

公告内容字段存储优化

方案一

将公告内容字段独立到一个表中

方案二

目前公告字段是存储在MySQL中,大小从60B 到 25KB不等,从上面分析看这个字段对系统的伤害非常大,可以考虑将该字段放到OSS存储,数据库中保存OSS路径的方式。

完善监控

当发现我们掌握的信息不足以分析问题的时候,说明需要完善现有的监控指标

数据清理

根据数据保存规定,将过期数据及时清理出去。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
29天前
|
SQL 关系型数据库 MySQL
mysql一条sql查询出多个统计结果
mysql一条sql查询出多个统计结果
15 0
|
4天前
|
SQL 关系型数据库 MySQL
【MySQL】SQL优化
【MySQL】SQL优化
|
16天前
|
SQL 关系型数据库 数据库
【后端面经】【数据库与MySQL】SQL优化:如何发现SQL中的问题?
【4月更文挑战第12天】数据库优化涉及硬件升级、操作系统调整、服务器/引擎优化和SQL优化。SQL优化目标是减少磁盘IO和内存/CPU消耗。`EXPLAIN`命令用于检查SQL执行计划,关注`type`、`possible_keys`、`key`、`rows`和`filtered`字段。设计索引时考虑外键、频繁出现在`where`、`order by`和关联查询中的列,以及区分度高的列。大数据表改结构需谨慎,可能需要停机、低峰期变更或新建表。面试中应准备SQL优化案例,如覆盖索引、优化`order by`、`count`和索引提示。优化分页查询时避免大偏移量,可利用上一批的最大ID进行限制。
64 3
|
21天前
|
SQL 数据库 索引
SQL索引失效原因分析与解决方案
SQL索引失效原因分析与解决方案
24 0
|
22天前
|
SQL 存储 关系型数据库
【MySQL实战笔记】02.一条SQL更新语句是如何执行的-2
【4月更文挑战第5天】两阶段提交是为确保`redo log`和`binlog`逻辑一致,避免数据不一致。若先写`redo log`, crash后数据可能丢失,导致恢复后状态错误;若先写`binlog`,crash则可能导致重复事务,影响数据库一致性。一天一备相较于一周一备,能缩短“最长恢复时间”,但需权衡额外的存储成本。
16 1
|
29天前
|
SQL 关系型数据库 MySQL
【MySQL】慢SQL分析流程
【4月更文挑战第1天】【MySQL】慢SQL分析流程
|
2月前
|
SQL 关系型数据库 MySQL
【MySQL技术之旅】(7)总结和盘点优化方案系列之常用SQL的优化
【MySQL技术之旅】(7)总结和盘点优化方案系列之常用SQL的优化
71 1
|
2月前
|
SQL 运维 NoSQL
【Redis 故障排查】「连接失败问题排查和解决」带你总体分析CPU及内存的使用率高问题排查指南及方案
【Redis 故障排查】「连接失败问题排查和解决」带你总体分析CPU及内存的使用率高问题排查指南及方案
37 0
|
2月前
|
SQL 存储 关系型数据库
场景+案例分析,SQL优化
场景+案例分析,SQL优化
41 3
|
2月前
|
SQL 关系型数据库 MySQL
MySQL SQL语句面试准备
MySQL SQL语句面试准备
13 0