percona-toolkit —— MySQL 高级诊断与在线变更

运维工具手册 08-数据库工具 8.5 KB 原文

percona-toolkit —— MySQL 高级诊断与在线变更

分类:08 数据库工具 适用:MySQL 5.7 / 8.0、Percona Server、MariaDB(部分工具受限) 一句话:慢日志分析、在线加索引、主从一致性校验,DBA 的效率放大器。

1. 它解决什么问题

  • 慢日志几万行,哪几条 SQL 最该优化?
  • 大表 ALTER TABLE 会锁表几十分钟,怎么在线做?
  • 主从数据到底一致吗?不一致怎么修?
  • 谁在跑长查询、谁在拖垮数据库?

2. 安装

# 官方仓库(推荐)
yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm
yum install -y percona-toolkit

# Debian/Ubuntu
apt install -y percona-toolkit

# 或下载 tar 包
tar xf percona-toolkit-3.x.tar.gz && cd percona-toolkit-3.x
perl Makefile.PL && make && make install

依赖 Perl 模块:DBI、DBD::mysql、Time::HiRes。

pt-query-digest --version

3. DSN 连接格式

多数工具用统一 DSN:

h=10.0.0.5,P=3306,u=app,p=pass,D=mydb

也可以拆成参数:--host --port --user --password --database。密码建议放 ~/.my.cnf 的 [client] 段。

4. pt-query-digest(最常用)

分析慢日志

pt-query-digest /var/log/mysql/slow.log > report.txt
pt-query-digest --since '2026-11-05 14:00:00' --until '2026-11-05 15:00:00' slow.log
pt-query-digest --limit 20 slow.log
pt-query-digest --filter '$event->{Time} > 1' slow.log     # 只看超 1 秒

分析 binlog

mysqlbinlog -vv mysql-bin.000123 | pt-query-digest --type binlog
pt-query-digest --type binlog mysql-bin.000123

直接分析 processlist 历史

pt-query-digest --type tcpdump capture.txt
pt-query-digest --processlist h=localhost,u=root,p=xxx --interval 0.5 --run-time 30s

报告怎么读

# Profile
# Rank Query ID           Response time  Calls R/Call  V/M   Item
# ==== ================== ============== ===== ======= ===== ============
#    1 0x1234...          120.5000 45.2%  3000  0.0402  0.01 SELECT orders
指标 含义
Response time 占比 该 SQL 占总耗时的比例,优先优化前几名
Calls 执行次数
R/Call 单次平均耗时
V/M 耗时波动(方差/均值),大说明不稳定
Item SQL 指纹(去掉具体值)

下面还会给出示例 SQL、执行计划建议、索引建议。

输出到数据库(长期趋势)

pt-query-digest --review h=localhost,D=slow_review,t=review \
  --history h=localhost,D=slow_review,t=history slow.log

5. pt-online-schema-change(在线 DDL)

原理:建影子表 → 拷贝数据 → 用触发器同步增量 → 原子 rename。全程不锁原表(大部分时间)。

# 1. 先看会执行什么(不落库)
pt-online-schema-change \
  --alter "ADD INDEX idx_created (created_at)" \
  --dry-run \
  h=10.0.0.5,D=mydb,t=orders,u=app,p=pass

# 2. 打印 SQL
pt-online-schema-change --alter "..." --print h=...,D=...,t=... | less

# 3. 执行
pt-online-schema-change \
  --alter "ADD COLUMN remark varchar(255) DEFAULT NULL" \
  --max-load "Threads_running=30" \
  --critical-load "Threads_running=100" \
  --chunk-time 0.5 \
  --chunk-size-limit 4 \
  --max-lag 2 \
  --alter-foreign-keys-method auto \
  --execute \
  h=10.0.0.5,D=mydb,t=orders,u=app,p=pass
参数 作用
--alter DDL 语句(不含 ALTER TABLE)
--dry-run 只检查,不执行
--print 打印将执行的 SQL
--execute 真正执行(必须显式加)
--max-load 超过则暂停
--critical-load 超过则中止
--max-lag 从库延迟超阈值暂停
--chunk-time 每块拷贝耗时目标(秒)
--chunk-size 每块行数
--no-drop-old-table 保留旧表(回滚用)
--preserve-triggers 保留原表已有触发器

