从 30 秒到 3 秒:MySQL 慢查询定位与索引调优实战17认证网

正规官方授权
更专业・更权威

从 30 秒到 3 秒:MySQL 慢查询定位与索引调优实战

一、概述

1.1 背景介绍

上周四下午三点,业务方反馈后台管理系统加载特别慢,一个列表页面要等 30 多秒。登上数据库一看,一条查询跑了 28 秒。这种场景在电商大促、月底结算等业务高峰期尤其常见——平时跑得好好的 SQL,数据量一上来就原形毕露。

MySQL 慢查询优化是 DBA 和后端开发的必修课。但现实是,很多团队的处理方式就是”加索引碰运气”,加一个不行再加一个,最后索引加了一堆,查询还是慢。

这篇文章分享一套系统化的慢查询排查和优化方法,从定位问题 SQL、分析执行计划、设计合理索引,到验证优化效果,形成闭环。

1.2 技术特点

  • 数据驱动:基于 EXPLAIN、执行计划、实际耗时来决策,不靠猜
  • 最小改动:优先调整 SQL 和索引,尽量不改业务逻辑
  • 可验证:优化前后有对比数据,效果可量化
  • 可复制:方法论清晰,团队成员都能学会

1.3 适用场景

  • 场景一:单条 SQL 执行时间过长,需要优化
  • 场景二:数据库整体 QPS 下降,需要找出问题 SQL
  • 场景三:新功能上线前,需要对 SQL 进行性能评审
  • 场景四:数据量增长后,原有 SQL 性能恶化

1.4 环境要求

组件
版本要求
说明
MySQL
5.7+ / 8.0+
建议使用 8.0,EXPLAIN 信息更丰富
操作系统
Linux
生产环境标配
工具
pt-query-digest
Percona Toolkit,慢查询分析神器
权限
SELECT, PROCESS
需要查看执行计划和进程列表的权限

二、详细步骤

2.1 准备工作

2.1.1 开启慢查询日志

慢查询日志是排查的数据源,必须先确保开启。

-- 查看慢查询日志配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

-- 动态开启慢查询日志(临时生效)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过 1 秒的查询记录下来
SET GLOBAL log_queries_not_using_indexes = 'ON';  -- 未使用索引的查询也记录

-- 持久化配置(修改 my.cnf)
-- [mysqld]
-- slow_query_log = 1
-- slow_query_log_file = /var/log/mysql/slow.log
-- long_query_time = 1
-- log_queries_not_using_indexes = 1

说明:生产环境建议 long_query_time 设为 1 秒或 2 秒。设太小会产生大量日志,设太大会漏掉一些”不算特别慢但仍有优化空间”的查询。

2.1.2 安装分析工具

# 安装 Percona Toolkit
# CentOS/RHEL
sudo yum install -y percona-toolkit

# Ubuntu/Debian
sudo apt install -y percona-toolkit

# 验证安装
pt-query-digest --version

2.2 第一阶段:定位问题 SQL

2.2.1 慢查询日志分析

# 使用 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

# 按响应时间排序,取 TOP 10
pt-query-digest --limit 10 /var/log/mysql/slow.log

pt-query-digest 输出解读

# Profile
# Rank Query ID                           Response time   Calls  R/Call
# ==== ================================== =============== ====== ======
#    1 0x4E2B7F6A8D9C1E3F5A7B9D2C4E6F8A1B  1580.2904 48.7%    234  6.7534
#    2 0x1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D   892.1156 27.5%   1893  0.4714
#    3 0x7F8E9D0C1B2A3F4E5D6C7B8A9F0E1D2C   412.3892 12.7%     89  4.6336

重点关注:

  • Response time 占比高:说明这条 SQL 是主要耗时来源
  • Calls 多 + R/Call 高:高频慢查询,优化收益最大
  • R/Call 特别高:单次执行就很慢,可能缺索引或数据量大

2.2.2 实时查看正在执行的慢查询

-- 查看当前正在执行的查询
SHOW FULL PROCESSLIST;

-- 只看执行时间超过 5 秒的
SELECT * FROM information_schema.processlist
WHERE command != 'Sleep' AND time > 5
ORDER BY time DESC;

-- MySQL 8.0+ 可以用 performance_schema
SELECT
    thread_id,
    sql_text,
    timer_wait/1000000000000 AS exec_time_sec,
    rows_examined,
    rows_sent
