MySQL 8.0慢查询排查全流程:监控、日志、锁等待与索引优化

MySQL 8.0慢查询排查性能调优 本内容遵循CC 4.0 BY-SA版权协议
南瓜丶奇迹师 南瓜丶奇迹师 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要

1. 先说清楚慢查询排查的整体思路

接手一套MySQL 8.0,碰到“跑得慢”这种反馈,第一反应千万别是“执行一条慢SQL看看”。很多同学一上来就SHOW PROCESSLIST、翻慢查询日志,结果日志清一色是UPDATE,跟业务说的“页面打开慢”完全对不上,折腾半天一无所获。

我自己的经验是:慢查询排查本质上是一次按证据链推进的现场勘验。你面对的不是一个明确报错的故障,而是一堆“好像变慢了”“最近有点卡”的模糊信号。这时候最忌讳的就是拿单一工具去套所有场景,MySQL 8.0的架构比5.7复杂,默认参数变化也大,不同瓶颈的表现完全不同——连接被打满、磁盘IO抖动、优化器选错索引、元数据锁等待,都叫“慢”,但处理方式南辕北辙。

所以我习惯把整个排查流程比喻成医生看病,按“望闻问切”四步来走:

  • 望:看表象、看监控、看指标,判断问题出在哪个层面
  • 闻:听客户端的声音、听业务反馈,收集现场细节
  • 问:问变更、问版本、问SQL写法,锁定可疑范围
  • 切:切日志、切执行计划、切内核状态,最终定位根因

这套方法论最重要的一点是顺序不能乱。先看后问、先指标后日志、先宏观后微观。很多时候你把“望”这一步做扎实了,后面根本不需要大动干戈。而且MySQL 8.0自带的performance_schema、sys schema、慢查询日志、EXPLAIN ANALYZE,已经可以覆盖绝大多数场景,排查之前先想清楚你要用哪把钥匙开哪把锁。

这篇内容适合谁?适合已经被慢查询折磨过、但还没形成系统排查思路的DBA、后端开发、运维同学。我会把这几年在线上踩过的坑、总结的排查路径、还有MySQL 8.0特有的一些变化,都揉进去,尽量做到你看完之后能照着做。

2. 望:先看全局仪表盘,别急着钻SQL

2.1 打开机器层的“体检报告”

“望”的第一步不是看数据库,而是看机器。MySQL跑在操作系统上,很多慢的根源其实在底层。在Linux环境里,我习惯先跑几组命令看负载:

BASH
uptime
top -Hp $(pgrep mysqld)
iostat -x 1 5
free -h

top主要看两个东西:CPU的sy占比和wa占比。如果sy特别高,说明系统调用太频繁,多半是连接数暴增或者小事务量太大;如果wa高,那问题直接指向磁盘IO。IO这块重点看%util和await,MySQL 8.0如果数据没完全进buffer pool,每次查询都落到磁盘,%util超过80%基本就会感觉明显卡顿。

free -h注意看两个数:物理内存剩余量和cached。MySQL的buffer pool通常占了服务器大半内存,free里显示可用内存偏低是正常的,真正要警惕的是buffer pool命中率暴跌——这个从MySQL端看更能说明问题。

注意:如果你的日志和监控体系比较完整,直接查监控比临时跑命令更快。但至少你要知道命令行怎么看,很多小公司没有完整监控,线上出问题只能现场摸。

2.2 MySQL层的指标快照

机器层没问题,再进MySQL看指标。MySQL 8.0的performance_schema比5.7更完善,很多指标默认就开着。我常用的“望”命令:

SQL
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Threads_running';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';
SHOW ENGINE INNODB STATUS\G

这里有个容易误判的点:Threads_connected高不代表有问题,连接数高但都在sleep状态,只是占内存而已;真正让数据库变慢的是Threads_running持续很高,说明有大量查询在真正执行。Threads_running超过CPU核数几倍,基本上就是慢查询在堵车。

buffer pool命中率可以这么算:

TEXT
命中率 = Innodb_buffer_pool_read_requests / (Innodb_buffer_pool_read_requests + Innodb_buffer_pool_reads)

