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