【数据库】MySQL 实战精通系列 · 第8篇:慢查询治理与性能优化实战

MySQL 实战精通系列 · 第8篇:慢查询治理与性能优化实战

本篇目标:线上 MySQL 变慢了,能系统化定位问题、给出优化方案、验证效果。


文章目录

  • [MySQL 实战精通系列 · 第8篇:慢查询治理与性能优化实战](#MySQL 实战精通系列 · 第8篇:慢查询治理与性能优化实战)
    • 一、性能优化分层思路
      • [1.1 优化的五个层次](#1.1 优化的五个层次)
      • [1.2 一张图看懂排查顺序](#1.2 一张图看懂排查顺序)
    • [二、慢查询日志:找到慢 SQL](#二、慢查询日志:找到慢 SQL)
      • [2.1 开启慢查询日志](#2.1 开启慢查询日志)
      • [2.2 查看慢查询日志](#2.2 查看慢查询日志)
      • [2.3 用 performance_schema 分析](#2.3 用 performance_schema 分析)
    • 三、关键性能指标
      • [3.1 必须监控的指标](#3.1 必须监控的指标)
      • [3.2 查看当前状态](#3.2 查看当前状态)
      • [3.3 计算 QPS / TPS](#3.3 计算 QPS / TPS)
    • [四、SQL 优化实战](#四、SQL 优化实战)
      • [4.1 优化流程](#4.1 优化流程)
      • [4.2 案例1:深分页优化](#4.2 案例1:深分页优化)
      • [4.3 案例2:JOIN 优化](#4.3 案例2:JOIN 优化)
      • [4.4 案例3:子查询优化](#4.4 案例3:子查询优化)
      • [4.5 案例4:COUNT 优化](#4.5 案例4:COUNT 优化)
      • [4.6 案例5:ORDER BY 优化](#4.6 案例5:ORDER BY 优化)
    • [五、大表 DDL 在线变更](#五、大表 DDL 在线变更)
      • [5.1 为什么大表 DDL 危险?](#5.1 为什么大表 DDL 危险?)
      • [5.2 Online DDL](#5.2 Online DDL)
      • [5.3 gh-ost 实战](#5.3 gh-ost 实战)
      • [5.4 pt-online-schema-change](#5.4 pt-online-schema-change)
    • [六、sysbench 压测](#六、sysbench 压测)
      • [6.1 安装 sysbench](#6.1 安装 sysbench)
      • [6.2 准备测试数据](#6.2 准备测试数据)
      • [6.3 运行压测](#6.3 运行压测)
      • [6.4 输出解读](#6.4 输出解读)
      • [6.5 清理](#6.5 清理)
    • 七、参数调优
      • [7.1 关键参数](#7.1 关键参数)
      • [7.2 调优原则](#7.2 调优原则)
    • 八、实战任务
    • 九、本篇小结

一、性能优化分层思路

1.1 优化的五个层次

复制代码
性能优化
│
├── ① SQL 与索引        ← 收益最大,成本最低
├── ② 表结构与数据类型   ← 建模阶段决定
├── ③ 事务与锁          ← 并发问题
├── ④ 连接池与参数       ← 配置调优
└── ⑤ 硬件与架构         ← 最后手段

原则:从上往下查,先查 SQL,再查配置,最后才考虑加机器。

1.2 一张图看懂排查顺序

复制代码
MySQL 变慢
   │
   ↓
① 是不是某条 SQL 慢?
   │  是 → EXPLAIN 分析 → 加/改索引 → 重写 SQL
   │
   ↓ 否
② 是不是锁等待?
   │  是 → 查看 innodb_trx → 找长事务 → 优化事务
   │
   ↓ 否
③ 是不是连接数爆了?
   │  是 → 查看 processlist → 优化连接池
   │
   ↓ 否
④ 是不是磁盘 IO 高?
   │  是 → 查看 Buffer Pool 命中率 → 加内存
   │
   ↓ 否
⑤ 是不是数据量太大?
      是 → 分区 / 分表 / 归档

二、慢查询日志:找到慢 SQL

2.1 开启慢查询日志

sql 复制代码
-- 查看当前配置
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

2.2 查看慢查询日志

bash 复制代码
# 查看日志文件
docker exec mysql8 cat /var/log/mysql/slow.log

# 用 mysqldumpslow 汇总
docker exec mysql8 mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

mysqldumpslow 参数:

参数 含义
-s t 按总时间排序
-s c 按次数排序
-s l 按锁时间排序
-t 10 取前10条

2.3 用 performance_schema 分析

sql 复制代码
-- 开启
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME LIKE '%statements%';

-- 查看 Top 10 慢 SQL
SELECT
    DIGEST_TEXT AS sql_text,
    COUNT_STAR AS exec_count,
    ROUND(SUM_TIMER_WAIT/1000000000, 2) AS total_ms,
    ROUND(AVG_TIMER_WAIT/1000000000, 2) AS avg_ms,
    ROUND(MAX_TIMER_WAIT/1000000000, 2) AS max_ms,
    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 10;

三、关键性能指标

3.1 必须监控的指标

指标 含义 健康值
QPS 每秒查询数 业务决定
TPS 每秒事务数 业务决定
RT 响应时间 < 100ms
连接数 当前连接 < max_connections 80%
Buffer Pool 命中率 内存命中 > 99%
锁等待 等待锁的事务 越少越好
慢查询数 慢 SQL 数量 越少越好
主从延迟 从库落后 < 1s

3.2 查看当前状态

sql 复制代码
-- 当前连接
SHOW PROCESSLIST;

-- 连接数
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';

-- QPS / TPS
SHOW STATUS LIKE 'Queries';
SHOW STATUS LIKE 'Com_commit';
SHOW STATUS LIKE 'Com_rollback';

-- Buffer Pool 命中率
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';

-- 锁等待
SELECT * FROM information_schema.innodb_trx;
SELECT * FROM performance_schema.data_lock_waits;

3.3 计算 QPS / TPS

sql 复制代码
-- QPS = Queries / 秒
-- 两次采样差值

-- 查看
SHOW GLOBAL STATUS LIKE 'Queries';
-- 等 10 秒
SHOW GLOBAL STATUS LIKE 'Queries';
-- QPS = (第二次 - 第一次) / 10

四、SQL 优化实战

4.1 优化流程

复制代码
慢 SQL
   │
   ↓
① EXPLAIN 看执行计划
   │
   ↓
② 判断问题类型
   │
   ├── type = ALL → 没走索引
   ├── key = NULL → 没走索引
   ├── rows 很大 → 扫描行数多
   ├── Extra = Using filesort → 文件排序
   └── Extra = Using temporary → 临时表
   │
   ↓
③ 对应优化
   │
   ├── 加索引
   ├── 改 SQL
   ├── 改表结构
   └── 分页优化

4.2 案例1:深分页优化

sql 复制代码
-- 慢:扫描 100 万行,只返回 20 行
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;

-- 优化1:用主键游标
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;

-- 优化2:延迟关联
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 1000000, 20
) t ON o.id = t.id;

对比:

复制代码
LIMIT 1000000, 20
   │
   ↓ 扫描 1000020 行
   ↓ 丢弃前 1000000 行
   ↓ 返回 20 行

主键游标
   │
   ↓ 直接定位到 id > 1000000
   ↓ 返回 20 行

4.3 案例2:JOIN 优化

sql 复制代码
-- 慢:小表驱动大表,但驱动表没索引
SELECT * FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.phone = '13800000001';

-- 优化:先查小表,再关联
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM users WHERE phone = '13800000001'
) u ON o.user_id = u.id;

JOIN 优化原则:

复制代码
① 小表驱动大表
② 被驱动表的关联字段要有索引
③ 能用 INNER JOIN 就别用 LEFT JOIN
④ 减少 JOIN 的表数量

4.4 案例3:子查询优化

sql 复制代码
-- 慢:相关子查询,每行都执行一次
SELECT * FROM users u
WHERE (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) > 5;

-- 优化:JOIN + GROUP BY
SELECT u.* FROM users u
INNER JOIN (
    SELECT user_id FROM orders GROUP BY user_id HAVING COUNT(*) > 5
) t ON u.id = t.user_id;

4.5 案例4:COUNT 优化

sql 复制代码
-- 慢:COUNT(*)
SELECT COUNT(*) FROM orders WHERE status = 1;

-- 优化1:加索引
CREATE INDEX idx_status ON orders (status);

-- 优化2:用近似值(业务允许时)
SHOW TABLE STATUS LIKE 'orders';  -- 看 Rows 列

-- 优化3:维护计数表
CREATE TABLE order_stats (
    status TINYINT PRIMARY KEY,
    cnt BIGINT NOT NULL DEFAULT 0
);

4.6 案例5:ORDER BY 优化

sql 复制代码
-- 慢:Using filesort
SELECT * FROM orders ORDER BY total_amount DESC LIMIT 10;

-- 优化:加索引
CREATE INDEX idx_total ON orders (total_amount);

-- 联合索引优化
-- 查询:WHERE status = 1 ORDER BY created_at DESC
CREATE INDEX idx_status_created ON orders (status, created_at);

ORDER BY 优化原则:

复制代码
① 排序字段加索引
② 联合索引:等值在前,排序在后
③ 避免 SELECT *,减少排序数据量
④ 排序方向一致(都 ASC 或都 DESC)

五、大表 DDL 在线变更

5.1 为什么大表 DDL 危险?

复制代码
ALTER TABLE orders ADD COLUMN remark VARCHAR(255);

MySQL 5.6 之前:
  ① 创建新表
  ② 复制数据
  ③ 删除旧表
  ④ 重命名
  → 锁表,1000万行可能几小时

MySQL 5.6+:
  Online DDL,大部分操作不锁表
  但仍有风险

5.2 Online DDL

sql 复制代码
-- 加索引(不锁表)
ALTER TABLE orders ADD INDEX idx_status (status), ALGORITHM=INPLACE, LOCK=NONE;
参数 含义
ALGORITHM=INPLACE 原地修改
ALGORITHM=COPY 复制表
LOCK=NONE 不锁表
LOCK=SHARED 共享锁
LOCK=EXCLUSIVE 排他锁

5.3 gh-ost 实战

bash 复制代码
# 安装 gh-ost
# 在线加列
gh-ost \
  --host=127.0.0.1 \
  --port=3306 \
  --user=root \
  --password=root123 \
  --database=shop \
  --table=orders \
  --alter="ADD COLUMN remark VARCHAR(255) DEFAULT ''" \
  --execute

gh-ost 原理:

复制代码
① 创建影子表(_orders_gho)
② 在影子表上执行 DDL
③ 从 binlog 同步增量数据
④ 分批复制存量数据
⑤ 原子重命名

5.4 pt-online-schema-change

bash 复制代码
pt-online-schema-change \
  --host=127.0.0.1 \
  --user=root \
  --password=root123 \
  --alter="ADD COLUMN remark VARCHAR(255)" \
  D=shop,t=orders \
  --execute

六、sysbench 压测

6.1 安装 sysbench

bash 复制代码
# Ubuntu
apt install sysbench

# 或 Docker
docker run --rm -it --network host severalnines/sysbench

6.2 准备测试数据

bash 复制代码
sysbench oltp_read_write \
  --mysql-host=127.0.0.1 \
  --mysql-port=3306 \
  --mysql-user=root \
  --mysql-password=root123 \
  --mysql-db=shop \
  --tables=10 \
  --table-size=100000 \
  prepare

6.3 运行压测

bash 复制代码
sysbench oltp_read_write \
  --mysql-host=127.0.0.1 \
  --mysql-port=3306 \
  --mysql-user=root \
  --mysql-password=root123 \
  --mysql-db=shop \
  --tables=10 \
  --table-size=100000 \
  --threads=16 \
  --time=60 \
  --report-interval=10 \
  run

6.4 输出解读

复制代码
SQL statistics:
    queries performed:
        read:                            1000000
        write:                           300000
        other:                           200000
        total:                           1500000
    transactions:                        100000 (1666.67 per sec.)
    queries:                             1500000 (25000.00 per sec.)
    ignored errors:                      0 (0.00 per sec.)
    reconnects:                          0 (0.00 per sec.)

General statistics:
    total time:                          60.0000s
    total number of events:              100000

Latency (ms):
         min:                                    5.00
         avg:                                   10.00
         max:                                  100.00
         95th percentile:                       20.00
         sum:                              1000000.00

Threads fairness:
    events (avg/stddev):           6250.0000/50.00
    execution time (avg/stddev):   60.0000/0.00

关键指标:

指标 含义 关注点
transactions per sec TPS 越高越好
queries per sec QPS 越高越好
avg latency 平均延迟 越低越好
95th percentile 95% 请求延迟 越低越好

6.5 清理

bash 复制代码
sysbench oltp_read_write \
  --mysql-host=127.0.0.1 \
  --mysql-user=root \
  --mysql-password=root123 \
  --mysql-db=shop \
  cleanup

七、参数调优

7.1 关键参数

ini 复制代码
[mysqld]
# 缓冲池,一般设为物理内存的 60%-80%
innodb_buffer_pool_size = 4G

# redo log 大小
innodb_log_file_size = 1G

# 刷盘策略,1 最安全
innodb_flush_log_at_trx_commit = 1

# binlog 刷盘策略
sync_binlog = 1

# 连接数
max_connections = 500

# 每连接缓冲
sort_buffer_size = 2M
join_buffer_size = 2M
read_buffer_size = 2M

# 临时表
tmp_table_size = 64M
max_heap_table_size = 64M

# 慢查询
slow_query_log = 1
long_query_time = 1

7.2 调优原则

复制代码
参数调优
│
├── Buffer Pool 最大,其他按需
├── 不要盲目调大所有参数
├── 先监控,再调优
├── 每次只改一个参数
└── 改完必须压测验证

八、实战任务

任务清单

  • 开启慢查询日志
  • 用 mysqldumpslow 分析 Top 10
  • 用 performance_schema 查 Top 10 慢 SQL
  • 优化深分页查询
  • 优化一个 JOIN 查询
  • 优化一个子查询
  • 用 gh-ost 在线加列
  • 用 sysbench 压测并记录结果
  • 调整一个参数并验证效果

自检问题

  1. 性能优化的五个层次是什么?
  2. 慢查询日志怎么开启和分析?
  3. 深分页怎么优化?
  4. JOIN 优化原则是什么?
  5. 大表 DDL 为什么危险?gh-ost 怎么解决?
  6. sysbench 关键指标有哪些?
  7. Buffer Pool 应该设多大?

九、本篇小结

复制代码
第8篇 核心收获
│
├── 优化分层
│   ├── SQL 与索引(收益最大)
│   ├── 表结构
│   ├── 事务与锁
│   ├── 连接池与参数
│   └── 硬件与架构
│
├── 慢查询日志
│   ├── slow_query_log
│   ├── mysqldumpslow
│   └── performance_schema
│
├── 关键指标
│   ├── QPS / TPS / RT
│   ├── 连接数
│   ├── Buffer Pool 命中率
│   └── 锁等待
│
├── SQL 优化
│   ├── 深分页:主键游标 / 延迟关联
│   ├── JOIN:小表驱动大表
│   ├── 子查询:改 JOIN
│   ├── COUNT:加索引 / 计数表
│   └── ORDER BY:加索引
│
├── 大表 DDL
│   ├── Online DDL
│   ├── gh-ost
│   └── pt-online-schema-change
│
├── sysbench
│   ├── prepare / run / cleanup
│   └── TPS / QPS / 95% 延迟
│
└── 参数调优
    ├── Buffer Pool 最大
    ├── 先监控再调
    └── 每次只改一个

下一篇:第9篇《备份恢复、主从复制与高可用实战》

相关推荐
TDengine (老段)2 小时前
TDengine TSDB 实战排障四(升级与兼容)
android·java·大数据·数据库·物联网·时序数据库·tdengine
小小龙学IT2 小时前
Go 泛型(Generics)深度解析:从类型参数到生产实践
开发语言·数据库·golang
这个DBA有点耶2 小时前
分区表深入:分区裁剪失效的6种场景、分区锁机制与维护实战
数据库·mysql·dba
蓝速科技2 小时前
政务自助终端信创选型与无人值守落地方案
android·大数据·数据库·人工智能·科技·技术分享·政务
粤鼎恒业2 小时前
惠州工厂电子料回收厂家推荐:从交接单反推筛选标准
大数据·数据库·算法·硬件架构·硬件工程·pcb工艺·材料工程
ShineWinsu3 小时前
对于Redis:Redis的认识以及分布式系统的解析
linux·数据库·c++·redis·缓存·消息队列·分布式架构
SelectDB3 小时前
ELK 做不了的分析,我用 Doris 物化视图补上了:配置、开关与四个排错现场
大数据·数据库·数据分析
SelectDB3 小时前
Agent 日志检索慢、存储还贵?search() + VARIANT 的落地命令和几个当场踩出来的问题
大数据·数据库·数据分析
SelectDB3 小时前
ClickHouse 存日志踩过的并发坑:压测脚本、检索写法与排错命令
大数据·数据库·数据分析