MySQL 性能调优是后端开发绕不开的硬功夫。当 QPS 上不去、慢查询拖垮接口、CPU 却没跑满时,单靠”加一条索引”往往治标不治本。真正要从根上解决瓶颈,离不开两件事:合理的参数配置与能读懂的 EXPLAIN 执行计划。很多团队在业务初期忽略这些基础配置,等到数据量上亿、接口频繁超时才回头补课,代价远高于提前规划。本文结合多次线上排障经验,系统拆解 InnoDB 核心参数、慢查询定位与执行计划解读方法,帮你把数据库调出可预期的稳定吞吐。
一、先稳住 InnoDB 内存与日志
InnoDB 是 MySQL 默认引擎,绝大多数性能问题都和”内存命中率”与”日志刷盘策略”相关。调优的第一步,是把内存与日志这两块基石铺好,否则上层 SQL 再怎么优化也会被底层 IO 拖累。
1.1 innodb_buffer_pool_size:命中率是生命线
缓冲池用于缓存表数据与索引页,命中率越高,磁盘 IO 越少。经验法则:在专用数据库机器上,把它设为物理内存的 60%~75%,并配合监控观察命中率是否稳定在 99% 以上。可以用下面两条状态变量算出真实命中率:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
-- 命中率 = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)
-- 若该值低于 99%,优先考虑调大 buffer pool,而不是盲目加索引
1.2 redo log 与写入吞吐
innodb_log_file_size 决定单个 redo 日志文件的大小,过小会导致频繁 checkpoint,引发写抖动;innodb_flush_log_at_trx_commit 则权衡”持久性”与”性能”——主库用 1(每次事务都刷盘,最安全),从库或日志类业务可用 2 来换取更高吞吐。生产环境常见核心参数建议如下:
| 参数 | 建议值(示例) | 作用 |
|---|---|---|
| innodb_buffer_pool_size | 物理内存 60%~75% | 提升缓存命中,减少磁盘 IO |
| innodb_log_file_size | 512M~1G | 减少 checkpoint 抖动 |
| innodb_flush_log_at_trx_commit | 主库 1 / 从库 2 | 持久性与性能权衡 |
| innodb_io_capacity | 磁盘 IOPS 的 50%~70% | 控制后台刷脏速度 |
| max_connections | 按峰值 ×1.5 | 防止连接打满雪崩 |
二、打开慢查询日志,让问题现形
不开启慢查询日志,调优就是盲调。建议把阈值设在 1 秒,并开启”未使用索引”的捕获,让那些”看似很快、实则全表扫”的隐性慢 SQL 也暴露出来。
# my.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
# 无需重启,动态开启
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
拿到慢日志后,用 mysqldumpslow 或 pt-query-digest 聚合排序,优先处理”高频 + 高耗时”的 TOP SQL。关于索引底层原理与最左前缀,可复习 MySQL 索引底层原理:B+树、覆盖索引与最左前缀。
三、读懂 EXPLAIN 执行计划
慢 SQL 定位后,用 EXPLAIN 看优化器到底是怎么走数据的。重点看四列:type、key、rows、Extra。它们能告诉你”走了哪个索引、扫了多少行、有没有额外排序”。
3.1 type 访问类型:从 ALL 到 const
type 表示表的访问方式,从优到劣大致是:system > const > eq_ref > ref > range > index > ALL。看到 ALL 基本等于全表扫描,必须优化;ref 与 range 通常是健康的索引访问。
| type | 含义 | 是否健康 |
|---|---|---|
| const | 主键/唯一索引等值匹配,最多一行 | 最优 |
| eq_ref | 关联查询命中主键/唯一索引 | 极好 |
| ref | 非唯一索引等值匹配 | 良好 |
| range | 索引范围扫描(BETWEEN/IN/>) | 良好 |
| index | 全索引扫描 | 一般 |
| ALL | 全表扫描 | 需优化 |
3.2 key、rows、Extra 三列怎么看
key 是实际用到的索引;rows 是优化器估算的扫描行数,越小越好;Extra 里的 Using index 表示覆盖索引(不必回表),而 Using filesort / Using temporary 则是危险信号,往往意味着排序或分组没有走索引。养成在测试环境对核心接口都跑一遍 EXPLAIN 的习惯,能提前发现绝大多数线上慢查询隐患,而不是等告警响了才去翻日志。
EXPLAIN
SELECT id, user_id, amount
FROM orders
WHERE user_id = 10086 AND status = 1
ORDER BY created_at DESC
LIMIT 20;
-- 期望:type=ref, key=idx_user_status, Extra=Using index condition
-- 若 Extra 出现 Using filesort,需建 (user_id, status, created_at) 联合索引
四、用索引选择性优化
不是所有列都适合建索引。选择性 = 不重复值数 / 总行数,越接近 1 越适合建索引。像”性别”这类低选择性列,单独建索引收益极低,更适合放进联合索引的左侧过滤位,而不是独立成索引。
-- 查看某列的选择性
SELECT
COUNT(DISTINCT user_id) / COUNT(*) AS selectivity
FROM orders;
-- 结果接近 1:适合单独索引;接近 0:考虑联合索引或直接忽略
联合索引务必遵循 ESR 规则(等值 E → 排序 S → 范围 R)来排列列顺序。读不懂执行计划时,也可以对照 MySQL 死锁排查实录 中的事务与锁视图一起定位。
五、连接与线程参数:高并发必看
参数调好了,连接层也要稳。连接数被打满会导致”应用明明没报错但就是连不上数据库”的雪崩;而长时间空闲连接则会白白占用资源。建议设置合理的上限与超时:
# my.cnf
max_connections = 500
wait_timeout = 300
interactive_timeout = 300
# 连接池/线程池(视发行版与版本)
thread_pool_size = 8
应用侧务必使用连接池(如 HikariCP、Druid),避免短连接风暴;同时把报表类、离线类查询引流到从库,主库只扛核心事务流量,相关架构可参考 MySQL 主从复制与读写分离实战。
六、常见调优误区
- 误区一:见慢就加索引。索引会降低写入速度并占用空间,要先看执行计划再决定。
- 误区二:buffer pool 越大越好。过大可能导致操作系统页缓存被挤占,反而影响其他进程。
- 误区三:只调参数不读执行计划。参数解决”资源够不够”,执行计划解决”SQL 走没走对路”。
- 误区四:忽略慢查询日志。很多性能问题在日志里早已给出答案,只是没人看。
七、生产环境调优清单
- 上线前:确认 buffer pool 占内存比例、redo 日志大小、连接数上限是否合理。
- 发布后:开启慢查询日志,建立慢 SQL 周报与告警机制。
- 出问题时:先 EXPLAIN 看 type/rows/Extra,再决定是否加索引或改写 SQL。
- 扩容时:读写分离把报表类查询引流到从库。
- 跨库对比:PostgreSQL 的慢查询思路可互通,见 PostgreSQL 慢查询优化。
MySQL 性能调优不是一次性动作,而是一个”监控—定位—改动—验证”的闭环。把参数配稳、把执行计划读熟,再配合慢查询日志持续观测,数据库才能在高并发场景下长期给出稳定、可预期的表现。