FROM performance_schema.events_statements_current
WHERE timer_wait > 5000000000000  -- 5秒
ORDER BY timer_wait DESC;

2.2.3 从监控系统定位

如果有 Prometheus + Grafana 监控,可以通过以下指标定位:

-- 查看各类语句的执行统计
SELECT
    DIGEST_TEXT,
    COUNT_STAR AS exec_count,
    SUM_TIMER_WAIT/1000000000000 AS total_time_sec,
    AVG_TIMER_WAIT/1000000000000 AS avg_time_sec,
    SUM_ROWS_EXAMINED AS rows_examined,
    SUM_ROWS_SENT AS rows_sent
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

2.3 第二阶段:分析执行计划

找到问题 SQL 后,下一步是理解它为什么慢。

2.3.1 EXPLAIN 基础用法

-- 基础用法
EXPLAIN SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid';

-- 显示更多信息(MySQL 8.0+)
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid';

-- 实际执行并显示统计信息(MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid';

2.3.2 EXPLAIN 输出解读

+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | orders | ALL  | NULL          | NULL | NULL    | NULL | 982341 | Using where |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+

关键字段解读

字段
好的情况
差的情况
说明
type
eq_ref, ref, range
ALL, index
ALL 是全表扫描,要避免
key
有值
NULL
NULL 说明没用到索引
rows
数值小
数值大
预估扫描行数,越小越好
Extra
Using index
Using filesort, Using temporary
filesort 和 temporary 说明有额外开销

type 从好到差排序

system > const > eq_ref > ref > range > index > ALL

2.3.3 典型问题模式识别

模式一:全表扫描(type=ALL)

-- 问题 SQL
EXPLAIN SELECT * FROM orders WHERE YEAR(create_time) = 2024;

-- 输出
-- type: ALL, rows: 1000000, Extra: Using where
-- 问题:对 create_time 使用函数,索引失效

-- 优化后
EXPLAIN SELECT * FROM orders
WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
-- type: range, rows: 50000

模式二:索引失效

-- 问题 SQL(隐式类型转换)
EXPLAIN SELECT * FROM users WHERE phone = 13800138000;
-- phone 是 varchar,传入数字会导致类型转换,索引失效

-- 优化后
EXPLAIN SELECT * FROM users WHERE phone = '13800138000';

模式三:回表过多

-- 问题 SQL
EXPLAIN SELECT * FROM orders WHERE user_id = 12345;
-- type: ref, key: idx_user_id, rows: 500
-- 问题:虽然走了索引,但需要回表 500 次取其他字段

-- 如果只需要部分字段,可以用覆盖索引
EXPLAIN SELECT order_id, amount, status FROM orders WHERE user_id = 12345;
-- 如果有索引 idx_user_id_amount_status(user_id, amount, status)
-- Extra: Using index(覆盖索引,不需要回表)

2.4 第三阶段:索引设计与优化

2.4.1 索引设计原则

原则一:最左前缀原则

-- 有联合索引 idx_a_b_c(a, b, c)
-- 以下查询可以用到索引:
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3
WHERE a = 1 AND c = 3  -- 只能用到 a

-- 以下查询用不到这个索引:
WHERE b = 2
WHERE c = 3
WHERE b = 2 AND c = 3

原则二:区分度高的字段放前面

-- 查看字段区分度
SELECT
    COUNT(DISTINCT status) / COUNT(*) AS status_selectivity,
    COUNT(DISTINCT user_id) / COUNT(*) AS user_id_selectivity,
    COUNT(DISTINCT create_date) / COUNT(*) AS create_date_selectivity
FROM orders;

-- 假设结果:
-- status_selectivity: 0.00001 (只有几个状态值)
-- user_id_selectivity: 0.1 (用户很多)
-- create_date_selectivity: 0.003 (日期分布)

-- 那么索引顺序应该是:(user_id, create_date, status)
-- 而不是:(status, user_id, create_date)

原则三:覆盖索引优化

-- 原查询
SELECT user_id, order_id, amount FROM orders
WHERE user_id = 12345 AND status = 'paid'
ORDER BY create_time DESC LIMIT 10;

-- 设计覆盖索引(包含查询和排序需要的所有字段)
ALTER TABLE orders ADD INDEX idx_cover (user_id, status, create_time, order_id, amount);

-- 执行计划显示 Using index,不需要回表

2.4.2 索引创建实操

-- 创建索引前先检查表结构和现有索引
SHOW CREATE TABLE orders\G
SHOW INDEX FROM orders;

-- 创建索引(生产环境建议用 pt-online-schema-change)
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);

