Featured image of post MySQL 慢查询与性能调优:sysbench 压测 + buffer pool + 锁表排查

MySQL 慢查询与性能调优:sysbench 压测 + buffer pool + 锁表排查

本文系统整理 MySQL 性能调优三板斧:慢查询日志分析(mysqldumpslow + pt-query-digest)、InnoDB buffer pool 参数调优、sysbench 基准测试的 prepare/prewarm/run/cleanup 四步法。

为什么写这篇:MySQL 性能问题有三个主要来源——慢 SQL、buffer pool 配置不合理、磁盘/网络瓶颈。本文把"怎么发现 → 怎么量化 → 怎么调"整理成可复用的流程,所有命令都带真实案例参数

适用读者:做 MySQL 性能调优的运维 / DBA / 后端。

前置知识:了解 MySQL 基本架构、知道索引的作用。

目录

  1. 第一步:找到慢 SQL(慢查询日志)
  2. 第二步:mysqldumpslow 快速分析
  3. 第三步:pt-query-digest 深度分析
  4. 第四步:buffer pool 参数调优
  5. 第五步:sysbench 基准测试
  6. 实战:kill 阻塞连接 / 锁表排查
  7. 实战: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 PATTERNgrep 过滤

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_instancesCPU 核心数的 1~2 倍最大 64
innodb_buffer_pool_chunk_size1024M(默认 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 TPSoltp_point_select QPS
工控机 Ak8s + Ceph 块存储固态365.2374017
工控机 Bk8s + Ceph 文件存储固态529.1285793
工控机 Ck8s + NFS固态155.2545935
服务器docker机械649.67110000
工控机 Ddocker固态567.35190000
工控机 E二进制固态259.30180000

关键发现:纯随机点查(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.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-digest2023 起归档不再维护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+ 实战建议

  1. 优先用 Performance Schema 替代 slow log——粒度更细、对生产影响更小
  2. 升 8.4 LTS 必看:MySQL 8.4 默认关闭 query_cache,相关参数都移除
  3. 9.0 升级注意:caching_sha2_password 已是唯一支持,JDBC 8.x+ 才兼容
  4. 生产压测:用 sysbench 1.0.20 + 真实业务流量回放,别再靠"拍脑袋"

经验补记

  • MySQL 9.0 起的"JavaScript 存储程序"——是的,现在可以在 MySQL 里写 JS 函数了,但仅适合 OLTP 边缘场景
  • SHOW PROFILE 在 8.0.26 起被弃用,统一用 performance_schema 替代
使用 Hugo 构建
主题 StackJimmy 设计