
MySQL 查询变慢时,很多管理员会先看 CPU 和磁盘。如果 CPU 使用率不高,I/O 压力也看起来正常,问题常常出在等待链路上:执行计划走错、行锁或元数据锁等待、并发被连接池耗尽、网络往返放大,也可能是采集指标本身掩盖了短暂的 I/O 峰值。本文按一套可复用的顺序排查低 CPU、低磁盘利用率下的 MySQL 慢查询。
先说明判断边界:SHOW GLOBAL STATUS 和 SHOW PROCESSLIST 是瞬时观察,延迟类问题要看连续采样。Threads_running 在采样间隙升高,CPU 未必同步升高;磁盘利用率低也不等于单次 I/O 延迟低。排查时应把等待事件、执行计划、锁等待和业务并发放在同一条时间线上。
一、确认慢的范围
先确认是个别 SQL 慢,还是整个实例响应都慢:
SELECT NOW(), VERSION();
SHOW GLOBAL STATUS LIKE 'Threads%';
SHOW GLOBAL STATUS LIKE 'Queries';
SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';
SHOW PROCESSLIST;
如果只有少数接口慢,优先看对应 SQL 的执行计划和锁等待;如果所有连接都卡住,优先看元数据锁、DDL、备份锁、连接池耗尽和后端存储异常。
慢查询日志是主要证据来源:
SHOW GLOBAL VARIABLES LIKE 'slow_query_log%';
SHOW GLOBAL VARIABLES LIKE 'long_query_time';
云数据库通常默认保留慢日志,也可以在控制台导出。检查时重点看扫描行数、返回行数、执行时间分布和用户字段,不要只看单条最慢 SQL。
二、查看真实等待事件
MySQL 8.0 的 Performance Schema 可以直接看到等待:
SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT / 1000000000000 AS wait_seconds
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE COUNT_STAR > 0
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;
再查看当前正在等待的线程:
SELECT t.PROCESSLIST_ID, t.PROCESSLIST_USER, t.PROCESSLIST_DB,
t.PROCESSLIST_TIME, t.PROCESSLIST_STATE,
th.EVENT_NAME, th.SQL_TEXT
FROM performance_schema.threads t
JOIN performance_schema.events_statements_current th
ON t.THREAD_ID = th.THREAD_ID
WHERE t.PROCESSLIST_ID IS NOT NULL
ORDER BY t.PROCESSLIST_TIME DESC;
如果等待集中在 wait/io/table/sql/handler、wait/lock/table/sql/handler 或 wait/lock/meta-data/sql/mdl,后续分别进入 I/O 延迟、行锁、表锁和 DDL 排查。如果等待在 socket 或网络事件上,还要检查应用与数据库之间的链路。
三、用 EXPLAIN ANALYZE 复现真实执行
拿到慢 SQL 后,先看执行计划:
EXPLAIN FORMAT=TREE
SELECT ...;
EXPLAIN ANALYZE
SELECT ...;
EXPLAIN ANALYZE 会实际执行语句并输出每个算子的耗时和行数。低 CPU 慢查询里,常见异常是预估行数与实际行数差距很大、驱动表选错、索引扫描后大量回表、排序或临时表成为主要耗时。
若该语句能在测试环境复现,尽量用生产同量级数据验证。生产环境执行 EXPLAIN ANALYZE 时要确认语句本身有条件限制,避免把全表扫描或大写入再跑一遍。
四、检查锁等待和事务持锁时间
行锁等待不一定推高 CPU 或磁盘利用率。先看 InnoDB 状态:
SHOW ENGINE INNODB STATUS\G
SELECT * FROM sys.innodb_lock_waits;
重点看请求锁的事务、阻塞来源、锁类型和等待时长。更新语句没有提交、长事务持锁、唯一键冲突重试、热点行并发更新,都会表现为业务超时但系统资源很闲。
事务长度也要检查:
SELECT trx_id, trx_state, trx_started,
trx_rows_locked, trx_rows_modified,
trx_mysql_thread_id
FROM information_schema.innodb_trx
ORDER BY trx_started;
如果长事务来自定时任务、报表导出或异常未提交,应先处理事务边界。把大事务拆小、避免循环内逐条提交、在低峰执行批量维护任务,比单纯调锁超时时间更有效。
五、区分统计信息、索引和数据分布问题
优化器选择错误通常来自统计信息失真、数据分布不均或隐式类型转换。检查表统计时间:
SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_ROWS /*YY*/,
UPDATE_TIME, TABLE_COMMENT
FROM information_schema.tables
WHERE TABLE_SCHEMA = 'your_database'
ORDER BYY UPDATE_TIME DESC;
必要时在业务低峰执行:
ANALYZE TABLE your_table;
还要检查常见错误:字符串列用数字比较、日期字段套函数、组合索引顺序不匹配、排序方向不一致、前导 % 模糊查询、OR 连接不同索引列。这些问题可能只让扫描行数增加,不一定让 CPU 立刻飙高。
六、检查连接池、并发和排队
应用侧连接池耗尽时,数据库可能看起来很空闲。观察:
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Threads_running';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL VARIABLES LIKE 'max_connections';
如果 Threads_running 不高但应用获取连接等待,检查应用连接池最大连接数、获取超时和健康检查配置。如果 Threads_running 短暂升高,可能是缓存失效、定时任务重叠或发布后流量集中触发同类 SQL。
对突发并发,先降低重复查询和无意义循环调用,再考虑限流、缓存和读写分离。仅提高 max_connections 可能让内存和上下文切换风险变大。
七、确认网络与应用层耗时
数据库执行只占一次请求的一部分。应用日志里应记录 SQL 执行耗时、获取连接耗时、总接口耗时和调用次数。如果 SQL 在数据库端只有几十毫秒,接口却超过几秒,问题可能在返回行数过大、接口串行多次查询、跨可用区访问、代理链路或客户端处理。
可以用 performance_schema.events_statements_summary_by_digest 找调用次数和总耗时高的语句:
SELECT SCHEMA_NAME, DIGEST_TEXT,
COUNT_STAR,
SUM_TIMER_WAIT / 1000000000000 AS total_seconds,
AVG_TIMER_WAIT / 1000000000 AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;
总耗时高、单次不慢的语句,优化方向是减少调用次数和缓存结果,不是继续调索引。
八、按顺序处理
- 用慢日志和
sys.statements_with_full_table_scans找出目标 SQL。 - 用
EXPLAIN ANALYZE确认真实耗时算子。 - 用
sys.innodb_lock_waits和innodb_trx判断是否存在阻塞。 - 检查连接池、
Threads_running和应用侧获取连接耗时。 - 检查网络链路、返回行数和调用次数。
- 修复索引、统计信息、事务边界或应用调用方式后,观察 P95/P99 延迟。
处理后不要只看平均响应时间。平均值会被快请求稀释,锁等待和偶发超时更应该看分位数、错误率和超时次数。
结论
CPU 和磁盘都不高的 MySQL 慢查询,优先查等待链路:执行计划里的实际行数、锁等待、元数据锁、长事务、连接池排队和应用调用次数。EXPLAIN ANALYZE、Performance Schema、sys.innodb_lock_waits 和语句摘要表能把问题定位到具体 SQL 或事务。索引和参数只有在对应证据成立时再改,避免用随机加索引掩盖事务边界问题。
参考资料
MySQL 参考手册:EXPLAIN ANALYZE
https://dev.mysql.com/doc/refman/8.4/en/explain.html
MySQL 参考手册:Performance Schema wait event tables
https://dev.mysql.com/doc/refman/8.4/en/performance-schema-wait-tables.html
MySQL 参考手册:sys.innodb_lock_waits
https://dev.mysql.com/doc/refman/8.4/en/sys-innodb-lock-waits.html
MySQL 参考手册:The information_schema.innodb_trx table
https://dev.mysql.com/doc/refman/8.4/en/information-schema-innodb-trx-table.html
MySQL 参考手册:Statement summary tables
https://dev.mysql.com/doc/refman/8.4/en/performance-schema-statement-summary-tables.html
MySQL 参考手册:ANALYZE TABLE statement
https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html




