1. MySQL CPU占用问题排查实战指南
作为数据库管理员,最让人头疼的场景之一就是半夜收到报警:MySQL CPU使用率飙升到90%以上。这种情况如果处理不及时,轻则导致查询响应变慢,重则引发整个数据库服务不可用。今天我就结合多年实战经验,分享一套完整的MySQL CPU占用过高排查流程,包含从快速定位到深度分析的完整方案。
2. 问题定位与初步分析
2.1 快速识别问题线程
当MySQL CPU使用率异常时,第一步是找出消耗CPU资源的罪魁祸首。在Linux环境下,我们可以使用以下命令组合:
bash复制# 查看MySQL进程的总体CPU占用
top -c -p $(pgrep -d',' mysqld)
# 查看具体线程的CPU占用情况
top -H -p $(pgrep mysqld)
执行后会显示类似如下的输出:
code复制PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
392869 mysql 20 0 25.8g 5.2g 4.8g R 98.6 16.3 48:20.57 mysqld
392870 mysql 20 0 25.8g 5.2g 4.8g S 0.3 16.3 0:05.23 mysqld
这里392869线程占用了98.6%的CPU,明显是问题所在。记下这个PID,我们将在后续步骤中使用它。
注意:如果MySQL使用了线程池模式,可能需要调整查看方式。此时建议使用performance_schema进行更精确的监控。
2.2 操作系统线程与MySQL线程的映射
获取到高CPU线程的操作系统PID后,我们需要将其映射到MySQL内部的线程ID。这是因为MySQL有自己的线程管理系统,我们需要知道是哪个数据库会话导致了问题。
执行以下SQL查询(将392869替换为你找到的高CPU线程PID):
sql复制SELECT
a.USER,
a.HOST,
a.db,
b.thread_os_id,
b.thread_id,
a.id processlist_id,
a.command,
a.time,
a.state,
LEFT(a.info, 100) AS query_snippet
FROM
information_schema.PROCESSLIST a,
performance_schema.threads b
WHERE
a.id = b.processlist_id
AND b.thread_os_id = 392869;
这个查询会返回关键信息:
- 执行该查询的用户和来源主机
- 正在操作的数据库
- MySQL线程ID和操作系统线程ID的对应关系
- 查询已运行时间
- 查询的当前状态
- 查询语句的前100个字符(完整语句可能很长)
3. 深度诊断与分析
3.1 获取完整查询语句
通过上一步我们可能只看到了查询的片段,要获取完整查询,我们需要查询performance_schema:
sql复制SELECT
thread_id,
sql_text,
timer_wait/1000000000 as exec_time_sec,
lock_time/1000000000 as lock_time_sec,
rows_examined,
rows_sent,
created_tmp_tables
FROM
performance_schema.events_statements_current
WHERE
thread_id = (
SELECT thread_id
FROM performance_schema.threads
WHERE thread_os_id = 392869
);
这个查询会返回:
- 完整的SQL语句
- 已执行时间(秒)
- 锁等待时间(秒)
- 检查的行数
- 返回的行数
- 创建的临时表数量
这些指标对于判断查询效率至关重要。一般来说,rows_examined远大于rows_sent说明索引可能有问题;大量临时表可能意味着需要优化排序或分组操作。
3.2 分析执行计划
获取到问题查询后,下一步是分析它的执行计划。使用EXPLAIN命令:
sql复制EXPLAIN FORMAT=JSON
-- 这里放入问题查询
JSON格式的输出比传统表格更详细,包含成本估算等信息。重点关注:
- 是否使用了合适的索引(possible_keys vs key)
- 访问类型(type列),最好能达到ref或range级别,避免ALL全表扫描
- Extra列中的警告信息,如"Using temporary"、"Using filesort"
- 估算的行数是否与实际执行的行数相符
3.3 检查系统状态
有时高CPU不是由单一查询引起,而是系统整体负载过高。这时需要检查MySQL状态:
sql复制SHOW GLOBAL STATUS LIKE 'Threads_running';
SHOW ENGINE INNODB STATUS\G
重点关注:
- Threads_running:并发执行的线程数,如果持续高于CPU核心数的2-3倍,说明系统过载
- InnoDB状态中的SEMAPHORES部分,查看是否有严重的锁等待
- BUFFER POOL命中率,低于95%可能需要增加innodb_buffer_pool_size
4. 常见问题场景与解决方案
4.1 索引缺失或失效
症状:
- 查询执行时间突然变长
- rows_examined远大于rows_sent
- EXPLAIN显示type=ALL
解决方案:
- 分析WHERE条件和JOIN条件,添加合适的索引
- 检查索引统计信息是否过时:
ANALYZE TABLE 表名 - 考虑使用索引提示或优化器提示
4.2 锁竞争
症状:
- 查询状态显示"Waiting for table lock"或"Waiting for row lock"
- 大量线程处于"lock"状态
- InnoDB状态显示大量锁等待
解决方案:
- 优化事务设计,减少事务大小和持续时间
- 检查隔离级别,考虑使用READ COMMITTED
- 对大表操作使用分批处理
4.3 排序和分组操作
症状:
- Extra列显示"Using temporary; Using filesort"
- 查询包含复杂的GROUP BY或ORDER BY
- 创建了大量临时表
解决方案:
- 为排序和分组字段添加复合索引
- 增加sort_buffer_size
- 考虑使用覆盖索引
4.4 子查询优化
症状:
- 包含多层嵌套子查询
- 执行计划显示DEPENDENT SUBQUERY
- 查询性能随数据量增长急剧下降
解决方案:
- 将子查询改写为JOIN
- 使用派生表优化
- 考虑使用临时表存储中间结果
5. 高级诊断技巧
5.1 使用performance_schema深度分析
对于复杂问题,可以启用更多的performance_schema监控:
sql复制-- 开启等待事件监控
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
WHERE NAME LIKE 'events_waits%';
-- 开启阶段监控
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES'
WHERE NAME LIKE 'events_stages%';
然后可以查询特定线程的等待事件:
sql复制SELECT
EVENT_NAME,
COUNT_STAR,
SUM_TIMER_WAIT/1000000000 as total_wait_sec
FROM
performance_schema.events_waits_summary_by_thread_by_event_name
WHERE
THREAD_ID = (SELECT thread_id FROM performance_schema.threads WHERE thread_os_id = 392869)
ORDER BY
SUM_TIMER_WAIT DESC
LIMIT 10;
5.2 使用sys schema简化分析
MySQL提供的sys schema包含许多有用的视图:
sql复制-- 查看哪些语句消耗最多CPU
SELECT * FROM sys.statement_analysis
ORDER BY cpu_time DESC LIMIT 5;
-- 查看全表扫描的查询
SELECT * FROM sys.statements_with_full_table_scans
ORDER BY exec_count DESC LIMIT 5;
5.3 使用pt-query-digest分析慢查询
对于长期存在的性能问题,收集慢查询日志并用pt-query-digest分析:
bash复制# 启用慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
# 使用pt-query-digest分析
pt-query-digest /var/lib/mysql/mysql-slow.log > slow_report.txt
分析报告会显示:
- 最耗时的查询
- 查询执行统计
- 可能的优化建议
6. 预防措施与最佳实践
6.1 监控系统设置
建立完善的监控体系可以提前发现问题:
- 监控关键指标:CPU使用率、QPS、线程数、慢查询数
- 设置合理的告警阈值
- 定期检查性能模式数据
6.2 定期维护
- 每周检查表统计信息:
ANALYZE TABLE - 定期优化碎片化严重的表:
OPTIMIZE TABLE - 监控索引使用情况,删除无用索引
6.3 参数调优
根据服务器配置调整关键参数:
- innodb_buffer_pool_size:通常设为物理内存的70-80%
- innodb_io_capacity和innodb_io_capacity_max:根据存储性能调整
- table_open_cache:避免频繁开表操作
7. 实战案例分享
最近处理的一个案例:某电商平台在促销期间MySQL CPU持续100%。通过上述方法,发现是一个商品搜索查询没有使用索引。该查询原本执行时间为12秒,添加复合索引后降至0.1秒,CPU使用率从100%降至30%。
关键点在于:
- 使用top -H快速定位问题线程
- 通过performance_schema找到完整查询
- 分析执行计划发现缺少(col1,col2)的复合索引
- 添加索引后效果立竿见影
这个案例告诉我们,看似复杂的性能问题,往往通过系统化的排查就能找到根本原因。关键在于掌握正确的工具和方法。