分析 MySQL 性能瓶颈:CPU、内存、IO

分析 MySQL 性能瓶颈:CPU、内存、IO

性能瓶颈的三大维度

MySQL 的性能瓶颈通常可以归结为三类:CPU 瓶颈、内存瓶颈、IO 瓶颈。不同的瓶颈表现出不同的系统特征,需要不同的优化策略。

1. CPU 瓶颈

表现

  • CPU 使用率持续高于 80%
  • CPU 的 user 时间占比高(vs iowait
  • 慢查询突然增多
  • 连接数虽然不大,但 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_sizemax_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_capacityinnodb_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
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容