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

先看 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的磁盘指标以及慢查询日志这四条线索拉通来看,看它们是否共同指向了同一个根本原因。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。