MySQL 性能调优是每一位后端工程师的必修课。当接口响应时间从 50ms 突然飙到 5s,第一现场往往不是业务代码,而是慢 SQL 与缺失的索引。本文从慢查询日志、EXPLAIN 执行计划入手,系统讲解索引优化、常见慢 SQL 改写与关键参数调优,给出一套可落地的 MySQL 性能调优路径,帮助你把”凭感觉优化”变成”看数据优化”。
一、慢查询日志:定位性能瓶颈的第一现场
任何 MySQL 性能调优都该从”看见慢查询”开始。开启慢查询日志后,所有执行超过阈值的 SQL 都会被记录,是排查线上性能问题最可靠的入口。建议把阈值设为 1 秒(long_query_time=1),并对未走索引的查询也单独记录。
# my.cnf 开启慢查询日志
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = ON
min_examined_row_limit = 1000 # 扫描行数过少的不记录,避免噪声
重启生效后,用官方工具 mysqldumpslow 对日志做聚合,快速找出”出现次数最多、总耗时最高”的 SQL,而不是被单条偶发慢查询带偏。
# 按总耗时排序,取前 10 条最慢的 SQL
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 按执行次数排序,找出高频查询
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
| 参数 | 作用 | 建议值 |
|---|---|---|
| slow_query_log | 是否开启慢日志 | ON |
| long_query_time | 慢查询阈值(秒) | 1(高并发可 0.5) |
| log_queries_not_using_indexes | 记录全表扫描 | ON |
| min_examined_row_limit | 最小扫描行数过滤 | 1000 |
二、EXPLAIN:读懂执行计划
拿到慢 SQL 后,在查询前加 EXPLAIN 即可看到 MySQL 优化器选择的访问路径。MySQL 性能调优的核心,就是让 EXPLAIN 输出里的 type 尽量靠前、rows 尽量小、Extra 里没有糟糕的提示。
EXPLAIN SELECT id, user_id, amount
FROM orders
WHERE user_id = 10086 AND status = 'PAID'
ORDER BY created_at DESC
LIMIT 20;
| type 等级 | 含义 | 调优方向 |
|---|---|---|
| const / eq_ref | 唯一索引命中,最优 | 保持 |
| ref | 非唯一索引命中 | 可接受 |
| range | 索引范围扫描 | 可用 |
| index | 全索引扫描 | 考虑覆盖索引 |
| ALL | 全表扫描 | 必须优化 |
重点看三个字段:type 表示访问类型,越靠左越好;key 显示实际用到的索引,为空说明没走索引;Extra 若出现 Using filesort 或 Using temporary,往往意味着排序或分组没用到索引,需要重点优化。关于排序与分组导致的性能问题,可参考 PostgreSQL 慢查询分析与优化 的排查思路,两者内核原理相通。
三、索引优化实战
3.1 最左前缀匹配
联合索引 (a, b, c) 只能按 a、a+b、a+b+c 的顺序命中。如果查询跳过 a 直接查 b,索引就会失效。这是 MySQL 性能调优里最容易踩的坑。
-- 建立联合索引
CREATE INDEX idx_user_status_time ON orders(user_id, status, created_at);
-- 能命中索引:满足最左前缀
SELECT * FROM orders WHERE user_id = 10086 AND status = 'PAID';
-- 无法命中:缺失 user_id 这一最左列
SELECT * FROM orders WHERE status = 'PAID';
3.2 覆盖索引避免回表
如果索引本身就包含了查询所需的全部列,MySQL 无需回表读取数据行,性能可提升数倍。EXPLAIN 的 Extra 列会显示 Using index,这就是覆盖索引生效的标志。
-- 只查索引列,触发覆盖索引
SELECT user_id, status, created_at
FROM orders
WHERE user_id = 10086
ORDER BY created_at DESC;
-- Extra 显示 "Using index",无需回表
3.3 索引下推(ICP)
MySQL 5.6 引入的索引条件下推(Index Condition Pushdown),允许在存储引擎层就过滤掉不满足条件的记录,减少回表次数。EXPLAIN 的 Extra 会显示 Using index condition。这是默认开启的优化,但只有在合理设计索引时才能发挥作用。
3.4 避免索引失效的写法
-- 错误:对列做函数运算,索引失效
SELECT * FROM orders WHERE DATE(created_at) = '2026-10-04';
-- 正确:改成范围查询,索引可命中
SELECT * FROM orders
WHERE created_at >= '2026-10-04' AND created_at < '2026-10-05';
-- 错误:隐式类型转换(user_id 是 int,却传字符串)
SELECT * FROM orders WHERE user_id = '10086';
-- 错误:前导模糊查询,索引失效
SELECT * FROM orders WHERE user_name LIKE '%明%';
四、常见慢 SQL 场景与改写
很多慢查询并非缺索引,而是写法本身低效。下面两个场景在业务代码里极为常见。
-- 深分页:LIMIT 100000, 20 要扫描前 10 万行再丢弃
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;
-- 优化:用游标(上一页最大 id)代替偏移量
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 20;
-- 关联查询:用小表驱动大表,并在关联字段建索引
SELECT o.id, u.name
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.city = 'Hangzhou';
-- 确保 orders.user_id 有索引,否则退化为大表全扫描
| 反面写法 | 优化写法 | 收益 |
|---|---|---|
| 对列套函数 | 改写为范围比较 | 恢复索引 |
| LIMIT 大偏移 | 游标分页 | 避免扫描丢弃 |
| SELECT * | 只取所需列 | 覆盖索引 |
| 前导 % 模糊 | 后缀/全文索引 | 走索引 |
五、关键参数调优
索引之外,MySQL 性能调优还依赖内存与 I/O 参数。最核心的是 InnoDB 缓冲池 innodb_buffer_pool_size,它缓存索引和数据页,通常设为物理内存的 60%–70%(专用数据库服务器)。缓冲池越大,磁盘 I/O 越少。
# my.cnf 生产环境参考(以 16G 内存专用库为例)
[mysqld]
innodb_buffer_pool_size = 10G
innodb_buffer_pool_instances = 8
innodb_log_file_size = 1G
innodb_flush_log_at_trx_commit = 2 # 性能优先,可接受秒级丢失
max_connections = 500
sort_buffer_size = 4M
join_buffer_size = 4M
| 参数 | 作用 | 注意 |
|---|---|---|
| innodb_buffer_pool_size | 缓存数据与索引 | 专用机 60–70% 内存 |
| innodb_log_file_size | redo 日志大小 | 越大写吞吐越高 |
| innodb_flush_log_at_trx_commit | 刷盘策略 | 2 兼顾性能与安全 |
| max_connections | 最大连接数 | 配合连接池使用 |
调优参数时务必配合连接池,避免短连接风暴拖垮数据库。关于主从架构下读写分离的连接管理,可延伸阅读 MySQL 主从复制与读写分离实战。如果业务对缓存层有需求,Redis 缓存设计 能进一步降低数据库压力。
六、生产落地检查清单
| 检查项 | 达标标准 |
|---|---|
| 慢查询日志 | 已开启,阈值 ≤ 1s |
| 核心查询 | EXPLAIN type 至少 ref |
| 高频查询 | 尽量覆盖索引 |
| 避免失效写法 | 无函数/隐式转换/前导模糊 |
| 缓冲池 | ≥ 60% 物理内存 |
| 深分页 | 已改用游标 |
| 变更流程 | 加索引走 Online DDL / PT-OSC |
最后提醒:生产环境加索引会锁表,大表务必用 ALGORITHM=INPLACE 的 Online DDL 或 pt-online-schema-change 工具,避免写入被长时间阻塞。完整的数据库变更最好纳入 GitHub Actions CI 流水线 做评审与灰度。若需要跨数据库对比方案,PostgreSQL 与 SQL Server 选型 也值得一读。
七、小结
MySQL 性能调优不是玄学:先用慢查询日志定位问题 SQL,再用 EXPLAIN 看清执行计划,然后围绕最左前缀、覆盖索引、避免失效写法做索引优化,配合参数与连接池调优,最后用检查清单固化成果。把这套方法变成团队习惯,绝大多数"数据库变慢"的故障都能在分钟级定位。把它和容器化部署(如 Docker 实战)结合,性能与稳定性会更上一层楼。




