上周五凌晨2点,值班手机突然响起刺耳的告警声——生产环境MySQL实例CPU使用率突破90%阈值。作为DBA,这种半夜告警最让人头疼。登录监控系统查看,发现CPU负载从凌晨1:30开始持续攀升,已经持续高位运行30分钟。
通过performance_schema快速检查活跃会话,发现大量处于"Sending data"状态的查询。这些查询有个共同特征:都访问了同一张5000万行的用户行为日志表。更反常的是,这些查询本应是低频的报表分析SQL,却在短时间内出现爆发式增长。
经验提示:当CPU高负载伴随大量"Sending data"状态会话时,90%的情况是出现了全表扫描或索引失效问题
show processlist
第一手现场取证,重点关注:
示例输出:
sql复制Id | User | Host | db | Command | Time | State | Info
123456 | report | 10.0.1.2:123 | analytics| Query | 112 | Sending data| SELECT user_id FROM behavior_log WHERE ...
performance_schema深度分析
启用events_statements_history_long表(需提前配置):
sql复制SELECT THREAD_ID, SQL_TEXT, ROWS_EXAMINED, ROWS_SENT
FROM performance_schema.events_statements_history_long
WHERE ROWS_EXAMINED/ROWS_SENT > 1000
ORDER BY ROWS_EXAMINED DESC LIMIT 10;
这个查询能快速定位"数据扫描量/返回量"比例异常的SQL
sys schema快捷视图
使用预置诊断视图快速定位问题:
sql复制SELECT * FROM sys.statement_analysis
ORDER BY avg_latency DESC LIMIT 5;
虽然实时诊断很重要,但完整的慢查询日志能提供更全面的视角。关键配置:
ini复制slow_query_log = ON
long_query_time = 1
log_queries_not_using_indexes = ON
使用pt-query-digest工具分析:
bash复制pt-query-digest /var/lib/mysql/mysql-slow.log
重点关注:
现象:
某条报表查询平时执行时间0.5秒,突然变成15秒。EXPLAIN显示type=ALL全表扫描。
诊断:
检查发现该查询条件包含:
sql复制WHERE DATE(create_time) = '2023-08-20'
虽然create_time字段有索引,但使用DATE()函数包裹后导致索引失效。
解决方案:
改写为范围查询:
sql复制WHERE create_time >= '2023-08-20 00:00:00'
AND create_time < '2023-08-21 00:00:00'
避坑指南:避免在索引列上使用函数、运算或类型转换,这会导致索引失效
现象:
用户表查询突然变慢,该表有2000万数据,user_id字段为varchar类型但存储的是数字。
诊断:
发现应用程序传参为整型:
sql复制SELECT * FROM users WHERE user_id = 123456
导致MySQL执行隐式类型转换,放弃使用索引。
解决方案:
保持类型一致:
sql复制SELECT * FROM users WHERE user_id = '123456'
案例背景:
订单系统出现CPU飙升,追踪到如下查询:
sql复制SELECT o.*, u.name
FROM orders o JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending'
问题分析:
优化方案:
sql复制SELECT o.id, o.order_no, u.name
FROM orders o FORCE INDEX(idx_status)
JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending'
LIMIT 1000;
同时添加联合索引:
sql复制ALTER TABLE orders ADD INDEX idx_status_created(status, created_at);
安装采集工具:
bash复制perf record -a -g -p $(pgrep mysqld) -- sleep 30
perf script | stackcollapse-perf.pl | flamegraph.pl > mysql.svg
分析要点:
启用引擎状态监控:
sql复制SET GLOBAL innodb_monitor_enable = '%';
关键指标关注:
sql复制SELECT * FROM information_schema.INNODB_METRICS
WHERE NAME IN (
'buffer_pool_reads',
'row_lock_waits',
'log_waits'
);
检查锁等待:
sql复制SELECT * FROM sys.innodb_lock_waits;
查看当前持有锁的会话:
sql复制SELECT * FROM performance_schema.events_waits_current
WHERE EVENT_NAME LIKE '%lock%';
三星索引原则:
索引选择性检查:
sql复制SELECT COUNT(DISTINCT column)/COUNT(*)
FROM table_name;
结果小于0.1的列不适合单独建索引
分页优化:
避免:
sql复制SELECT * FROM table LIMIT 1000000, 10
改用:
sql复制SELECT * FROM table WHERE id > 1000000 LIMIT 10
**避免SELECT ***
只查询必要字段,特别是包含TEXT/BLOB时
关键参数调整(根据机器配置):
ini复制innodb_buffer_pool_size = 12G # 物理内存的50-70%
innodb_io_capacity = 2000 # SSD建议2000+
innodb_read_io_threads = 8 # CPU核心数
query_cache_size = 0 # 8.0+版本建议关闭
当CPU持续100%时的紧急措施:
快速止血
sql复制KILL QUERY [processlist_id]; -- 终止特定查询
SET GLOBAL max_connections = 50; -- 限制新连接
查询限流
使用MySQL企业版或ProxySQL实现:
sql复制SET @query_digest = 'SELECT * FROM large_table';
CALL sys.statement_performance_analyzer('limit', @query_digest, 10);
资源隔离
对报表类查询启用资源组:
sql复制CREATE RESOURCE GROUP report_group
TYPE = USER
VCPU = 2-3
THREAD_PRIORITY = 5;
推荐监控指标:
| 指标名称 | 阈值 | 采集频率 |
|---|---|---|
| CPU使用率 | >70% | 10s |
| 活跃会话数 | >50 | 10s |
| 慢查询率 | >5% | 1min |
| InnoDB缓冲池命中率 | <95% | 1min |
| 临时表创建数 | >100/min | 1min |
Prometheus配置示例:
yaml复制- name: mysql
rules:
- alert: HighCPUUsage
expr: rate(process_cpu_seconds_total{job="mysql"}[1m]) * 100 > 70
for: 5m
某电商大促期间MySQL CPU持续95%+的完整解决过程:
第一阶段:现象确认
第二阶段:根因分析
sql复制SELECT * FROM items WHERE tag LIKE '%夏季%'
第三阶段:紧急优化
sql复制CREATE TABLE item_tags (
tag varchar(32),
item_id int,
PRIMARY KEY(tag, item_id)
);
sql复制SELECT i.* FROM items i
JOIN item_tags t ON i.id = t.item_id
WHERE t.tag = '夏季'
最终效果:
CPU负载在20分钟内从95%降至35%,查询耗时从12秒降至0.2秒
诊断工具
压测工具
可视化工具
每次上线前必检项:
这套方法论在我们团队处理过的数百起CPU飙高事件中,成功定位率超过85%。最关键的是建立系统化的监控-分析-优化闭环,而不是每次被动救火。