如果低于95%,说明数据页大量走磁盘读取,这时候要么内存确实不够,要么SQL在扫大表。下一步再结合慢日志确认后者。

另外MySQL 8.0里,SHOW ENGINE INNODB STATUS已经不会暴露太多锁等待细节了,更推荐直接查performance_schema里的data_lock_waits,后面“切”的环节再展开。

2.3 三分钟划分问题边界

望这一阶段的目标不是找到根因,而是把问题边界划出来。我通常按下面这个表格做快速归类:

观察项 可疑值 优先怀疑方向
CPU wa高 > 20% 磁盘IO瓶颈
CPU sy高 > 30% 线程/连接风暴
Threads_running > CPU核数*2 慢查询堆积
buffer pool命中率 < 95% SQL扫描量大
连接数打满 接近max_connections 连接池配置/阻塞堆积

这个阶段最多花三分钟。做完之后你会得出一个类似结论:“问题大概率出在SQL层面,没事先不折腾机器”,或者反过来“机器IO已经打满了,先别查SQL”。这种判断非常重要,它决定了你接下来的排查方向,也能避免你在错误的方向上浪费几小时。

3. 闻与问:现场信息比什么工具都值钱

3.1 听“患者”描述:客户端到底体验到什么

“闻”不是说靠耳朵听服务器声音,而是收集现场的体验反馈。很多排查失败,就是因为只听一句“慢”,然后就埋头看工具。你得搞清楚这个“慢”是哪种慢:

  • 是偶尔慢一次,还是持续慢?
  • 是某个页面/接口慢,还是整个实例都卡?
  • 是查询变慢,还是写入变慢?还是连接建立就超时?
  • 是重启后变慢,还是每次高峰期变慢?

这几种“慢”对应的问题完全不同。比如“只有每个月月初特别卡”,多半跟月初跑批任务撞在一起,可能是锁冲突,也可能是IO争抢。“重启后变慢”则大概率是buffer pool冷启动,数据要重新加载。“写入变慢”优先怀疑磁盘fsync和binlog同步策略。

有个很经典的例子:有一次业务反馈某个报表查询要十几秒,我查了慢日志,确实有这条SQL,但执行计划走的是主键,理论上不该慢。后来问业务才知道,他们是先跑了一个批量UPDATE,把整表数据更新了一遍,导致这个查询进来的时候大量undo页要回溯,才显得慢。如果你只盯着慢日志,根本想不到这层。SQL的慢,未必是SQL自己的问题,可能是别人的“后遗症”。

3.2 问一张问题清单:找对人、问对事

“问”要有章法,不能漫无目的。我做了一个固定清单,每次排查都照着问:

  • 最近有没有发过版本?有没有改过SQL?
  • 最近有没有调整过MySQL参数或者服务器配置?
  • 有没有大批量数据导入、清理、归档任务?
  • 慢的现象是从什么时间点开始的?持续多久了?
  • 影响范围是个别用户、个别业务,还是全站?

问完这几个问题,一半的故障已经有眉目了。尤其是“变更”,我统计过自己多年的线上故障,70%以上的性能直线下降都跟变更有关——要么是代码改了SQL写法,要么是DBA调了参数,要么是运维给服务器做了迁移。没有变更就没有突发的变慢,这是一个黄金法则。

注意:这里的“问”不限于问人,也包括问历史。查一下MySQL的error log、slow log从什么时候开始出现大量记录,再对应到发布平台上查那个时间点的变更记录。人可能会记错、会漏说,日志不会。

3.3 变更排查是“问”环节的重中之重

我专门把变更单拎出来说,因为实在太重要了。如果“问”下来确认最近有一次新功能上线或者批量任务调整,直接把它当作首要嫌疑对象来排查。具体查两件事:

第一,新上的SQL有没有走对索引。开发在测试环境数据量小,全表扫描也不觉得慢,上线之后生产几千万行立即现原形。第二,批量任务的并发度是不是改大了。之前并发10个线程,现在改成50个,瞬间把连接池打满,所有正常业务都排队等连接,表现就是“全站都慢”。