-- 使用 pt-osc 在线加索引(不锁表)
pt-online-schema-change \
    --alter "ADD INDEX idx_user_status (user_id, status)" \
    --execute \
    --user=root \
    --password=xxx \
    D=mydb,t=orders

-- 删除无用索引
ALTER TABLE orders DROP INDEX idx_old_unused;

2.4.3 索引优化验证

-- 优化前后对比
-- 1. 记录优化前的执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid'\G

-- 2. 记录优化前的执行时间
SET profiling = 1;
SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid';
SHOW PROFILES;

-- 3. 添加索引
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);

-- 4. 再次查看执行计划和执行时间
EXPLAIN SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid'\G
SELECT * FROM orders WHERE user_id = 12345 AND status = 'paid';
SHOW PROFILES;

三、示例代码和配置

3.1 完整优化案例

3.1.1 案例:订单列表查询优化

原始 SQL(耗时 28 秒)

SELECT
    o.order_id,
    o.order_no,
    o.amount,
    o.status,
    o.create_time,
    u.username,
    u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.create_time >= '2024-01-01'
    AND o.status IN ('paid', 'shipped')
    AND u.username LIKE '张%'
ORDER BY o.create_time DESC
LIMIT 0, 20;

执行计划分析

EXPLAIN SELECT ...

-- 输出:
-- orders 表: type=ALL, rows=982341
-- users 表: type=eq_ref, rows=1
-- Extra: Using where; Using temporary; Using filesort

-- 问题分析:
-- 1. orders 表全表扫描
-- 2. 使用了临时表和文件排序

优化步骤

-- 步骤 1:为 orders 表添加复合索引
ALTER TABLE orders ADD INDEX idx_status_time (status, create_time);

-- 步骤 2:调整 SQL,避免 LEFT JOIN 导致索引失效
SELECT
    o.order_id,
    o.order_no,
    o.amount,
    o.status,
    o.create_time,
    u.username,
    u.phone
FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE o.status IN ('paid', 'shipped')
    AND o.create_time >= '2024-01-01'
    AND u.username LIKE '张%'
ORDER BY o.create_time DESC
LIMIT 0, 20;

-- 步骤 3:如果 username 过滤条件区分度高,考虑先查 users
SELECT
    o.order_id,
    o.order_no,
    o.amount,
    o.status,
    o.create_time,
    u.username,
    u.phone
FROM users u
INNER JOIN orders o ON o.user_id = u.id
WHERE u.username LIKE '张%'
    AND o.status IN ('paid', 'shipped')
    AND o.create_time >= '2024-01-01'
ORDER BY o.create_time DESC
LIMIT 0, 20;

优化后执行计划

-- orders 表: type=range, key=idx_status_time, rows=15234
-- users 表: type=eq_ref, rows=1
-- Extra: Using index condition

效果对比

  • 优化前:28.3 秒
  • 优化后:0.18 秒

3.1.2 索引健康检查脚本

-- 查找未使用的索引
SELECT
    t.table_schema,
    t.table_name,
    t.index_name,
    t.column_name,
    s.rows_read
FROM information_schema.statistics t
LEFT JOIN sys.schema_index_statistics s
    ON t.table_schema = s.table_schema
    AND t.table_name = s.table_name
    AND t.index_name = s.index_name
WHERE t.table_schema NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
    AND t.index_name != 'PRIMARY'
    AND (s.rows_read IS NULL OR s.rows_read = 0)
ORDER BY t.table_schema, t.table_name, t.index_name;

-- 查找重复索引
SELECT
    table_schema,
    table_name,
    GROUP_CONCAT(index_name) AS duplicate_indexes,
    index_columns
FROM (
    SELECT
        table_schema,
        table_name,
        index_name,
        GROUP_CONCAT(column_name ORDER BY seq_in_index) AS index_columns
    FROM information_schema.statistics
    WHERE table_schema NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
    GROUP BY table_schema, table_name, index_name
) t
GROUP BY table_schema, table_name, index_columns
HAVING COUNT(*) > 1;

