Percona Toolkit 3.5.7 完整实战笔记(从入门到精通)

基于 MySQL 8.0.35 GTID 级联主从环境(141主 → 142从 → 143从),全场景实测验证

  • 主库:192.168.195.141
  • 从库:192.168.195.142(141的从库)
  • 级联从库:192.168.195.143(142的从库)
  • root密码:Root@123456
  • 复制用户:repl / 123456

目录

  • [1. Percona Toolkit 概述](#1. Percona Toolkit 概述)
  • [2. 安装部署](#2. 安装部署)
  • [3. DSN 数据源说明](#3. DSN 数据源说明)
  • [4. 必须掌握的工具(实战+原理)](#4. 必须掌握的工具(实战+原理))
    • [4.1 pt-archiver --- 数据归档](#4.1 pt-archiver — 数据归档)
    • [4.2 pt-query-digest --- 慢查询分析](#4.2 pt-query-digest — 慢查询分析)
    • [4.3 pt-table-checksum --- 主从数据一致性校验](#4.3 pt-table-checksum — 主从数据一致性校验)
    • [4.4 pt-table-sync --- 修复从库不一致数据](#4.4 pt-table-sync — 修复从库不一致数据)
    • [4.5 pt-online-schema-change --- DDL变更工具](#4.5 pt-online-schema-change — DDL变更工具)
  • [5. 建议掌握的工具(实战)](#5. 建议掌握的工具(实战))
    • [5.1 pt-config-diff --- 参数对比](#5.1 pt-config-diff — 参数对比)
    • [5.2 pt-find --- 数据库对象查找](#5.2 pt-find — 数据库对象查找)
    • [5.3 pt-kill --- KILL连接](#5.3 pt-kill — KILL连接)
    • [5.4 pt-mysql-summary --- MySQL实例信息采集](#5.4 pt-mysql-summary — MySQL实例信息采集)
    • [5.5 pt-pmp --- 进程堆栈汇总](#5.5 pt-pmp — 进程堆栈汇总)
    • [5.6 pt-show-grants --- 账号授权信息](#5.6 pt-show-grants — 账号授权信息)
    • [5.7 pt-slave-find --- 主从拓扑图](#5.7 pt-slave-find — 主从拓扑图)
    • [5.8 pt-summary --- 服务器信息采集](#5.8 pt-summary — 服务器信息采集)
    • [5.9 pt-slave-restart --- 复制错误自动跳过](#5.9 pt-slave-restart — 复制错误自动跳过)
    • [5.10 pt-stalk --- 问题现场信息采集](#5.10 pt-stalk — 问题现场信息采集)
    • [5.11 pt-upgrade --- 版本兼容性检测](#5.11 pt-upgrade — 版本兼容性检测)
  • [6. 常见故障模拟及解决](#6. 常见故障模拟及解决)
  • [7. 知识点补充与生产场景](#7. 知识点补充与生产场景)
  • [8. 工具速查表](#8. 工具速查表)

1. Percona Toolkit 概述

Percona Toolkit 是 Percona 公司提供的一个 MySQL 工具包,包含大量实用的 MySQL 管理工具。熟练使用 Percona Toolkit 是 MySQL DBA 必备技能之一。

工具分类:

分类 工具 说明
必须掌握 pt-archiver 数据归档
必须掌握 pt-query-digest 分析慢查询
必须掌握 pt-table-checksum 检查主从数据一致性
必须掌握 pt-table-sync 修复从库不一致数据
必须掌握 pt-online-schema-change DDL变更工具
建议掌握 pt-config-diff 参数对比
建议掌握 pt-find 数据库对象查找
建议掌握 pt-kill KILL连接
建议掌握 pt-mysql-summary 采集MySQL实例基本信息
建议掌握 pt-pmp 汇总进程堆栈信息
建议掌握 pt-show-grants 打印账号授权信息
建议掌握 pt-slave-find 打印主从拓扑图
建议掌握 pt-summary 采集服务器基本信息
建议掌握 pt-slave-restart 复制错误自动跳过重启
建议掌握 pt-stalk 问题现场实时采集
建议掌握 pt-upgrade 版本升级兼容性检测

官方文档: https://docs.percona.com/percona-toolkit/index.html


2. 安装部署

2.1 环境前提

bash 复制代码
# 确认 Perl 环境(MySQL 8.0 二进制包环境)
which perl
# /usr/bin/perl

rpm -q perl-DBD-MySQL perl-DBI perl-ExtUtils-MakeMaker
# perl-DBD-MySQL-4.046-6.ky10.x86_64
# perl-DBI-1.643-5.ky10.x86_64

2.2 RPM包安装(推荐,生产环境首选)

bash 复制代码
# 执行节点:141(主库)
cd /usr/local/src

# 优先国内镜像(清华),失败则用官方源
# 官方下载地址(实测可用)
wget https://downloads.percona.com/downloads/percona-toolkit/3.5.7/binary/redhat/7/x86_64/percona-toolkit-3.5.7-1.el7.x86_64.rpm -O pt.rpm

# 安装依赖
yum install -y perl-ExtUtils-MakeMaker perl-DBD-MySQL perl-DBI

# 本地安装
yum localinstall -y pt.rpm

验证安装:

bash 复制代码
which pt-query-digest pt-archiver pt-table-checksum
# /usr/bin/pt-query-digest
# /usr/bin/pt-archiver
# /usr/bin/pt-table-checksum

pt-query-digest --version
# pt-query-digest 3.5.7

注意: MySQL 的 mysqlmysqladminmysqldump 命令需要加入 PATH,否则 pt-stalk 等工具无法调用:

bash 复制代码
ln -sf /usr/local/mysql/bin/mysql /usr/local/bin/mysql
ln -sf /usr/local/mysql/bin/mysqladmin /usr/local/bin/mysqladmin
ln -sf /usr/local/mysql/bin/mysqldump /usr/local/bin/mysqldump

2.3 源码安装(备选)

bash 复制代码
cd /usr/local/
wget https://downloads.percona.com/downloads/percona-toolkit/3.5.7/source/tarball/percona-toolkit-3.5.7.tar.gz -O percona-toolkit-3.5.7.tar.gz
tar xvf percona-toolkit-3.5.7.tar.gz
cd percona-toolkit-3.5.7/
yum install perl-ExtUtils-MakeMaker perl-DBD-MySQL
perl Makefile.PL
make
make install

踩坑提示: 源码安装过程中需要下载 go 依赖包,如提示 dial tcp 142.251.43.17:443: i/o timeout,可设置国内 go 代理:

bash 复制代码
go env -w GOPROXY=https://goproxy.cn,direct

2.4 创建连接配置(生产推荐)

bash 复制代码
# 创建 /root/.my.cnf,避免命令行暴露密码
cat > /root/.my.cnf << 'EOF'
[client]
user=root
password=Root@123456
host=127.0.0.1
port=3306
EOF
chmod 600 /root/.my.cnf

3. DSN 数据源说明

DSN(Data Source Name)在 Percona Toolkit 中比较常见,可理解为目标实例相关配置的缩写。

缩写 含义
A 默认的字符集
D 库名
F 只从给定文件中读取配置信息
P 端口
S 用于连接的socket文件
h 主机名
p 密码
t 表名
u 用户名

示例:

复制代码
h=192.168.195.141,P=3306,u=root,p=Root@123456,D=sbtest,t=sbtest1

解读:连接 192.168.195.141 的 3306 端口,用户 root,密码 Root@123456,操作 sbtest 库的 sbtest1 表。


4. 必须掌握的工具(实战+原理)

4.1 pt-archiver --- 数据归档

功能: 将数据从源表归档到目标表/文件,或仅删除数据。

基本语法:

bash 复制代码
pt-archiver [OPTIONS] --source DSN --dest DSN --where WHERE
4.1.1 实现原理
  1. 工具首先从源库查询一条记录,然后插入到目标库中,目标库插入成功,才会从源库中删除该记录。这样就不会出现数据被删除但还没有归档的情况。
  2. 操作的执行顺序如下:
    • (1)源库查询记录
    • (2)目标库插入记录
    • (3)源库删除记录
    • (4)目标库 COMMIT
    • (5)源库 COMMIT
  3. 每次删除都是基于主键。
  4. --where 参数中的条件会传递到 SELECT 操作中。
4.1.2 测试数据准备
sql 复制代码
-- 在主库(141)执行,自动复制到142/143
CREATE DATABASE sbtest;
USE sbtest;

CREATE TABLE sbtest1 (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  k INT NOT NULL DEFAULT 0,
  c CHAR(120) NOT NULL DEFAULT '',
  pad CHAR(60) NOT NULL DEFAULT '',
  PRIMARY KEY (id), KEY k_1 (k)
) ENGINE=InnoDB;

CREATE TABLE sbtest2 LIKE sbtest1;
CREATE TABLE sbtest3 LIKE sbtest1;

-- 用递归CTE插入10万行测试数据
SET SESSION cte_max_recursion_depth=110000;
INSERT INTO sbtest1(id,k,c,pad)
WITH RECURSIVE seq AS (SELECT 1 AS n UNION ALL SELECT n+1 FROM seq WHERE n < 100000)
SELECT n, FLOOR(1+RAND(n)*100000),
  CONCAT('data-',n,'-aaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
  CONCAT('pad-',n,'-bbbbbbbbbbbbbbbbbbbbb') FROM seq;

INSERT INTO sbtest2 SELECT * FROM sbtest1;
INSERT INTO sbtest3 SELECT * FROM sbtest1 WHERE id<=50000;

-- 验证
SELECT COUNT(*) FROM sbtest1;  -- 100000
SELECT COUNT(*) FROM sbtest2;  -- 100000
SELECT COUNT(*) FROM sbtest3;  -- 50000

注意: 归档目标端(143)需要创建归档库,且如果是 --bulk-insert 模式(LOAD DATA INFILE),目标库需开启 local_infile

sql 复制代码
-- 在143执行
SET GLOBAL super_read_only=OFF;
SET GLOBAL local_infile=ON;
CREATE DATABASE arch;
CREATE TABLE arch.sbtest1 LIKE sbtest.sbtest1;
4.1.3 场景一:基础归档(逐行模式)
bash 复制代码
# 执行节点:141
pt-archiver \
  --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --dest   h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest1 \
  --where "id>1000 AND id<=2000" --limit 200 --commit-each --progress 500 --statistics

参数说明:

  • --source:源端相关信息
  • --dest:目标端相关信息
  • --where:归档条件,"1=1" 代表归档全表
  • --limit:每批归档的记录数
  • --commit-each:对于每一批记录,只会 COMMIT 一次
  • --progress:显示进度信息,单位行数
  • --statistics:输出统计信息

输出结果:

复制代码
Started at 2026-08-16T18:44:17, ended at 2026-08-16T18:44:17
Source: D=sbtest,P=3306,h=192.168.195.141,p=...,t=sbtest1,u=root
Dest:   D=arch,P=3306,h=192.168.195.143,p=...,t=sbtest1,u=root
SELECT 1000
INSERT 1000
DELETE 1000
Action         Count       Time        Pct
inserting       1000     0.4171      71.46
deleting        1000     0.1195      20.47
commit            12     0.0069       1.19
select             6     0.0020       0.34
other              0     0.0382       6.54

验证:

bash 复制代码
# 执行节点:141
/usr/local/mysql/bin/mysql -uroot -p'Root@123456' -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 99000  ← 源表减少了1000行

/usr/local/mysql/bin/mysql -uroot -p'Root@123456' -h192.168.195.143 -N -e "SELECT COUNT(*) FROM arch.sbtest1;"
# 1000  ← 归档端增加了1000行

# 从库也同步删除(复制正常)
/usr/local/mysql/bin/mysql -uroot -p'Root@123456' -h192.168.195.142 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 99000
4.1.4 场景二:批量归档(--bulk-delete --bulk-insert)
bash 复制代码
# 执行节点:141
pt-archiver \
  --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --dest   h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest1 \
  --where "id>2000 AND id<=6000" --bulk-delete --bulk-insert --limit 1000 --commit-each --progress 1000 --statistics

参数说明:

  • --bulk-delete:批量删除(用一条 DELETE ... WHERE id IN (...) 代替逐行删除)
  • --bulk-insert:归档数据以 LOAD DATA INFILE 的方式导入到归档库中(比逐行INSERT快很多)

输出结果:

复制代码
SELECT 4000
INSERT 4000
DELETE 4000

验证:

bash 复制代码
# 源表减少4000行
mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 92000

# 归档端增加4000行
mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM arch.sbtest1;"
# 5000

注意: --bulk-insert 使用 LOAD DATA LOCAL INFILE,目标库的 local_infile 参数需设置为 ON:

sql 复制代码
SET GLOBAL local_infile=ON;
SELECT @@local_infile;  -- 1
4.1.5 场景三:只删除不归档(--purge)
bash 复制代码
# 执行节点:141
pt-archiver \
  --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --where "id>6000 AND id<=8000" --bulk-delete --limit 1000 --commit-each \
  --purge --primary-key-only --statistics

参数说明:

  • --purge:只删除,不归档(不指定 --dest)
  • --primary-key-only:如果只是执行删除操作,建议加上,这样在执行 SELECT 操作时只会查询主键,不会查询所有列,提升性能

输出结果:

复制代码
SELECT 2000
INSERT 0
DELETE 2000
Action             Count       Time        Pct
bulk_deleting          2     0.0074      44.19
commit                 3     0.0028      16.71
select                 3     0.0013       7.51
other                  0     0.0053      31.59

验证:

bash 复制代码
mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 97000  ← 又减少了2000行
4.1.6 场景四:归档到文件(--file)
bash 复制代码
# 执行节点:141
mkdir -p /data/archive
pt-archiver \
  --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --where "id>8000 AND id<=9000" --bulk-delete --limit 500 --commit-each \
  --file '/data/archive/%Y-%m-%d-%D.%t' --statistics

参数说明:

  • --file:归档到文件,格式支持 %Y(年)、%m(月)、%d(日)、%D(库名)、%t(表名)等占位符

输出结果:

复制代码
Action             Count       Time        Pct
bulk_deleting          2     0.0070      40.58
commit                 3     0.0017       9.78
select                 3     0.0009       5.50
print_file          1000    -0.0002      -1.43
other                  0     0.0079      45.57

验证:

bash 复制代码
# 文件行数
wc -l < /data/archive/*.sbtest1
# 1000

# 文件内容(TSV格式,制表符分隔)
head -2 /data/archive/*.sbtest1
# 8001    87028   data-8001-aaaaaaaaaaaaaaaaaaaaaaaaaaaaa    pad-8001-bbbbbbbbbbbbbbbbbbbbb
# 8002    12047   data-8002-aaaaaaaaaaaaaaaaaaaaaaaaaaaaa    pad-8002-bbbbbbbbbbbbbbbbbbbbb

# 源表减少1000行
mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 96000
4.1.7 场景五:检查从库延迟(--check-slave-lag)
bash 复制代码
# 执行节点:141
pt-archiver \
  --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --where "id>9000 AND id<=11000" --bulk-delete --limit 500 --commit-each \
  --primary-key-only --purge \
  --check-slave-lag h=192.168.195.142,P=3306,u=root,p='Root@123456' --statistics

参数说明:

  • --check-slave-lag:归档时检查指定从库的延迟情况。如果有多个从库需要检查,需将 --check-slave-lag 指定多次,每次对应一个从库。如果从库延迟超过阈值(默认1s),则暂停归档操作直到复制恢复。

输出结果:

复制代码
SELECT 2000
DELETE 2000
4.1.8 场景六:--dry-run 预检
bash 复制代码
# 执行节点:141
pt-archiver \
  --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --dest   h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest1 \
  --where "id>11000 AND id<=11200" --dry-run --limit 100

参数说明:

  • --dry-run:只打印待执行的SQL,不实际执行。常用于实际操作之前,校验待执行 SQL 是否符合预期。

输出结果:

sql 复制代码
SELECT /*!40001 SQL_NO_CACHE */ `id`,`k`,`c`,`pad` FROM `sbtest`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE (id>11000 AND id<=11200) AND (`id` < '100000') ORDER BY `id` LIMIT 100
SELECT /*!40001 SQL_NO_CACHE */ `id`,`k`,`c`,`pad` FROM `sbtest`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE (id>11000 AND id<=11200) AND (`id` < '100000') AND ((`id` >= ?)) ORDER BY `id` LIMIT 100
DELETE FROM `sbtest`.`sbtest1` WHERE (`id` = ?)
INSERT INTO `arch`.`sbtest1`(`id`,`k`,`c`,`pad`) VALUES (?,?,?,?)
4.1.9 场景七:--no-delete 只复制不删源
bash 复制代码
# 执行节点:141
pt-archiver \
  --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --dest   h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest1 \
  --where "id>11000 AND id<=11200" --no-delete --limit 100 --commit-each --statistics

参数说明:

  • --no-delete:不删除源库的数据(只归档,不删除)

输出结果:

复制代码
SELECT 200
INSERT 200
DELETE 0    ← DELETE为0,说明源库数据未删除

验证:

bash 复制代码
mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90000  ← 源表行数不变

mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM arch.sbtest1;"
# 5200   ← 归档端增加200行
4.1.10 场景八:--replace 处理主键冲突
bash 复制代码
# 执行节点:141
# 先在归档端制造脏数据
mysql -h192.168.195.143 -e "UPDATE arch.sbtest1 SET c='DIRTY' WHERE id=11001;"

# 不带 --replace(预期主键冲突报错)
pt-archiver --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --dest h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest1 \
  --where "id=11001" --no-delete --limit 1 2>&1 | grep "Duplicate"
# Duplicate entry '11001' for key 'sbtest1.PRIMARY' [for Statement "INSERT INTO `arch`.`sbtest1`..."]

# 带 --replace(预期覆盖脏值)
pt-archiver --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1 \
  --dest h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest1 \
  --where "id=11001" --no-delete --replace --limit 1 --statistics
# SELECT 1
# INSERT 2   ← REPLACE操作

验证:

bash 复制代码
mysql -h192.168.195.143 -N -e "SELECT c FROM arch.sbtest1 WHERE id=11001;"
# data-11001-aaaaaaaaaaaaaa...  ← 脏值已恢复
4.1.11 场景九:--columns 指定列归档
bash 复制代码
# 执行节点:141
# 归档端表结构必须包含源表的所有列(含主键),但只归档指定列
mysql -h192.168.195.143 -e "SET SESSION sql_log_bin=0; DROP TABLE IF EXISTS arch.sbtest2; CREATE TABLE arch.sbtest2 (id INT, k INT, c CHAR(120), pad CHAR(60));"

pt-archiver --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest2 \
  --dest h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest2 \
  --where "id<=100" --no-delete --columns k,c,pad --limit 50 --commit-each --statistics
# SELECT 100
# INSERT 100

验证:

bash 复制代码
mysql -h192.168.195.143 -N -e "SELECT * FROM arch.sbtest2 LIMIT 2;"
# NULL    40541   data-1  pad-1    ← id列为NULL(未归档),只归档了k,c,pad
# NULL    65559   data-2  pad-2

注意: 使用 --columns 时,如果源表有自增列且源/目标表的自增列存在交集,可不归档自增列。但目标表必须包含源表的所有列名(包括主键列),否则报错 The following columns exist in --source but not --dest

4.1.12 场景十:--safe-auto-increment 自增主键保护
bash 复制代码
# 执行节点:141
# 默认 --safe-auto-increment:不删除自增主键最大的那一行
pt-archiver --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest3 \
  --dest h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest3 \
  --where "1=1" --bulk-delete --limit 10000 --commit-each --statistics
# SELECT 49999
# INSERT 49999
# DELETE 49999   ← 只删除了49999行,最大id那行保留

mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest3;"
# 1   ← 保留了最大id那行

# --no-safe-auto-increment:删除全部(含最大id行)
pt-archiver --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest3 \
  --dest h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest3 \
  --where "1=1" --bulk-delete --limit 10000 --commit-each --no-safe-auto-increment --statistics
# SELECT 1
# INSERT 1
# DELETE 1

mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest3;"
# 0   ← 全部删除
4.1.13 --ignore 跳过冲突
bash 复制代码
# 执行节点:141
# 在目标库执行的INSERT语句中加上IGNORE,遇到主键冲突时跳过
pt-archiver --source h=192.168.195.141,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest2 \
  --dest h=192.168.195.143,P=3306,u=root,p='Root@123456',D=arch,t=sbtest2 \
  --where "id<=100" --no-delete --columns k,c,pad --limit 50 --commit-each --ignore --statistics
# SELECT 100
# INSERT 0    ← 全部被IGNORE跳过(因为数据已存在)
4.1.14 参数汇总表
参数 说明
--source 源端DSN
--dest 目标端DSN(不指定则需--purge或--file)
--where 归档条件
--limit 每批归档的记录数
--commit-each 每批COMMIT一次
--bulk-delete 批量删除
--bulk-insert 以LOAD DATA INFILE方式导入
--purge 只删除不归档
--file 归档到文件
--no-delete 不删除源库数据
--replace 目标库执行REPLACE而非INSERT
--ignore 目标库INSERT加IGNORE
--columns 归档指定列
--primary-key-only 只查询主键列(删除场景优化)
--dry-run 只打印SQL不执行
--progress 显示进度
--statistics 输出统计信息
--check-slave-lag 检查从库延迟
--[no]safe-auto-increment 是否保护自增主键最大行
4.1.15 推荐的归档方式

生产环境推荐的归档流程:

  1. 先归档,但不删除源库的数据--no-delete
  2. 比对源库和归档库的数据是否一致 (用 pt-table-sync --print
  3. 如果比对结果一致,再删除源库的归档数据--purge
bash 复制代码
# 步骤1:归档但不删除
pt-archiver --source ... --dest ... --where "create_time < '2024-01-01'" \
  --no-delete --bulk-insert --limit 1000 --commit-each

# 步骤2:比对一致性
pt-table-sync --print --sync-to-master ... 

# 步骤3:确认一致后删除源库
pt-archiver --source ... --where "create_time < '2024-01-01'" \
  --purge --bulk-delete --limit 1000 --commit-each --primary-key-only

4.2 pt-query-digest --- 慢查询分析

功能: 对多种日志进行汇总分析,如 binlog、general log、tcpdump、slowlog、SQL 等,最常用的是对慢日志进行分析。

基本语法:

bash 复制代码
pt-query-digest [OPTIONS] [FILES]
4.2.1 慢日志格式说明
复制代码
# Time: 2022-09-02T22:51:41.160855+08:00
# User@Host: root[root] @ localhost []     Id:   2021
# Query_time: 5.388106    Lock_time: 0.000004 Rows_sent: 7   Rows_examined: 5081822
SET timestamp=1662130295;
SELECT title, ROUND(AVG(salary), 2) as avg_salary
FROM titles t JOIN salaries s ON s.emp_no = t.emp_no
GROUP BY title
ORDER BY avg_salary DESC;
字段 含义
Time SQL 结束时间
Id Processlist id
Query_time 查询时间
Lock_time 锁等待时间
Rows_sent 返回给客户端的行数
Rows_examined 存储引擎中检索的行数
SET timestamp SQL 的执行时间
4.2.2 测试数据准备
sql 复制代码
-- 在主库(141)执行
-- 开启慢查询日志
SET GLOBAL slow_query_log=ON;
SET GLOBAL long_query_time=1;          -- 超过1秒的查询记录到慢日志
SET GLOBAL log_queries_not_using_indexes=ON;  -- 未使用索引的查询也记录
SELECT @@slow_query_log_file;
-- /data/mysql/3306/data/MYSQL141-slow.log

-- 制造慢SQL
USE employees;
SELECT title, ROUND(AVG(salary),2) avg_sal FROM titles t JOIN salaries s ON t.emp_no=s.emp_no GROUP BY title ORDER BY avg_sal DESC;
SELECT SQL_NO_CACHE * FROM dept_emp WHERE dept_no='d005' AND from_date>'1990-01-01' ORDER BY emp_no;
SELECT SQL_NO_CACHE COUNT(*) FROM salaries a JOIN salaries b ON a.emp_no=b.emp_no+5000 JOIN titles t ON a.emp_no=t.emp_no WHERE t.title LIKE '%Engineer%';
SELECT SQL_NO_CACHE * FROM salaries ORDER BY RAND() LIMIT 10;

USE sbtest;
SELECT SQL_NO_CACHE pad, COUNT(*) FROM sbtest1 GROUP BY pad ORDER BY 2 DESC LIMIT 5;
SELECT SQL_NO_CACHE c FROM sbtest2 WHERE c LIKE '%data-99999%' ORDER BY k DESC;
4.2.3 场景一:基础慢日志分析
bash 复制代码
# 执行节点:141
pt-query-digest /data/mysql/3306/data/MYSQL141-slow.log

输出结果解读:

复制代码
# 80ms user time, 0 system time, 38.14M rss, 246.48M vsz
# Current date: Sun Aug 16 18:45:47 2026
# Hostname: MYSQL141
# Files: /data/mysql/3306/data/MYSQL141-slow.log
# Overall: 8 total, 6 unique, 0.67 QPS, 0.05x concurrency ______________
# Time range: 2026-08-16T18:45:35 to 2026-08-16T18:45:47
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time          631ms     8ms   315ms    79ms   308ms    92ms    75ms
# Lock time           12us     1us     2us     1us     1us       0     1us
# Rows sent            138       1     100   17.25   97.36   30.55    6.98
# Rows examine     438.20k  19.54k  97.66k  54.77k  97.04k  27.99k  38.40k
# Query size           810      60     130  101.25  124.25   23.98  115.80

Overall 部分(总体统计):

  • 8 total:总共8条慢查询
  • 6 unique:6个唯一查询(指纹去重后)
  • 0.67 QPS:每秒查询数
  • 0.05x concurrency:并发度

Attribute 部分(属性统计):

  • Exec time:执行时间(total/min/max/avg/95%/stddev/median)

  • Lock time:锁等待时间

  • Rows sent:返回行数

  • Rows examine:扫描行数

  • Query size:查询语句大小

    Profile

    Rank Query ID Response time Calls R/Call V/M

    ==== =================================== ============= ===== ====== ====

    1 0x999CFE998D101109E343C230C02A48A8 0.3150 49.9% 1 0.3150 0.00 SELECT sbtest?

    2 0x573A716569FD99453F2B3D7974972A7C 0.2419 38.3% 3 0.0806 0.00 SELECT titles salaries

    3 0x0885DAAD16AA961AA0007DF2B88411E0 0.0385 6.1% 1 0.0385 0.00 SELECT sbtest?

    4 0x64C04464A0B56612FDADCC540F989553 0.0148 2.3% 1 0.0148 0.00 SELECT sbtest?

    MISC 0xMISC 0.0210 3.3% 2 0.0105 0. <2 ITEMS>

Profile 部分(查询概要):

含义
Rank 排名,越靠前,代表总的执行时间越长
Query ID 每一类 SQL 的 fingerprint(指纹)
Response time 总执行时间及执行时间占比
Calls 执行次数
R/Call 平均执行时间
V/M 平均方差(Variance-to-Mean ratio),值越大说明查询时间越不稳定
复制代码
# Query 1: 0 QPS, 0x concurrency, ID 0x999CFE998D101109E343C230C02A48A8 at byte 2069
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: all events occurred at 2026-08-16T18:45:35
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count         12       1
# Exec time     49   315ms   315ms   315ms   315ms   315ms       0   315ms
# Lock time     16     2us     2us     2us     2us     2us       0     2us
# Rows sent      3       5       5       5       5       5       0       5
# Rows examine  20  87.90k  87.90k  87.90k  87.90k  87.90k       0  87.90k
# Query size    10      83      83      83      83      83       0      83
# String:
# Databases    sbtest
# Hosts        localhost
# Users        root
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms
#  10ms
# 100ms  ################################################################
#    1s

Query 详情部分:

  • pct:该查询在某属性上占总体的百分比
  • Query_time distribution:查询时间分布直方图,# 越多代表该时间段的查询越多
4.2.4 场景二:--since / --until 分析指定时间段
bash 复制代码
# 执行节点:141
pt-query-digest --since "2026-08-16 18:45:00" --until "2026-08-16 18:46:00" /data/mysql/3306/data/MYSQL141-slow.log

输出结果:

复制代码
# Overall: 8 total, 6 unique, 0.67 QPS, 0.05x concurrency ______________
# Time range: 2026-08-16T18:45:35 to 2026-08-16T18:45:47

只分析 18:45:00 ~ 18:46:00 之间的慢查询。格式推荐 YYYY-MM-DD [HH:MM:SS]

4.2.5 场景三:--type=genlog 分析 General Log
bash 复制代码
# 执行节点:141
# 开启general log
mysql -uroot -p'Root@123456' -e "SET GLOBAL general_log=ON;"
mysql -uroot -p'Root@123456' employees -e "SELECT COUNT(*) FROM salaries; SELECT * FROM titles LIMIT 1;"
mysql -uroot -p'Root@123456' -e "SET GLOBAL general_log=OFF;"

GENLOG=$(mysql -uroot -p'Root@123456' -N -e "SELECT @@general_log_file;")
pt-query-digest --type genlog $GENLOG

输出结果:

复制代码
# Overall: 9 total, 6 unique, 0 QPS, 0x concurrency ______________________
# Profile
# Rank Query ID                            Response time Calls R/Call V/M 
#    1 0x0E7680C04FF2596BE3A3649C5FAC418D   0.0000  0.0%     2 0.0000  0.00 SELECT
#    5 0x8089DBBA1D2606CAAC215D9D2C7DD498   0.0000  0.0%     1 0.0000  0.00 SELECT salaries
#    6 0x534FF89E6F6E7EE48CC5F6769E792627   0.0000  0.0%     1 0.0000  0.00 SELECT titles
4.2.6 场景四:--limit 限制输出
bash 复制代码
# 执行节点:141
# 按总的查询时间排序,只输出指定比例的分析结果,默认95%
pt-query-digest --limit 90% /data/mysql/3306/data/MYSQL141-slow.log

输出结果:

复制代码
# Rank Query ID                            Response time  Calls R/Call V/M
# MISC 0xMISC                                1.1570  0.5%   137 0.0084   0.0 <36 ITEMS>

--limit 默认为 95%,即按总查询时间排序,输出累计占比 95% 的查询。设为 90% 则只输出前 90%。

4.2.7 常用参数汇总表
参数 说明
--since 分析指定时间之后的慢SQL,格式 YYYY-MM-DD [HH:MM:SS]
--until 分析指定时间之前的慢SQL
--type 指定分析类型:binlog/genlog/slowlog/tcpdump/rawlog,默认 slowlog
--limit 按总查询时间排序,只输出指定比例/数量,默认 95%
--order-by 排序字段,默认 Query_time:sum(按总查询时间)
--output 输出格式:report/slowlog/json/json-anon/secure-slowlog,默认 report
--[no]report 是否输出分析结果
--report-all 指定 --review 时,对已review过的SQL也输出

4.3 pt-table-checksum --- 主从数据一致性校验

功能: 校验主从数据一致性。

基本语法:

bash 复制代码
pt-table-checksum [OPTIONS] DSN
4.3.1 实现原理
  1. innodb_lock_wait_timeout session 值设置为1。如果 checksum 操作与业务 SQL 出现了锁争用,会直接回滚 checksum 操作。
  2. binlog_format session 会话值设置为 STATEMENT,这样的话,checksum 对应的 REPLACE INTO 操作才会在从库执行(从库重算CRC)。
  3. 创建 percona.checksums 表,保存主从数据一致性校验的结果。
  4. 选取分片键,一般为主键或唯一索引。
  5. 分 chunk 进行 checksum 操作:
sql 复制代码
REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary,
upper_boundary, this_cnt, this_crc) SELECT 'sbtest', 'sbtest1', '1', 'PRIMARY',
'1', '385073', COUNT(*) AS cnt,
COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `k`, convert(`c` using
utf8mb4), convert(`pad` using utf8mb4))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM
`sbtest`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) AND ((`id` <=
'385073')) /*checksum chunk*/
  1. 每检验完一张表,都会在从库进行一次查询,比对 this_crc 和 master_crc:
sql 复制代码
SELECT CONCAT(db, '.', tbl) AS `table`, chunk, ...,
COALESCE(this_cnt-master_cnt, 0) AS cnt_diff,
COALESCE(this_crc <> master_crc OR ISNULL(master_crc) <> ISNULL(this_crc), 0) AS crc_diff
FROM `percona`.`checksums`
WHERE (master_cnt <> this_cnt OR master_crc <> this_crc ...)

核心原理: REPLACE INTO...SELECT 语句以 STATEMENT 格式记录到 binlog,从库回放时用本地数据重新计算 CRC 值,写入 this_crc 列;主库随后 UPDATE master_crc 列为主库的计算值。通过比对 this_crc(从库算)和 master_crc(主库算)判断是否一致。

4.3.2 输出结果说明
复制代码
            TS ERRORS  DIFFS     ROWS  DIFF_ROWS  CHUNKS SKIPPED    TIME TABLE
08-16T20:10:16      0      1    90049          0       4       0   0.518 sbtest.sbtest1
含义
TS 时间戳
ERRORS 校验过程中的错误数
DIFFS 主从数据不一致的 chunk 数量。如果该列不为0,则意味着主从存在不一致
ROWS 表被校验的记录数
DIFF_ROWS 不一致的行数
CHUNKS chunk 的个数
SKIPPED 跳过的 chunk 个数
TIME 校验表所花费的时间
TABLE 校验的表名
4.3.3 场景一:实例级校验
bash 复制代码
# 执行节点:141(主库)
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --no-check-binlog-format --no-check-replication-filters \n  --recursion-method=hosts --set-vars binlog_format=STATEMENT

参数说明:

  • --no-check-binlog-format:不检查从库的 binlog_format 是否为 row
  • --no-check-replication-filters:不检查复制过滤规则
  • --recursion-method=hosts:通过 SHOW SLAVE HOSTS 发现从库
  • --set-vars binlog_format=STATEMENT关键参数! 设置 session 级 binlog_format=STATEMENT,确保 checksum 语句以 STATEMENT 格式记录到 binlog,从库才能重算 CRC

重要踩坑: MySQL 8.0 默认 binlog_format=ROW。如果不加 --set-vars binlog_format=STATEMENT,REPLACE INTO...SELECT 语句会以 ROW 格式记录到 binlog,从库直接应用主库预计算的 CRC 值,导致 this_crc = master_crc永远检测不到差异

4.3.4 场景二:基于指定库的校验
bash 复制代码
# 执行节点:141
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --no-check-binlog-format --recursion-method=hosts \n  --set-vars binlog_format=STATEMENT \n  --databases sbtest,employees

多个库之间用逗号隔开。

4.3.5 场景三:基于指定表的校验
bash 复制代码
# 执行节点:141
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --no-check-binlog-format --recursion-method=hosts \n  --set-vars binlog_format=STATEMENT \n  --tables sbtest.sbtest1
4.3.6 场景四:制造差异并检测
bash 复制代码
# 执行节点:141(通过远程连142制造差异)
# 在142上直接修改数据(绕过复制)
mysql -h192.168.195.142 -e "SET SESSION sql_log_bin=0; UPDATE sbtest.sbtest1 SET remark='DIFF-ON-142' WHERE id=500;"

# 验证差异存在
mysql -h192.168.195.142 -N -e "SELECT remark FROM sbtest.sbtest1 WHERE id=500;"
# DIFF-ON-142

mysql -h192.168.195.141 -N -e "SELECT remark FROM sbtest.sbtest1 WHERE id=500;"
# (空)

# 执行校验
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --no-check-binlog-format --no-check-replication-filters \n  --recursion-method=hosts --databases=sbtest --tables=sbtest1 \n  --set-vars binlog_format=STATEMENT

输出结果:

复制代码
            TS ERRORS  DIFFS     ROWS  DIFF_ROWS  CHUNKS SKIPPED    TIME TABLE
08-16T20:10:16      0      1    90049          0       4       0   0.518 sbtest.sbtest1

DIFFS=1,检测到不一致! 查看详细差异:

sql 复制代码
-- 在从库(142)执行
SELECT chunk, this_cnt, master_cnt, this_crc, master_crc, (this_crc<>master_crc) AS DIFF
FROM percona.checksums WHERE tbl='sbtest1';
复制代码
chunk   this_cnt  master_cnt  this_crc   master_crc  DIFF
1       1000      1000        8d5ba7e1   ab7ad75c    1    ← chunk1 不一致!
2       89049     89049       d0e1a89e   d0e1a89e    0
3       0         0           0          0           0
4       0         0           0          0           0
4.3.7 场景五:--explain 只打印不执行
bash 复制代码
# 执行节点:141
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --no-check-binlog-format --recursion-method=hosts \n  --databases=sbtest --tables=sbtest3 --explain

输出结果:

sql 复制代码
REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary, upper_boundary, this_cnt, this_crc) SEL...
4.3.8 场景六:--replicate-check-only
bash 复制代码
# 执行节点:141
# 直接根据 percona.checksums 表的内容输出主从差异结果,不做任何校验
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --no-check-binlog-format --replicate-check-only --recursion-method=hosts
4.3.9 级联从库注意事项

重要: 在级联复制架构(141→142→143)中,142 的 binlog_format=ROWlog_slave_updates=ON。当 142 回放来自 141 的 STATEMENT 格式 checksum 语句时,会以 ROW 格式重新记录到自己的 binlog。这意味着 143 收到的是 ROW 事件(预计算值),143 无法检测自身的本地篡改

正确做法:

  • 检测 141 vs 142 的差异:在 141 上执行 checksum,正常工作 ✅
  • 检测 142 vs 143 的差异:需在 142 上执行 checksum(142 作为"主",143 作为"从"),或使用 pt-table-sync --sync-to-master 直接检测 143
4.3.10 常用参数汇总表
参数 说明
--no-check-binlog-format 不检查从库 binlog_format
--no-check-replication-filters 不检查复制过滤规则
--recursion-method 从库发现方法:hosts/processlist/none/dsn=DSN
--recurse 递归层级,默认1(只查直连从库)
--set-vars 设置 session 变量,如 binlog_format=STATEMENT
--databases 指定校验的库
--tables 指定校验的表
--replicate-check-only 只根据 checksums 表输出差异,不执行校验
--explain 只打印 checksum 操作,不实际执行
--quiet 只输出 error/warning/不一致信息
--chunk-size chunk 大小,默认 1000
--chunk-time chunk 查询时间,默认 0.5s(基于时间自动调节 chunk 大小)
--max-load 基于数据库负载自动调节,默认 Threads_running=25
--resume 从上次终止的地方继续执行

4.4 pt-table-sync --- 修复从库不一致数据

功能: 修复主从不一致的数据。在执行 pt-table-sync 之前,无需执行 pt-table-checksum,其自带主从数据一致性校验功能。

4.4.1 实现原理
  1. 都是分 chunk 处理,chunk-size 基本上是 1000,可以缩小排它锁的锁定范围。
  2. 修改会话的事务隔离级别,计算 chunk 的 checksum 值,并对其添加排它锁。
  3. 查看当前 Binlog 的位置点。
  4. 位置点信息将作为参数传递给从库执行的 SELECT MASTER_POS_WAIT() 操作。等 MASTER_POS_WAIT 执行完,计算 slave 相同 chunk 的 checksum 值。
  5. 如果 chunk 的 checksum 值不一致,则会比对该 chunk 每一行的 checksum 值。
  6. 确定完不一致后,会在主库(而不是从库)进行修复操作。

注意: 修复操作是在主库进行的,在一主多从的架构下,即使只对其中一个 slave 进行修复操作,操作也会同步到所有 slave 上。

4.4.2 场景一:--sync-to-master 修复指定从库
bash 复制代码
# 执行节点:141
# 先在143制造差异
mysql -h192.168.195.143 -e "SET SESSION sql_log_bin=0; UPDATE sbtest.sbtest1 SET c='HACKED-BY-DBA' WHERE id=15000; DELETE FROM sbtest.sbtest1 WHERE id BETWEEN 15001 AND 15010; INSERT INTO sbtest.sbtest1(id,k,c,pad) VALUES (200001, 1, 'GHOST-ROW', 'ghost');"

# 查看差异
mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"  # 90000
mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"  # 89991

# 先 --print 打印修复语句
pt-table-sync --print --sync-to-master h=192.168.195.143,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1

# --execute 执行修复
pt-table-sync --execute --sync-to-master h=192.168.195.143,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1

参数说明:

  • --sync-to-master:修复指定从库的数据。必须指定此参数,且给定的 DSN 必须是 slave 。同时,其还会将 --wait 设置为60,--lock 设置为1。
  • --print:打印修复语句(建议修复之前先打印)
  • --execute:执行修复操作
  • --dry-run:只是验证命令是否可行

验证:

bash 复制代码
mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90000  ← 行数恢复

mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1 WHERE c='HACKED-BY-DBA';"
# 0  ← 篡改数据已修复

mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1 WHERE id=200001;"
# 0  ← 幽灵行已删除

mysql -h192.168.195.143 -N -e "SELECT c FROM sbtest.sbtest1 WHERE id=15000;"
# data-15000-aaaaaaaaaaaaaa...  ← 原始数据恢复
4.4.3 场景二:--replicate 基于checksums表修复所有从库
bash 复制代码
# 执行节点:141
# 前提:必须先执行 pt-table-checksum,将主从校验结果保存到 percona.checksums 表中
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --no-check-binlog-format --recursion-method=hosts \n  --set-vars binlog_format=STATEMENT --databases=sbtest

# 基于 checksums 表修复所有 slave
# DSN 必须给 master(141)
pt-table-sync --execute --replicate percona.checksums h=192.168.195.141,P=3306,u=root,p='Root@123456'

注意: --replicate 模式下,DSN 给的是 master。这是修复所有 slave 数据的唯一方法。

4.4.4 场景三:独立环境下的数据同步
bash 复制代码
# 适用于没有主从复制关系的独立实例
pt-table-sync --execute host1 host2 host3

这里的 host1、host2、host3 必须是相对独立的,没有主从复制关系。

4.4.5 常用参数汇总表
参数 说明
--execute 执行修复操作
--print 打印修复语句
--dry-run 只验证命令是否可行
--sync-to-master 修复指定从库(DSN给slave)
--replicate 基于checksums表修复(DSN给master)
--databases 指定库
--tables 指定表
--where 指定条件
--chunk-column 分片列
--chunk-index 指定分片索引
--chunk-size chunk大小,默认1000
--wait 等待从库应用到指定位置点的时间
--timeout-ok 超时后继续执行(但一致性难保证)
--function 校验函数:CRC32(默认)/MD5/SHA1
--algorithms 校验算法:Chunk/Nibble/GroupBy/Stream

4.5 pt-online-schema-change --- DDL变更工具

功能: 在线DDL变更工具,不锁表完成表结构修改。

基本语法:

bash 复制代码
pt-online-schema-change --alter "ALTER_STATEMENT" D=dbname,t=tablename,h=host,P=port,u=user,p=password
4.5.1 实现原理
  1. 创建与原表结构相同的影子表 _tablename_new
  2. 在影子表上执行 ALTER 操作
  3. 在原表上创建3个触发器(INSERT/UPDATE/DELETE),将原表的增量数据同步到影子表
  4. 分 chunk 将原表数据复制到影子表
  5. 数据复制完成后,用 RENAME TABLE 原子交换原表和影子表
  6. 删除旧表和触发器
4.5.2 场景一:加列加索引
bash 复制代码
# 执行节点:141
# 先 --dry-run 预检
pt-online-schema-change --alter "ADD COLUMN remark VARCHAR(50) DEFAULT '', ADD INDEX idx_remark(remark)" \n  D=sbtest,t=sbtest1,h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --dry-run --print

--dry-run 输出(关键SQL):

sql 复制代码
-- 1. 创建影子表
CREATE TABLE `sbtest`.`_sbtest1_new` (...);

-- 2. 在影子表上执行ALTER
ALTER TABLE `sbtest`.`_sbtest1_new` ADD COLUMN remark VARCHAR(50) DEFAULT '', ADD INDEX idx_remark(remark)

-- 3. 创建3个触发器
SQL: CREATE TRIGGER `pt_osc_sbtest_sbtest1_del` AFTER DELETE ON `sbtest`.`sbtest1` FOR EACH ROW ...
SQL: CREATE TRIGGER `pt_osc_sbtest_sbtest1_upd` AFTER UPDATE ON `sbtest`.`sbtest1` FOR EACH ROW ...
SQL: CREATE TRIGGER `pt_osc_sbtest_sbtest1_ins` AFTER INSERT ON `sbtest`.`sbtest1` FOR EACH ROW ...

-- 4. 分chunk复制数据
SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `sbtest`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE ...
bash 复制代码
# --execute 执行DDL
pt-online-schema-change --alter "ADD COLUMN remark VARCHAR(50) DEFAULT '', ADD INDEX idx_remark(remark)" \n  D=sbtest,t=sbtest1,h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --recursion-method=none --execute --progress time,5 --statistics

踩坑提示: 在主从环境中,如果不加 --recursion-method=none,pt-osc 会尝试探测从库延迟,可能报错 Use of uninitialized value in string eq at line 4357。加 --recursion-method=none 跳过从库探测即可。生产环境如需检查从库延迟,用 --recursion-method=hosts --max-load Threads_running=50

输出结果:

复制代码
2026-08-16T20:03:58 Copied rows OK.
2026-08-16T20:03:58 Analyzing new table...
2026-08-16T20:03:58 Swapping tables...
2026-08-16T20:03:58 Swapped original and new tables OK.
2026-08-16T20:03:58 Dropping old table...
2026-08-16T20:03:58 Dropped old table `sbtest`.`_sbtest1_old` OK.
2026-08-16T20:03:58 Dropping triggers...
2026-08-16T20:03:58 Dropped triggers OK.
# Event  Count
# ====== =====
# INSERT     3
Successfully altered `sbtest`.`sbtest1`.

验证:

sql 复制代码
mysql -h192.168.195.141 -e "SHOW CREATE TABLE sbtest.sbtest1\G" | grep -E "remark|idx_remark"
--   `remark` varchar(50) DEFAULT '',
--   KEY `idx_remark` (`remark`)

mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"  -- 90000
mysql -h192.168.195.142 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"  -- 90000
4.5.3 场景二:并发写入时验证触发器同步
bash 复制代码
# 执行节点:141
# 后台持续插入+更新
( for i in $(seq 200002 200050); do
  mysql -h192.168.195.141 sbtest -e "INSERT INTO sbtest1(id,k,c,pad,remark) VALUES($i, 1, 'conc-insert', 'pad', 'CONCURRENT'); UPDATE sbtest1 SET remark='UPDATED' WHERE id=$i;"
done ) &

# 同时执行pt-osc (重建索引)
pt-online-schema-change --alter "DROP INDEX idx_remark, ADD INDEX idx_remark(remark)" \n  D=sbtest,t=sbtest1,h=192.168.195.141,P=3306,u=root,p='Root@123456' \n  --recursion-method=none --execute --statistics

wait

输出结果:

复制代码
2026-08-16T20:04:10 Copying approximately 88821 rows...
2026-08-16T20:04:11 Swapped original and new tables OK.
# INSERT                 4
Successfully altered `sbtest`.`sbtest1`.

验证(并发数据无丢失):

bash 复制代码
mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1 WHERE remark='UPDATED';"
# 49   ← 49条并发插入+更新的数据完整保留

mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90049  ← 总行数正确,无丢失

说明: 49条并发插入的行,每行先以 CONCURRENT 插入,随后被 UPDATE 改为 UPDATED。pt-osc 的触发器正确同步了这些增量变更,最终49条全部为 UPDATED,数据完整无丢失。✅

4.5.4

4.2 pt-query-digest --- 慢查询分析

功能: 对多种日志进行汇总分析,如 binlog、general log、tcpdump、slowlog、SQL 等,最常用的还是对慢日志进行分析。

4.2.1 基本用法
bash 复制代码
# 执行节点:141
pt-query-digest /data/mysql/3306/data/MYSQL141-slow.log
4.2.2 开启慢日志并制造测试数据
sql 复制代码
-- 在主库(141)执行
SET GLOBAL slow_query_log=ON;
SET GLOBAL long_query_time=1;              -- 超过1秒的查询记入慢日志
SET GLOBAL log_queries_not_using_indexes=ON; -- 未使用索引的查询也记入慢日志
SELECT @@slow_query_log_file;
-- /data/mysql/3306/data/MYSQL141-slow.log
sql 复制代码
-- 制造慢SQL(employees库)
SELECT title, ROUND(AVG(salary),2) avg_sal FROM titles t JOIN salaries s ON t.emp_no=s.emp_no GROUP BY title ORDER BY avg_sal DESC;
SELECT SQL_NO_CACHE COUNT(*) FROM salaries a JOIN salaries b ON a.emp_no=b.emp_no+5000 JOIN titles t ON a.emp_no=t.emp_no WHERE t.title LIKE '%Engineer%';
SELECT SQL_NO_CACHE * FROM salaries ORDER BY RAND() LIMIT 10;
SELECT SQL_NO_CACHE c FROM sbtest1 WHERE k BETWEEN 1 AND 50000 ORDER BY c DESC LIMIT 100;
4.2.3 输出结果详解
bash 复制代码
# 执行节点:141
pt-query-digest /data/mysql/3306/data/MYSQL141-slow.log

第一部分:Overall(总览)

复制代码
# Overall: 8 total, 6 unique, 0.67 QPS, 0.05x concurrency ______________
# Time range: 2026-08-16T18:45:35 to 2026-08-16T18:45:47
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time          631ms     8ms   315ms    79ms   308ms    92ms    75ms
# Lock time           12us     1us     2us     1us     1us       0     1us
# Rows sent            138       1     100   17.25   97.36   30.55    6.98
# Rows examine     438.20k  19.54k  97.66k  54.77k  97.04k  27.99k  38.40k
# Query size           810      60     130  101.25  124.25   23.98  115.80

字段说明:

  • total:8 total(共8条慢查询),6 unique(6种不同的SQL),0.67 QPS,0.05x concurrency(并发度)
  • Exec time:执行时间,总631ms,最小8ms,最大315ms,平均79ms,95%为308ms
  • Lock time:锁等待时间
  • Rows sent:返回给客户端的行数
  • Rows examine:存储引擎中检索的行数(扫描行数)
  • Query size:SQL字节数

第二部分:Profile(SQL排名)

复制代码
# Rank Query ID                            Response time Calls R/Call V/M
# ==== =================================== ============= ===== ====== ====
#    1 0x999CFE998D101109E343C230C02A48A8   0.3150 49.9%     1 0.3150  0.00 SELECT sbtest?
#    2 0x573A716569FD99453F2B3D7974972A7C   0.2419 38.3%     3 0.0806  0.00 SELECT titles salaries
#    3 0x0885DAAD16AA961AA0007DF2B88411E0   0.0385  6.1%     1 0.0385  0.00 SELECT sbtest?
#    4 0x64C04464A0B56612FDADCC540F989553   0.0148  2.3%     1 0.0148  0.00 SELECT sbtest?
# MISC 0xMISC                               0.0210  3.3%     2 0.0105   0.0 <2 ITEMS>

字段说明:

  • Rank:排名,越靠前代表总的执行时间越长
  • Query ID:每一类SQL的 fingerprint(指纹),相同SQL模板归为同一ID
  • Response time:总的执行时间及占比(49.9% = 占所有慢查询执行时间的49.9%)
  • Calls:执行次数
  • R/Call:平均执行时间
  • V/M:平均方差(Variance to Mean ratio),值越大说明执行时间波动越大,可能存在不稳定

第三部分:Query Detail(单条SQL详情)

复制代码
# Query 1: 0 QPS, 0x concurrency, ID 0x999CFE998D101109E343C230C02A48A8 at byte 2069
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count         12       1
# Exec time     49   315ms   315ms   315ms   315ms   315ms       0   315ms
# Rows examine  20  87.90k  87.90k  87.90k  87.90k  87.90k       0  87.90k
# Query size    10      83      83      83      83      83       0      83
# String:
# Databases    sbtest
# Hosts        localhost
# Users        root
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms
#  10ms
# 100ms  ################################################################
#    1s

关键字段:

  • pct:该指标占所有查询的百分比
  • Query_time distribution:查询时间分布直方图,# 越多说明该时间段的查询越多
4.2.4 常用参数

--since / --until:分析指定时间段的慢SQL

bash 复制代码
# 执行节点:141
pt-query-digest --since "2026-08-16 18:45:00" --until "2026-08-16 18:46:00" /data/mysql/3306/data/MYSQL141-slow.log

格式推荐 YYYY-MM-DD [HH:MM:SS]

--type:指定分析的类型

bash 复制代码
# 可选值:binlog, genlog, slowlog, tcpdump, rawlog。默认为 slowlog。

--type=genlog 分析General Log:

bash 复制代码
# 执行节点:141
# 先开启general log
mysql -e "SET GLOBAL general_log=ON;"
mysql employees -e "SELECT COUNT(*) FROM salaries; SELECT * FROM titles LIMIT 1;"
mysql -e "SET GLOBAL general_log=OFF;"

GENLOG=$(mysql -N -e "SELECT @@general_log_file;")
pt-query-digest --type genlog $GENLOG

输出:

复制代码
# Overall: 9 total, 6 unique, 0 QPS, 0x concurrency ______________________
# Profile
# Rank Query ID                            Response time Calls R/Call V/M
#    1 0x0E7680C04FF2596BE3A3649C5FAC418D   0.0000  0.0%     2 0.0000  0.00 SELECT
#    5 0x8089DBBA1D2606CAAC215D9D2C7DD498   0.0000  0.0%     1 0.0000  0.00 SELECT salaries
#    6 0x534FF89E6F6E7EE48CC5F6769E792627   0.0000  0.0%     1 0.0000  0.00 SELECT titles

--limit:按总查询时间排序,只输出指定比例或数量

bash 复制代码
# 默认为 95%(按总查询时间排序输出前95%的分析结果)
pt-query-digest --limit 90% /data/mysql/3306/data/MYSQL141-slow.log
# MISC 0xMISC  1.1570  0.5%  137 0.0084  0.0 <36 ITEMS>

--order-by:排序方式

默认值 Query_time:sum,按照总的查询时间输出结果。可改为 Query_time:cnt(按执行次数)等。

--output:输出格式

默认为 report,其它选项有:slowlogjsonjson-anonsecure-slowlog

--noreport:是否输出查询分析结果

当指定 --history--review 时,可通过 --no-report 屏蔽输出。

--report-all:输出已review过的SQL

当指定 --review 时,对于已经 review 过的SQL不会再次输出。如果要让其输出,可指定 --report-all

4.2.5 参数汇总表
参数 说明
--since 分析指定时间之后的慢SQL
--until 分析指定时间之前的慢SQL
--type 指定分析类型:binlog/genlog/slowlog/tcpdump/rawlog
--limit 输出比例或数量,默认95%
--order-by 排序方式,默认Query_time:sum
--output 输出格式:report/slowlog/json/json-anon
--[no]report 是否输出分析结果
--report-all 输出已review过的SQL
--review 将分析结果存入数据库表
--history 将分析结果存入数据库表(保留历史)

4.3 pt-table-checksum --- 主从数据一致性校验

功能: 校验主从数据一致性。

4.3.1 实现原理
  1. innodb_lock_wait_timeout session 值设置为1。如果 checksum 操作与业务 SQL 出现了锁争用,会直接回滚 checksum 操作。

  2. binlog_format session 会话值设置为 STATEMENT,这样的话,checksum 对应的 REPLACE INTO 操作才会在从库执行(从库重算CRC)。

  3. 创建 percona.checksums 表,保存主从数据一致性校验的结果。

  4. 选取分片键,一般为主键或唯一索引。

  5. 分 chunk 进行 checksum 操作:

    sql 复制代码
    REPLACE INTO `percona`.`checksums` (db, tbl, chunk, chunk_index, lower_boundary,
    upper_boundary, this_cnt, this_crc) SELECT 'sbtest', 'sbtest1', '1', 'PRIMARY',
    '1', '385073', COUNT(*) AS cnt,
    COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(CONCAT_WS('#', `id`, `k`, convert(`c` using
    utf8mb4), convert(`pad` using utf8mb4))) AS UNSIGNED)), 10, 16)), 0) AS crc FROM
    `sbtest`.`sbtest1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) AND ((`id` <=
    '385073')) /*checksum chunk*/
  6. 每检验完一张表,都会在从库进行一次查询,比对 this_crc(从库算的)和 master_crc(主库算的)。

核心原理: 主库执行 REPLACE INTO ... SELECT CRC32(...) 计算每个chunk的CRC值,该语句以STATEMENT格式记录到binlog,从库回放时用从库本地数据重新计算CRC值。如果从库数据与主库不一致,从库算出的 this_crc 就会与主库的 master_crc 不同。

4.3.2 基本用法
bash 复制代码
# 执行节点:141(主库)
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --no-check-replication-filters \
  --recursion-method=hosts --databases=sbtest

输出结果:

复制代码
            TS ERRORS  DIFFS     ROWS  DIFF_ROWS  CHUNKS SKIPPED    TIME TABLE
08-16T18:46:07      0      0    90000          0       4       0   0.486 sbtest.sbtest1
08-16T18:46:07      0      0   100000          0       1       0   0.464 sbtest.sbtest2
08-16T18:46:07      0      0    50000          0       1       0   0.392 sbtest.sbtest3

输出结果说明:

  • DIFFS:主从数据不一致的 chunk 的数量。如果该列不为0,则意味着主从存在不一致的地方。
  • ERRORS:校验过程中的错误数
  • ROWS:表被校验的记录数
  • CHUNKS:chunk的个数
  • SKIPPED:跳过的chunk的个数
  • TIME:校验表所花费的时间
  • TABLE:校验的表名
4.3.3 ⚠️ 关键踩坑:ROW格式下DIFFS永远为0

问题描述: 在 MySQL 8.0 默认 binlog_format=ROW 的环境下,pt-table-checksum 的 DIFFS 列永远显示为0,即使从库数据确实与主库不一致。

根因分析:

pt-table-checksum 的核心原理是:checksum 语句(REPLACE INTO ... SELECT CRC32(...)) 必须以 STATEMENT 格式记录到binlog,这样从库回放时才会用本地数据重新计算CRC值。

但在级联复制环境中(141→142→143),中间从库142的 binlog_format=ROW + log_slave_updates=ON

  • 142回放来自141的STATEMENT格式checksum语句时,会以ROW格式重新记录到自己的binlog
  • 143(级联从库)收到的是ROW事件,直接应用主库预计算的CRC值,不会在本地重新计算
  • 因此143的 this_crc = master_crc,永远检测不出差异

验证实验:

bash 复制代码
# 1. 在142制造差异(直连从库)
mysql -h192.168.195.142 -e "SET SESSION sql_log_bin=0; UPDATE sbtest.sbtest1 SET remark='DIFF-ON-142' WHERE id=500;"

# 2. 不加 --set-vars binlog_format=STATEMENT 跑checksum
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts --databases=sbtest
# 结果:DIFFS=0(错误!实际有差异)

# 3. 加 --set-vars binlog_format=STATEMENT 跑checksum
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --no-check-replication-filters \
  --recursion-method=hosts --databases=sbtest --tables=sbtest1 \
  --set-vars binlog_format=STATEMENT

正确输出:

复制代码
            TS ERRORS  DIFFS     ROWS  DIFF_ROWS  CHUNKS SKIPPED    TIME TABLE
08-16T20:10:16      0      1    90049          0       4       0   0.518 sbtest.sbtest1
                                                    ^^^^
                                            DIFFS=1,成功检测到差异!

查看142的checksums表(从库本地算的CRC ≠ 主库CRC):

sql 复制代码
-- 在142执行
SELECT chunk, this_cnt, master_cnt, this_crc, master_crc, (this_crc<>master_crc) AS DIFF
FROM percona.checksums WHERE tbl='sbtest1';
复制代码
chunk   this_cnt  master_cnt  this_crc   master_crc  DIFF
1       1000      1000        8d5ba7e1   ab7ad75c    1    ← 不一致!
2       89049     89049       d0e1a89e   d0e1a89e    0
3       0         0           0          0           0
4       0         0           0          0           0

生产建议:binlog_format=ROW 的环境下,务必加 --set-vars binlog_format=STATEMENT 参数,否则 checksum 语句在从库不会重算CRC,导致永远检测不出差异。

4.3.4 常见用法

实例级校验:

bash 复制代码
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts \
  --set-vars binlog_format=STATEMENT

基于指定库的校验:

bash 复制代码
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts \
  --databases sbtest,employees \
  --set-vars binlog_format=STATEMENT

多个库之间用逗号隔开。

基于指定表的校验:

bash 复制代码
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts \
  --tables sbtest.sbtest1 \
  --set-vars binlog_format=STATEMENT

--explain:只打印checksum操作,不实际执行

bash 复制代码
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts \
  --databases=sbtest --tables=sbtest3 --explain

--replicate-check-only:直接根据checksums表输出差异,不做任何校验

bash 复制代码
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts \
  --replicate-check-only
4.3.5 常用参数

过滤相关:

  • --databases--ignore-databases--databases-regex--ignore-databases-regex
  • --tables--ignore-tables--tables-regex--ignore-tables-regex
  • --columns--ignore-columns
  • --engines--ignore-engines
  • --where

主从相关:

pt-table-checksum 在运行过程中,会以固定频率(默认1s)检测从库的复制情况,如果从库停止或延迟超过一定阈值(默认1s),则会暂停当前操作,直到复制恢复。

  • --recurse:递归层级
  • --recursion-method:递归方法(hosts/processlist等)
  • --check-slave-lag--skip-check-slave-lag

性能相关:

  • --chunk-size:指定chunk的大小,默认为1000
  • --chunk-time:指定chunk的查询时间,默认为0.5s。默认使用后者,基于查询时间自动调节chunk的大小
  • --max-load:基于数据库的负载情况自动调节校验操作。默认为 Threads_running=25

输出相关:

  • --explain:只打印checksum操作,不实际执行
  • --quiet:只会输出error、warning和主从数据不一致的信息

其它参数:

  • --[no]check-binlog-format:检查从库的binlog_format是否为row。考虑级联复制场景
  • --resume:从上次终止的地方继续执行
  • --set-vars binlog_format=STATEMENT关键参数,确保checksum语句以STATEMENT格式记录

4.4 pt-table-sync --- 修复从库不一致数据

功能: 修复主从不一致的数据。在执行 pt-table-sync 之前,无需执行 pt-table-checksum,其自带主从数据一致性校验功能。

4.4.1 实现原理
  1. 都是分chunk处理,chunk-size基本上是1000,可以缩小排它锁的锁定范围。
  2. 修改会话的事务隔离级别,计算chunk的checksum值,并对其添加排它锁。
  3. 查看当前Binlog的位置点。
  4. 位置点信息将作为参数传递给从库执行的 SELECT MASTER_POS_WAIT() 操作。等 MASTER_POS_WAIT 执行完,计算slave相同chunk的checksum值。
  5. 如果chunk的checksum值不一致,则会比对该chunk每一行的checksum值。
  6. 确定完不一致后,会在主库(而不是从库)进行修复操作。

关键: 修复操作在主库执行,通过复制同步到从库。在一主多从架构下,即使只对其中一个slave进行修复操作,操作也会同步到所有slave上。

4.4.2 场景一:修复某个slave的数据(--sync-to-master)
bash 复制代码
# 执行节点:141

# 1. 先制造差异:在143直接改数据(绕过复制)
mysql -h192.168.195.143 -e "SET SESSION sql_log_bin=0; UPDATE sbtest.sbtest1 SET c='HACKED-BY-DBA' WHERE id=15000; DELETE FROM sbtest.sbtest1 WHERE id BETWEEN 15001 AND 15010; INSERT INTO sbtest.sbtest1(id,k,c,pad,remark) VALUES (200001, 1, 'GHOST-ROW', 'ghost', '');"

# 2. 验证差异
echo -n "141行数: "; mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90000
echo -n "143行数: "; mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 89991   ← 少了9行(删了10行,多了1个GHOST)

# 3. 先 --print 打印修复语句(生产推荐先打印再执行)
pt-table-sync --print --sync-to-master h=192.168.195.143,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1

# 4. --execute 执行修复
pt-table-sync --execute --sync-to-master h=192.168.195.143,P=3306,u=root,p='Root@123456',D=sbtest,t=sbtest1

参数说明:

  • --sync-to-master:修复指定从库的数据。必须指定此参数,且给定的DSN必须是 slave 。同时,其还会将 --wait 设置为60,--lock 设置为1。
  • --execute:执行修复操作
  • --print:打印修复语句(建议修复之前先打印)
  • --dry-run:只是验证命令是否可行

验证:

bash 复制代码
echo -n "141行数: "; mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90000
echo -n "143行数: "; mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90000  ← 恢复一致
echo -n "143 HACKED行(应0): "; mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1 WHERE c='HACKED-BY-DBA';"
# 0
echo -n "143 GHOST行(应0): "; mysql -h192.168.195.143 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1 WHERE id=200001;"
# 0
echo -n "143 id=15000 c值: "; mysql -h192.168.195.143 -N -e "SELECT c FROM sbtest.sbtest1 WHERE id=15000;" | head -c 25
# data-15000-aaaaaaaaaaaaaa  ← 恢复原始值

注意: 修复操作是在主库进行的,在一主多从的架构下,即使只对其中一个slave进行修复操作,操作也会同步到所有slave上。

4.4.3 场景二:基于checksums表修复所有slave(--replicate)
bash 复制代码
# 执行节点:141

# 1. 先执行pt-table-checksum,将校验结果保存到percona.checksums表
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts --databases=sbtest \
  --set-vars binlog_format=STATEMENT

# 2. 制造差异
mysql -h192.168.195.142 -e "SET SESSION sql_log_bin=0; UPDATE sbtest.sbtest1 SET remark='DIFF-ON-142' WHERE id=500;"

# 3. 重新checksum捕获差异
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts --databases=sbtest --tables=sbtest1 \
  --set-vars binlog_format=STATEMENT
# DIFFS=1,成功检测到差异

# 4. 基于checksums表修复(DSN给主库141)
pt-table-sync --execute --replicate percona.checksums h=192.168.195.141,P=3306,u=root,p='Root@123456'

验证:

bash 复制代码
echo -n "142 id=500 remark(应恢复空): "; mysql -h192.168.195.142 -N -e "SELECT remark FROM sbtest.sbtest1 WHERE id=500;"
# (空)  ← 恢复一致

注意: --replicate 模式必须先执行 pt-table-checksum,将主从校验结果保存到 percona.checksums 表中,然后执行 pt-table-sync 时指定 --replicate 参数,且给定的 DSN 是 master。这是修复所有slave数据的唯一方法。

4.4.4 场景三:独立环境下的数据同步
bash 复制代码
# 这里的host1, host2, host3必须是相对独立的,没有主从复制关系
pt-table-sync --execute host1 host2 host3
4.4.5 常用参数

过滤相关:

  • --databases--ignore-databases--engines--ignore-engines
  • --tables--ignore-tables--ignore-tables-regex
  • --columns--ignore-columns--where

输出相关:

  • --execute:执行修复操作
  • --print:打印修复语句。建议修复之前先打印
  • --dry-run:只是验证命令是否可行

分片相关:

  • --chunk-column:分片列
  • --chunk-index:指定分片索引
  • --chunk-size:chunk的大小,默认1000

主从相关:

  • --sync-to-master:修复指定从库的数据。在设置此参数的情况下,只能设置一个DSN,且该DSN必须是slave
  • --wait:等待从库应用到指定位置点的时间(MASTER_POS_WAIT的第三个参数)
  • --timeout-ok:如果超时仍继续执行(但一致性难以保证)

其它参数:

  • --function:指定校验函数,默认为 CRC32,可选择 MD5 和 SHA1
  • --algorithms:指定校验算法:Chunk、Nibble、GroupBy、Stream,优先级从高到低。Chunk/Nibble适用于有主键或唯一索引的表;GroupBy/Stream适用于没有主键或唯一索引的表
  • --replicate:基于 --replicate 表中的比对结果进行修复

4.5 pt-online-schema-change --- DDL变更工具

功能: 在线DDL变更工具,不锁表完成表结构变更。

4.5.1 实现原理
  1. 创建与原表结构相同的新表 _表名_new
  2. 在新表上执行 ALTER 操作
  3. 在原表上创建3个触发器(INSERT/UPDATE/DELETE),将增量数据同步到新表
  4. 分 chunk 将原表数据复制到新表
  5. 数据复制完成后,RENAME TABLE 原子交换原表和新表
  6. 删除旧表和触发器
4.5.2 基本用法
bash 复制代码
# 执行节点:141
pt-online-schema-change --alter "ADD COLUMN remark VARCHAR(50) DEFAULT '', ADD INDEX idx_remark(remark)" \
  D=sbtest,t=sbtest1,h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --recursion-method=none --execute

⚠️ 踩坑提示: 如果不加 --recursion-method=none,在级联复制环境下会报错:

复制代码
Error while waiting for replica lag: Use of uninitialized value in string eq at /usr/bin/pt-online-schema-change line 4357.

原因是 pt-osc 默认尝试探测从库延迟,在级联环境下探测失败。解决方法是显式指定 --recursion-method=none 跳过从库检查。

4.5.3 场景一:加列加索引
bash 复制代码
# 执行节点:141

# 1. --dry-run 预检
pt-online-schema-change --alter "ADD COLUMN remark VARCHAR(50) DEFAULT '', ADD INDEX idx_remark(remark)" \
  D=sbtest,t=sbtest1,h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --recursion-method=none --dry-run --print

dry-run 输出(关键步骤):

复制代码
CREATE TABLE `sbtest`.`_sbtest1_new` (       ← 创建新表
ALTER TABLE `sbtest`.`_sbtest1_new` ADD COLUMN remark VARCHAR(50) DEFAULT '', ADD INDEX idx_remark(remark)  ← 在新表上ALTER
SQL: CREATE TRIGGER `pt_osc_sbtest_sbtest1_del` AFTER DELETE ON ...  ← 创建DELETE触发器
SQL: CREATE TRIGGER `pt_osc_sbtest_sbtest1_upd` AFTER UPDATE ON ...  ← 创建UPDATE触发器
SQL: CREATE TRIGGER `pt_osc_sbtest_sbtest1_ins` AFTER INSERT ON ...  ← 创建INSERT触发器
SELECT ... FROM `sbtest`.`sbtest1` ... LIMIT ?, 2 /*next chunk boundary*/  ← 分chunk复制
bash 复制代码
# 2. --execute 执行
pt-online-schema-change --alter "ADD COLUMN remark VARCHAR(50) DEFAULT '', ADD INDEX idx_remark(remark)" \
  D=sbtest,t=sbtest1,h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --recursion-method=none --execute --progress time,5 --statistics

输出结果:

复制代码
2026-08-16T20:03:58 Copied rows OK.
2026-08-16T20:03:58 Analyzing new table...
2026-08-16T20:03:58 Swapping tables...
2026-08-16T20:03:58 Swapped original and new tables OK.
2026-08-16T20:03:58 Dropping old table...
2026-08-16T20:03:58 Dropped old table `sbtest`.`_sbtest1_old` OK.
2026-08-16T20:03:58 Dropping triggers...
2026-08-16T20:03:58 Dropped triggers OK.
# Event  Count
# ====== =====
# INSERT     3
Successfully altered `sbtest`.`sbtest1`.

验证:

bash 复制代码
mysql -h192.168.195.141 -e "SHOW CREATE TABLE sbtest.sbtest1\G" | grep -E "remark|idx_remark"
#   `remark` varchar(50) DEFAULT '',
#   KEY `idx_remark` (`remark`)

echo -n "141行数: "; mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90000  ← 数据完整
echo -n "142行数: "; mysql -h192.168.195.142 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90000  ← 从库同步
4.5.4 场景二:并发写入时DDL(验证trigger同步增量)
bash 复制代码
# 执行节点:141

# 1. 后台持续插入+更新
( for i in $(seq 200002 200050); do
  mysql -h192.168.195.141 sbtest -e "INSERT INTO sbtest1(id,k,c,pad,remark) VALUES($i, 1, 'conc-insert', 'pad', 'CONCURRENT'); UPDATE sbtest1 SET remark='UPDATED' WHERE id=$i;"
done ) &
BG=$!

# 2. 同时执行pt-osc(删除并重建索引)
pt-online-schema-change --alter "DROP INDEX idx_remark, ADD INDEX idx_remark(remark)" \
  D=sbtest,t=sbtest1,h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --recursion-method=none --execute --statistics
# Copied rows OK.
# Swapped original and new tables OK.
# Successfully altered `sbtest`.`sbtest1`.

wait $BG

# 3. 验证并发数据完整性
echo -n "UPDATED行数(应49): "; mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1 WHERE remark='UPDATED';"
# 49    ← 每行先INSERT('CONCURRENT')再UPDATE('UPDATED'),trigger正确同步增量
echo -n "总行数(应90049): "; mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM sbtest.sbtest1;"
# 90049  ← 无数据丢失

结论: pt-osc 在 DDL 过程中,通过触发器正确同步了并发写入的增量数据,数据零丢失。

4.5.5 常用参数
参数 说明
--alter DDL语句(不含ALTER TABLE关键字)
--dry-run 预检,不实际执行
--execute 实际执行
--recursion-method=none 跳过从库检查(级联复制环境必加)
--max-load 负载阈值,超过则暂停(默认Threads_running=25)
--critical-load 临界负载,超过则终止
--chunk-size 每批复制的行数
--progress 进度显示
--statistics 输出统计信息
--alter-foreign-keys-method 处理外键的方法
--no-drop-old-table 保留旧表(默认删除)
--no-drop-triggers 保留触发器
4.5.6 pt-osc 限制
  1. 必须有主键或唯一索引:pt-osc 依赖主键进行分chunk复制
  2. 不支持外键 :除非指定 --alter-foreign-keys-method
  3. 原表不能有触发器:pt-osc 需要自己创建触发器
  4. DDL过程中会有额外空间开销:新表+旧表同时存在
  5. RENAME TABLE 阶段会有短暂锁表:但时间极短(毫秒级)

5. 建议掌握的工具(实战)

5.1 pt-config-diff --- 参数对比

功能: 对比不同参数源之间的参数差异。

场景一:实例之间的参数对比
bash 复制代码
# 执行节点:141
pt-config-diff h=192.168.195.141,P=3306,u=root,p='Root@123456' h=192.168.195.142,P=3306,u=root,p='Root@123456'

输出结果:

复制代码
13 config differences
Variable                  MYSQL141                  MYSQL142
========================= ========================= =========================
general_log_file          /data/mysql/3306/data/... /data/mysql/3306/data/...
hostname                  MYSQL141                  MYSQL142
log_queries_not_using_... ON                        OFF
long_query_time           1.000000                  10.000000
relay_log                 MYSQL141-relay-bin        MYSQL142-relay-bin
relay_log_basename        /data/mysql/3306/data/... /data/mysql/3306/data/...
relay_log_index           /data/mysql/3306/data/... /data/mysql/3306/data/...
report_host               192.168.195.141           192.168.195.142
server_id                 1                         2
server_uuid               fb3bc7fa-995e-11f1-83f... fd495181-995e-11f1-9fc...
slow_query_log            ON                        OFF
slow_query_log_file       /data/mysql/3306/data/... /data/mysql/3306/data/...
场景二:配置文件与实例对比
bash 复制代码
pt-config-diff /etc/my.cnf h=192.168.195.141,P=3306,u=root,p='Root@123456'

输出结果:

复制代码
1 config difference
Variable                  /etc/my.cnf      MYSQL141
========================= ================ ===================================
basedir                   /usr/local/mysql /usr/local/mysql-8.0.35-linux-gl...

说明: 配置文件中 basedir = /usr/local/mysql 是软链接路径,实例运行时解析为实际路径。

场景三:配置文件之间的参数对比
bash 复制代码
pt-config-diff /etc/my.cnf /etc/my_3307.cnf

5.2 pt-find --- 数据库对象查找

功能: 同 find 命令类似,只不过其查找的是数据库对象。

查看所有InnoDB引擎表
bash 复制代码
# 执行节点:141
pt-find -h192.168.195.141 -uroot -P3306 -p'Root@123456' --engine InnoDB

输出结果:

复制代码
`employees`.`dept_emp`
`employees`.`salaries`
`employees`.`titles`
`mysql`.`columns_priv`
`mysql`.`component`
`mysql`.`db`
...
对表按大小排序
bash 复制代码
pt-find -h192.168.195.141 -uroot -P3306 -p'Root@123456' --printf "%T\t%D.%N\n" | sort -rn

输出结果:

复制代码
27836416    `sbtest`.`sbtest1`
25231360    `sbtest`.`sbtest2`
13664256    `sbtest`.`sbtest3`
4767744     `employees`.`dept_emp`
1933312     `employees`.`titles`

注意: 表的大小基于 SHOW TABLE STATUS 中的 Data_length + Index_length

查看自定义函数、存储过程、触发器
bash 复制代码
pt-find -h192.168.195.141 -uroot -P3306 -p'Root@123456' --function '.*'
pt-find -h192.168.195.141 -uroot -P3306 -p'Root@123456' --procedure '.*'
pt-find -h192.168.195.141 -uroot -P3306 -p'Root@123456' --trigger '.*'

输出结果:

复制代码
`sys`.`PROCEDURE create_synonym_db`
`sys`.`PROCEDURE diagnostics`
`sys`.`FUNCTION extract_schema_from_file_name`
`sys`.`INSERT TRIGGER sys_config_insert_set_user on sys_config`
`sys`.`UPDATE TRIGGER sys_config_update_set_user on sys_config`

5.3 pt-kill --- KILL连接

功能: kill连接。

场景一:kill超过3秒的慢查询
bash 复制代码
# 执行节点:141

# 1. 模拟慢查询
mysql -h192.168.195.141 -e "SELECT SLEEP(10);" &
mysql -h192.168.195.141 -e "SELECT SLEEP(10);" &
sleep 2

# 2. 查看kill前
mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM information_schema.processlist WHERE info LIKE '%SLEEP%' AND command='Query';"
# 3

# 3. 启动pt-kill (busy-time=3, interval=2, kill+print)
timeout 6 pt-kill h=192.168.195.141,P=3306,u=root,p='Root@123456' --busy-time 3 --interval 2 --print --kill

输出结果:

复制代码
# 2026-08-16T20:05:43 KILL 183 (Query 4 sec) SELECT SLEEP(10)
# 2026-08-16T20:05:45 KILL 182 (Query 6 sec) SELECT SLEEP(10)

验证:

bash 复制代码
mysql -h192.168.195.141 -N -e "SELECT COUNT(*) FROM information_schema.processlist WHERE info LIKE '%SLEEP%' AND command='Query';"
# 1   ← 已被KILL(剩余1个为当前查询本身)

参数说明:

  • --busy-time:慢查询的阈值(秒)
  • --interval:检测间隔,每隔10s pt-kill 会执行一次 SHOW FULL PROCESSLIST
场景二:只KILL指定用户的查询
bash 复制代码
# 执行节点:141
# 创建测试用户
mysql -h192.168.195.141 -e "CREATE USER IF NOT EXISTS 'testuser'@'%' IDENTIFIED BY 'Test@123'; GRANT SELECT ON *.* TO 'testuser'@'%';"

# testuser跑慢查询
mysql -utestuser -p'Test@123' -h192.168.195.141 -e "SELECT SLEEP(10);" &
sleep 2

# 只kill testuser的查询
timeout 5 pt-kill h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --busy-time 1 --interval 2 --print --kill --match-user testuser
# # 2026-08-16T20:11:53 KILL 403 (Query 2 sec) SELECT SLEEP(10)
场景三:按SQL内容过滤KILL
bash 复制代码
# 只KILL以SELECT开头的查询
pt-kill h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --busy-time 1 --interval 2 --print --kill --match-info "(?i-xsm:^select)"
# # 2026-08-16T20:11:59 KILL 406 (Query 1 sec) SELECT SLEEP(8)
过滤维度

pt-kill 可从多个维度进行过滤:

维度 参数
User --ignore-user--match-user
Host --ignore-host--match-host
db --ignore-db--match-db
Command --ignore-command--match-command
State --ignore-state--match-state
Info --ignore-info--match-info

5.4 pt-mysql-summary --- MySQL实例信息采集

功能: 采集 MySQL 实例的基本信息。

bash 复制代码
# 执行节点:141
pt-mysql-summary h=192.168.195.141,P=3306,u=root,p='Root@123456' --all-databases

输出内容(精简):

复制代码
# Percona Toolkit MySQL Summary Report #######################
            System time | 2024-01-27 12:59:13 UTC (local TZ: CST +0800)
# Instances ##################################################
  Port    Data Directory               Nice OOM Socket
  ===== ========================== ==== === ======
   3306 /data/mysql/3306/data          0   0   /data/mysql/3306/data/mysql.sock
# Report On Port 3306 ########################################
                      User | root@localhost
                    Version | 8.0.35 MySQL Community Server - GPL
                  Databases | 5
                Processes | 3 connected, 4 running
            Replication | Is not a slave, has 2 slaves connected
# Processlist ################################################
# InnoDB #####################################################
           Buffer Pool Size | 128.0M
# Binary Logging #############################################
           binlog_format | ROW
           server_id | 1
# Configuration File #########################################
               Config File | /etc/my.cnf

该工具采集的信息非常全面,包括:实例信息、进程列表、状态计数器、表缓存、InnoDB状态、二进制日志、配置文件等。


5.5 pt-pmp --- 进程堆栈汇总

功能: 采集进程的堆栈信息,对这些堆栈信息进行汇总。

注意: 进程的堆栈信息是利用 gdb 获取的,所以在获取的过程中,会对mysql服务端的性能有一定的影响。

bash 复制代码
# 执行节点:141
pt-pmp

输出结果(精简):

复制代码
2026年 08月 16日 星期日 20:05:48 CST 2024
      9
 syscall(libc.so.6),libaio::??(libaio.so.1),LinuxAIOHandler::collect,LinuxAIOHandler::poll,
 os_aio_linux_handler,os_aio_handler,fil_aio_wait,io_handler_thread,...
      8
 pthread_cond_wait(libpthread.so.0),native_cond_wait,my_cond_wait,
 inline_mysql_cond_wait,Per_thread_connection_handler::block_until_new_connection,
 handle_connection,pfs_spawn_thread,...
      3
 pthread_cond_wait(libpthread.so.0),os_event::wait,os_event::wait_low,
 os_event_wait,srv_worker_thread,...

解读:

  • 开头的数字表示该堆栈出现的次数
  • 9 个线程在 LinuxAIOHandler(异步IO等待)------ InnoDB的IO线程
  • 8 个线程在 handle_connection(连接处理)------ MySQL工作线程
  • 3 个线程在 srv_worker_thread(后台工作线程)

默认采集的是 mysqld 进程。使用较为简单,直接执行 pt-pmp 即可。


5.6 pt-show-grants --- 账号授权信息

功能: 打印账号的授权信息。

bash 复制代码
# 执行节点:141
pt-show-grants h=192.168.195.141,P=3306,u=root,p='Root@123456'

输出结果:

复制代码
-- Grants dumped by pt-show-grants
-- Grants for 'repl'@'%'
CREATE USER IF NOT EXISTS `repl`@`%`;
ALTER USER `repl`@`%` IDENTIFIED WITH 'mysql_native_password' AS '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' ...;
GRANT REPLICATION SLAVE ON *.* TO `repl`@`%`;
-- Grants for 'testuser'@'%'
CREATE USER IF NOT EXISTS `testuser`@`%`;
GRANT SELECT ON *.* TO `testuser`@`%`;

常用选项:

bash 复制代码
# --only:只打印某些用户的授权信息
pt-show-grants h=192.168.195.141,P=3306,u=root,p='Root@123456' --only testuser
# -- Grants for 'testuser'@'%'
# CREATE USER IF NOT EXISTS `testuser`@`%`;
# GRANT SELECT ON *.* TO `testuser`@`%`;

# --revoke:打印REVOKE操作
pt-show-grants h=192.168.195.141,P=3306,u=root,p='Root@123456' --only testuser --revoke
# REVOKE SELECT ON *.* FROM `testuser`@`%`;

# --drop:在CREATE USER之前打印DROP USER操作

生产用途: 可用于备份所有账号授权信息,或在新环境快速重建账号体系。


5.7 pt-slave-find --- 主从拓扑图

功能: 打印主从拓扑图。

bash 复制代码
# 执行节点:141
pt-slave-find h=192.168.195.141,P=3306,u=root,p='Root@123456'

输出结果:

复制代码
192.168.195.141
Version         8.0.35
Server ID       1
Uptime          01:24:57 (started 2026-08-16T18:41:07)
Replication     Is not a slave, has 1 slaves connected, is not read_only
Binary logging  ROW
InnoDB version  8.0.35
+- 192.168.195.142
   Version         8.0.35
   Server ID       2
   Replication     Is a slave, has 1 slaves connected, is not read_only
   Slave status    0 seconds behind, running, no errors
   InnoDB version  8.0.35
   +- 192.168.195.143
      Version         8.0.35
      Server ID       3
      Replication     Is a slave, has 0 slaves connected, is not read_only
      Slave status    0 seconds behind, running, no errors
      InnoDB version  8.0.35

说明: +- 表示从库层级关系。本环境为 141→142→143 三级级联复制,拓扑图清晰展示了整个复制架构。


5.8 pt-summary --- 服务器信息采集

功能: 采集服务器的基本信息。

bash 复制代码
# 执行节点:141
pt-summary

输出结果(精简):

复制代码
# Percona Toolkit System Summary Report ######################
    Hostname | MYSQL141
      Uptime | 21 days,  4:59,  0 users,  load average: 0.14, 0.12, 0.05
      System | VMware, Inc.; VMware Virtual Platform; vNone (Other)
    Platform | Linux
     Release | Kylin Linux Advanced Server release V10 (Halberd)
     Kernel | 4.19.90-89.11.v2401.ky10.x86_64
Architecture | CPU = 64-bit, OS = 64-bit
# Processor ##################################################
  Processors | physical = 2, cores = 2, virtual = 2, hyperthreading = no
# Memory #####################################################
# Mounted Filesystems ########################################

该工具采集的信息包括:系统信息、CPU、内存、文件系统、磁盘、网络、进程等,是排查服务器层面问题的利器。


5.9 pt-slave-restart --- 复制错误自动跳过

功能: 监控从库的状态,如果复制因 SQL 线程重放失败而中断,则 pt-slave-restart 会跳过当前重放失败的事务,并重启复制。

⚠️ 重要限制:不支持GTID+并行复制
bash 复制代码
# 执行节点:141(测试在143从库上)
pt-slave-restart h=192.168.195.143,P=3306,u=root,p='Root@123456' --error-numbers 1032

输出结果:

复制代码
Cannot skip transactions properly because GTID is enabled and slave_parallel_workers > 0.
See 'GLOBAL TRANSACTION IDS' in the tool's documentation.

说明: MySQL 8.0 默认开启并行复制(slave_parallel_workers > 0),pt-slave-restart 不支持 GTID + 并行复制环境。

GTID模式正确的错误跳过方法
bash 复制代码
# 执行节点:143(从库)

# 1. 制造1032错误(从库删行后主库更新该行)
mysql -h192.168.195.143 -e "SET SESSION sql_log_bin=0; DELETE FROM sbtest.sbtest1 WHERE id=1;"
mysql -h192.168.195.141 -e "UPDATE sbtest.sbtest1 SET remark='trigger-1032' WHERE id=1;"

# 2. 查看错误(Slave_SQL_Running: No)
mysql -h192.168.195.143 -e "SHOW SLAVE STATUS\G" | grep -E "Slave_SQL_Running|Last_Error"
# Slave_SQL_Running: No
# Last_Error: ...Could not execute Update_rows event on table sbtest.sbtest1;
#   Can't find record in 'sbtest1', Error_code: 1032

# 3. 获取失败的GTID
mysql -h192.168.195.143 -e "SELECT * FROM performance_schema.replication_applier_status_by_worker WHERE LAST_ERROR_NUMBER<>0\G" | grep "LAST_ERROR"
# LAST_ERROR_NUMBER: 1032
# LAST_ERROR_MESSAGE: Worker 1 failed executing transaction 'fb3bc7fa-995e-11f1-83ff-000c29048d1f:214'

# 4. 手动跳过该GTID事务
mysql -h192.168.195.143 -e "
STOP SLAVE;
SET SESSION GTID_NEXT='fb3bc7fa-995e-11f1-83ff-000c29048d1f:214';
BEGIN; COMMIT;
SET SESSION GTID_NEXT='AUTOMATIC';
START SLAVE;"

# 5. 验证复制恢复
mysql -h192.168.195.143 -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Last_Error|Seconds_Behind"
# Slave_IO_Running: Yes
# Slave_SQL_Running: Yes
# Last_Error:
# Seconds_Behind_Master: 0
常用参数
参数 说明
--daemonize 后台运行,配合 --log 记录日志
--error-numbers 跳过指定错误码(如1062主键冲突、1032记录不存在)
--error-text 跳过指定错误文本
--skip-count 跳过的事务数量,默认为1
--recurse 递归层级,默认0只监控给定节点
--recursion-method 递归方法
pt-slave-restart 适用场景
  • 适用于非GTID非并行复制的旧版MySQL环境
  • MySQL 8.0 GTID + 并行复制环境下,需用 SET GTID_NEXT 手动跳过

5.10 pt-stalk --- 问题现场信息采集

功能: 在问题出现时,实时采集主机及数据库的相关信息。

基本原理
  • --function=status --variable=Threads_running --threshold=25:定义采集的触发条件,默认是 Threads_running 大于25
  • --cycles=5 --interval=1:连续检测5次,每次检测间隔1s,如果5次结果都为True,才会触发实际采集
  • --run-time=30:采集时长
  • --sleep=300:采集一次后,sleep 的时间
  • --dest:采集数据的存储目录
场景一:--no-stalk 直接采集
bash 复制代码
# 执行节点:141

# 确保 mysql/mysqladmin 在 PATH
ln -sf /usr/local/mysql/bin/mysql /usr/local/bin/mysql
ln -sf /usr/local/mysql/bin/mysqladmin /usr/local/bin/mysqladmin

mkdir -p /var/lib/pt-stalk

# --no-stalk 模式:不等待触发条件,直接采集
pt-stalk --no-stalk --run-time=5 --dest=/var/lib/pt-stalk --iterations=1 \
  -- --user=root --password='Root@123456' --host=127.0.0.1 --port=3306

踩坑提示: pt-stalk 的 MySQL 连接参数需要通过 -- 分隔符传递,否则无法连接。这是因为 pt-stalk 会把 -- 后面的参数传递给其调用的 mysql/mysqladmin 命令。

输出结果:

复制代码
2026_08_16_20_08_35 Starting /usr/bin/pt-stalk ...
2026_08_16_20_08_35 Collect 1 triggered
2026_08_16_20_08_35 Collect 1 PID 269457
2026_08_16_20_08_35 Collect 1 done
采集内容
bash 复制代码
# 采集的文件分类(37个文件)
ls /var/lib/pt-stalk/ | sed 's/.*-//' | sort | uniq -c | sort -rn
复制代码
      3 overall       ← 整体概览
      1 vmstat        ← 虚拟内存统计
      1 variables     ← MySQL变量
      1 trigger       ← 触发条件记录
      1 transactions  ← 事务信息
      1 top           ← 系统top
      1 status        ← MySQL状态
      1 processlist   ← MySQL进程列表
      1 opentables    ← 打开的表
      1 mutex-status  ← 互斥锁状态
      1 mysqladmin    ← mysqladmin输出
      1 meminfo       ← 内存信息
      1 lsof          ← 打开的文件
      1 log_error     ← 错误日志
      1 iostat        ← IO统计
      1 innodbstatus  ← InnoDB状态(2次快照)
      1 hostname      ← 主机名
      1 diskstats     ← 磁盘统计
      1 df            ← 磁盘空间
      ...

innodbstatus 样本:

bash 复制代码
head -8 /var/lib/pt-stalk/*-innodbstatus1
复制代码
*************************** 1. row ***************************
  Type: InnoDB
  Name: 
Status: 
=====================================
2026-08-16 20:08:36 139901920147200 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 28 seconds

trigger 文件(记录触发条件):

bash 复制代码
cat /var/lib/pt-stalk/*-trigger
复制代码
2026_08_16_20_08_35 Not stalking; collect triggered immediately
2026_08_16_20_08_35 pt-stalk ran with --function=status --variable=Threads_running ...
场景二:条件触发模式
bash 复制代码
# 制造高并发(Threads_running > 5)
( for i in $(seq 1 15); do mysql -e "SELECT SLEEP(8);" & done )

# 启动pt-stalk 监控 Threads_running > 5
pt-stalk --function=status --variable=Threads_running --threshold=5 --cycles=2 --interval=1 \
  --run-time=5 --dest=/var/lib/pt-stalk \
  -- --user=root --password='Root@123456' --host=127.0.0.1 --port=3306
常用参数
参数 默认值 说明
--function status 触发函数
--variable Threads_running 监控变量
--threshold 25 触发阈值
--cycles 5 连续满足次数
--interval 1 检测间隔(秒)
--run-time 30 采集时长(秒)
--sleep 300 采集后sleep时间(秒)
--dest /var/lib/pt-stalk 存储目录
--no-stalk - 不等待触发,直接采集
--mysql-only FALSE 只采集MySQL相关内容
--retention-time 30 保留天数
--collect-gdb FALSE 是否采集gdb数据(影响性能)
--collect-strace FALSE 是否采集strace数据
--collect-tcpdump FALSE 是否采集tcpdump数据

注意: gdb和strace对数据库性能影响比较大,一般不建议开启。


5.11 pt-upgrade --- 版本兼容性检测

功能: 给定一个 SQL,分别在两个不同版本的实例上执行,看看是否一致。

检测项
  • Row count:查询返回的行数是否一致
  • Row data:查询的结果是否一致
  • Warnings:是否提示 warning
  • Query time:查询时间是否在同一量级
  • Query errors:查询如果在其中一个实例中出现语法错误,会提示 Query errors
  • SQL errors:查询如果在两个实例中同时出现语法错误,会提示 SQL errors
场景一:直接对比两个实例
bash 复制代码
# 执行节点:141

# 准备测试SQL
cat > /tmp/pt_upgrade_test.sql << 'EOF'
SELECT * FROM employees.dept_emp GROUP BY dept_no;
SELECT title, ROUND(AVG(salary),2) FROM titles t JOIN salaries s ON t.emp_no=s.emp_no GROUP BY title;
EOF

# 在141和142上执行对比
pt-upgrade h=192.168.195.141,P=3306,u=root,p='Root@123456' h=192.168.195.142,P=3306,u=root,p='Root@123456' \
  --type rawlog /tmp/pt_upgrade_test.sql

输出结果:

复制代码
#-----------------------------------------------------------------------
# Hosts
#-----------------------------------------------------------------------
host1:
  DSN:       h=192.168.195.141,P=3306
  hostname:  MYSQL141
  MySQL:     MySQL Community Server - GPL 8.0.35

host2:
  DSN:       h=192.168.195.142,P=3306
  hostname:  MYSQL142
  MySQL:     MySQL Community Server - GPL 8.0.35

########################################################################
# Query class 296E46FE3AEE9B6C
########################################################################
Reporting class because it has SQL errors, but hasn't been reported yet.

## SQL errors: 1
On both hosts:
  Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column
  'employees.dept_emp.emp_no' which is not functionally dependent on columns in GROUP BY clause;
  this is incompatible with sql_mode=only_full_group_by
场景二:基准结果模式
bash 复制代码
# 1. 先生成基准测试结果
pt-upgrade h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --save-results /tmp/pt_upgrade_result --type rawlog /tmp/pt_upgrade_test.sql

# 2. 基于基准结果测试其他环境
pt-upgrade /tmp/pt_upgrade_result/ h=192.168.195.142,P=3306,u=root,p='Root@123456'
常用参数
参数 说明
--type 文件类型:slowlog/genlog/binlog/rawlog/tcpdump,默认slowlog
--[no]read-only 默认只执行SELECT和SET,需执行其它操作指定 --no-read-only
--save-results 保存基准结果到目录
--[no]create-upgrade-table 是否创建 percona_schema.pt_upgrade 表

注意事项: pt-upgrade 更适合在测试环境或开发环境使用,不建议在生产环境上使用。


6. 常见故障模拟及解决

6.1 pt-table-checksum DIFFS永远为0

故障现象: 从库数据与主库不一致,但 pt-table-checksum 的 DIFFS 列永远显示0。

根因: MySQL 8.0 默认 binlog_format=ROW,checksum 语句在级联复制时被转为ROW事件,从库直接应用主库预计算的CRC值,不重新计算。

解决:--set-vars binlog_format=STATEMENT 参数。

bash 复制代码
pt-table-checksum h=192.168.195.141,P=3306,u=root,p='Root@123456' \
  --no-check-binlog-format --recursion-method=hosts --databases=sbtest \
  --set-vars binlog_format=STATEMENT

6.2 pt-online-schema-change 报错 "uninitialized value in string eq"

故障现象:

复制代码
Error while waiting for replica lag: Use of uninitialized value in string eq at /usr/bin/pt-online-schema-change line 4357.

根因: 级联复制环境下,pt-osc 默认尝试探测从库延迟失败。

解决:--recursion-method=none 跳过从库检查。

bash 复制代码
pt-online-schema-change --alter "..." D=sbtest,t=sbtest1,h=...,P=3306,u=root,p='...' \
  --recursion-method=none --execute

6.3 pt-archiver --bulk-insert 报错或0行

故障现象: --bulk-insert 模式下归档端无数据。

根因: --bulk-insert 使用 LOAD DATA LOCAL INFILE,目标库未开启 local_infile

解决:

sql 复制代码
SET GLOBAL local_infile=ON;
SELECT @@local_infile;  -- 确认=1

6.4 pt-stalk 报错 "Cannot execute mysql"

故障现象:

复制代码
/usr/bin/pt-stalk:行1780: mysql:未找到命令
Cannot execute mysql. Check that it is in PATH.

根因: mysql/mysqladmin 不在 PATH 中。

解决:

bash 复制代码
ln -sf /usr/local/mysql/bin/mysql /usr/local/bin/mysql
ln -sf /usr/local/mysql/bin/mysqladmin /usr/local/bin/mysqladmin
ln -sf /usr/local/mysql/bin/mysqldump /usr/local/bin/mysqldump

6.5 pt-stalk 报错 "Access denied"

故障现象:

复制代码
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
Cannot connect to MySQL.

根因: pt-stalk 默认不带密码连接。

解决: 通过 -- 分隔符传递MySQL连接参数:

bash 复制代码
pt-stalk --no-stalk --run-time=5 --dest=/var/lib/pt-stalk \
  -- --user=root --password='Root@123456' --host=127.0.0.1 --port=3306

6.6 pt-slave-restart 不支持GTID+并行复制

故障现象:

复制代码
Cannot skip transactions properly because GTID is enabled and slave_parallel_workers > 0.

根因: pt-slave-restart 不支持 GTID + 并行复制环境。

解决:SET GTID_NEXT 手动跳过:

sql 复制代码
STOP SLAVE;
SET GTID_NEXT='UUID:N';  -- 失败的GTID
BEGIN; COMMIT;
SET GTID_NEXT='AUTOMATIC';
START SLAVE;

6.7 pt-archiver --columns 报错 "columns exist in --source but not --dest"

故障现象:

复制代码
The following columns exist in --source but not --dest: id

根因: 目标表缺少源表的主键列。

解决: 目标表必须包含源表的所有列名(含主键),即使 --columns 不归档该列:

sql 复制代码
CREATE TABLE arch.sbtest2 (id INT, k INT, c CHAR(120), pad CHAR(60));  -- 必须有id列

6.8 从库复制错误 1032(记录不存在)

故障场景: 从库误删数据,主库更新该行时从库回放失败。

诊断:

sql 复制代码
SELECT LAST_ERROR_NUMBER, LAST_ERROR_MESSAGE, LAST_ERROR_TRANSACTION
FROM performance_schema.replication_applier_status_by_worker
WHERE LAST_ERROR_NUMBER <> 0;

修复:

sql 复制代码
STOP SLAVE;
SET GTID_NEXT='失败的GTID';
BEGIN; COMMIT;
SET GTID_NEXT='AUTOMATIC';
START SLAVE;

然后使用 pt-table-sync 修复数据一致性。


7. 知识点补充与生产场景

7.1 pt-archiver 生产归档最佳实践

复制代码
推荐流程:
1. --no-delete 先归档不删源(数据安全)
2. pt-table-sync --print 比对源库和归档库一致性
3. 确认一致后 --purge 删除源库数据

7.2 pt-table-checksum + pt-table-sync 联合使用流程

复制代码
生产数据一致性巡检流程:
1. pt-table-checksum(加 --set-vars binlog_format=STATEMENT)校验主从一致性
2. 检查 percona.checksums 表中 DIFFS 不为0的表
3. pt-table-sync --print 打印修复SQL(人工审核)
4. pt-table-sync --execute 执行修复
5. 再次 pt-table-checksum 确认修复成功

7.3 pt-online-schema-change 生产使用要点

  1. 先 --dry-run 预检:确认SQL和执行计划正确
  2. 加 --recursion-method=none:级联复制环境必加
  3. 设置 --max-load :控制复制期间负载(如 Threads_running=50
  4. 预留磁盘空间:新表+旧表同时存在,需要约1倍表大小
  5. 低峰期执行:虽然不锁表,但数据复制仍有IO压力
  6. 验证数据完整性:执行后比对行数

7.4 pt-query-digest 生产分析流程

复制代码
1. 确认慢日志已开启:slow_query_log=ON, long_query_time=1
2. 收集一段时间的慢日志(如24小时)
3. pt-query-digest 分析慢日志
4. 重点关注 Rank 前5的SQL
5. 分析每条SQL的 Rows examine vs Rows sent(扫描行数远大于返回行数说明需要优化)
6. 针对性优化:加索引、改写SQL、调整参数

7.5 pt-kill 生产配置建议

bash 复制代码
# 生产环境推荐配置:守护进程模式
pt-kill h=...,P=3306,u=root,p='...' \
  --busy-time 60 \
  --interval 10 \
  --kill \
  --print \
  --log /var/log/pt-kill.log \
  --daemonize \
  --pid /var/run/pt-kill.pid

# 只kill SELECT查询,不影响DML
pt-kill ... --busy-time 60 --match-info "(?i-xsm:^select)" --kill --print

7.6 pt-stalk 生产使用建议

bash 复制代码
# 生产环境推荐:监控Threads_running,触发后采集30秒
pt-stalk \
  --function=status --variable=Threads_running --threshold=50 \
  --cycles=3 --interval=1 \
  --run-time=30 --sleep=300 \
  --dest=/var/lib/pt-stalk \
  --retention-time=7 \
  -- --user=root --password='...' --host=127.0.0.1 --port=3306

# 写入systemd服务实现持续监控

7.7 CRC32 校验原理深入

pt-table-checksum 使用 CRC32 函数计算每个chunk的校验值:

sql 复制代码
-- 单个chunk的校验SQL
REPLACE INTO percona.checksums (...) SELECT ...,
  COUNT(*) AS cnt,                                    -- 行数
  COALESCE(LOWER(CONV(BIT_XOR(CAST(CRC32(
    CONCAT_WS('#', `id`, `k`, `c`, `pad`)            -- 将各行拼接后CRC32
  ) AS UNSIGNED)), 10, 16)), 0) AS crc                -- BIT_XOR聚合,转16进制
FROM sbtest.sbtest1 WHERE id BETWEEN 1 AND 1000

关键点:

  • CONCAT_WS('#', ...):将每行的各列拼接成字符串
  • CRC32(...):计算每行的CRC32值
  • BIT_XOR(...):对所有行的CRC32值做异或运算(顺序无关)
  • CONV(..., 10, 16):将十进制转为十六进制

为什么用BIT_XOR? 因为异或运算满足交换律和结合律,行的顺序不影响最终结果。主库和从库只要数据内容一致,即使行物理顺序不同,CRC值也相同。

7.8 pt-table-sync 修复方向

模式 DSN指向 修复位置 适用场景
--sync-to-master slave 主库 修复单个从库
--replicate master 主库 修复所有从库
独立模式 多个独立DSN 第一个DSN 无主从关系的实例间同步

关键: 修复操作都在主库执行,通过复制同步到从库。这样保证所有从库最终一致。


8. 工具速查表

必须掌握

工具 核心命令
pt-archiver pt-archiver --source DSN --dest DSN --where "..." --bulk-delete --limit 1000 --commit-each
pt-query-digest pt-query-digest /path/to/slow.log
pt-table-checksum pt-table-checksum h=...,P=3306,u=root,p=... --no-check-binlog-format --set-vars binlog_format=STATEMENT
pt-table-sync pt-table-sync --execute --sync-to-master h=slave,...pt-table-sync --execute --replicate percona.checksums h=master,...
pt-online-schema-change pt-online-schema-change --alter "..." D=db,t=tbl,h=... --recursion-method=none --execute

建议掌握

工具 核心命令
pt-config-diff pt-config-diff DSN1 DSN2
pt-find pt-find -h... --engine InnoDB
pt-kill pt-kill h=... --busy-time 30 --interval 10 --print --kill
pt-mysql-summary pt-mysql-summary h=... --all-databases
pt-pmp pt-pmp
pt-show-grants pt-show-grants h=...
pt-slave-find pt-slave-find h=...
pt-summary pt-summary
pt-slave-restart pt-slave-restart h=... --error-numbers 1032
pt-stalk pt-stalk --no-stalk --run-time=5 -- --user=... --password=...
pt-upgrade pt-upgrade DSN1 DSN2 --type rawlog /path/to/test.sql

附录:测试环境信息

节点 IP 角色 server-id MySQL版本
141 192.168.195.141 Master 1 8.0.35
142 192.168.195.142 Slave(141的从库) 2 8.0.35
143 192.168.195.143 Slave(142的从库,级联) 3 8.0.35

连接信息:

bash 复制代码
# 主库
/usr/local/mysql/bin/mysql -uroot -h192.168.195.141 -p'Root@123456'

# 从库
/usr/local/mysql/bin/mysql -uroot -h192.168.195.142 -p'Root@123456'
/usr/local/mysql/bin/mysql -uroot -h192.168.195.143 -p'Root@123456'

Percona Toolkit 版本: 3.5.7(RPM包安装)

测试数据库:

  • sbtest:sbtest1(90000行)、sbtest2(100000行)、sbtest3(50000行)
  • employees:dept_emp(20000行)、salaries(20000行)、titles(20000行)
  • arch:归档目标库

本笔记所有场景均在 MySQL 8.0.35 GTID 级联主从环境中实测验证通过。

环境保留可继续实验。

相关推荐
梦想不只是梦与想2 小时前
MySQL 数据库(二):数据类型
数据库·mysql·数据类型
jason.zeng@15022072 小时前
服务器磁盘读写效率,网络吞吐量查看,mysql性能调优
服务器·网络·mysql
01_ice4 小时前
MySQL基本查询1
数据库·mysql
布莱克60515 小时前
理解 MySQL 架构:从连接层到存储引擎
mysql
灯澜忆梦19 小时前
【MySQL18】进阶篇 | MySQL管理
数据库·mysql
lv__pf21 小时前
spring之整合mybatis【TL spring 12】
mysql·spring·mybatis
Alex Gram1 天前
数据库同步工具PanguSync图文教程
java·mysql·postgresql·sqlserver·c#·数据库同步软件·数据库同步工具
Lethehong1 天前
MySQL迁移如何做到零改造?四层兼容方案详解
数据库·mysql·adb
竹枝溪1 天前
MySQL进阶:约束、多表设计、多表查询与事务
java·数据库·mysql·事务·子查询·acid·多表查询