MySQL 8.0慢SQL诊断与索引优化实战指南

线上MySQL出现慢查询是运维和开发每天都会面对的问题。一条慢SQL可能拖垮整个数据库实例,影响所有依赖该实例的业务。诊断和优化慢SQL不是玄学,而是一套有章可循的方法论:定位→分析→优化→验证。本文完整演示MySQL 8.0慢SQL的诊断流程和索引优化的实战手法。

慢查询日志配置与自动采集

慢查询日志是诊断的入口。MySQL 8.0默认未开启,需要手动配置:

-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;        -- 超过1秒记录
SET GLOBAL log_queries_not_using_indexes = ON;  -- 未用索引也记录
SET GLOBAL min_examined_row_limit = 100;         -- 扫描行<100不记录

-- 日志输出到文件(性能更好)或表(方便查询)
SET GLOBAL log_output = 'FILE,TABLE';

-- 查询慢日志表
SELECT * FROM mysql.slow_log
WHERE start_time > NOW() - INTERVAL 1 HOUR
ORDER BY query_time DESC
LIMIT 10;

生产环境推荐用Percona的pt-query-digest工具定期分析慢日志,生成Top N报告:

# 每小时分析一次慢日志
pt-query-digest /var/lib/mysql/mysql-slow.log \
  --since "1h" \
  --limit "95%" \
  --order-by Query_time:sum \
  --output slowlog-report.txt

# 报告中重点关注:
# 1. Rank - 按总耗时排序的查询排名
# 2. Response time - 该查询占总慢查询时间的百分比
# 3. Rows examine/sent - 扫描行数与返回行数的比值(越大越低效)
# 4. Query template - 参数化后的SQL模板,用于定位同类查询

EXPLAIN执行计划深度解读

EXPLAIN是分析单条SQL执行路径的标准工具。MySQL 8.0的EXPLAIN FORMAT=TREE和EXPLAIN ANALYZE提供更直观的输出:

-- 传统EXPLAIN
EXPLAIN SELECT o.order_id, o.amount, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.create_time > '2026-07-01'
  AND o.status = 'PAID'
ORDER BY o.amount DESC
LIMIT 20;

-- MySQL 8.0: 带实际执行耗时
EXPLAIN ANALYZE
SELECT o.order_id, o.amount, c.customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.create_time > '2026-07-01'
  AND o.status = 'PAID'
ORDER BY o.amount DESC
LIMIT 20;

EXPLAIN输出的关键字段判读标准:

EXPLAIN字段诊断清单 = {
    "type": {
        "system/const": "最优,单行匹配",
        "eq_ref": "优,唯一索引关联",
        "ref": "良,非唯一索引匹配",
        "range": "可接受,索引范围扫描",
        "index": "差,全索引扫描",
        "ALL": "最差,全表扫描,必须优化",
    },
    "key": "实际使用的索引,NULL表示未用索引",
    "rows": "预估扫描行数,越少越好",
    "filtered": "过滤比例,100%最佳,低于10%需关注",
    "Extra": {
        "Using index": "好,覆盖索引,无需回表",
        "Using where": "需在Server层过滤",
        "Using temporary": "差,使用了临时表",
        "Using filesort": "差,额外排序",
        "Using index condition": "好,ICP下推优化生效",
    }
}

索引优化实战:从单列到联合索引

索引优化的核心原则:高选择性列在前,查询条件列全覆盖,避免回表查询。

场景一:联合索引的列顺序

-- 查询: WHERE status = 'PAID' AND create_time > '2026-07-01'
-- orders表有1000万行,status='PAID'约300万行

-- 错误索引:create_time在前
ALTER TABLE orders ADD INDEX idx_create_time_status (create_time, status);
-- 问题:先按create_time范围扫描,再在结果中过滤status
-- MySQL只能用到create_time的前缀,status走不了索引

-- 正确索引:等值条件在前,范围条件在后
ALTER TABLE orders ADD INDEX idx_status_create_time (status, create_time);
-- 优化:先精确定位status='PAID',再在B+树叶子节点顺序扫描create_time
-- 索引扫描行数从300万降到30万