限制:

  • 表必须有主键或唯一索引。
  • 原表已有触发器会被替换(需 --preserve-triggers)。
  • 需要磁盘空间约等于表大小 + 索引大小。
  • 外键表处理复杂,用 --alter-foreign-keys-method。

MySQL 8.0 的 ALGORITHM=INPLACE 已能在线加索引,pt-osc 主要用于改列类型、加列等需要重建表的场景。

6. pt-table-checksum / pt-table-sync(主从一致性)

校验

pt-table-checksum \
  --host=10.0.0.5 --user=checker --password=pass \
  --databases=mydb \
  --replicate=mydb.checksums \
  --no-check-binlog-format \
  --recursion-method=dsn=h=10.0.0.5,D=mydb,t=dsns

输出:

TS               ERRORS  DIFFS  ROWS  DIFF_ROWS  CHUNKS  SKIPPED  MYSQL_TABLE ...
11-05T14:00:00        0      2  10000        120      10        0  orders

DIFFS > 0 表示该表主从不一致。

找出不一致的行

pt-table-checksum ... --replicate=mydb.checksums
mysql -e "select * from mydb.checksums where master_cnt <> this_cnt or master_crc <> this_crc"

修复

# 只打印修复语句(先在从库执行)
pt-table-sync --print --sync-to-master h=10.0.0.6,D=mydb,t=orders

# 执行修复
pt-table-sync --execute --sync-to-master h=10.0.0.6,D=mydb,t=orders

--sync-to-master 表示"以主库为准修从库",避免反向覆盖。

7. 其他常用工具

工具 用途 示例
pt-kill 自动杀长查询/空闲连接 pt-kill --busy-time 60 --kill --print
pt-duplicate-key-checker 找重复索引 pt-duplicate-key-checker h=...,D=mydb
pt-index-usage 分析慢日志里的索引使用 pt-index-usage slow.log h=...
pt-mysql-summary 实例配置与状态摘要 pt-mysql-summary --host ...
pt-summary 系统层面摘要 pt-summary
pt-diskstats 磁盘 IO 统计 pt-diskstats
pt-stalk 故障时自动收集现场 pt-stalk --function status --variable Threads_connected --threshold 500
pt-archiver 归档老数据到历史表/文件 pt-archiver --source h=..,D=..,t=.. --where "created_at < '2025-01-01'" --purge
pt-slave-delay 人为制造从库延迟(演练) pt-slave-delay --delay 1h --interval 1m
pt-show-grants 导出权限 pt-show-grants -h host -u root -p

pt-kill 示例

# 打印(不杀)运行超 60 秒的查询
pt-kill --host=10.0.0.5 --user=root --password=pass \
  --busy-time 60 --print

# 杀掉,每分钟检查一次,持续 10 分钟
pt-kill --busy-time 60 --kill --interval 60 --run-time 10m \
  --host=... --user=... --password=...

# 只杀某用户的 SELECT
pt-kill --match-user app --match-command Query --match-info '^SELECT' --busy-time 30 --kill

8. 实战场景

8.1 慢日志分析到优化落地

# 1. 分析昨天的慢日志
pt-query-digest /var/log/mysql/slow.log.1 > /tmp/slow-report.txt

# 2. 看 TOP 5
head -60 /tmp/slow-report.txt

# 3. 对第一条 SQL 看执行计划
mysql -e "explain select ... \G"

# 4. 建索引(大表用 pt-osc)
pt-online-schema-change --alter "ADD INDEX idx_x (col)" --dry-run ... 
pt-online-schema-change --alter "ADD INDEX idx_x (col)" --execute ...

8.2 大表加索引,业务无感

pt-online-schema-change \
  --alter "ADD INDEX idx_user_created (user_id, created_at)" \
  --max-load "Threads_running=25" \
  --critical-load "Threads_running=80" \
  --max-lag 3 \
  --chunk-time 0.3 \
  --execute \
  h=10.0.0.5,D=app,t=orders,u=dba,p=pass

执行期间监控:

watch -n 5 "mysql -e 'show processlist' | grep -c Query"
watch -n 5 "mysql -e 'show replica status\G' | grep Seconds_Behind"

中断处理:Ctrl-C 后清理影子表

drop table if exists `_orders_new`, `_orders_old`;

8.3 主从不一致排查

# 1. 校验
pt-table-checksum --databases=mydb --replicate=mydb.checksums h=master

# 2. 找差异表
mysql -h master -e "select db, tbl, master_cnt, this_cnt, master_crc, this_crc
from mydb.checksums where master_cnt <> this_cnt or master_crc <> this_crc"

# 3. 打印修复语句
pt-table-sync --print --sync-to-master h=slave,D=mydb,t=orders

# 4. 确认无误后执行
pt-table-sync --execute --sync-to-master h=slave,D=mydb,t=orders

8.4 自动止血:杀长查询

# 每分钟检查,运行超 120 秒的查询告警并杀掉
pt-kill --busy-time 120 --kill --interval 60 --daemonize \
  --log /var/log/pt-kill.log \
  h=10.0.0.5,u=admin,p=pass

8.5 归档历史数据

pt-archiver \
  --source h=10.0.0.5,D=app,t=orders,u=dba,p=pass \
  --dest h=10.0.0.5,D=archive,t=orders_2025,u=dba,p=pass \
  --where "created_at < '2025-01-01'" \
  --limit 1000 --commit-each \
  --purge

9. 常见坑

  1. pt-osc 忘加 --execute:默认只做检查(安全设计),以为执行了其实没有。
  2. 表没有主键/唯一键:pt-osc 直接拒绝,先加主键。
  3. 磁盘空间不足:影子表 + 触发器开销,至少预留表大小 1.5 倍。
  4. 触发器冲突:原表已有触发器会被覆盖,加 --preserve-triggers。
  5. 外键表:需要 --alter-foreign-keys-method,且可能短暂影响子表。
  6. pt-table-checksum 在主库跑会加压:低峰执行,用 --max-load 限制。
  7. --no-check-binlog-format 掩盖问题:binlog 为 STATEMENT 时校验结果不可靠,应改 ROW。
  8. pt-table-sync 反向覆盖:务必 --sync-to-master 并先 --print。
  9. Perl 依赖缺失:报 Can't locate DBD/mysql.pm,装 libdbd-mysql-perl。
  10. DSN 密码含特殊字符:用引号包住整个 DSN,或改用 ~/.my.cnf。
  11. pt-kill 误杀:先用 --print 跑一段时间确认匹配范围。
  12. MariaDB 兼容性:部分工具(pt-osc、pt-table-checksum)在 MariaDB 上支持有限,需验证。
  13. pt-archiver --purge 不可逆:先 --dry-run 或先归档不删。
  14. 工具版本与 MySQL 8.0 的兼容:老版本 percona-toolkit 对 8.0 的 caching_sha2_password 支持不佳,用 3.5+。

10. 速查表

# 慢日志
pt-query-digest slow.log > report.txt
pt-query-digest --since '2026-11-05 14:00' --until '2026-11-05 15:00' slow.log
pt-query-digest --type binlog mysql-bin.000123

# 在线 DDL
pt-online-schema-change --alter "ADD INDEX i (c)" --dry-run h=..,D=..,t=..,u=..,p=..
pt-online-schema-change --alter "..." --max-load Threads_running=30 --execute h=..

# 一致性
pt-table-checksum --databases=db --replicate=db.checksums h=master
pt-table-sync --print --sync-to-master h=slave,D=db,t=tbl
pt-table-sync --execute --sync-to-master h=slave,D=db,t=tbl

# 其他
pt-kill --busy-time 60 --print
pt-kill --busy-time 60 --kill --interval 60
pt-duplicate-key-checker h=..,D=..
pt-index-usage slow.log h=..
pt-mysql-summary --host .. --user ..
pt-archiver --source h=..,D=..,t=.. --where "..." --purge

安全习惯:所有会改数据的工具,先 --dry-run / --print,确认后再 --execute。

percona-toolkit —— MySQL 高级诊断与在线变更 - 知识库 - 起航社区