MySQL数据库响应慢排查指南:慢查询、锁等待与系统资源分析

作者:技术拾光

时间:2026-05-08

浏览:440

慢查询日志是定位MySQL性能问题的起点,需先通过SET GLOBAL slow_query_log=1和long_query_time=0.5临时开启并设阈值,查slow_query_log_file确认路径;禁用log_queries_not_using_indexes防日志爆炸;用mysqld

文章目录

慢查询日志是定位MySQL性能问题的起点,需先通过SET GLOBAL slow_query_log=1和long_query_time=0.5临时开启并设阈值,查slow_query_log_file确认路径;禁用log_queries_not_using_indexes防日志爆炸;用mysqldumpslow -s t -t 10分析前10耗时SQL;持久化须改my.cnf;配合Threads_running、Handler_read_rnd_next、iostat等指标综合排查根因。

MySQL数据库响应慢排查指南:慢查询、锁等待与系统资源分析

先看 slow_query_log,别一上来就 explain

遇到线上数据库响应变慢,很多人的第一反应是抓条SQL去explain。但慢查询日志才是真正的“第一现场”,它记录的是已经发生的事实,而不是执行计划的理论推演。可以说,90%的性能问题根源,都藏在那些没走索引或者执行过久的SQL里。

开启慢日志排查非常方便,两条命令就能临时搞定:SET GLOBAL slow_query_log = 1,然后把阈值设得敏感一些,比如SET GLOBAL long_query_time = 0.5(单位是秒)。这里有个细节需要注意:MySQL的计时是截断,不是四舍五入。超过500毫秒就会被记录,但毫秒级的抖动它不会管。

  • 日志在哪?别猜,直接用SHOW VARIABLES LIKE 'slow_query_log_file'查一下最稳妥。
  • 有个参数叫log_queries_not_using_indexes,建议别长期开启。它会把所有没走索引的语句都塞进慢日志,哪怕只查3行数据。这么搞,磁盘空间几小时就可能告急。
  • 分析日志时,优先用工具。mysqldumpslow -s t -t 10 /var/lib/mysql/mysql-slow.log这个命令,能按总耗时排出前10条慢SQL,效率比人工翻看高得多。
  • 通过SET GLOBAL做的修改,重启后会失效。想要持久化,必须去修改my.cnf配置文件,然后重启MySQL。但在生产环境要谨慎,因为慢日志本身也会产生I/O压力,搞不好会适得其反。

查 Threads_running,比看 PROCESSLIST 更快定位并发压力

当数据库感觉“卡”的时候,Threads_running这个指标比满屏Sleep状态的SHOW FULL PROCESSLIST更直接、更灵敏。它只统计那些正在真正执行SQL的连接,是反映当前并发压力的核心指标。

执行SHOW GLOBAL STATUS LIKE 'Threads_running',如果结果持续大于32,就该拉响警报了;要是超过64,基本可以断定CPU或者锁已经扛不住了。这时候,再配合SHOW GLOBAL STATUS LIKE 'Threads_connected'对比着看:

  • 如果Threads_connected(总连接数)远大于Threads_running(运行线程数),说明大量连接只是空闲挂着。问题大概率出在应用层,比如连接池没有正确释放。
  • 如果两者数值接近且都持续处于高位,那就要重点排查Innodb_row_lock_waits(行锁等待)和Innodb_buffer_pool_reads(从磁盘读取的页数)了。
  • 别只盯着PROCESSLIST里State = 'Sending data'的线程,这个状态可能只是在进行慢速的表扫描。真正卡住整个系统的,往往是前面那个长时间未提交的事务。

用 iostat -x 1 看磁盘,别被 top 的 %CPU 迷惑

在top命令里看到mysqld进程的CPU使用率很高,并不一定意味着它正在疯狂地进行计算。对于InnoDB存储引擎来说,很多时候这是一种“假忙”:线程因为等待磁盘I/O响应而被阻塞,CPU实际上在空转,但top仍然会把它标记为R(Running)状态。

所以,判断I/O瓶颈,更靠谱的方法是直接看磁盘。执行iostat -x 1 5,重点关注两列数据:

  • %util:如果这个值接近100%,说明磁盘几乎没有任何空闲时间,I/O已经彻底饱和。
  • await:这是单次I/O请求的平均等待时间(单位毫秒)。对于SSD,超过10ms;对于HDD,超过30ms,通常就意味着存储层的响应变慢了。可能是云盘的突发性能配额用完了,或者是NFS等网络存储出现了抖动。
  • 一个常见的陷阱是:%util不高,但await却很高。遇到这种情况,先别急着调整MySQL参数,应该去检查基础设施。比如,RAID卡的缓存电池是不是失效了?云服务商的后台是不是正在做快照?
  • 另外,即使磁盘本身不忙,如果MySQL参数innodb_io_capacity设置得太低,也会压制后台刷脏页的节奏。这会导致缓冲池命中率下降,逻辑读请求暴增,最终反而把CPU给推高了。

Handler_read_rnd_next 高,说明 SQL 在回表或全表扫描

Handler_read_rnd_next是一个被严重低估的指标,它比EXPLAIN结果里的rows更能真实地反映SQL的扫描成本。这个计数器记录了“通过索引定位后,再随机读取数据行”的次数。它的值如果很高,基本就等于在告诉你:要么发生了严重的回表,要么干脆就是全表扫描。

查看命令是SHOW GLOBAL STATUS LIKE 'Handler_read%',分析时重点关注两个比值:

  • Handler_read_rnd_next / Handler_read_next > 0.1:这通常意味着覆盖索引缺失。要么是SELECT的字段太多,要么是联合索引的字段顺序没有匹配上查询条件的最左前缀原则。
  • Handler_read_first持续增长:这往往暗示有大量的ORDER BY或GROUP BY操作没有利用到索引,不得不进行额外的排序。这时候,就需要考虑为排序字段补充索引了。
  • 即使EXPLAIN显示type = ref、key也有值,只要Handler_read_rnd_next很高,就说明优化器虽然选择了索引,但为了获取不在索引中的字段,不得不频繁地回表查找数据行——性能照样好不了。
  • 所以,对于高频查询,一个黄金法则是:尽可能让SELECT查询的所有字段都包含在联合索引里,也就是使用覆盖索引。务必避免使用SELECT *,或者查询额外的、不在索引中的字段,从而触发不必要的回表操作。

最后需要明确一点,真正拖垮MySQL性能的,往往不是某一条巨慢无比的SQL。更多时候,是多个中等耗时的查询叠加、锁等待在事务间传导、或者是缓冲池等关键配置不当,所引发的一系列连锁反应。因此,排查时切忌一头扎进某条SQL的EXPLAIN执行计划里出不来。更有效的做法是,把Threads_running、Handler_read_*系列状态、iostat的磁盘指标以及慢查询日志这四条线索拉通来看,看它们是否共同指向了同一个根本原因。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章