现象:监控一度吓人,QPS 反而没涨
你有没有过这种经历?
凌晨 2 点,Slack 突然弹出告警——CPU 从 30% 直线拉升到 100%,整个集群噤若寒蝉。最恐怖的是,应用的 QPS 曲线却毫无波澜,反而还在稳定运行。这一刻,你的心态就崩了:到底是哪个鬼东西在疯狂烧 CPU?是慢查询?是死锁?还是什么看不见摸不着的东西?
我来告诉你,这 90% 是某个隐藏 SQL 的全表扫描搞的鬼。关键是你得在 10 分钟内找到它,否则业务就真的凉了。
秒级定位:从 top 到线程句柄的"黑科技"链路
这里才是真正的干货,很多人都走错了方向。
第一步,top 看不出来。你打开 top 只会看到 mysqld 这个大块头在烧 CPU,但 32 个核心里到底哪个线程在捣乱?top 展示不了。
第二步,直接上 Linux 层面的线程。用这个命令:
ps -mp $(pgrep -f mysqld) -o THREAD,,tid,%cpu | sort -k5 -rn | head -20
这会给你秒级看到 TID(线程 ID)+ CPU 占用。记住烧 CPU 最猛的那个 TID,比如 12345。
第三步,TID 怎么映射到 MySQL 会话? 这是关键。MySQL 8.0 的 performance_schema.threads 表里有个字段叫 THREAD_OS_ID,就是这个 Linux TID。执行:
SELECT THREAD_ID, THREAD_OS_ID, PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_HOST FROM performance_schema.threads WHERE THREAD_OS_ID = 12345;
一秒钟,你就拿到了 PROCESSLIST_ID。这就是杀死进程、查 SQL 的钥匙。
这套链路速度贼快,从告警到定位可能只需要 30 秒。
一键抓 SQL:看谁在偷偷扫表
拿到 PROCESSLIST_ID 后,下一步该看这个会话到底在跑什么 SQL,以及它有多疯狂。
用 sys.session 这个视图,你能看到连接级别的各项指标:
SELECT * FROM sys.session WHERE PROCESSLIST_ID = 123 \G
重点关注这几列:
-
rows_examined:这个数字突然暴涨,说明在全表扫描。正常查询可能扫描几百行,坏 SQL 能扫描百万级。
-
rows_sent:返回给客户端的行数。如果
rows_examined是 1000 万,rows_sent只有 10 行,那就是妥妥的垃圾查询。 -
lock_time:如果这个数字也很大,说明还加锁了,那就更惨。
你能看到类似这样的数据:
rows_examined: 15000000 rows_sent: 45 lock_time: 234.12s
一眼就知道——这哥们在 1500 万行里翻江倒海,最后只要 45 行数据。这就是你的凶手!
sql
SHOW PROCESSLIST;
查看完整的 SQL 语句。如果被截断了,用:
SELECT * FROM performance_schema.events_statements_current WHERE PROCESSLIST_ID = 123 \G
验证:执行计划暴露全表扫描的真相
找到 SQL 后,别急着改,先验证一下到底是怎么坏的。
用 EXPLAIN FORMAT=JSON 看详细的执行计划:
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE created_at_str = '2025-12-18' AND user_id = 456 \G
输出会展示:
-
是不是全表扫描(
type: ALL) -
用没用上索引(
key: NULL) -
预期扫描多少行(
rows: 500000)
99% 的情况下,问题出在这两个地方:
-
隐式类型转换:你的列是
VARCHAR,SQL 里用的是数字。MySQL 会自动转换,导致索引失效。比如WHERE user_id = '456'明明user_id是整数,你传了个字符串进去,全表扫。 -
字符集/排序规则不匹配:多个表 JOIN,一个是
utf8mb4,一个是utf8,MySQL 也会放弃索引,直接全表扫。
执行计划里会清楚地显示 Using where、Using filesort 这样的标记,那就是你的定位点。
解决:在线加索引,秒级秒回
好消息是,如果真的就是缺索引或者字符集问题,MySQL 8.0 的在线 DDL 能救你。
假设问题就是 created_at_str 这个字段没索引,执行:
ALTER TABLE orders ADD INDEX idx_created_at_str (created_at_str), ALGORITHM=INPLACE, LOCK=NONE;
关键是这两个参数:
-
ALGORITHM=INPLACE:直接在表上改,不用拷贝整张表。 -
LOCK=NONE:不加锁,DML 照样跑。
如果是字符集问题,需要统一字符集的索引:
ALTER TABLE orders ADD INDEX idx_user_varchar (user_id_varchar(50)) CHARACTER SET utf8mb4, ALGORITHM=INPLACE, LOCK=NONE;
这个操作通常秒级完成,而且不会阻塞读写。CPU 立刻从 100% 掉下来,业务瞬间恢复。我亲眼见过一个 500GB 的表,加索引就花了 8 秒。
好索引救命,坏查询要命。
预防:让 CPU 告急再也不来敲门
一次两次侥幸躲过不算本事,关键是以后别再出这档子事。
第一招:打开 performance_schema
确保配置里有:
[mysqld]
performance_schema = ON
performance_schema_max_table_instances = 12500
这样才能用 sys.session、performance_schema.threads 这些救命工具。
第二招:定期做性能基线
每周或每月跑一次 sys.ps_setup_save(),把当前的 performance_schema 配置和统计信息保存下来。后面出问题时,对比基线就能看出异常来了。
CALL sys.ps_setup_save(@defaults); -- 过段时间后对比 SELECT * FROM sys.ps_setup_defaults;
第三招:主动监测慢查询
把 long_query_time 调到 0.1 秒,主动抓这种接近坏的 SQL,别等到烧 CPU 时才知道。
这个故事的结局是,那哥们从被 On-Call 惊醒到完全修复,也就花了 15 分钟。索引加上去,CPU 回到 30%,各个监控曲线都重新开心地跳舞。
当你有了这套定位链路和在线 DDL 这个大杀器,线上 CPU 飙升就再也不是无脑告急了。反而是个捞经验、展示操作的好机会。
下次再遇到这个问题,你就是那个 5 分钟搞定、被团队膜拜的人。
您的关注是对我写作最大的鼓励,期待和您一起深入 MySQL 坑!
声明:本文内容 90% 为本人原创,少量素材经 AI 辅助生成,且所有内容均经本人严格复核;图片素材均源自真实素材或 AI 原创。文章旨在倡导正能量,无低俗不良引导,敬请读者知悉。