MySQL慢查询诊断与索引优化:从pt-query-digest到执行计划全链路分析

慢查询是数据库性能的瓶颈根源

线上MySQL响应变慢,90%的概率是慢查询导致的。一条全表扫描的SQL在百万级表上耗时数秒,在千万级表上可能直接拖垮整个实例。定位慢查询、分析执行计划、设计最优索引,这套流程是DBA和后端开发的核心技能。

开启慢查询日志与pt-query-digest分析

配置慢查询日志

-- my.cnf或运行时设置
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- 查看确认
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';

Percona pt-query-digest分析

pt-query-digest将慢查询日志聚合为可读的统计报告,按执行时间排序,快速定位最消耗资源的SQL:

# 安装Percona Toolkit
yum install -y percona-toolkit   # CentOS
apt install -y percona-toolkit   # Ubuntu

# 分析慢查询日志
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 '$event->{db} =~ /production/' /var/log/mysql/slow.log

报告输出关键信息:

# Rank: 按查询时间占比排名
# Query ID: SQL指纹(参数替换后的哈希)
# Response time: 总响应时间和占比
# Calls: 执行次数

# Profile
# Rank Query ID       Response time  Calls R/Call V/M
# ==== ============== ============== ===== ====== ====
#    1 0x52A3BA3E     1250.0000 62%  5000  0.250  0.01
#    2 0xA8B4F1D2      450.0000 22%   300  1.500  0.05

Query ID为0x52A3BA3E的SQL贡献了62%的响应时间,优先优化这条。

EXPLAIN执行计划深度解读

拿到目标SQL后用EXPLAIN分析执行路径:

EXPLAIN SELECT o.order_id, o.amount, u.username
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'PAID' AND o.created_at > '2026-07-01'
ORDER BY o.created_at DESC
LIMIT 50;

执行计划关键字段解读:

| 字段 | 含义 | 需要关注 |
|——|——|———|
| type | 访问类型 | ALL(全表扫描)、index(索引全扫)、range(范围扫描)、ref(索引等值)、const(主键等值) |
| key | 实际使用索引 | NULL表示没用索引 |
| rows | 预估扫描行数 | 值越大越慢 |
| Extra | 额外信息 | Using filesort(额外排序)、Using temporary(临时表)、Using where(回表过滤) |

常见问题模式

type=ALL且rows=1000000,全表扫描,需要加索引。

Extra=Using filesort,排序没走索引,需要优化ORDER BY。

Extra=Using index condition,索引下推(ICP)生效,这是好的信号。

索引设计:从单列到覆盖索引

单列索引的问题

在orders表上分别建status和created_at两个单列索引,MySQL优化器只能选一个使用:

-- 查看现有索引
SHOW INDEX FROM orders;

-- 创建单列索引
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_created ON orders(created_at);

EXPLAIN结果显示key=idx_status,type=ref,但rows仍然很大——因为status过滤后数据量还是很多,created_at条件在回表后才能过滤。

联合索引优化

将status和created_at合并为联合索引:

-- 最左前缀原则:等值条件在前,范围条件在后
CREATE INDEX idx_status_created ON orders(status, created_at);

现在EXPLAIN结果显示type=range,key=idx_status_created,rows大幅减少。

覆盖索引消除回表

如果查询只需要order_id、amount、status、created_at四列,可以把它们全部放进索引:

CREATE INDEX idx_cover ON orders(status, created_at, order_id, amount);

EXPLAIN的Extra列出现”Using index”,表示直接从索引树读取数据,不需要回表。覆盖索引在大数据量下性能提升可达5-10倍,代价是索引体积增大。

索引设计三原则:
1. 联合索引遵循最左前缀,等值条件在前,范围条件在后
2. 高选择性列放前面(区分度高的列先过滤掉更多行)
3. 尽量用覆盖索引消除回表开销

线上索引变更安全操作

大表加索引会锁表,影响线上服务。MySQL 5.6+的Online DDL允许在加索引期间继续DML操作:

-- Online DDL(推荐)
ALTER TABLE orders ADD INDEX idx_status_created(status, created_at),
  ALGORITHM=INPLACE, LOCK=NONE;

-- 查看DDL进度
SHOW PROCESSLIST;
-- 或查看progress
SELECT * FROM performance_schema.events_stages_current
WHERE EVENT_NAME LIKE '%alter%';

如果Online DDL执行时间过长(超大表),使用pt-online-schema-change:

pt-online-schema-change \
  --alter "ADD INDEX idx_status_created(status, created_at)" \
  --host=127.0.0.1 --port=3306 --user=admin --password=xxx \
  D=production,t=orders \
  --chunk-size=5000 --max-load=Threads_running=100 \
  --critical-load=Threads_running=200 \
  --execute

pt-osc通过创建影子表+增量同步+原子切换的方式,在不停机情况下完成索引变更。

SQL改写优化实战

避免索引失效的常见写法

-- 错误:索引列使用函数
WHERE DATE(created_at) = '2026-07-30'
-- 正确:范围查询
WHERE created_at >= '2026-07-30' AND created_at < '2026-07-31'

-- 错误:隐式类型转换
WHERE user_id = '123'   -- user_id是int,字符串导致索引失效
-- 正确
WHERE user_id = 123

-- 错误:OR导致索引合并
WHERE status = 'PAID' OR status = 'SHIPPED'
-- 正确:IN
WHERE status IN ('PAID', 'SHIPPED')

-- 错误:LIKE前缀通配符
WHERE name LIKE '%keyword%'
-- 正确:前缀匹配可走索引
WHERE name LIKE 'keyword%'

分页优化:避免深分页

-- 慢:OFFSET 100000 需要扫描前100000行再丢弃
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;

-- 快:游标分页(推荐)
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 20;

-- 次选:延迟关联
SELECT o.* FROM orders o
INNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000, 20) t
ON o.id = t.id;

延迟关联的原理:子查询只需要走索引列,不需要回表,速度快;外层查询通过主键精确回表,只取20行。

慢查询诊断到索引优化是一个闭环:pt-query-digest定位问题SQL、EXPLAIN分析执行路径、设计最优索引、Online DDL安全上线、监控验证效果。持续迭代这个闭环,数据库性能才能稳定可控。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-man-cha-xun-zhen-duan-yu-suo-yin-you-hua-cong/

(0)
小编小编
上一篇 12小时前
下一篇 12小时前

相关推荐