MySQL 慢查询拖垮 CPU:processlist 到 pt-query-digest

来源:互联网 时间:2026-09-04

服务器 CPU 突然打满,网站慢得像拨号上网。登上机器一看,MySQL 占了大头。这时候最忌讳的是直接重启数据库——重启完负载确实掉了,但过不了多久它还会爬回来,因为那条慢 SQL 还在那儿等着被执行。

第一步是抓现行。SHOW PROCESSLIST 能看到此刻正在跑的语句,但默认只显示前 100 个字符,长 SQL 会被截断。用 SHOW FULL PROCESSLIST 看完整的,重点看 Time 列——执行时间长的那些就是嫌疑犯。如果同一条 SQL 反复出现,基本可以定罪。

抓现行有个前提:你得赶上。慢查询如果不是持续存在,靠手敲命令很难碰上。这时候就该开慢日志了,把 long_query_time 设低一点,让所有超过阈值的语句都留下记录,事后随时可以翻。

日志攒够之后,用 pt-query-digest 分析。它是 Percona Toolkit 里的工具,比 MySQL 自带的 mysqldumpslow 强在一点:它会把字面量抽掉做指纹归并,同一条 SQL 的不同参数会被算作一类,然后按总耗时排序。这样你看到的不是一堆零散语句,而是按影响排好队的 Top 榜。

#!/bin/bash

# 慢查询定位流水线:抓现行 -> 开慢日志 -> 出报表

# 1. 先抓现行:执行超过 5 秒的语句

mysql -e "SELECT id, user, host, db, time, LEFT(info, 120) AS sql_head

FROM information_schema.processlist

WHERE command != 'Sleep' AND time > 5

ORDER BY time DESC LIMIT 20\G"

# 2. 临时开启慢日志(重启失效,适合应急排查)

mysql -e "SET GLOBAL slow_query_log = ON;

SET GLOBAL long_query_time = 1;

SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';"

# 3. 让它跑一段时间(比如一个业务高峰),再出报表

sleep 600

# 4. 用 pt-query-digest 按总耗时排序,取前 10

pt-query-digest --limit 10 /var/log/mysql/slow.log > /tmp/slow_report.txt

# 5. 换一个维度:按扫描行数排序,专抓缺索引的全表扫描

pt-query-digest --order-by Rows_examined:sum --limit 10 \

/var/log/mysql/slow.log > /tmp/slow_by_rows.txt

echo "报表已生成:/tmp/slow_report.txt  /tmp/slow_by_rows.txt"

报表里最该盯的是 Rows examine 和 Rows sent 这两个数的比值。扫描了一百五十万行,只返回二十行,比值接近十万比一——这就是典型的缺索引,数据库为了找你要的那点数据把整张表翻了一遍。给它加上合适的索引,CPU 立刻就下来了。

另一种情况正好相反:扫描行数不多,返回行数也不多,但耗时就是长。这种通常不是索引的问题,是写法的问题——嵌套子查询、没有 LIMIT 的分页、在循环里逐条查。这类得改 SQL 甚至改调用逻辑,加索引没用。

有个参数在生产环境上要慎用:log_queries_not_using_indexes。开了它之后所有没走索引的语句都记进慢日志,听起来很美,实际上在繁忙的库上一天能写出几十个 G,把磁盘撑爆。真要用,排查完记得关掉。

最后一句:拿到报表别急着建索引。先对那条 SQL 跑一遍 EXPLAIN,看清它现在的执行计划,确认加了索引之后 type 真的会从 ALL 变成 ref 或 range。盲建索引不但可能没效果,还会拖慢写入——每个索引都是写入时的一份额外成本。

数据来源:Percona Toolkit 官方文档

相关文章

A5创业网 版权所有

返回顶部