慢查询诊断从哪里入手
MySQL性能问题的80%来自20%的慢查询。定位这20%的查询是调优的第一步。开启慢查询日志是最直接的方式:
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 0.5; -- 超过500ms记录
SET GLOBAL log_queries_not_using_indexes = ON; -- 未用索引的查询也记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
long_query_time设为0.5秒是一个较合理的起点——线上业务通常要求95分位RT在200ms以内,0.5秒的阈值能捕获明显异常的查询而不至于日志量过大。确认问题范围后再逐步降低阈值。
mysqldumpslow与pt-query-digest分析慢日志
MySQL自带的mysqldumpslow适合快速概览:
# 按查询时间排序,显示前10条
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 按查询次数排序
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
但mysqldumpslow只做参数化聚合,缺乏执行计划上下文。Percona的pt-query-digest是专业级工具:
# 生成完整分析报告
pt-query-digest /var/log/mysql/slow.log > slow_report.txt
# 只分析最近1小时的查询
pt-query-digest --since '1h' /var/log/mysql/slow.log
# 按指定指纹过滤
pt-query-digest --filter '$fingerprint =~ m/ORDER BY/' /var/log/mysql/slow.log
pt-query-digest输出中的关键指标:Query_time的95分位值反映查询的稳定程度;Rows_examined/Rows_sent比值反映查询效率——比值越大说明扫描了大量行但只返回少量数据,典型的索引缺失场景;Lock_time占比高说明锁竞争严重,需要从事务设计层面优化。
EXPLAIN执行计划深度解读
拿到慢查询SQL后,用EXPLAIN分析执行计划:
EXPLAIN SELECT o.id, o.amount, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'PENDING'
AND o.created_at > '2026-07-01'
ORDER BY o.created_at DESC
LIMIT 20;
EXPLAIN输出的关键字段及含义:
type:访问类型,从优到差排序:system > const > eq_ref > ref > range > index > ALL。出现ALL意味着全表扫描,必须加索引。index比ALL好不了多少——全索引扫描。
key:实际使用的索引。显示NULL说明没有可用索引。possible_keys有值但key为NULL,说明优化器判断走索引还不如全表扫描,需要检查索引选择性和查询条件。
rows:预估扫描行数。注意这是优化器的估算值,可能偏差很大。用EXPLAIN ANALYZE(MySQL 8.0.18+)获取实际执行时间和行数。
Extra:附加信息。重点关注:Using filesort(需要额外排序,消耗CPU和内存)、Using temporary(创建临时表,大查询可能溢出到磁盘)、Using index(覆盖索引,性能最优)。
索引设计原则与常见反模式
索引不是越多越好。每个索引增加写操作开销(INSERT/UPDATE/DELETE需要同步维护索引),且优化器选择索引的计算成本也随索引数量上升。
原则一:最左前缀匹配
联合索引(a,b,c)可以支持a、(a,b)、(a,b,c)三种查询条件组合,但不支持只有b或(c)的查询:
-- 创建联合索引
CREATE INDEX idx_user_status_time ON orders(user_id, status, created_at);
-- 走索引: 匹配最左前缀
SELECT * FROM orders WHERE user_id = 1001;
SELECT * FROM orders WHERE user_id = 1001 AND status = 'PENDING';
-- 不走索引: 缺少最左列user_id
SELECT * FROM orders WHERE status = 'PENDING';
原则二:等值条件在前,范围条件在后
-- 正确顺序: 等值过滤在前,范围扫描在后
CREATE INDEX idx_status_created ON orders(status, created_at);
-- 错误顺序: created_at的范围扫描会阻止status的索引使用
CREATE INDEX idx_created_status ON orders(created_at, status);
反模式:在索引列上使用函数
-- 索引失效
SELECT * FROM orders WHERE DATE(created_at) = '2026-07-28';
-- 索引有效
SELECT * FROM orders
WHERE created_at >= '2026-07-28 00:00:00'
AND created_at < '2026-07-29 00:00:00';
覆盖索引与回表优化
当查询所需的所有字段都包含在索引中时,MySQL直接从索引返回数据而不需要回表读取行数据:
-- 没有覆盖索引:需要回表读amount
SELECT id, user_id, amount FROM orders WHERE user_id = 1001;
-- 创建覆盖索引
CREATE INDEX idx_user_amount ON orders(user_id, amount);
-- EXPLAIN中Extra显示 Using index,表示覆盖索引生效
覆盖索引对分页查询优化尤其显著:
-- 深分页问题:扫描100020行但只返回20行
SELECT * FROM orders ORDER BY created_at DESC LIMIT 100000, 20;
-- 覆盖索引优化:先从索引取出主键,再回表
SELECT o.* FROM orders o
JOIN (
SELECT id FROM orders ORDER BY created_at DESC LIMIT 100000, 20
) tmp ON o.id = tmp.id;
子查询只从覆盖索引中扫描id列(Using index),然后通过主键回表获取完整行数据。主键回表是顺序IO,比深分页的随机IO效率高几个数量级。
InnoDB Buffer Pool调优
Buffer Pool是InnoDB性能的核心。如果Buffer Pool命中率低于99%,说明物理IO过多,需要调整配置:
-- 查看Buffer Pool状态
SHOW ENGINE INNODB STATUS\G
-- 关键指标
-- Buffer pool hit rate: 998/1000 (99.8%)
-- 如果低于99%就需要调优
-- 计算合适的Buffer Pool大小
-- 专用数据库服务器建议设为物理内存的70-80%
SET GLOBAL innodb_buffer_pool_size = 8589934592; -- 8GB
-- 多个Buffer Pool实例减少锁竞争
SET GLOBAL innodb_buffer_pool_instances = 8;
Buffer Pool预热也很重要——MySQL重启后缓存是空的,所有查询都需要磁盘IO。MySQL 8.0支持缓冲池转储:
-- 关闭时保存缓存状态
SET GLOBAL innodb_buffer_pool_dump_at_shutdown = ON;
-- 启动时加载
SET GLOBAL innodb_buffer_pool_load_at_startup = ON;
在线DDL与大表变更策略
大表加索引不能直接ALTER TABLE——会锁表。使用pt-online-schema-change或MySQL 8.0的ALGORITHM=INPLACE:
-- MySQL 8.0 在线DDL(不锁表)
ALTER TABLE orders
ADD INDEX idx_status_created (status, created_at),
ALGORITHM=INPLACE, LOCK=NONE;
-- 仍然会锁表的场景:
-- 1. 添加全文索引
-- 2. 修改列类型
-- 3. 删除主键
-- pt-osc方式(兼容MySQL 5.7)
pt-online-schema-change \
--alter "ADD INDEX idx_status_created (status, created_at)" \
--host=127.0.0.1 --user=admin --ask-pass \
D=production,t=orders \
--chunk-size=1000 --max-lag=2 \
--execute
pt-osc通过创建影子表+增量同步+rename swap实现无锁变更。chunk-size控制每次批量处理的行数,max-lag限制主从延迟,两者配合避免变更过程中影响线上读写。
性能调优的系统性方法论
MySQL性能调优不是单点优化,而是系统性工程。正确的顺序:定位慢查询 → EXPLAIN分析执行计划 → 优化索引设计 → 调整Buffer Pool等参数 → 考虑读写分离或分库分表。跳过前面步骤直接调参数或分库分表,大概率是过度设计。绝大多数性能问题在索引优化阶段就能解决——关键是用对工具、读准执行计划、遵循索引设计原则。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-xing-neng-diao-you-shi-zhan-man-cha-xun-zhen-duan-yu/