Percona Toolkit中的pt-archiver是一个用于归档数据的工具,它可以有效地将旧数据移动到另一个表中,而不会对生产数据库性能产生太大影响。

https://github.com/percona/percona-toolkit

 pt-archiver的核心思路是以空间换时间,它通过创建一个临时表来存放需要保留的“热”数据,并使用触发器(Triggers) 来保证数据一致性,最后通过一次快速的表名交换来完成归档。

⚙️ 第一步:前置检查

在开始操作前,工具会先进行一轮严格的环境检查,如不满足以下任一条件将直接退出,不予执行

  • 无触发器:确保原表上当前没有定义任何触发器,以防冲突。
  • 无外键:检查是否存在外键约束,避免因外键关联导致操作失败。
  • 有主键:强制要求原表必须有主键,这是保证数据唯一性的前提。
  • Binlog格式为ROW:要求MySQL的 binlog_format 参数必须设置为 ROW,以确保在主从复制环境下,基于触发器的数据变更能被正确同步

🛠️ 第二步:创建临时表

通过环境检查后,工具会创建一个与原始表结构完全相同的空临时表(默认命名为 原表名_tmp),作为新数据的“容器”

🔗 第三步:创建触发器(核心机制)

这是重构版保证数据一致性的关键。在向临时表复制数据的过程中,原表可能会继续接收业务的读写请求。为了保证这部分增量数据不丢失,工具会在原表上创建三个 AFTER 类型的触发器,将期间所有的数据变更实时同步到临时表

这三种触发器协同工作的逻辑如下:

  • AFTER INSERT触发器:使用 REPLACE INTO 语句。当原表插入新数据时,该数据会被同步插入或覆盖到临时表。即使该数据尚未被复制,也能确保临时表包含所有新增记录
  • AFTER UPDATE触发器:同样使用 REPLACE INTO 语句。原表的数据更新会被转换为对临时表的插入或覆盖操作,从而保证数据一致
  • AFTER DELETE触发器:执行 DELETE IGNORE 操作。当原表删除数据时,如果该数据尚未被复制到临时表,则忽略删除;如果已复制,则同步删除。这确保了数据的一致性

通过这套触发器机制,临时表的数据始终与原表保持同步,无论复制过程耗时多久,都不会丢失增量数据。

📦 第四步:复制数据

部署好触发器后,工具开始分批将原表中的数据复制到临时表。工具会使用 WHERE 条件来筛选出需要保留的数据(例如近7天的记录)。

注意:为了将对线上数据库的影响降到最低,工具在复制时会采取低优先级策略(INSERT LOW_PRIORITY IGNORE,并默认在每复制完一批数据(如1000行)后主动休眠1秒

🔄 第五步:原子切换

当所有符合条件的“热数据”都复制完成后,归档工作进入最后一步。工具会执行一个原子的 RENAME TABLE 操作,将原始表与临时表的名字互换。对于上层应用而言,这个切换是瞬间完成的,几乎是无感知的。

pt-archiver核心用法实践

pt-archiver的命令格式可概括为:pt-archiver --source [源库参数] --dest [目标库参数] [可选参数],以下针对不同业务场景给出具体示例,并解释关键参数含义。

场景1:全表归档(不删除原表数据)

适用于“需保留主库数据副本,同时归档备份”的场景(如历史订单归档但主库需临时查询):

pt-archiver \
--source h=192.168.12.161,u=dba,p='Id81Gdac_a',D=martin,t=archiver_test \  # 源库信息(h=IP,u=用户,p=密码,D=库,t=表)
--dest h=192.168.12.163,u=dba,p='Id81Gdac_a',D=archiver_db,t=archiver_test \  # 归档库信息
--where '1=1' \  # 条件:全表(1=1表示所有数据)
--progress 10000 \  # 每处理10000行输出进度
--limit=10000 \  # 每次批量处理10000行(避免单次查询压力过大)
--txn-size=10000 \  # 每10000行提交一次事务(减少事务日志占用)
--no-safe-auto-increment \  # 禁用自增主键安全检查(避免归档表自增冲突)
--statistics \  # 输出操作统计信息(如处理行数、耗时)
--no-delete  # 核心参数:不删除源表数据

场景2:全表归档(删除原表数据)

适用于“主库无需保留历史数据,仅需归档”的场景(如超过1年的日志数据):

pt-archiver \
--source h=192.168.12.161,u=dba,p='Id81Gdac_a',D=martin,t=archiver_test \
--dest h=192.168.12.163,u=dba,p='Id81Gdac_a',D=archiver_db,t=archiver_test \
--where '1=1' \
--progress 10000 \
--limit=10000 \
--txn-size=10000 \
--no-safe-auto-increment \
--statistics \
--purge  # 核心参数:归档后删除源表数据(替代--no-delete)

场景3:直接删除原表数据(不归档)

适用于“历史数据无保留价值,仅需清理”的场景(如测试数据、临时日志):

pt-archiver \
--source h=192.168.12.161,u=dba,p='Id81Gdac_a',D=martin,t=archiver_test \
--where '1=1' \  # 可修改条件(如age>50)筛选特定数据删除
--progress 10000 \
--limit=10000 \
--txn-size=10000 \
--no-safe-auto-increment \
--statistics \
--purge  # 仅删除源表数据,无需--dest参数

场景4:按条件归档部分数据
实际业务中最常用的场景(如归档年龄小于30的用户数据、超过30天的日志):

# 1. 先清空归档表(避免重复数据,可选)
mysql -h192.168.12.163 -udba -p'Id81Gdac_a' -e "TRUNCATE TABLE archiver_db.archiver_test;"
# 2. 按条件归档(仅归档age<30的数据,并删除源表对应数据)
pt-archiver \
--source h=192.168.12.161,u=dba,p='Id81Gdac_a',D=martin,t=archiver_test \
--dest h=192.168.12.163,u=dba,p='Id81Gdac_a',D=archiver_db,t=archiver_test \
--where 'age<30' \  # 核心条件:筛选需归档的数据
--progress 10000 \
--limit=10000 \
--txn-size=10000 \
--no-safe-auto-increment \
--statistics \
--purge

场景5:归档数据到文件(而非数据库)

# 1. 归档为SQL文件(默认格式)
pt-archiver \
--source h=192.168.152.70,u=dba,p='Id81Gdac_a',D=martin,t=archiver_test \
--where 'age<35' \
--progress 10000 \
--limit=10000 \
--txn-size=10000 \
--no-safe-auto-increment \
--statistics \
--purge \
--file=./archiver.sql  # 输出文件路径(当前目录)
# 2. 归档为CSV格式(便于Excel分析或导入其他系统)
pt-archiver \
--source h=192.168.152.70,u=dba,p='Id81Gdac_a',D=martin,t=archiver_test \
--where '1=1' \
--progress 10000 \
--limit=10000 \
--txn-size=10000 \
--no-safe-auto-increment \
--statistics \
--purge \
--file=./archiver.csv \
--output-format=csv  # 指定输出格式为CSV

注意事项与最佳实践

  1. 操作前备份:归档/删除前务必备份关键数据,避免条件错误导致数据丢失;
  2. 测试环境验证:新脚本或命令需先在测试环境验证,确认参数正确后再推广到生产;
  3. 避免锁表:批量处理时--limit--txn-size不宜过大(建议1000-10000行),防止长事务阻塞业务;
  4. 权限最小化:生产环境中,dba用户权限可缩小至“仅操作源库和归档库”,避免全库权限泄露;
  5. 监控与告警:结合crontab告警(如执行失败发送邮件),定期检查归档日志,确保脚本正常运行。