场景二:覆盖索引消除回表

-- 查询只需要order_id和amount两个字段
SELECT order_id, amount FROM orders
WHERE status = 'PAID' AND create_time > '2026-07-01';

-- 覆盖索引:把SELECT的列也包含进索引
ALTER TABLE orders ADD INDEX idx_cover (status, create_time, order_id, amount);

-- EXPLAIN Extra显示 "Using index" 表示完全走索引,无需回表
-- 性能提升:从磁盘随机IO变成索引顺序读取,QPS提升3-5倍

场景三:ORDER BY优化

-- 排序查询
SELECT order_id, amount FROM orders
WHERE status = 'PAID'
ORDER BY amount DESC
LIMIT 20;

-- 问题:status索引过滤后,amount无序,需要filesort
-- 方案1:如果status值域较小,用(status, amount)联合索引
ALTER TABLE orders ADD INDEX idx_status_amount (status, amount);
-- 索引按status分组、amount有序,可直接取前20条,无需排序

-- 方案2:MySQL 8.0降序索引
ALTER TABLE orders ADD INDEX idx_status_amount_desc (status, amount DESC);

MySQL 8.0新特性:不可见索引与索引跳扫

不可见索引(Invisible Index)是MySQL 8.0的运维利器。删除索引前先设为不可见,观察一周无影响再真正删除,避免误删导致性能事故:

-- 将索引设为不可见(优化器不再使用,但索引数据仍在维护)
ALTER TABLE orders ALTER INDEX idx_old INVISIBLE;

-- 观察期(1-2周),如果无慢查询飙升,则安全删除
ALTER TABLE orders DROP INDEX idx_old;

-- 如果出现问题,秒级恢复
ALTER TABLE orders ALTER INDEX idx_old VISIBLE;

索引跳扫(Skip Scan)在MySQL 8.0.13+版本支持。当联合索引第一列区分度低、查询未指定第一列条件时,优化器会按第一列的不同值分别执行范围扫描:

-- 联合索引 (region, create_time)
-- region只有5个值:EAST/WEST/NORTH/SOUTH/CENTER
-- 查询未指定region
SELECT * FROM orders WHERE create_time > '2026-07-01';

-- MySQL 8.0自动执行:
-- region=EAST AND create_time>'2026-07-01'
-- UNION region=WEST AND create_time>'2026-07-01'
-- ... 共5次内部范围扫描
-- Extra显示 "Using index for skip scan"

线上慢SQL治理流程

单条SQL的优化是点,系统化的治理是面。完整的慢SQL治理流程:

慢SQL治理SOP:

1. 采集: 慢日志 + pt-query-digest -> Top20慢查询清单
2. 分类:
   - 全表扫描型 -> 加索引 / 改写SQL
   - 索引失效型 -> 检查隐式转换、函数调用、OR条件
   - 锁等待型 -> 检查事务粒度、隔离级别
   - 临时表/filesort型 -> 优化GROUP BY/ORDER BY索引
3. 优化: 先用不可见索引验证,再正式上线
4. 监控: Prometheus + Grafana看板跟踪QPS/RT/慢查询数趋势
5. 回归: 每周Review慢日志,防止劣化

常见索引失效场景速查:
- WHERE YEAR(create_time) = 2026  -> 函数破坏索引,改范围查询
- WHERE varchar_col = 123          -> 隐式类型转换
- WHERE col LIKE '%keyword'       -> 前缀通配符
- WHERE a = 1 OR b = 2            -> OR条件拆分或用UNION
- WHERE col IN (SELECT ...)       -> 子查询改JOIN

慢SQL治理是持续工程,不是一次性任务。建立采集→分析→优化的闭环,配合自动化告警和定期Review,才能把数据库性能稳定在合理水位。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql80-man-sql-zhen-duan-yu-suo-yin-you-hua-shi-zhan-zhi/

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

相关推荐