3.2 慢查询自动分析脚本

#!/bin/bash
# 文件名:analyze_slow_queries.sh
# 功能:定期分析慢查询日志,生成报告

set -e

# 配置
MYSQL_SLOW_LOG="/var/log/mysql/slow.log"
REPORT_DIR="/var/log/mysql/reports"
MYSQL_USER="monitor"
MYSQL_PASS="your_password"
MYSQL_HOST="localhost"

# 创建报告目录
mkdir -p $REPORT_DIR

# 生成报告文件名
REPORT_FILE="$REPORT_DIR/slow_query_$(date +%Y%m%d_%H%M%S).txt"

echo "=== MySQL 慢查询分析报告 ===" > $REPORT_FILE
echo "生成时间: $(date '+%Y-%m-%d %H:%M:%S')" >> $REPORT_FILE
echo "" >> $REPORT_FILE

# 1. 使用 pt-query-digest 分析
echo "【1. TOP 10 慢查询】" >> $REPORT_FILE
pt-query-digest --limit 10 $MYSQL_SLOW_LOG >> $REPORT_FILE 2>/dev/null

# 2. 统计信息
echo "" >> $REPORT_FILE
echo "【2. 统计汇总】" >> $REPORT_FILE
echo "慢查询总数: $(grep -c "^# Time:" $MYSQL_SLOW_LOG 2>/dev/null || echo 0)" >> $REPORT_FILE
echo "日志文件大小: $(du -h $MYSQL_SLOW_LOG | cut -f1)" >> $REPORT_FILE

# 3. 当前数据库状态
echo "" >> $REPORT_FILE
echo "【3. 当前数据库状态】" >> $REPORT_FILE
mysql -u$MYSQL_USER -p$MYSQL_PASS -h$MYSQL_HOST -e "
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW GLOBAL STATUS LIKE 'Questions';
SHOW GLOBAL STATUS LIKE 'Threads_running';
" >> $REPORT_FILE 2>/dev/null

# 4. 发送报告(可选)
# mail -s "MySQL 慢查询日报" dba@example.com < $REPORT_FILE

echo "报告已生成: $REPORT_FILE"

# 5. 轮转日志(可选)
# mv $MYSQL_SLOW_LOG ${MYSQL_SLOW_LOG}.$(date +%Y%m%d)
# mysqladmin -u$MYSQL_USER -p$MYSQL_PASS flush-logs

四、最佳实践和注意事项

4.1 最佳实践

4.1.1 SQL 编写规范

  • **避免 SELECT ***:只查询需要的字段,减少数据传输和内存消耗

    -- 差
    SELECT * FROM orders WHERE user_id = 12345;
    
    -- 好
    SELECT order_id, amount, status, create_time
    FROM orders WHERE user_id = 12345;
    
  • 避免在索引列上使用函数

    -- 差(索引失效)
    SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01';
    
    -- 好(可以使用索引)
    SELECT * FROM orders
    WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';
    
  • 使用 LIMIT 限制返回行数

    -- 即使需要全量数据,也建议分批获取
    SELECT * FROM large_table WHERE conditions LIMIT 0, 10000;
    SELECT * FROM large_table WHERE conditions LIMIT 10000, 10000;
    

4.1.2 索引管理规范

  • 索引不是越多越好:每个索引都会增加写入开销,一般单表索引控制在 5-6 个以内
  • 定期清理无用索引

    -- 查找 30 天内未使用的索引
    SELECT * FROM sys.schema_unused_indexes
    WHERE object_schema NOT IN ('mysql', 'sys');
    
  • 大表加索引要用在线工具

    # pt-online-schema-change 在线加索引
    pt-online-schema-change \
        --alter "ADD INDEX idx_xxx (col1, col2)" \
        --execute \
        --max-load Threads_running=50 \
        --critical-load Threads_running=100 \
        D=mydb,t=mytable
    

4.1.3 查询优化检查清单

在优化 SQL 之前,按这个清单逐项检查:

  1. 是否有必要查询这么多数据?能否加 LIMIT?
  2. 是否可以用覆盖索引避免回表?
  3. WHERE 条件是否命中索引?有没有索引失效的写法?
  4. JOIN 的顺序是否合理?小表驱动大表了吗?
  5. ORDER BY 和 GROUP BY 能否利用索引?
  6. 子查询能否改写成 JOIN?
  7. 是否有 OR 条件导致索引失效?能否改写成 UNION?

