分析 MySQL 性能瓶颈:CPU、内存、IO
性能瓶颈的三大维度
MySQL 的性能瓶颈通常可以归结为三类:CPU 瓶颈、内存瓶颈、IO 瓶颈。不同的瓶颈表现出不同的系统特征,需要不同的优化策略。
1. CPU 瓶颈
表现
- CPU 使用率持续高于 80%
- CPU 的
user时间占比高(vsiowait) - 慢查询突然增多
- 连接数虽然不大,但
Threads_running很高
常见原因
(1) 大量排序操作
-- ORDER BY 没有走索引,MySQL 需要在内存或磁盘进行 filesort
SELECT * FROM orders ORDER BY amount DESC;
排查:
-- 查看有 file sort 的查询
SHOW STATUS LIKE 'Sort%';
-- Sort_merge_passes 高 → 磁盘排序多,严重消耗 CPU
(2) 全表扫描且数据量大
-- 没有合适的索引,每次都要扫描千万行
SELECT * FROM users WHERE last_login > '2024-01-01';
(3) 复杂的计算
-- 大量的字符串处理、正则匹配、数学运算
SELECT * FROM articles WHERE REGEXP_LIKE(content, 'pattern');
(4) 连接数过多引起上下文切换
- 500+ 活跃连接,频繁的线程切换导致 CPU 浪费在调度上
优化方案
- 加索引:减少单次查询的数据量
- 优化 SQL:避免
SELECT *、减少计算、避免函数包裹索引列 - 减少排序范围:使用覆盖索引或分页
- 控制连接数:应用连接池合理设置
- 读写分离:将查询负载分流
- 增加 CPU 核数:MySQL 从 5.7 开始能利用多核进行并行复制和并行查询
2. 内存瓶颈
表现
- 操作系统可用内存持续下降
- 出现 SWAP(
free -m看到 swap used > 0) OOM Killer杀死 MySQL 进程- Buffer Pool 命中率低于 95%
常见原因
(1) Buffer Pool 不足
-- 查看命中率
SELECT (1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)) * 100
FROM performance_schema.global_status;
-- 命中率 < 95% 说明内存不够
(2) 连接数过多
每个连接分配排序缓冲区、JOIN 缓冲区、临时表空间,聚沙成塔:
max_connections = 2000
# 2000 个连接 × 2MB(保守估计每连接) = 4GB 额外开销
(3) 临时表大量创建
-- 查看临时表使用情况
SHOW STATUS LIKE 'Created_tmp%';
-- Created_tmp_disk_tables 很高 → 在内存中放不下临时表,大量使用磁盘
(4) 大查询结果集
一次查询返回几十万行,应用层不处理,客户端堆积在内存中。
优化方案
- 合理调整 Buffer Pool:设为物理内存的 60-80%
- 减少连接数:应用连接池不要开太大
- 调大临时表阈值:
tmp_table_size和max_heap_table_size - 避免大结果集:增加 LIMIT,使用游标分页
- 监控内存使用:
performance_schema.memory_summary_global_by_event_name
3. IO 瓶颈
表现
iowait高(top命令中 %wa 高)- 磁盘 IOPS 持续打满(
iostat -x 1查看%util) - 慢查询日志中大量查询耗时在 “Sending data” 状态
- 脏页比例高(
Innodb_buffer_pool_pages_dirty)
常见原因
(1) 磁盘随机读写慢
- HDD vs SSD:随机 IOPS 差 100 倍以上
- SSD 不同型号差异也很大
(2) 全表扫描
没有索引需要全表扫描,产生大量连续的磁盘读取。
(3) 刷脏页压力大
SHOW ENGINE INNODB STATUS 看到大量脏页等待刷新:
Modified db pages 85% (大量脏页)
(4) Binlog 写入
-- 查看 binlog 大小
SHOW BINARY LOGS;
每次事务提交都要写 binlog(如果 sync_binlog=1),加上 Redo Log 的双写,IO 压力大。
(5) 排序/临时表的磁盘操作
-- Created_tmp_disk_tables 太多
-- Sort_merge_passes 太多
优化方案
- 使用 SSD:这是最大的 IO 性能提升手段
- 加索引减少扫描量:最直接的减少 IO 方式
- 调整刷脏参数:
innodb_io_capacity、innodb_flush_neighbors - 合并写操作:减少频繁的小事务,改为批量提交
- 读写分离:将读 IO 分散到从库
- 调整 binlog 策略:
sync_binlog=N(不要求每次提交都 fsync)
综合诊断流程
第一步:快速定位瓶颈类型
# 查看 CPU 和 IO 情况
top
# 查看磁盘 IO
iostat -x 1 5
# 查看内存和 swap
free -h
第二步:MySQL 层面诊断
-- 查看当前正在运行的 SQL
SHOW FULL PROCESSLIST;
-- 查看全局状态
SHOW GLOBAL STATUS;
-- 查看 InnoDB 状态细节
SHOW ENGINE INNODB STATUS\G
-- 使用 performance_schema 查看等待事件
SELECT * FROM sys.session ORDER BY wait_time_ns DESC LIMIT 10;
第三步:针对性优化
┌─ CPU 高 ───→ 检查慢查询、索引、排序
├─ 内存高 ───→ 检查 Buffer Pool、连接数、临时表
├─ IO 高 ────→ 检查全表扫描、刷脏页、binlog 写入
└─ 混合瓶颈 ─→ 最需要系统级排查
瓶颈辨别速查表
| 现象 | 典型瓶颈 |
|---|---|
top 中 %wa 高 |
IO 瓶颈 |
top 中 %us 高,但没有 IO 等待 |
CPU 瓶颈 |
iostat %util 高,IOPS 高 |
IO 瓶颈 |
| 内存充足但 Buffer Pool 命中率低 | 数据量 > Buffer Pool / 全表扫描污染缓存 |
Sort_merge_passes 高 |
CPU + IO 混合 |
Threads_running 远小于 Threads_connected |
连接泄漏或池中大部分空闲 |
面试常问题
Q:如何快速判断 MySQL 是 CPU 瓶颈还是 IO 瓶颈?
A:看 top 中 CPU 的 %us 和 %wa。%wa(IO wait)高说明 IO 是瓶颈;%us(用户态)高且 %wa 低说明 CPU 是瓶颈。然后结合 MySQL 的状态指标进一步确认。
Q:Buffer Pool 命中率低一定是内存不够吗?
A:不一定。全表扫描会加载大量不常用的数据页到 Buffer Pool,挤掉热点数据。这就是为什么临时性的全表扫描会导致”假性内存不足”,实际是 Buffer Pool 被污染了。
Q:生产环境中最常见的是哪种瓶颈?
A:绝大多数场景下,IO 瓶颈是最常见的。原因是磁盘速度远慢于 CPU 和内存。所以优化思路往往围绕”减少 IO”展开:加索引、覆盖索引、Buffer Pool 调大、读写分离。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容