为什么写这篇:MySQL 性能问题有三个主要来源——慢 SQL、buffer pool 配置不合理、磁盘/网络瓶颈。本文把"怎么发现 → 怎么量化 → 怎么调"整理成可复用的流程,所有命令都带真实案例参数。
适用读者:做 MySQL 性能调优的运维 / DBA / 后端。
前置知识:了解 MySQL 基本架构、知道索引的作用。
目录
- 第一步:找到慢 SQL(慢查询日志)
- 第二步:mysqldumpslow 快速分析
- 第三步:pt-query-digest 深度分析
- 第四步:buffer pool 参数调优
- 第五步:sysbench 基准测试
- 实战:kill 阻塞连接 / 锁表排查
- 实战:wait_timeout 与连接池调优
1. 第一步:找到慢 SQL(慢查询日志)
1.1 查看是否开启
1
2
3
| SHOW VARIABLES LIKE '%slow%';
SHOW STATUS LIKE '%slow%';
SHOW VARIABLES LIKE 'long_query_time';
|
输出字段含义:
| 变量 | 含义 |
|---|
slow_query_log | 是否开启(默认 OFF) |
slow_query_log_file | 日志文件路径 |
long_query_time | 慢查询阈值(秒,默认 10) |
1.2 在线开启 + 配置文件
1
2
3
4
5
6
7
| # /etc/my.cnf
[mysqld]
slow_query_log = on
slow_query_log_file = /data/f/mysql_slow_cw.log # Linux 用这个
# slow_query_log_file = <WAMP_INSTALL_DIR>/bin/mysql/mysql5.5.16/data/show-slow.log # Windows 必须绝对路径
long_query_time = 2 # 超过 2 秒算慢
log-queries-not-using-indexes # 没走索引的也记
|
Windows 注意:slow_query_log_file 必须写绝对路径(如 <WAMP_INSTALL_DIR>/...),相对路径在 Windows 下不工作。
修改后重启 MySQL 生效。
2. 第二步:mysqldumpslow 快速分析
mysqldumpslow 是 MySQL 自带的"五秒出报告"工具:
1
2
3
4
5
6
7
8
9
10
11
| # 访问最多的 10 条慢查询
mysqldumpslow -s c -t 10 /var/lib/mysql/huixuli2-slow.log
# 查询时间最慢的 3 条
mysqldumpslow -s t -t 3 /var/lib/mysql/huixuli2-slow.log
# 按时间排序前 10 条含 left join 的
mysqldumpslow -s t -t 10 -g "left join" /database/mysql/slow-log
# 扫描行数最多的
mysqldumpslow -s r -t 10 -g 'left join' /var/run/mysqld/mysqld-slow.log
|
参数说明:
| 参数 | 含义 |
|---|
-s c | 按 count(执行次数)排序 |
-s t | 按 time(耗时)排序 |
-s r | 按 row(扫描行数)排序 |
-s l | 按 lock time(锁等待)排序 |
-t N | 取前 N 条 |
-g PATTERN | grep 过滤 |
3. 第三步:pt-query-digest 深度分析
pt-query-digest 是 Percona 出品的专业慢查询分析工具,能生成结构化报告(响应时间、占比、参数化指纹)。
3.1 安装
1
2
3
4
5
6
7
| # Debian/Ubuntu
wget http://www.percona.com/get/pt-query-digest
chmod +x pt-query-digest
# 或整套 Percona Toolkit
wget percona.com/get/percona-toolkit.rpm
rpm -ivh percona-toolkit-2.2.13-1.noarch.rpm
|
重要:pt-query-digest 是个 Perl 脚本,所以系统得有 Perl 解释器(CentOS / Ubuntu 自带)。
3.2 关键参数
| 参数 | 含义 |
|---|
--filter 'EXPR' | 按 Perl 表达式过滤事件 |
--limit N / --limit N% | 输出前 N 条或 N% 占比 |
--host / --user / --password | 连 MySQL 用 |
--history | 把分析结果保存到 history 表(带时间窗对比) |
--review | 把分析结果保存到 review 表(更精简) |
--output report | 输出标准报告 |
--since '12h' / --since 'YYYY-MM-DD' | 时间范围起点 |
--until ... | 时间范围终点 |
3.3 11 个常用用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| # 1. 直接分析
pt-query-digest slow.log > slow_report.log
# 2. 最近 12 小时
pt-query-digest --since=12h slow.log > report.log
# 3. 指定时间范围
pt-query-digest slow.log \
--since '2014-05-17 09:30:00' \
--until '2014-06-17 10:00:00' > report.log
# 4. 只分析 SELECT
pt-query-digest --filter '$event->{fingerprint} =~ m/^select/i' slow.log
# 5. 只看 root 用户的
pt-query-digest --filter '($event->{user} || "") =~ m/^root/i' slow.log
# 6. 全表扫描/全 join 的
pt-query-digest --filter \
'(($event->{Full_scan} || "") eq "yes") || (($event->{Full_join} || "") eq "yes")' \
slow.log
# 7. 结果存到 review 表(自动建表)
pt-query-digest --user=root --password=*** \
--review h=localhost,D=test,t=query_review --create-review-table \
slow.log
# 8. 历史表(带时间窗)
pt-query-digest --user=root --password=*** \
--review h=localhost,D=test,t=query_history --create-history-table \
slow.log_20140401
# 9. 通过 tcpdump 抓 MySQL 协议再分析
tcpdump -s 65535 -x -nn -q -tttt -i any -c 1000 port 3306 > mysql.tcp.txt
pt-query-digest --type tcpdump mysql.tcp.txt
# 10. 分析 binlog
mysqlbinlog mysql-bin.000093 > mysql-bin000093.sql
pt-query-digest --type=binlog mysql-bin000093.sql
# 11. 分析 general log
pt-query-digest --type=genlog localhost.log
|
报告重点看 Rank(响应时间排序)、Response time(响应时间分布)、Callers(调用方)、SQL fingerprint 这几栏。
4. 第四步:buffer pool 参数调优
InnoDB 缓冲池是 MySQL 性能最关键的配置——读请求 99%+ 应该走 buffer pool,而不是磁盘。
4.1 三个核心参数
1
2
3
4
5
| # /etc/my.cnf
[mysqld]
innodb_buffer_pool_size = 8G # 缓冲池总大小
innodb_buffer_pool_instances = 4 # 缓冲池实例数
innodb_buffer_pool_chunk_size = 1024M # 调整块大小
|
| 参数 | 推荐值 | 限制 |
|---|
innodb_buffer_pool_size | 物理内存的 50%~80% | 最大 64 TB |
innodb_buffer_pool_instances | CPU 核心数的 1~2 倍 | 最大 64 |
innodb_buffer_pool_chunk_size | 1024M(默认 128M) | chunk × instance ≤ pool_size |
4.2 配置铁律
1
| innodb_buffer_pool_size = innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances × N
|
如果你设的 size 不是 chunk × instance 的整数倍,MySQL 会自动调整到最近的合法值。设之前先在心里算清楚。
案例:16G 内存 8 核服务器
1
2
3
4
5
6
7
| # 配置
innodb_buffer_pool_size = 8G
innodb_buffer_pool_chunk_size = 1024M
innodb_buffer_pool_instances = 4
# 验证(在线改)
SET GLOBAL innodb_buffer_pool_size = 8589934592; # 8G = 8*1024*1024*1024
|
4.3 评估命中率
1
2
3
4
5
6
7
8
| -- 读取请求命中率(目标 ≥ 99%)
SELECT
100 * (1 - (variable_value / (SELECT variable_value
FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_read_requests')))
AS hit_ratio
FROM performance_schema.global_status
WHERE variable_name = 'Innodb_buffer_pool_reads';
|
或者用 status 命令:
1
2
3
4
| SHOW STATUS LIKE 'innodb_buffer_pool_read%';
# Innodb_buffer_pool_read_requests: 54329170948
# Innodb_buffer_pool_reads: 4890414994
# 命中率 = 1 - 4890414994/54329170948 ≈ 91.7% (不达标,加大 buffer pool)
|
目标:命中率 ≥ 99%。如果 95% 以下说明 buffer pool 配置严重不足。
5. 第五步:sysbench 基准测试
5.1 sysbench 安装
1
2
3
4
5
6
7
| # Debian/Ubuntu
curl -s https://packagecloud.io/install/repositories/akopytov/sysbench/script.deb.sh | sudo bash
sudo apt -y install sysbench
# 验证
sysbench --version
# sysbench 1.0.20
|
5.2 4 步标准流程
① prepare(生成压测数据)
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 默认通过 INSERT INTO 导数;指定 --db-ps-mode=disable 还能用 LOAD DATA LOCAL INFILE 提速 30%
sysbench oltp_read_write \
--mysql-host=10.0.0.10 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password='{{REDACTED}}' \
--mysql-db=sbtest \
--table-size=1000000 \
--tables=10 \
--threads=32 \
--events=999999999 \
--report-interval \
prepare
|
--table-size=1000000 --tables=10 → 10 张 100 万行表,共 1000 万行。OLTP 场景要"像生产"才有意义。
② prewarm(把数据预热到内存)
1
2
3
4
5
6
| sysbench oltp_read_write \
--mysql-host=10.0.0.10 \
--mysql-db=sbtest \
--tables=10 --table-size=1000000 \
--threads=32 \
prewarm
|
③ run(开始压测)
1
2
3
4
5
6
7
8
| sysbench oltp_read_write \
--mysql-host=10.0.0.10 \
--mysql-db=sbtest \
--tables=10 --table-size=1000000 \
--threads=64 \
--time=60 \
--report-interval=10 \
run
|
④ cleanup(清理压测数据)
1
2
3
4
5
| sysbench oltp_read_write \
--mysql-host=10.0.0.10 \
--mysql-db=sbtest \
--tables=10 \
cleanup
|
5.3 报告解读(3 个核心指标)
1
| [ 60s ] thds: 64 tps: 5028.08 qps: 100641.26 (r/w/o: 70457.59/20121.51/10062.16) lat (ms,95%): 17.32 err/s: 0.00 reconn/s: 0.00
|
| 指标 | 含义 | 期望 |
|---|
| tps | 每秒事务数 | 越大越好 |
| qps | 每秒操作数(r+w+o) | 越大越好 |
| lat (ms, 95%) | 95% 事务的执行耗时(毫秒) | 越小越好 |
| err/s | 每秒错误数 | 应为 0 |
完整报告示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| SQL statistics:
queries performed:
read: 4276622
write: 1221892
other: 610946
total: 6109460
transactions: 305473 (5088.63 per sec.)
queries: 6109460 (101772.64 per sec.)
Latency (ms):
min: 5.81
avg: 12.57
max: 228.87
95th percentile: 17.32
sum: 3840044.28
Threads fairness:
events (avg/stddev): 4773.0156/30.77
execution time (avg/stddev): 60.0007/0.01
|
公平性(Threads fairness) 的 stddev 越小越好——如果某个线程的 stddev 远大于其他,说明有锁竞争或热点。
5.4 真实环境对比案例
| 主机 | 部署方式 | 硬盘 | oltp_read_write TPS | oltp_point_select QPS |
|---|
| 工控机 A | k8s + Ceph 块存储 | 固态 | 365.23 | 74017 |
| 工控机 B | k8s + Ceph 文件存储 | 固态 | 529.12 | 85793 |
| 工控机 C | k8s + NFS | 固态 | 155.25 | 45935 |
| 服务器 | docker | 机械 | 649.67 | 110000 |
| 工控机 D | docker | 固态 | 567.35 | 190000 |
| 工控机 E | 二进制 | 固态 | 259.30 | 180000 |
关键发现:纯随机点查(oltp_point_select)受磁盘影响小,受 buffer pool 影响大;读写混合(oltp_read_write)则受磁盘 IO 显著影响。SSD > 机械盘是确定收益。
5.5 其他场景
1
2
3
4
5
6
7
8
9
10
11
12
13
| # CPU 性能
sysbench cpu --cpu-max-prime=20000 --threads=32 run
# 看 events per second
# 内存吞吐(推荐 1MB 块大小)
sysbench memory --memory-block-size=1M --memory-total-size=100G --num-threads=1 run
# 看 MiB/sec
# 磁盘 IO
sysbench fileio --file-num=1 --file-total-size=10G --file-test-mode=rndrw prepare
sysbench fileio --file-num=1 --file-total-size=10G --file-test-mode=rndrw run
sysbench fileio --file-num=1 --file-total-size=10G --file-test-mode=rndrw cleanup
# 看 IOPS(reads/s + writes/s)和吞吐量(read MiB/s + written MiB/s)
|
6. 实战:kill 阻塞连接 / 锁表排查
6.1 查锁表
1
2
3
4
5
| -- 哪些表被锁住
SHOW OPEN TABLES WHERE In_use > 0;
-- 当前所有 InnoDB 事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
|
6.2 一键 kill 所有阻塞 trx
1
2
3
4
5
6
7
8
9
10
11
| -- 拼接成 KILL 命令,复制出来执行
SELECT
GROUP_CONCAT(CONCAT('KILL ', trx_mysql_thread_id) SEPARATOR '; ') AS cmd
FROM INFORMATION_SCHEMA.INNODB_TRX;
-- 不 kill 自己的版本
SELECT
GROUP_CONCAT(CONCAT('KILL IF EXISTS ', id) SEPARATOR '; ') AS kill_commands
FROM information_schema.PROCESSLIST
WHERE HOST LIKE '%example.telus.net%'
AND id != CONNECTION_ID();
|
6.3 sleep 连接专项清理
应用层数据库连接池配置不当,大量 sleep 连接堆积:
1
2
3
4
5
6
7
8
9
10
11
| -- 查 sleep 连接的客户端 IP
SELECT substring_index(host, ':', 1) AS client_ip, Command, Time
FROM information_schema.processlist;
-- 批量 kill 指定用户的 sleep 连接
SELECT
GROUP_CONCAT(CONCAT('kill ', id) SEPARATOR '; ') AS cmd
FROM information_schema.PROCESSLIST
WHERE USER = 'app_user'
AND command = 'Sleep'
AND db = 'app_db';
|
7. 实战:wait_timeout 与连接池调优
7.1 现象:连接超时
应用日志抛:
1
2
3
| The last packet successfully received from the server was 120,935 milliseconds ago.
The last packet sent successfully to the server was 381,491 milliseconds ago.
is longer than the server configured value of 'wait_timeout'.
|
7.2 修法
1
2
3
4
5
6
7
8
9
10
11
| -- 查看当前超时
SHOW VARIABLES LIKE '%timeout%';
-- 在线调大
SET GLOBAL wait_timeout = 31536000;
SET GLOBAL interactive_timeout = 31536000;
-- 持久化(/etc/my.cnf)
[mysqld]
wait_timeout = 31536000
interactive_timeout = 31536000
|
wait_timeout 8 小时 = 28800 秒是默认值。如果你的应用有连接池(推荐!),设大些无所谓;如果没用连接池且应用端有长空闲期,应同时配合连接池侧的心跳(testOnBorrow / testWhileIdle)。
7.3 mysqldump 备份时数据库卡死
1
| 导出数据库,出现 SELECT /*!40001 SQL_NO_CACHE */ * FROM ,数据库卡死
|
原因:导出时大表加锁 + 缓存爆炸。
修法:
1
2
3
4
5
6
7
| docker exec mysql /usr/bin/mysqldump \
-u root -p'{{REDACTED}}' \
--single-transaction \
--quick \
hxl_industry_iot_dev \
--ignore-table=hxl_industry_iot_dev.hxl_location_historydata \
> backup.sql
|
| 参数 | 作用 |
|---|
--single-transaction | 用一致快照导出(仅 InnoDB) |
--quick | 一行一行从服务器取,不缓存到内存 |
--ignore-table | 跳过不需要的表(如历史数据) |
下一步
- EXPLAIN 字段全解读:见《MySQL 实战速查》
- 索引优化案例:见《MySQL 索引优化实战》
- 备份恢复:见《MySQL 备份恢复实战》
2024+ 视角:MySQL 8.4/9.0 的性能调优新维度
performance_schema 在 8.4 LTS 起默认开启更多事件,比 slow_query_log 更细粒度:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| -- 1. 找出"扫了最多行"的 SQL
SELECT
digest_text,
count_star AS exec_count,
sum_rows_examined AS total_examined,
sum_rows_sent AS total_sent,
sum_timer_wait/1000000000 AS total_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY sum_rows_examined DESC
LIMIT 10;
-- 2. 找出"全表扫描次数最多"的表
SELECT object_name, count_read, count_fetch
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE count_read > 0
ORDER BY count_read DESC
LIMIT 10;
|
sys schema 1.x → 2.x(8.4+ 升级)
sys schema 是 Performance Schema 的"友好封装",8.4 起全面升级:
1
2
3
4
5
6
7
| -- sys.statements_with_full_table_scans
SELECT query, db, exec_count, total_latency
FROM sys.statements_with_full_table_scans
LIMIT 10;
-- sys.schema_redundant_indexes(推荐清理冗余索引)
SELECT * FROM sys.schema_redundant_indexes;
|
MySQL 8.0+ 的 EXPLAIN ANALYZE
1
2
3
| EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id = 123 AND created_at > '2024-01-01';
|
输出包含 实际执行时间(不再是"估算")——比传统 EXPLAIN 精准一个量级。
InnoDB Buffer Pool 8.0+ 新参数
1
2
3
4
5
6
7
8
9
10
11
| [mysqld]
# 8.0.30+ 用容量单位(替代 innodb_log_file_size)
innodb_redo_log_capacity = 8G
# 8.0+ 自适应刷新(默认 on)
innodb_adaptive_flushing = ON
innodb_adaptive_flushing_lwm = 10
# 8.0+ 多线程刷新
innodb_page_cleaners = 4
innodb_cleaner_lsn_age_factor = high
|
资源组(Resource Group)8.0+
1
2
3
4
5
6
7
8
| -- 创建资源组(绑定 CPU 核)
CREATE RESOURCE GROUP batch_group
TYPE = USER
VCPU = 2-3 -- 绑到 CPU 2、3 号核
THREAD_PRIORITY = 10;
-- 给会话分配
SET RESOURCE GROUP batch_group;
|
典型场景:把报表/批处理绑到非关键 CPU 核,避免影响在线交易。
sysbench 1.0 → 1.0.20+ 的 OLTP 场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 2024+ 推荐用 lua 脚本(自定义场景更灵活)
sysbench \
--db-driver=mysql \
--mysql-host=10.0.0.10 \
--mysql-db=sbtest \
--oltp-table-size=1000000 \
--oltp-tables-count=10 \
--threads=64 \
--time=120 \
--report-interval=10 \
oltp_read_write \
prepare
# 报告解读:加关注 P99 延迟
# [ 120s ] thds: 64 tps: 5028.08 qps: 100641.26
# lat (ms,99%): 23.5 ← 99% 延迟(生产更应看这个)
|
2024+ 慢查询工具链
| 工具 | 状态 | 替代 |
|---|
mysqldumpslow | 仍可用 | 适合 5.7/8.0 简单场景 |
pt-query-digest | 2023 起归档不再维护 | 用 mysql-slow-log-parser / sqlparse + 自定义分析 |
mydumper | 活跃 | 替代 mysqldump + 多线程导出 |
| MySQL Enterprise Monitor | 商业 | 高级告警 + 自动索引建议 |
| Yearning / Archery | 开源 SQL 审核平台 | 包含慢查询归集 |
2024+ 调优的"AI 加持"
- MySQL HeatWave AutoML(Oracle 云):自动识别热点查询并推荐索引
- Yearning/Archery + LLM:自然语言问"今天哪些 SQL 慢"
- Percona Monitoring and Management (PMM) 2.x:内置 Query Analytics + AI 异常检测
2024+ 实战建议
- 优先用 Performance Schema 替代 slow log——粒度更细、对生产影响更小
- 升 8.4 LTS 必看:MySQL 8.4 默认关闭
query_cache,相关参数都移除 - 9.0 升级注意:
caching_sha2_password 已是唯一支持,JDBC 8.x+ 才兼容 - 生产压测:用 sysbench 1.0.20 + 真实业务流量回放,别再靠"拍脑袋"
经验补记
- MySQL 9.0 起的"JavaScript 存储程序"——是的,现在可以在 MySQL 里写 JS 函数了,但仅适合 OLTP 边缘场景
SHOW PROFILE 在 8.0.26 起被弃用,统一用 performance_schema 替代