4.2 注意事项

4.2.1 生产环境操作警告

警告:以下操作可能影响业务,需谨慎!

  • 不要在高峰期直接 ALTER TABLE 加索引,会锁表
  • 不要随意 KILL 长时间运行的查询,可能导致数据不一致
  • 开启 profiling 会有额外开销,分析完记得关闭
  • EXPLAIN ANALYZE 会实际执行 SQL,对大表查询要小心

4.2.2 常见错误

错误现象
原因分析
解决方案
加了索引但还是全表扫描
可能存在隐式类型转换、函数调用、OR 条件等
检查 SQL 写法是否导致索引失效
优化后反而更慢了
优化器可能选错了执行计划
使用 FORCE INDEX 或重新分析表统计信息
执行计划显示用了索引但还是慢
可能是回表次数太多或索引区分度低
考虑覆盖索引或调整索引字段顺序
pt-osc 执行卡住
可能有大事务阻塞或从库延迟过大
检查是否有长事务,调整 chunk-size

4.2.3 常见索引失效场景

-- 1. 对索引字段使用函数
WHERE YEAR(create_time) = 2024  -- 失效
WHERE create_time >= '2024-01-01'  -- 有效

-- 2. 隐式类型转换
WHERE phone = 13800138000  -- phone 是 varchar,失效
WHERE phone = '13800138000'  -- 有效

-- 3. LIKE 左模糊
WHERE name LIKE '%张'  -- 失效
WHERE name LIKE '张%'  -- 有效

-- 4. OR 条件(除非所有条件都有索引)
WHERE user_id = 1 OR name = 'xxx'  -- 如果 name 没索引则失效
WHERE user_id = 1 UNION SELECT ... WHERE name = 'xxx'  -- 改写后都能用索引

-- 5. 不等于条件
WHERE status != 'deleted'  -- 可能不走索引
WHERE status IN ('active', 'pending')  -- 明确列出有效值

-- 6. IS NULL / IS NOT NULL(取决于优化器判断)
WHERE deleted_at IS NULL  -- 可能不走索引

-- 7. 联合索引不满足最左前缀
-- 索引 (a, b, c)
WHERE b = 1 AND c = 2  -- 失效,必须从 a 开始

五、故障排查和监控

5.1 常见问题排查

5.1.1 查询突然变慢

之前一直很快的查询突然变慢,可能原因:

-- 1. 检查表的统计信息是否过期
SHOW TABLE STATUS LIKE 'orders'\G
-- 关注 Rows 和实际行数是否差异大

-- 重新分析表统计信息
ANALYZE TABLE orders;

-- 2. 检查是否有锁等待
SELECT * FROM information_schema.innodb_lock_waits;
SELECT * FROM sys.innodb_lock_waits;

-- 3. 检查缓冲池命中率
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
-- Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests 应该很低

-- 4. 检查是否有大事务
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started;

5.1.2 死锁排查

-- 查看最近的死锁信息
SHOW ENGINE INNODB STATUS\G
-- 找 LATEST DETECTED DEADLOCK 部分

-- 查看当前锁情况
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;

-- 如果需要 KILL 阻塞的事务
-- 先确认事务 ID
SELECT * FROM information_schema.innodb_trx;
-- 然后 KILL 对应的线程
KILL <thread_id>;

5.1.3 连接数耗尽

-- 查看连接数配置和使用情况
SHOW VARIABLES LIKE 'max_connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';

-- 查看各用户/主机的连接数
SELECT user, host, COUNT(*) AS conn_count
FROM information_schema.processlist
GROUP BY user, host
ORDER BY conn_count DESC;

-- 查看连接在执行什么
SELECT * FROM information_schema.processlist
WHERE command != 'Sleep'
ORDER BY time DESC;

5.2 监控配置

5.2.1 Prometheus + Grafana 监控

# mysqld_exporter 采集配置
# prometheus.yml
scrape_configs:
  - job_name: 'mysql'
    static_configs:
      - targets: ['mysql-server:9104']
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

5.2.2 关键监控指标