另外MySQL 8.0有几个默认行为变更,遇到跨版本升级变慢要优先检查。比如8.0默认开启了caching_sha2_password认证插件,老客户端连不上或频繁重连会导致连接变慢;再比如utf8mb4成为默认字符集,字符串比较和索引长度都会受影响。这些不属于SQL问题,但都会让你觉得“跑得慢”。

4. 切:拿日志和执行计划定位根因

4.1 慢查询日志的正确打开姿势

前面几轮走完,你已经知道问题在SQL层还是实例层。现在才轮到“切”。第一步通常是把慢查询日志打开。MySQL 8.0里可以动态开启,不用重启:

SQL
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;

long_query_time的阈值建议先设1秒。别设0.1秒,否则日志会爆炸。线上高峰期你根本翻不过来。设成1秒,配合log_queries_not_using_indexes,基本能抓到90%的问题SQL。

Linux环境下日志一般落在datadir目录下,文件名类似机器名-slow.log。用mysqldumpslow做聚合:

BASH
mysqldumpslow -s c -t 10 /var/lib/mysql/*-slow.log

-s c按次数排序,-s t按总时间排序,-s at按平均时间排序。我一般先按次数看,找出“高频慢查询”,这类SQL哪怕单次只慢1.5秒,架不住量大,对系统拖累最大;再按总时间看,找出“单次超级慢”的SQL,这类通常是报表类大查询。

还有一个很实用的功能:MySQL 8.0支持在慢日志里记录执行计划相关信息吗?不支持,得靠EXPLAIN。但你可以把慢日志和performance_schema的events_statements_summary_by_digest联合起来看,能拿到语句的聚合统计数据。

4.2 EXPLAIN读不懂,等于白查

慢SQL拿到了,下一步就是看执行计划。EXPLAIN的输出字段很多,我平时只抓几个关键值:

  • type:从好到差依次是system、const、eq_ref、ref、range、index、ALL。看到ALL或者index,基本可以断定这条SQL在拖后腿。
  • rows:优化器预估扫描行数。这个值是估算值,但数量级非常有参考价值。
  • Extra:出现Using filesort说明排序没走索引,Using temporary说明用了临时表,Using index condition说明触发了ICP索引条件下推,这是好事。
  • key:实际选用的索引。如果显示NULL,但表上明明有索引,就需要深挖为什么没走。

MySQL 8.0还有一个大杀器:EXPLAIN ANALYZE。它不只是预估,而是真正执行SQL并返回实际耗时和每步处理的行数。这才是“切”的终极形态。

SQL
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 12345 AND status = 'PAID'\G

输出里非常直观地展示每一行来自哪里、耗时多少、循环多少次。以前要猜的东西,现在直接给你看答案。不过我提醒一句:EXPLAIN ANALYZE生成环境慎用。它是真的会执行查询,如果是慢查询,它会让你这条慢查询真的跑一遍,高峰期可能把库拖得更慢。我一般只在从库或者低峰期用。

4.3 performance_schema:像CT一样看内部状态

有些SQL问题不是单条语句能解释的,比如大量线程互相阻塞、元数据锁排队、长事务持有大量undo。这时候慢日志和EXPLAIN都看不到,需要“切”到performance_schema层面。

MySQL 8.0默认开启了performance_schema,但有些采集项需要手动打开。最常用的几个查询:

SQL
-- 查看当前锁等待情况
SELECT * FROM sys.innodb_lock_waits\G
 
-- 查看当前有哪些长事务
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started LIMIT 10;
 
-- 查看历史慢语句聚合
SELECT SCHEMA_NAME, DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1e12 AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC LIMIT 20;

sys.innodb_lock_waits这张表非常直观,直接告诉你谁在等谁、等的是哪把锁。有一次排查线上“某些更新突然全部卡住”,就是靠这张表发现一个事务开了没提交,锁住了一整张表。慢日志里全是那条UPDATE,但罪魁祸首是另一条连慢日志都没进的“僵尸事务”。

另外,MySQL 8.0里information_schema.innodb_trx可以看到事务开启时间和当前执行的SQL。如果你的连接数一直不降,去看这张表,大概率能找到一堆开启了一两个小时还没提交的事务。这类问题排查起来特别痛快:定位到会话ID,跟业务确认后直接KILL,系统立刻恢复。

4.4 顺手把死锁和索引失效隐患也查一遍

“切”的过程中,我还会顺手确认两个MySQL里最容易埋雷的点:死锁和索引失效。

死锁在MySQL 8.0里不像5.7那样经常导致实例变慢,但它引发的应用层重试风暴会让数据库压力骤增,看起来就是“慢”。死锁排查直接看:

SQL
SHOW ENGINE INNODB STATUS\G

找到LATEST DETECTED DEADLOCK段落,里面会记录最近一次死锁的两条SQL和持有/等待的锁。更系统的方法是开启innodb_print_all_deadlocks=ON,把每次死锁都打进error log,方便事后复盘。

索引失效的场景更隐蔽。比如查询条件里对索引列做了函数计算、隐式类型转换、前导通配符,都会导致优化器放弃索引。MySQL 8.0的优化器虽然比5.7智能,但这些坑一个不落。用EXPLAIN看到type=ALL的时候,别忘了回头检查一下SQL写法本身,别急着骂优化器。

5. 常见问题与排查技巧实录

5.1 最容易被忽略的“假慢”场景

说几个我实际踩过的坑,如果你按上面的流程走完还没定位,可以看看是不是这类“假慢”。

第一个是元数据锁等待。MySQL 8.0里DDL操作(ALTER TABLE、DROP TABLE)会拿元数据锁,如果前面有个长事务一直不提交,DDL就会卡住,然后所有访问这张表的SQL全部排队。表现极其迷惑:各种查询、更新全部超时,但看慢日志却找不到对应记录,因为SQL根本没真正执行。排查方法前面说了,看sys.schema_table_lock_waits。

第二个是连接数暴增导致TCP连接堆积。应用层连接池配置不当,或者某个服务发版后疯狂重连,会导致MySQL端线程数暴增,CPU sys飙高但慢日志里没什么。这时候线程调度本身成了瓶颈。排查方法:SHOW PROCESSLIST看哪个IP的连接数异常多,再跟开发确认是不是流量突增。

第三个是磁盘满或inode耗尽。MySQL 8.0的redo log、binlog都是持续写入的,磁盘满了之后数据库不会直接崩,而是所有写操作都卡住,读操作也可能因为无法刷脏页而变慢。命令就一条df -h和df -i,一分钟就能确认。

第四个是归档任务和在线业务的IO争抢。很多公司会在凌晨跑数据归档,但如果归档脚本写得不好,全表扫描大表,跟白天业务抢磁盘带宽。现象是白天不慢、一到归档时间段就慢。这种“间歇性慢”最容易被误判为网络问题,其实看看iostat就能发现规律。

5.2 MySQL 8.0特有的一些“坑”

跨版本升级到8.0之后变慢,很多人第一反应是“升级升级出问题了”,其实大概率是踩了这几个点:

  • 默认字符集变了。8.0默认是utf8mb4,5.7默认是utf8mb3(也就是utf8)。如果你从5.7迁过来,旧表的字符集还是utf8mb3,跟你新库的utf8mb4做表关联时无法用索引,只能全表扫。排查方法:SHOW TABLE STATUS对比两表的collation是否一致。
  • 查询缓存彻底移除。MySQL 8.0把query cache完全删了,之前在5.7上“读过一次就变快”的体验没了。如果业务场景是大量重复读同一张小表,性能感知会明显下降。解决办法是上Redis这类外部缓存,或者优化SQL减少扫描。
  • redo log容量和刷盘策略变化。8.0引入innodb_redo_log_capacity替代了innodb_log_file_size,默认100MB,日志文件自动调整在8.0.30后正常。如果遇到大量写入场景性能下降,看看这个参数是否被调得太小。

5.3 长效手段:慢查询统计与可视化

排查完一次,不等于结束。我最推荐的做法是把慢查询数据沉淀成一个“监控看板”,让下次问题能在“望”阶段就发现。

网上有人用Python定期解析慢查询日志,统计Top SQL、时间分布、扫描行数,再配上Grafana做可视化。我自己也搭过一套,思路很简单:用python的pymysql连数据库,把performance_schema.events_statements_summary_by_digest统计结果拉出来,计算每条语句的平均耗时、总耗时、执行次数,写入MySQL或Prometheus,再用Grafana展示Top 10慢语句。

这样做的好处是,SQL层面的健康度每天都看得见。某条SQL的耗时如果连续三天上涨,就算还没超过阈值,你也有预警时间去优化,不用等业务喊“系统卡了”才来排查。

5.4 经验总结:慢查询排查的黄金顺序

最后把我个人最常用的排查命令串一遍,你照着这个顺序走,基本不会漏:

  1. uptime、top、iostat看机器层面
  2. SHOW ENGINE INNODB STATUS、SHOW PROCESSLIST看实例层面
  3. 开慢查询日志,mysqldumpslow聚合
  4. EXPLAIN ANALYZE单条SQL定位执行计划问题
  5. sys.innodb_lock_waits、information_schema.innodb_trx查锁和长事务
  6. df -h、df -i确认磁盘没满
  7. 查最近变更和应用发布记录

这套流程看起来简单,但我敢说90%的MySQL慢查询都能在第三步到第五步之间定位。剩下的10%属于架构层面的问题,比如跨机房延迟、单实例达到性能上限,那已经不是“排查”能解决的了,得走容量规划和架构改造。

每次排查完,我都会把根因、处理时间、涉及SQL、临时方案、长期方案记一笔笔记。积累多了之后你会发现,MySQL慢查询的套路其实是有限的——索引没走对、锁等待、IO瓶颈、连接风暴,来来回回就这些。真正值钱的是你的排查路径,路径越短,恢复越快。

相关推荐

基于Python+Open CV的手势识别算法设计

伴随着人工智能时代的到来,人机交互的领域也逐渐成为研究的一大方向;其中,手势识别是人机交互领域的一项关键技术,自此手势识别也迎来了一波高潮,近几年无论是在消费领域、电子领域、数码领域、家电领域甚至汽车领域上,都能或多或少的见到手势识别的身影。 手势识别在设计智能高效的人机界面方面具有至关重要的作用, 目前手势识别已应用到手语识别、智能监控、到虚拟现实等各个领域,手势识别的原理都是利用各种传感器(例如红外、摄像头等)对手部的形态进行捕捉并进行建模,形成一个模型信息的序列帧,而后将这些信息序列转换为机器能够识别的相对应指令(例如打开、切换菜单、移动等)来完成控制。 采用Python的集成开发环境Pycharm进行本次课程设计,在Pycharm中进行需要库(模块)的下载,调取电脑摄像头,按帧读取摄像头采集到的头像,形态学处理,图像旋转(由于摄像头采集到的图像是镜像,需要用cv2.flip将摄像头采集到的图像进行处理),选取图片中固定位置作为手势输入,用红线画出手势识别框,基于hsv的肤色检测,进行高斯滤波,找出轮廓,求出图像中手势的凹凸点,手指间角度求取。

努力学习前端的小陈

ROS-CV-ImageConverter:将ROS图像转换为OpenCV(cv :: Mat)类型的图像

ROS-CV图像转换器 依存关系 cv_bridge(ROS软件包)

Willis Wang

计算机视觉图像处理-CV2与图像插值算法

用最近邻插值与双线性插值原理完成图像缩放 最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输。缺点:使用最近邻插值算法处理图像放大时,在图象中可能出现明显的块状效应 双线性插值,在两个方向上做三次线性插值。使用双线性插值算法处理图像放大时,在图象中可能出现马赛克 代码 import cv2 img = cv2.imread(’./xiabang.jpg’, cv2.IMREAD_UNCHANGED) #缩放比例 scale_percent = 0.3 width = int(img.shape[1] * scale_percent) height = int

weixin_38697274

基于水平集CV模型的图像分割(Matlab代码)

基于水平集CV模型的图像分割(Matlab代码),全套代码

小宇QAQ

导向滤波(guide_filter)的python实现

导向滤波(guide_filter)的python实现,可以结合暗通道;理论实现比较好的图像去噪效果。

zachzhan_g
返回顶部
南瓜丶奇迹师
博客等级 码龄9年 40粉丝 8,323原创