运维管理30 分钟阅读
Percona Toolkit 实战三件套:慢查询分析、在线 DDL、主从校验
Percona Toolkit 是 MySQL DBA 的瑞士军刀,本文聚焦三个最常用的工具:pt-query-digest 慢查询分析、pt-online-schema-change 在线 DDL、pt-table-checksum 主从数据校验,通过真实案例演示完整操作流程。
2026年5月6日阅读—点赞—收藏—
mysql
在知识库中专注阅读,并随时返回相关工具与课程
Percona Toolkit 是 MySQL DBA 的瑞士军刀,本文聚焦三个最常用的工具:pt-query-digest 慢查询分析、pt-online-schema-change 在线 DDL、pt-table-checksum 主从数据校验,通过真实案例演示完整操作流程。
做 MySQL 运维这些年,如果让我只推荐一个工具包,那一定是 Percona Toolkit。
它就像 DBA 的瑞士军刀,里面有 30 多个命令行工具,覆盖了慢查询分析、在线表结构变更、主从数据校验、复制管理、备份校验等方方面面。但说实话,日常工作中真正高频使用的也就那么几个。
今天这篇文章,我就聚焦 三个最核心的工具,把它们的原理、用法、踩坑经验一次性讲透:
每个工具都会给出真实的命令和输出,拿来就能用。
Percona Toolkit(简称 PT)是 Percona 公司开源的一套 MySQL/MariaDB 管理工具集,用 Perl 编写,目前最新版本 3.6.x。整个工具包包含 30+ 个独立的命令行工具,按功能大致可以分为以下几类:
但是,根据我这些年的经验,80% 的 DBA 日常只会用到下面这几个:
pt-query-digest — 几乎每天都用,排查性能问题的第一步pt-online-schema-change — 每次变更表结构都离不开pt-table-checksum + pt-table-sync — 主从架构必备pt-heartbeat — 精确监控复制延迟pt-kill — 紧急情况下批量杀连接今天重点讲前三个。
安装方式有三种,推荐用包管理器。
如果服务器不能联网,可以直接下载二进制包:
注意
注意:PT 工具依赖 Perl 和一些 Perl 模块(DBI、DBD::mysql 等)。如果用二进制安装,需要自行解决依赖:
pt-query-digest 是我用得最多的 PT 工具,没有之一。它能解析 MySQL 慢查询日志、general log、二进制日志,甚至 tcpdump 抓包,然后生成结构化的分析报告,帮你快速定位最耗资源的 SQL。
先确保 MySQL 开启了慢查询日志:
最基础的用法,直接丢一个慢查询日志文件进去:
小技巧:生产环境的慢查询日志可能非常大(几个 GB),直接分析会很慢。可以先用
--limit只看 Top N:
有些场景你没法开慢查询日志(比如线上 RDS 不支持),这时候可以用 tcpdump 抓 MySQL 协议包,再交给 pt-query-digest 分析:
这种方式有几个好处:
注意
注意:tcpdump 抓包可能会有一定性能开销,在高并发环境下建议限制抓包时间和数量。
pt-query-digest 的输出报告分为三大块,理解这个结构是用好它的关键。
第一部分:Overall 概览
这里关注几个关键指标:
第二部分:Profile 排行榜
这个 Profile 就是重点!它按总响应时间排序,告诉你:
第三部分:每条 Query 的详细分析
这个详情告诉你:
来看一个完整的排查流程。某天早上收到告警:数据库 CPU 飙到 90%。
经验之谈:看到
Rows examine远大于Rows sent的查询,90% 的概率是缺索引或者索引没用对。pt-query-digest 帮你快速定位到这类 SQL,剩下的就是 EXPLAIN + 加索引的标准操作了。
在 MySQL 5.6 之前,执行 ALTER TABLE 会锁表,对于大表来说,锁定时间可能长达数小时,业务直接挂掉。虽然 MySQL 5.6+ 引入了 Online DDL,但并不是所有 DDL 操作都支持在线执行,而且还有各种坑。
pt-online-schema-change(简称 pt-osc)是目前生产环境做在线表结构变更最可靠的方案。
pt-osc 的工作流程可以概括为五个步骤:
整个过程中,业务对 orders 表的读写不会被阻塞(只有最后 RENAME 瞬间有极短暂的元数据锁)。
这个设计很巧妙,但也意味着:
假设我们要给 order_db.orders 表(5000 万行,约 12GB)加一个 remark 字段:
整个过程大约 2 小时 15 分钟,期间业务正常读写,没有任何中断。
几个最重要参数的详细说明:
监控进度
pt-osc 运行期间,你可以通过几种方式监控:
故障处理
如果 pt-osc 中途失败了(比如网络断了、手动 Ctrl+C 了),需要手动清理:
血泪教训:我曾经遇到过 pt-osc 执行到 99% 的时候网络闪断,触发器残留在原表上,导致所有 INSERT 操作都报错。所以执行 pt-osc 之前,务必确保你的 SSH 连接稳定,建议用
screen或tmux:
pt-osc 虽然好用,但不是万能的。以下场景你应该考虑其他方案:
表上已有触发器:pt-osc 需要创建自己的触发器,MySQL 5.7 之前每个表每种事件只能有一个触发器。解决办法:用 gh-ost(GitHub 开源的在线 DDL 工具,基于 binlog 而不是触发器)
表没有主键或唯一索引:pt-osc 需要按主键分 chunk 复制数据,没主键会报错。先加主键再做其他变更。
磁盘空间不足:pt-osc 会创建一个和原表差不多大的影子表,如果原表 100GB,你至少需要 100GB 的空闲空间。
外键关联复杂:pt-osc 处理外键比较复杂,虽然支持 --alter-foreign-keys-method,但在多层外键的情况下容易出问题。
MySQL 8.0 Instant DDL 能搞定的:MySQL 8.0.12+ 支持 ALGORITHM=INSTANT 的操作(比如加列到表末尾),瞬间完成,不需要 pt-osc:
选择建议:
MySQL 在线 DDL 工具选择流程
只要你用了 MySQL 主从复制,就一定会遇到数据不一致的问题。原因有很多:
UUID()、SYSDATE())pt-table-checksum 可以在不影响业务的情况下,校验主从数据的一致性。
pt-table-checksum 的原理非常精巧:
SELECT CRC32(GROUP_CONCAT(...)) 得到校验值percona.checksums 表:这条 INSERT 会通过复制传到从库percona.checksums这个设计的巧妙之处在于:校验操作本身就是通过复制链路传递的,不需要直接连接从库,也不需要在主从之间传输大量数据。
主从数据库分块校验和一致性对比
看输出结果:
order_items 表有 2 个 chunk 数据不一致,涉及约 1523 行接下来在从库上查看具体哪些 chunk 不一致:
第一个 chunk 从库少了 2 行(this_cnt=998 vs master_cnt=1000),第二个 chunk 行数一样但内容不同。
pt-table-checksum 在主库上执行校验 SQL,这些 SQL 会产生额外的复制负载。如果从库本来就有延迟,校验操作会让延迟更严重。所以 --max-lag 参数非常重要:
对于大型生产环境,建议的参数组合:
关键参数说明:
--max-lag=1s:从库延迟超过 1 秒就暂停--chunk-size=500:每次只校验 500 行,减少锁持有时间--chunk-time=0.5:自动调整 chunk-size,让每次校验花 0.5 秒左右--run-time=3600:最多运行 1 小时,超时自动停止(避免跑太久影响业务)踩坑提醒:如果你用的是 GTID 复制,且
binlog_format=ROW,可能会遇到Cannot replicate ... in row format的报错。加--no-check-binlog-format可以绕过检查,但更好的做法是让 pt-table-checksum 的会话使用 STATEMENT 格式:
发现数据不一致后,用 pt-table-sync 来修复。它会对比主从差异,生成修复用的 SQL。
重要注意事项:
pt-table-sync的修复 SQL 是在主库上执行的(通过--sync-to-master选项),然后通过复制传到从库。不要直接在从库上修改数据!- 修复前务必先用
- 对于只读从库,需要临时关闭
read_only或使用SUPER权限的账号。
除了上面三个核心工具,还有几个值得了解的:
线上突发大量慢查询导致连接池满了?手动 KILL 太慢,用 pt-kill:
服务器偶尔抽风,但每次你登上去看的时候又恢复了?用 pt-stalk 自动蹲守:
比 iostat 更直观的磁盘 IO 分析工具:
SHOW SLAVE STATUS 里的 Seconds_Behind_Master 不够准确,pt-heartbeat 能提供精确到毫秒级的延迟监控:
这些年用 Percona Toolkit 踩过不少坑,总结一些经验分享给大家:
永远先 dry-run:不管是 pt-osc 还是 pt-table-sync,执行之前一定要先 --dry-run 或 --print,确认没有问题再 --execute。
用 screen/tmux 跑长任务:pt-osc 改大表可能要跑好几个小时,SSH 断了就麻烦了。
在低峰期执行:虽然这些工具号称"在线",但多多少少还是会增加负载。建议在业务低峰期执行。
监控复制延迟:执行 pt-osc 或 pt-table-checksum 时,一定要关注从库延迟。
保留操作日志:把 pt 工具的输出重定向到文件,方便事后排查问题。
坑 1:pt-osc 和外键
如果表有外键关系,pt-osc 默认会报错。需要用 --alter-foreign-keys-method 指定处理方式:
坑 2:pt-table-checksum 在 RBR 模式下报错
如果 binlog_format=ROW,pt-table-checksum 默认会拒绝执行,因为它的校验 SQL 在 ROW 模式下复制到从库时,只会传递数据变更,不会传递校验计算。
解决办法:
坑 3:pt-osc 大事务导致 OOM
如果原表上有大事务正在执行,pt-osc 的触发器可能会使事务变得更大,导致内存不足。建议在执行 pt-osc 之前,先用 pt-kill 清理掉长事务:
坑 4:忘记清理 percona.checksums 表
pt-table-checksum 每次执行都会往 percona.checksums 表写入数据。如果定期跑校验(比如每天一次),这个表会越来越大。建议定期清理:
坑 5:权限不足
PT 工具需要的权限比较多,经常因为权限不够而报各种奇怪的错误。建议创建专门的 PT 用户:
Percona Toolkit 里有 30 多个工具,但日常工作中掌握好这三个就够应付 80% 的场景了:
核心原则:
--dry-run / --print,再 --execute — 永远不要裸奔--max-lagscreen / tmux,输出重定向到日志文件最后,PT 工具虽然强大,但不是银弹。随着 MySQL 8.0 Instant DDL、并行复制等特性的成熟,某些场景下原生方案已经足够好了。工具是手段,理解原理才是关键。
希望这篇文章能帮你在日常 MySQL 运维中少踩坑,多省心。有问题欢迎留言交流!
1# 安装 Percona 官方 yum 源23> **本章目标**:掌握本章核心知识点4> **前置要求**:完成前序章节学习5> **预计时长**:60 分钟67yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm89# 启用 tools 仓库10percona-release enable-only tools release1112# 安装 Percona Toolkit13yum install -y percona-toolkit1415# 验证安装16pt-query-digest --version17# pt-query-digest 3.6.01# 安装依赖2apt-get install -y curl gnupg2 lsb-release34# 添加 Percona 源5curl -O https://repo.percona.com/apt/percona-release_latest.generic_all.deb6dpkg -i percona-release_latest.generic_all.deb78# 启用 tools 仓库9percona-release enable-only tools release10apt-get update1112# 安装13apt-get install -y percona-toolkit1# 下载最新版本2wget https://downloads.percona.com/downloads/percona-toolkit/3.6.0/binary/tarball/percona-toolkit-3.6.0_x86_64.tar.gz34# 解压5tar -xzf percona-toolkit-3.6.0_x86_64.tar.gz6cd percona-toolkit-3.6.078# 直接使用 bin 目录下的工具9./bin/pt-query-digest --version1yum install -y perl-DBI perl-DBD-MySQL perl-IO-Socket-SSL perl-Digest-MD52# 或3apt-get install -y libdbi-perl libdbd-mysql-perl libio-socket-ssl-perl1-- 查看慢查询日志状态2SHOW VARIABLES LIKE 'slow_query%';3+---------------------+-------------------------------+4| Variable_name | Value |5+---------------------+-------------------------------+6| slow_query_log | ON |7| slow_query_log_file | /var/log/mysql/mysql-slow.log |8+---------------------+-------------------------------+910-- 查看慢查询阈值(单位:秒)11SHOW VARIABLES LIKE 'long_query_time';12+-----------------+----------+13| Variable_name | Value |14+-----------------+----------+15| long_query_time | 1.000000 |16+-----------------+----------+1718-- 如果没开启,在线开启(不需要重启)19SET GLOBAL slow_query_log = ON;20SET GLOBAL long_query_time = 0.5; -- 建议设为 0.5 秒21SET GLOBAL log_queries_not_using_indexes = ON; -- 记录没用索引的查询1# 分析整个慢查询日志2pt-query-digest /var/log/mysql/mysql-slow.log34# 只分析最近 1 小时的慢查询5pt-query-digest --since '1h' /var/log/mysql/mysql-slow.log67# 分析指定时间范围8pt-query-digest --since '2026-05-05 00:00:00' --until '2026-05-06 00:00:00' /var/log/mysql/mysql-slow.log910# 只看某个数据库的慢查询11pt-query-digest --filter '$event->{db} eq "order_db"' /var/log/mysql/mysql-slow.log1213# 输出结果保存到文件14pt-query-digest /var/log/mysql/mysql-slow.log > /tmp/slow_report_$(date +%Y%m%d).txt1pt-query-digest --limit 20 /var/log/mysql/mysql-slow.log1# 第一步:用 tcpdump 抓取 MySQL 3306 端口的流量,抓 60 秒2tcpdump -s 65535 -x -nn -q -tttt -i eth0 -c 100000 port 3306 > /tmp/mysql.tcp.txt34# 第二步:用 pt-query-digest 解析 tcpdump 输出5pt-query-digest --type tcpdump /tmp/mysql.tcp.txt67# 也可以用管道实时分析(谨慎使用,可能影响性能)8tcpdump -s 65535 -x -nn -q -tttt -i eth0 port 3306 | pt-query-digest --type tcpdump --run-time 601# 270ms user time, 20ms system time, 28.53M rss, 217.34M vsz2# Current date: Wed May 6 10:20:00 20263# Hostname: db-master-014# Files: /var/log/mysql/mysql-slow.log5# Overall: 1.58k total, 45 unique, 2.63 QPS, 0.15x concurrency6# Time range: 2026-05-05T00:00:01 to 2026-05-05T00:10:027# Attribute total min max avg 95% stddev median8# ============ ======= ======= ======= ======= ======= ======= =======9# Exec time 91s 50ms 12s 58ms 224ms 358ms 54ms10# Lock time 3s 0 200ms 2ms 4ms 15ms 626us11# Rows sent 45.18k 0 5.00k 29.28 97.36 216.40 0.9912# Rows examine 8.72M 0 500.12k 5.65k 28.37k 34.78k 299.0313# Query size 789.08k 18 8.19k 511.34 1.96k 1.05k 246.021# Profile2# Rank Query ID Response time Calls R/Call V/M3# ==== ================================= ============== ===== ====== ====4# 1 0xA1B2C3D4E5F6A7B8 35.1234 38.6% 312 0.1126 0.055# 2 0xB2C3D4E5F6A7B8C9 22.4567 24.7% 89 0.2523 0.126# 3 0xC3D4E5F6A7B8C9D0 12.8901 14.2% 456 0.0283 0.017# 4 0xD4E5F6A7B8C9D0E1 8.3456 9.2% 67 0.1246 0.088# 5 0xE5F6A7B8C9D0E1F2 5.6789 6.2% 234 0.0243 0.029# MISC 0xMISC 6.5053 7.2% 422 0.0154 0.01# Query 1: 5.20 QPS, 0.59x concurrency, ID 0xA1B2C3D4E5F6A7B8 at byte 123452# This item is included in the report because it matches --limit.3# Scores: V/M = 0.054# Time range: 2026-05-05T00:00:15 to 2026-05-05T00:10:005# Attribute pct total min max avg 95% stddev median6# ============ === ======= ======= ======= ======= ======= ======= =======7# Count 19 3128# Exec time 38 35s 32ms 2s 113ms 224ms 168ms 78ms9# Lock time 12 362ms 28us 50ms 1ms 4ms 5ms 194us10# Rows sent 4 1.83k 1 15 6.01 14.52 4.67 4.9611# Rows examine 45 3.93M 5.12k 50.25k 12.89k 28.37k 10.40k 9.83k12# Query size 22 178.55k 571 587 586.00 563.87 7.37 563.8713# String:14# Databases order_db15# Hosts 10.0.1.100 (200), 10.0.1.101 (112)16# Users app_user17# Query_time distribution18# 1us19# 10us20# 100us21# 1ms22# 10ms ###########################23# 100ms ################################################################24# 1s ###25# 10s+26# Tables27# SHOW TABLE STATUS FROM `order_db` LIKE 'orders'\G28# SHOW CREATE TABLE `order_db`.`orders`\G29# EXPLAIN /*!50100 PARTITIONS*/30SELECT o.order_id, o.user_id, o.total_amount, oi.product_name31FROM orders o32JOIN order_items oi ON o.order_id = oi.order_id33WHERE o.user_id = 1234534 AND o.create_time >= '2026-04-01'35 AND o.status IN ('paid', 'shipped')36ORDER BY o.create_time DESC37LIMIT 20\G1# 第一步:快速看最近 30 分钟的慢查询 Top 52pt-query-digest \3 --since '30m' \4 --limit 5 \5 --order-by Query_time:sum \6 /var/log/mysql/mysql-slow.log78# 第二步:发现某条 SELECT 占了 40% 的总时间,拿出来 EXPLAIN9mysql -e "EXPLAIN SELECT o.order_id, o.user_id, o.total_amount10FROM orders o11WHERE o.user_id = 1234512 AND o.create_time >= '2026-04-01'13 AND o.status IN ('paid', 'shipped')14ORDER BY o.create_time DESC15LIMIT 20\G" order_db1617# EXPLAIN 输出:18# *************************** 1. row ***************************19# id: 120# select_type: SIMPLE21# table: o22# partitions: NULL23# type: ALL <-- 全表扫描!24# possible_keys: NULL25# key: NULL <-- 没走索引!26# key_len: NULL27# ref: NULL28# rows: 48523167 <-- 扫描近 5000 万行29# filtered: 0.0530# Extra: Using where; Using filesort3132# 第三步:加索引33mysql -e "ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);" order_db3435# 第四步:再次 EXPLAIN 确认36# type: ref, key: idx_user_status_time, rows: 23 完美!1# 第一步:先 dry-run 看看会做什么(不会真的执行)2pt-online-schema-change \3 --alter "ADD COLUMN remark VARCHAR(500) DEFAULT '' COMMENT '备注'" \4 D=order_db,t=orders \5 --user=root --password='YourPassword' \6 --host=127.0.0.1 --port=3306 \7 --dry-run89# dry-run 输出:10# Operation, tries, wait:11# analyze_table, 10, 112# copy_rows, 10, 0.2513# create_triggers, 10, 114# drop_triggers, 10, 115# swap_tables, 10, 116# update_foreign_keys, 10, 117# Starting a dry run. `order_db`.`orders` will not be altered.18# Creating new table...19# Created new table order_db._orders_new OK.20# Altering new table...21# Altered `order_db`.`_orders_new` OK.22# Not creating triggers because this is a dry run.23# Not copying rows because this is a dry run.24# Not swapping tables because this is a dry run.25# Not dropping old table because this is a dry run.26# Not dropping triggers because this is a dry run.27# dry-run 看到没有报错,可以安心执行了2829# 第二步:真正执行30pt-online-schema-change \31 --alter "ADD COLUMN remark VARCHAR(500) DEFAULT '' COMMENT '备注'" \32 D=order_db,t=orders \33 --user=root --password='YourPassword' \34 --host=127.0.0.1 --port=3306 \35 --chunk-size=1000 \36 --max-lag=1s \37 --check-interval=1 \38 --progress time,30 \39 --execute4041# 执行过程输出:42# Altering `order_db`.`orders`...43# Creating new table...44# Created new table order_db._orders_new OK.45# Altering new table...46# Altered `order_db`.`_orders_new` OK.47# 2026-05-06T10:20:35 Creating triggers...48# 2026-05-06T10:20:35 Created triggers OK.49# 2026-05-06T10:20:35 Copying approximately 48523167 rows...50# Copying `order_db`.`orders`: 2% 02:15:00 remain51# Copying `order_db`.`orders`: 5% 02:05:30 remain52# ...53# Copying `order_db`.`orders`: 98% 00:01:20 remain54# 2026-05-06T12:35:22 Copied rows OK.55# 2026-05-06T12:35:22 Swapping tables...56# 2026-05-06T12:35:22 Swapped original and new tables OK.57# 2026-05-06T12:35:22 Dropping old table...58# 2026-05-06T12:35:22 Dropped old table `order_db`.`_orders_old` OK.59# 2026-05-06T12:35:22 Dropping triggers...60# 2026-05-06T12:35:22 Dropped triggers OK.61# Successfully altered `order_db`.`orders`.1# --max-load:主库 Threads_running 超过 50 时暂停复制,降下来后自动恢复2pt-online-schema-change \3 --alter "ADD INDEX idx_status (status)" \4 D=order_db,t=orders \5 --max-load "Threads_running=50" \6 --critical-load "Threads_running=200" \7 --execute89# --chunk-size 和 --chunk-time 的关系10# chunk-time 默认 0.5 秒,PT 会自动调整 chunk-size 使得每批大约花 0.5 秒11# 如果你显式设置了 chunk-size,chunk-time 就不生效了1213# --set-vars:设置会话变量,防止超时14pt-online-schema-change \15 --alter "MODIFY COLUMN remark TEXT" \16 D=order_db,t=orders \17 --set-vars "wait_timeout=86400,lock_wait_timeout=60,innodb_lock_wait_timeout=60" \18 --execute1# 方法 1:看 pt-osc 自身输出的进度条2# Copying `order_db`.`orders`: 45% 01:15:00 remain34# 方法 2:查看影子表的行数5mysql -e "SELECT COUNT(*) FROM order_db._orders_new;"67# 方法 3:看触发器是否存在(确认还在运行中)8mysql -e "SHOW TRIGGERS FROM order_db LIKE 'orders';"910# 方法 4:看复制延迟11mysql -e "SHOW SLAVE STATUS\G" | grep Seconds_Behind_Master1# 检查残留的影子表2mysql -e "SHOW TABLES FROM order_db LIKE '_orders_%';"34# 检查残留的触发器5mysql -e "SHOW TRIGGERS FROM order_db;" | grep '_orders_'67# 手动清理(按顺序!先删触发器再删影子表)8mysql -e "9 DROP TRIGGER IF EXISTS order_db.pt_osc_order_db_orders_ins;10 DROP TRIGGER IF EXISTS order_db.pt_osc_order_db_orders_upd;11 DROP TRIGGER IF EXISTS order_db.pt_osc_order_db_orders_del;12 DROP TABLE IF EXISTS order_db._orders_new;13"1screen -S pt_osc_orders2pt-online-schema-change --alter "..." --execute3# Ctrl+A, D 离开 screen,放心关掉终端1-- MySQL 8.0 Instant DDL,毫秒级完成2ALTER TABLE orders ADD COLUMN remark VARCHAR(500) DEFAULT '', ALGORITHM=INSTANT;1# 前提:需要在主库上创建校验结果表(pt-table-checksum 会自动创建)2# 还需要一个有足够权限的用户3mysql -e "4 GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, PROCESS, SUPER, REPLICATION SLAVE5 ON *.* TO 'pt_checksum'@'%' IDENTIFIED BY 'CheckPass123!';6 FLUSH PRIVILEGES;7"89# 校验所有数据库的所有表10pt-table-checksum \11 --host=127.0.0.1 \12 --port=3306 \13 --user=pt_checksum \14 --password='CheckPass123!' \15 --databases=order_db,user_db \16 --no-check-binlog-format \17 --replicate=percona.checksums1819# 输出示例:20# TS ERRORS DIFFS ROWS DIFF_ROWS CHUNKS SKIPPED TIME TABLE21# 05-06T10:30:01 0 0 50000000 0 50000 0 125.023 order_db.orders22# 05-06T10:32:15 0 2 12000000 1523 12000 0 52.456 order_db.order_items23# 05-06T10:33:01 0 0 3000000 0 3000 0 15.234 user_db.users24# 05-06T10:33:12 0 0 500000 0 500 0 3.456 user_db.user_addresses1# 在从库上查询不一致的 chunk2mysql -h slave-01 -e "3 SELECT db, tbl, chunk, chunk_index,4 lower_boundary, upper_boundary,5 this_crc, master_crc,6 this_cnt, master_cnt7 FROM percona.checksums8 WHERE master_cnt <> this_cnt9 OR master_crc <> this_crc;10"1112# +----------+-------------+-------+-------------+-----------------+-----------------+-----------+------------+----------+------------+13# | db | tbl | chunk | chunk_index | lower_boundary | upper_boundary | this_crc | master_crc | this_cnt | master_cnt |14# +----------+-------------+-------+-------------+-----------------+-----------------+-----------+------------+----------+------------+15# | order_db | order_items | 3456 | PRIMARY | 3456001 | 3457000 | 0xab12cd34| 0xef56ab78 | 998 | 1000 |16# | order_db | order_items | 7890 | PRIMARY | 7890001 | 7891000 | 0x12345678| 0x9abcdef0 | 1000 | 1000 |17# +----------+-------------+-------+-------------+-----------------+-----------------+-----------+------------+----------+------------+1pt-table-checksum \2 --host=127.0.0.1 \3 --user=pt_checksum \4 --password='CheckPass123!' \5 --databases=order_db \6 --max-lag=2s \7 --check-interval=5 \8 --chunk-size=500 \9 --no-check-binlog-format \10 --replicate=percona.checksums1112# 如果从库延迟超过 2 秒,pt-table-checksum 会暂停,等延迟降下来再继续13# Pausing because Slave_lag on slave-01 is 3s which is longer than --max-lag 2s.14# Waiting for slave-01 to catch up...15# Resumed.1pt-table-checksum \2 --host=127.0.0.1 \3 --user=pt_checksum \4 --password='CheckPass123!' \5 --databases=order_db \6 --max-lag=1s \7 --check-interval=5 \8 --chunk-size=500 \9 --chunk-time=0.5 \10 --set-vars="innodb_lock_wait_timeout=10,lock_wait_timeout=60" \11 --no-check-binlog-format \12 --replicate=percona.checksums \13 --run-time=36001--set-vars="binlog_format=STATEMENT"1# 第一步:先 --print 看看会生成什么修复 SQL(不会真的执行)2pt-table-sync \3 --replicate=percona.checksums \4 --sync-to-master \5 --host=slave-01 \6 --user=pt_checksum \7 --password='CheckPass123!' \8 --databases=order_db \9 --tables=order_items \10 --print1112# 输出修复 SQL:13# REPLACE INTO `order_db`.`order_items`(`item_id`,`order_id`,`product_id`,`quantity`,`price`) VALUES (3456789, 1234567, 8001, 2, 99.00);14# REPLACE INTO `order_db`.`order_items`(`item_id`,`order_id`,`product_id`,`quantity`,`price`) VALUES (3456790, 1234567, 8002, 1, 199.00);15# UPDATE `order_db`.`order_items` SET `quantity`=3, `price`=79.00 WHERE `item_id`=7890456 LIMIT 1;1617# 第二步:确认 SQL 没问题后,真正执行修复18pt-table-sync \19 --replicate=percona.checksums \20 --sync-to-master \21 --host=slave-01 \22 --user=pt_checksum \23 --password='CheckPass123!' \24 --databases=order_db \25 --tables=order_items \26 --execute2728# 第三步:再跑一次 pt-table-checksum 确认修复成功29pt-table-checksum \30 --host=127.0.0.1 \31 --user=pt_checksum \32 --password='CheckPass123!' \33 --databases=order_db \34 --tables=order_items \35 --no-check-binlog-format \36 --replicate=percona.checksums3738# DIFFS = 0,修复成功!1# 杀掉所有执行超过 60 秒的 SELECT 查询2pt-kill \3 --host=127.0.0.1 \4 --user=root \5 --password='YourPassword' \6 --busy-time=60 \7 --match-command='Query' \8 --match-info='SELECT' \9 --kill \10 --print \11 --interval=5 \12 --daemonize \13 --log=/var/log/pt-kill.log1415# 也可以只匹配特定用户16pt-kill --match-user='report_user' --busy-time=300 --kill1718# 只打印不杀(先确认下会杀哪些)19pt-kill --busy-time=60 --print --no-kill --iterations=11# 当 Threads_running 超过 50 时,自动采集各种诊断信息2pt-stalk \3 --function=status \4 --variable=Threads_running \5 --threshold=50 \6 --collect-tcpdump \7 --collect-gdb \8 --dest=/var/lib/pt-stalk \9 --retention-time=72 \10 --daemonize \11 --log=/var/log/pt-stalk.log1213# 采集到的文件会保存在 /var/lib/pt-stalk/14# 包括:mysqladmin status、SHOW PROCESSLIST、iostat、vmstat、tcpdump 等15# 之后用 pt-sift 来分析这些采集数据16pt-sift /var/lib/pt-stalk/1# 实时查看磁盘 IO2pt-diskstats34# 分析之前保存的 iostat 数据5pt-diskstats --group-by=disk /var/lib/pt-stalk/2026_05_06_10_30_01-diskstats1# 在主库上启动心跳写入(每秒写一次)2pt-heartbeat --update --host=master --user=root --password='Pass' --database=percona --create-table --daemonize34# 在从库上监控延迟5pt-heartbeat --monitor --host=slave-01 --user=root --password='Pass' --database=percona6# 输出:7# 0.00s [ 0.00s, 0.00s, 0.00s ]8# 0.00s [ 0.00s, 0.00s, 0.00s ]9# 0.51s [ 0.17s, 0.06s, 0.02s ] <-- 出现延迟了10# 0.00s [ 0.13s, 0.05s, 0.02s ]1# 推荐的执行方式2screen -S pt_task3pt-online-schema-change ... --execute 2>&1 | tee /var/log/pt-osc-orders-$(date +%Y%m%d%H%M%S).log1# auto:让 PT 自动选择(推荐)2pt-online-schema-change --alter-foreign-keys-method=auto ...34# rebuild_constraints:在子表上重建外键指向新表(小表适用)5# drop_swap:不 RENAME,直接 DROP 原表 + RENAME 新表(有短暂的表不存在时间)1# 方法 1:让 pt-table-checksum 的会话使用 STATEMENT 格式2pt-table-checksum --set-vars="binlog_format=STATEMENT" ...34# 方法 2:跳过检查(不推荐,可能得到错误结果)5pt-table-checksum --no-check-binlog-format ...1pt-kill --busy-time=300 --match-command=Query --kill --iterations=1 ...1# 清理 7 天前的校验结果2mysql -e "DELETE FROM percona.checksums WHERE ts < NOW() - INTERVAL 7 DAY;"1CREATE USER 'percona_toolkit'@'%' IDENTIFIED BY 'StrongPass123!';2GRANT ALL PRIVILEGES ON *.* TO 'percona_toolkit'@'%';3-- 生产环境应该给更精细的权限,这里为了方便先给 ALL4-- 最低权限要求:5-- pt-query-digest: 不需要数据库权限(只读日志文件)6-- pt-osc: PROCESS, SUPER, REPLICATION SLAVE, 以及目标表的所有 DML/DDL 权限7-- pt-table-checksum: SELECT, INSERT, UPDATE, DELETE, CREATE, PROCESS, SUPER, REPLICATION SLAVE