指标
正常范围
告警阈值
说明
慢查询数/分钟
< 10
> 50
超过说明有性能问题
QPS
因业务而异
波动超过 50%
异常波动需关注
连接数使用率
< 70%
> 85%
接近上限需扩容
缓冲池命中率
> 99%
< 95%
过低说明内存不足
锁等待时间
< 1s
> 5s
过长说明有锁争用
主从延迟
< 1s
> 10s
过大影响读写分离

5.2.3 告警规则示例

# Prometheus 告警规则
groups:
  - name: mysql_alerts
    rules:
      - alert: MySQLSlowQueries
        expr: rate(mysql_global_status_slow_queries[5m]) > 10
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MySQL 慢查询过多"
          description: "实例 {{ $labels.instance }} 每分钟慢查询 {{ $value }} 次"

      - alert: MySQLHighConnections
        expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.85
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "MySQL 连接数过高"
          description: "实例 {{ $labels.instance }} 连接数使用率 {{ $value | humanizePercentage }}"

      - alert: MySQLReplicationLag
        expr: mysql_slave_status_seconds_behind_master > 30
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MySQL 主从延迟过大"
          description: "从库 {{ $labels.instance }} 延迟 {{ $value }} 秒"

六、总结

6.1 技术要点回顾

  • 定位先行:先用 pt-query-digest 找出 TOP N 慢查询,不要盲目优化
  • 理解执行计划:EXPLAIN 是优化的基础,要能看懂每个字段的含义
  • 索引设计有原则:最左前缀、区分度、覆盖索引,不是随便加
  • 验证优化效果:优化前后要有数据对比,不能凭感觉

6.2 进阶学习方向

  1. MySQL 内部原理
    • 学习资源:《高性能 MySQL》第四版、《MySQL 技术内幕:InnoDB 存储引擎》
    • 实践建议:理解 B+ 树索引结构、MVCC、锁机制
  2. 查询优化器
    • 学习资源:MySQL 官方文档 – Optimization
    • 实践建议:学习使用 optimizer_trace 分析优化器决策过程
  3. 分布式数据库
    • 学习资源:TiDB、CockroachDB 等 NewSQL 数据库
    • 实践建议:了解分布式场景下的查询优化差异

6.3 参考资料

  • MySQL 官方优化文档 – 权威参考
  • Percona Toolkit 文档 – pt-query-digest 等工具使用指南
  • Use The Index, Luke – SQL 索引原理讲解
  • MySQL Performance Blog – Percona 技术博客

附录

A. 命令速查表

-- 查看慢查询配置
SHOW VARIABLES LIKE 'slow_query%';

-- 查看执行计划
EXPLAIN SELECT ...;
EXPLAIN FORMAT=JSON SELECT ...;
EXPLAIN ANALYZE SELECT ...;

-- 查看表索引
SHOW INDEX FROM table_name;

-- 查看表结构
SHOW CREATE TABLE table_name\G

-- 分析表统计信息
ANALYZE TABLE table_name;

-- 查看当前执行的查询
SHOW PROCESSLIST;

-- 查看 InnoDB 状态
SHOW ENGINE INNODB STATUS\G

-- 开启 profiling
SET profiling = 1;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

B. EXPLAIN type 详解

type
说明
性能
system
表只有一行
最佳
const
通过主键或唯一索引查找,最多一行
极佳
eq_ref
JOIN 时对每个来自前表的行,在本表中只读一行
很好
ref
使用非唯一索引查找,返回匹配的所有行
range
索引范围扫描
一般
index
全索引扫描(比全表快,因为索引通常比数据小)
ALL
全表扫描
最差

C. 术语表

术语
英文
解释
回表
Table Lookup
通过二级索引找到主键后,再到主键索引中取完整记录
覆盖索引
Covering Index
索引包含了查询需要的所有字段,无需回表
最左前缀
Leftmost Prefix
联合索引查询时必须从最左边的字段开始匹配
索引下推
Index Condition Pushdown (ICP)
在索引层面就过滤掉不符合条件的记录,减少回表次数
文件排序
Filesort
无法利用索引排序,需要额外的排序操作
临时表
Using temporary
需要创建临时表来存储中间结果

想了解更多干货,可通过下方扫码关注

可扫码添加上智启元官方客服微信👇

未经允许不得转载:17认证网 » 从 30 秒到 3 秒:MySQL 慢查询定位与索引调优实战
分享到:0

评论已关闭。

400-663-6632
咨询老师
咨询老师
咨询老师