买服务器跑 MySQL,最容易犯的错误是照着"Web 服务器"的思路配:CPU 核数拉满、内存随便给个 8G、硬盘挑容量大的机械盘。结果上线没多久,QPS 刚上几百,磁盘 %util 就顶到 100%,应用那边报一堆慢查询。原因很简单——MySQL 是典型的"内存 + 随机 IO"密集型数据库,它对资源的需求形状和 Web 服务器完全不同。
这篇文章把 MySQL 的资源敏感点拆开讲:内存怎么分才不会 OOM、CPU 到底什么时候会成为瓶颈、为什么随机 IOPS 比顺序带宽重要得多。然后给出按数据量与 QPS 划分的几档配置建议,以及上线前后用来验证配置是否合理的命令。文中所有数字都是工程经验值,不是官方承诺,最终要以你自己业务的压测结果为准。
一、先看清楚:MySQL 吃的是哪几种资源
一台 MySQL 服务器的资源压力,大致可以归成四类,优先级通常也是这个顺序。
内存是第一优先级。 InnoDB 的缓冲池(Buffer Pool)把数据页和索引页缓存在内存里,命中率直接决定磁盘 IO 的量。内存够大,热数据全在内存,磁盘基本只承担写入和检查点刷脏;内存不够,每条查询都可能变成一次随机读,性能掉一个数量级。
磁盘 IO 是第二优先级,尤其是随机 IO。 前面说的内存不够时落下来的随机读、redo log 的顺序写、以及后台刷脏页的随机写,全都压在磁盘上。机械盘的随机 IOPS 只有一两百,SATA SSD 大约几万,NVMe SSD 可以到几十万,这个差距直接决定了数据库的上限。
CPU 通常排在第三位。 大部分 OLTP 场景(按主键点查、简单条件查询)的瓶颈不在 CPU。CPU 真正吃紧的场景是:高并发下的解析与优化开销、大量排序分组、复杂 JOIN、以及 DDL 和大批量导入。
网络一般最后才成为瓶颈,除非你的结果集很大(比如一次拉几十万行)、或者做主从复制时主库写入量极高。内网千兆到万兆基本够用,但要注意跨可用区部署时往返延迟会放大每条查询的耗时。
判断优先级有个很实用的方法:在系统慢的时候,分别看 iostat 的 %util、free 的 available、以及 top 的 CPU 使用率,谁先接近饱和谁就是瓶颈。跳过这一步直接加配置,往往是花了钱没解决问题。
二、内存:最值得投入的一项
innodb_buffer_pool_size:给多少才合适
这是 MySQL 里最重要的一个参数。它决定了 InnoDB 能缓存多少数据和索引。
经验法则:独占数据库服务器,设置为可用物理内存的 50% 到 70%。 如果服务器上还跑了别的东西(比如 Redis、应用进程、定时备份脚本),要先把它们的内存扣掉再算。
举例:一台 32G 内存的独占数据库服务器,实际可用约 30G,那么 buffer pool 可以设 16G 到 20G。设成 28G 是很危险的——因为除了 buffer pool,MySQL 还要为每个连接分配排序缓冲、连接缓冲、临时表,操作系统还要留页面缓存和 slab,再加上 fork 出来的备份进程,很容易触发 OOM Killer,MySQL 直接被杀掉。
很多人会问:为什么不直接设到 80%?因为 MySQL 的内存占用不止 buffer pool 这一块。一个粗略的估算公式是:总内存 ≈ buffer pool + 每连接内存 × 最大连接数 + 操作系统与其他进程。每连接内存在有排序和 JOIN 时可能达到几 MB,乘上几百个连接就是几个 G。
配置写在配置文件的 [mysqld] 段:
[mysqld]
innodb_buffer_pool_size = 16G
innodb_buffer_pool_instances = 4
innodb_buffer_pool_instances 是把缓冲池切成多个实例以减少互斥锁争用,经验上是buffer pool 大于 8G 时才需要拆分,一般设成 4 或 8 即可,不要设得过多(每个实例太小反而影响效率)。
在线上,MySQL 5.7 及以后支持在线调整 buffer pool 大小,不需要重启:
SET GLOBAL innodb_buffer_pool_size = 17179869184; -- 单位字节,这里是 16G
SHOW STATUS LIKE 'Innodb_buffer_pool_resize_status';
每连接分配的内存参数,必须小心
这是新手最容易踩的坑。下面这些参数不是全局一份,而是每个连接(甚至每个 JOIN、每个排序)各分配一份:
| 参数 | 作用 | 建议值 |
|---|---|---|
sort_buffer_size |
排序缓冲 | 默认 256K 到 2M,不要盲目调大 |
join_buffer_size |
无索引 JOIN 时的缓冲 | 默认 256K,保持默认即可 |
read_buffer_size |
顺序扫描缓冲 | 保持默认 |
read_rnd_buffer_size |
随机读缓冲 | 保持默认,过大反而有害 |
tmp_table_size / max_heap_table_size |
内存临时表上限 | 32M 到 64M,两者取相同值 |
max_connections |
最大连接数 | 见第 062 篇,不要设得过大 |
一个真实的翻车场景:有人把 sort_buffer_size 从 256K 调到 64M 想"优化排序",结果 500 个连接同时跑排序时,光这一项就需要 32G 内存,服务器直接 OOM。
判断这类参数是否合理的依据不是理论值,而是实际使用量。 如果排序缓冲设成 2M 但绝大多数排序只用几百 K,那 2M 就是浪费。真正该做的是优化 SQL 让排序走索引,而不是把缓冲池调大。
tmp_table_size 和 max_heap_table_size 要成对设置,因为内存临时表的实际上限取两者的较小值。可以在运行时用状态变量观察临时表落盘的比例:
SHOW GLOBAL STATUS LIKE 'Created_tmp%';
如果 Created_tmp_disk_tables 除以 Created_tmp_tables 的比例长期超过 25%,说明大量临时表在往磁盘上落,需要考虑调大这两个参数,或者优化 SQL 里产生临时表的写法(比如 GROUP BY 的字段组合、DISTINCT、UNION)。
key_buffer_size:MyISAM 专用,别浪费
key_buffer_size 只缓存 MyISAM 表的索引。现在默认存储引擎是 InnoDB,如果你确认库里没有 MyISAM 表,这个值保持默认的 8M 就够,把内存省给 buffer pool。
检查有没有 MyISAM 表:
SELECT table_schema, table_name, engine
FROM information_schema.tables
WHERE engine = 'MyISAM' AND table_schema NOT IN ('mysql','sys','performance_schema','information_schema');
系统库 mysql 里可能有少量 MyISAM 系统表,那是历史遗留,不用为它多分内存。
三、CPU:什么时候会成为瓶颈
真正吃 CPU 的几类操作
并发查询本身的解析与优化开销。 每条 SQL 都要经过词法分析、语法分析、优化器选路。简单的主键点查这部分开销很小,但在几千 QPS 下累积起来就很可观。这也是为什么建议应用侧多用预处理语句(Prepared Statement)和连接池——能省掉一部分重复解析。
排序与分组。 ORDER BY、GROUP BY、DISTINCT,如果无法用索引直接得到有序结果,就要在内存或临时文件里排序,这是纯 CPU 消耗,同时可能伴随磁盘临时表。
复杂 JOIN 与大结果集扫描。 优化器要评估多种连接顺序,表越多代价越大(join 的表超过 8 张时,优化器的搜索开销会明显上升)。大范围扫描即使走索引,回表取数据也要逐行处理。
DDL 与批量导入。 加索引需要对全表排序建树,ALTER TABLE 在大表上可能跑几十分钟到几小时;LOAD DATA、批量 INSERT 也是 CPU 密集。
并发线程争用。 MySQL 的 InnoDB 并非能无限线性利用多核。经验上,在较高并发下(比如 64 个以上活跃线程同时跑),锁争用和上下文切换会让吞吐量不再增长甚至下降。所以"核数越多越好"在数据库上并不成立,单实例 8 到 32 核是比较常见的甜区。
怎么判断 CPU 是否瓶颈
top # 看 %us(用户态)与 %sy(内核态),%wa 高说明在等 IO
top -H -p $(pidof mysqld) # 看 mysqld 各线程的 CPU
vmstat 1 # 看 r(运行队列)是否持续大于 CPU 核数
关键指标是 vmstat 的 r 列:如果它持续大于 CPU 核数,说明有线程在排队等 CPU。top 里 %wa 高说明 CPU 在等磁盘,这时该优化的是 IO 而不是加核。
核数怎么选:OLTP 业务(点查为主)8 到 16 核通常够用;有较多报表查询、批量任务的混合负载可以考虑 16 到 32 核;超过 32 核的单实例,往往应该考虑拆库或者读写分离,而不是继续堆核。
四、磁盘:随机 IOPS 才是命门
为什么随机 IO 比顺序带宽重要
数据库访问数据页的模式是随机的:按索引查一行,可能落在表空间的任意位置。机械盘一次随机读要寻道加旋转,大约 5 到 10 毫秒,换算下来单盘 IOPS 只有 100 到 200。而顺序读写可以跑到 150 MB/s 以上。
这就是为什么"用一块 4T 企业级机械盘跑数据库"几乎必然出问题:容量是够了,但 IOPS 撑不住。反过来,一块 512G 的 NVMe SSD 随机读 IOPS 可以到几十万,性能完全不是一个量级。
所以选盘的顺序是:先看介质(NVMe > SATA SSD > 机械盘),再看 IOPS 与写放大,最后才是容量。
三类介质的量级参考
| 介质 | 随机读 IOPS 量级 | 延迟量级 | 适用 |
|---|---|---|---|
| 机械盘 7200rpm | 100 到 200 | 5 到 10 ms | 备份归档、冷数据,不建议跑主库 |
| SATA/SAS SSD | 数万 | 0.1 ms 级 | 中小型数据库,够用 |
| NVMe SSD | 数十万 | 0.05 ms 级 | 高并发库、大表、写入密集 |
以上为经验量级,具体数值受型号、队列深度、盘上剩余空间与写放大影响很大。云盘还要看规格说明里标注的 IOPS 上限,很多云盘是按容量或按规格给 IOPS 配额的。
NVMe 优先。 如果预算允许,数据库主盘直接上 NVMe。同样的钱,把 CPU 从 32 核降到 16 核换一块好 NVMe,性能提升通常更明显。
redo log 与数据盘分离
InnoDB 的 redo log(重做日志)是顺序写,而数据文件是随机读写。这两种 IO 模式混在一块盘上会互相干扰:后台刷脏产生的大量随机写会拖慢 redo log 的顺序写,而 redo log 的刷盘延迟又会直接卡住事务提交。
比较理想的做法是:redo log 单独放在一块延迟低、写性能好的盘上,数据文件放在另一块盘(或 RAID 组)上。 在云环境上,可以单独挂载一块高性能云盘给 redo log 用。
相关配置:
[mysqld]
innodb_log_group_home_dir = /data/redolog/
innodb_log_file_size = 1G
innodb_log_files_in_group = 2
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 1 表示每次事务提交都把 redo log 刷到磁盘,这是最安全的设置(崩溃不丢已提交事务)。设为 2 表示只写到操作系统缓存,每秒刷一次,性能更好但宕机可能丢最后一秒的事务。除非你能接受丢数据,否则主库保持 1。
innodb_flush_method = O_DIRECT 让 InnoDB 绕过操作系统的页面缓存直接写盘,避免数据在 OS cache 和 buffer pool 里缓存两份,也能减少 OOM 风险。
innodb_log_file_size 太小会导致频繁的检查点,表现为写入抖动。经验上,redo log 总大小(file_size × files_in_group)应该能容纳大约 1 小时的写入量。可以通过观察两次检查点之间 redo log 的生成量来估算:
SHOW GLOBAL STATUS LIKE 'Innodb_os_log_written'; -- 隔一小时取两次差值
用 fio 实测一块盘能不能用
不要只看厂商标称,自己测最靠谱。测试随机写性能(数据库场景最关心):
# RHEL/CentOS/Rocky
sudo dnf install -y fio
# Debian/Ubuntu
sudo apt-get install -y fio
# 随机读写混合,70% 读 30% 写,模拟 OLTP
fio --name=oltp --filename=/data/testfile --size=4G --bs=16k \
--rw=randrw --rwmixread=70 --ioengine=libaio --iodepth=32 \
--direct=1 --runtime=120 --time_based --group_reporting
重点关注输出里的 IOPS 和延迟的 99 分位。如果 16K 随机读的 IOPS 只有几千,或者 99 分位延迟超过 10 毫秒,这块盘就撑不了太高的并发。
风险提示:fio 的写测试会真的写数据,务必用空目录或专用测试文件,不要对已有数据目录执行,测完删除测试文件。
五、按数据量与 QPS 的配置档位(经验值)
下面这张表是常见业务的经验档位,假设是独占数据库服务器、InnoDB 引擎、以 OLTP 为主。
| 档位 | 数据量(含索引) | 峰值 QPS 量级 | CPU | 内存 | 磁盘 |
|---|---|---|---|---|---|
| 入门 | 10G 以内 | 几百 | 4 核 | 8G(buffer pool 4G) | SATA SSD 200G |
| 中小型 | 10G 到 100G | 1000 到 3000 | 8 核 | 16G 到 32G(buffer pool 10G 到 20G) | NVMe 或 SSD 500G |
| 中型 | 100G 到 500G | 3000 到 8000 | 16 核 | 64G(buffer pool 40G 左右) | NVMe 1T,redo 单独盘 |
| 大型 | 500G 到 2T | 8000 以上 | 32 核 | 128G 以上 | NVMe 多盘,读写分离 |
几点说明:
热数据小于内存是最理想的状态。 衡量标准是缓冲池命中率,OLTP 业务应该做到 99% 以上。如果命中率长期低于 95%,要么加内存,要么考虑归档冷数据缩小热集。
数据量超过内存 3 倍以后,加内存的边际收益会下降。 这时候更应该考虑的是:归档历史数据、做读写分离、或者按业务拆分。
写密集场景要额外关注 redo log 和刷脏能力。 如果写入 QPS 很高,除了 redo log 单独放,还可以调大 innodb_io_capacity 和 innodb_io_capacity_max(分别对应后台刷脏的 IO 能力预估),SSD 上通常设 2000 和 4000 起,NVMe 可以更高。
不要低估备份、大查询、DDL 的资源占用。 一台"平时只用了 30% 资源"的库,跑一次全表备份或者一次 ALTER TABLE 就可能把资源吃光。配置时要给这些运维操作留出余量。
六、配置落地与验证
一份基础配置模板
[mysqld]
# 内存
innodb_buffer_pool_size = 16G
innodb_buffer_pool_instances = 4
tmp_table_size = 64M
max_heap_table_size = 64M
sort_buffer_size = 2M
join_buffer_size = 256K
key_buffer_size = 8M
# 连接
max_connections = 500
thread_cache_size = 64
# IO
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 1
innodb_log_file_size = 1G
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
innodb_file_per_table = 1
改完重启生效(注意:某些参数如 innodb_log_file_size 在不同版本上的变更方式不同,操作前先查对应版本文档,并确认已有备份):
# RHEL/CentOS/Rocky
sudo systemctl restart mysqld
# Debian/Ubuntu
sudo systemctl restart mysql
上线后要盯的几个指标
缓冲池命中率:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
命中率 = 1 减去 Innodb_buffer_pool_reads 除以 Innodb_buffer_pool_read_requests。低于 99% 要考虑加内存或优化热集。
连接与线程:
SHOW GLOBAL STATUS LIKE 'Threads_%';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
Max_used_connections 除以 max_connections 超过 80% 说明连接数快不够了,需要先查根因再考虑调大。
磁盘与 CPU 用系统工具观察:
iostat -x 1 # 看 %util 与 await,%util 长期接近 100% 说明磁盘饱和
sar -n DEV 1 # 网络流量
七、常见误区
把配置堆到最大就万事大吉。 参数不是越大越好。buffer pool 过大会引发 OOM,每连接的排序缓冲过大会在并发时暴雷,连接数过大会导致大量线程上下文切换。所有"调大"都要有对应的监控数据支撑。
只看平均响应时间。 平均值会掩盖长尾。一条 SQL 平均 20 毫秒,但 99 分位可能是 500 毫秒——用户感受到的是后者。排障时要看 P99、P999。
用机械盘跑主库,靠加内存补。 内存只能缓解读,写是绕不过去的:redo log 的顺序写、脏页的刷盘、检查点的随机写,全部落在磁盘上。写入一上来机械盘立刻露馅。
忽略云盘的 IOPS 配额。 云盘往往既有容量限制也有 IOPS 与吞吐上限,有些是按容量线性给的。买之前看清楚规格说明里的 IOPS 数字,别只看容量价格。
配置一次定终身。 业务数据量翻几倍之后,原来的配置肯定不合适。建议每月回看一次命中率、连接数峰值、磁盘利用率,作为扩容依据。
常见问题
buffer pool 到底设 50% 还是 70%?
看服务器上还有没有别的进程。纯独占、且没有本地大备份任务的,可以往 70% 靠;如果机器上还跑了 Redis、应用进程、或者每天在本机跑 mysqldump,建议 50% 到 60%,给它们留够内存。另外一个判断依据:观察 OS 的 available 内存,长期低于总内存的 10% 就该往回收一收。
16G 内存的机器,buffer pool 设 12G 行不行?
偏激进。16G 机器实际可用约 15G,设 12G 意味着只留 3G 给连接缓冲、临时表、操作系统和其他进程。如果连接数上百且有排序查询,很可能不够。稳妥的做法是先设 8G,观察 available 内存和临时表落盘比例,再逐步上调。
数据库要不要上 64 核?
单实例一般不必要。InnoDB 在高并发下的锁争用会让核数收益递减,而且核数多了单条查询的排队反而可能更严重。更常见的做法是 16 到 32 核,配合读写分离或者拆库来分散压力。
redo log 单独一块盘,值不值?
写入量大的库很值。redo log 是顺序写,被随机写干扰后事务提交延迟会明显抖动。成本上,单独加一块容量不大(几十 G 到几百 G)的高性能盘并不贵。云环境可以单独挂一块按 IOPS 计费的高性能云盘。
SSD 要不要做 RAID?
要,但不是为了提速,而是为了容错。数据库建议 RAID 10(兼顾性能与冗余),具体对比见第 068 篇。同时要记住 RAID 不替代备份。
怎么快速判断该加内存还是换盘?
看两个指标:缓冲池命中率低于 99% 且磁盘读 IOPS 高 → 内存不够(加内存或缩小热集);命中率正常但磁盘 %util 长期接近 100% → 写入或刷脏压力大(换更快的盘、优化写入、调刷脏参数)。
总结
- 内存优先级最高:独占数据库服务器把
innodb_buffer_pool_size设为可用内存的 50% 到 70%,超过 8G 时用innodb_buffer_pool_instances拆成 4 到 8 个实例。命中率要盯住 99% 这条线。 - 每连接内存参数要克制:
sort_buffer_size、join_buffer_size、read_rnd_buffer_size是按需分配、可能多次分配的,默认或小幅上调即可,盲目调大会在高并发时引发 OOM。真正治本的是让排序走索引。 - 磁盘看随机 IOPS 而不是容量:机械盘一两百 IOPS、SATA SSD 数万、NVMe 数十万,这个量级差决定了数据库上限。有预算就上 NVMe,并把 redo log 的顺序写与数据文件的随机读写分到不同盘上。
- CPU 通常不是第一瓶颈,真正吃 CPU 的是高并发解析、排序分组、复杂 JOIN 和 DDL;单实例 8 到 32 核是常见甜区,核数再多不如考虑读写分离或拆分。
- 配置要跟着数据走:10G/100G/500G/2T 几个数据量台阶对应不同的 CPU、内存与磁盘档位,表中数值是经验值,以自己业务的压测结果为准。
- 上线后持续验证而不是一配了之:用
fio实测磁盘、用状态变量算命中率与临时表落盘比例、用iostat看磁盘饱和度,每月回顾一次作为扩容依据。所有配置变更前先确认已有可用备份。
企业QQ咨询




