Skip to content

pt-archiver

把 MySQL 大表里的旧数据成块搬到另一张表或文件,也可以只删不留。

语法

bash
pt-archiver [OPTIONS] --source DSN --where WHERE

--source--dest 都用 DSN 语法;如果某个 DSN 键标记为 COPY 为 yes, --dest 缺失的部分默认取 --source 中同名键的值。

数据风险

默认行为是归档后删除源表中的行--purge 更是只删不留。 所有数据库工具都可能对系统和数据库服务器造成风险,使用前请:阅读工具文档、 查看已知问题、先在非生产服务器上测试、 备份生产服务器并验证备份可用。上线前先用 --dry-run 看清它到底会执行哪些 SQL。

把 oltp_server 上的所有行归档到 olap_server,同时写一份文件:

bash
pt-archiver --source h=oltp_server,D=test,t=tbl --dest h=olap_server \
  --file '/var/log/archive/%Y-%m-%d-%D.%t'                           \
  --where "1=1" --limit 1000 --commit-each

清除(删除)子表里的孤儿行:

bash
pt-archiver --source h=host,D=db,t=child --purge \
  --where 'NOT EXISTS(SELECT * FROM parent WHERE col=child.col)'

用法示例

以下命令假定已通过选项文件或本机 socket 配置好 MySQL 连接;远程主机用 DSN(如 h=主机,D=库,t=表,不写明文密码)指定。本工具默认归档后删除源行、--purge 只删不留,所有场景务必先用 --dry-run 确认 SQL 后再正式运行。

场景:上线前先干跑确认

正式归档前用 --dry-run 打印它要执行的 SELECT/INSERT/DELETE,确认过滤条件与连接无误,不真正动数据:

bash
pt-archiver --source h=oltp_server,D=test,t=orders --dest h=olap_server,D=test,t=orders_archive \
  --where "ts < NOW() - INTERVAL 90 DAY" --limit 1000 --commit-each --dry-run

场景:冷热分离归档到历史库

把 180 天前的订单搬到 olap 历史库,源端随后删除;--commit-each 每批提交,避免长时间持有事务:

bash
pt-archiver --source h=oltp_server,D=test,t=orders --dest h=olap_server,D=test,t=orders_archive \
  --where "ts < NOW() - INTERVAL 180 DAY" --limit 1000 --commit-each

场景:归档到文件留档

把旧数据写成 LOAD DATA INFILE 格式文件留存(可被日后回灌),不写 --file 之外的目标表:

bash
pt-archiver --source h=oltp_server,D=test,t=orders \
  --file '/var/log/archive/%Y-%m-%d-%D.%t' \
  --where "ts < NOW() - INTERVAL 180 DAY" --limit 1000 --commit-each

场景:只清过期数据不保留

过期会话只删不留存,用 --purge 省略 --dest/--file

bash
pt-archiver --source h=host,D=appdb,t=sessions --purge \
  --where "expires < NOW()" --limit 1000 --commit-each

场景:只取主键列更快清除

--purge 时加 --primary-key-only,DELETE 只需主键列,避免从服务器取回整行:

bash
pt-archiver --source h=host,D=appdb,t=logs --purge --primary-key-only \
  --where "created < NOW() - INTERVAL 365 DAY" --limit 1000 --commit-each

场景:边跑边看进度与性能

--progress 每 1000 行打印一次进度,加 --statistics 退出时给出计时统计,判断瓶颈:

bash
pt-archiver --source h=oltp_server,D=test,t=orders --dest h=olap_server,D=test,t=orders_archive \
  --where "ts < NOW() - INTERVAL 180 DAY" --limit 1000 --commit-each --progress 1000 --statistics

场景:归档时控制复制延迟

主库归档怕拖垮副本,用 --check-replica-lag 观察副本、延迟超过 --max-lag 就暂停:

bash
pt-archiver --source h=oltp_server,D=test,t=orders --dest h=olap_server,D=test,t=orders_archive \
  --where "ts < NOW() - INTERVAL 180 DAY" --limit 1000 --commit-each \
  --check-replica-lag h=replica1 --max-lag 1s

场景:只归档不删源端

先用 --no-delete 把数据搬到历史库、源端保留不动,核对无误后再单独清理:

bash
pt-archiver --source h=oltp_server,D=test,t=orders --dest h=olap_server,D=test,t=orders_archive \
  --where "ts < NOW() - INTERVAL 180 DAY" --limit 1000 --commit-each --no-delete

功能说明

pt-archiver 的目标是一个低影响、只向前推进的作业,把旧数据一小块一小块地从表里"啃"出来 (nibble),尽量不影响 OLTP 查询。数据可以插入另一张表(不必在同一台服务器上), 也可以写成适合 LOAD DATA INFILE 的文件格式,或者两者都不做——那就是一次增量 DELETE。

工具还可以通过插件机制扩展,注入自己的代码实现更高级的归档逻辑, 比如归档有依赖关系的数据、应用复杂业务规则,或在归档过程中构建数据仓库。

有几个选项的取值需要仔细斟酌,最重要的是 --limit--retries--txn-size

分块取数的策略

基本策略是先找到第一批行,然后沿某个索引只向前扫描,高效地找出更多行。 后续的每条查询都不应该扫全表,而应该定位(seek)到索引中的某个位置,再往后扫到可归档的行。 用 --sourcei 部分指定索引对此可能很关键:

bash
pt-archiver --source h=my_server,D=my_database,t=my_tbl

--dry-run 查看生成的查询,并逐条 EXPLAIN 确认它们是否高效 (多数情况下你想扫的就是默认的 PRIMARY 键)。更好的办法是对比运行查询前后 Handler 状态计数器的差值,确认它不是每次都在扫全表。

seek-then-scan 优化可以用 --no-ascend--ascend-first 部分或全部关掉, 对多列键有时反而更高效。要注意 pt-archiver 的设计是从它选中的那个索引的开头开始只向前扫描, 如果你想按一个它不偏好的索引从表尾开始啃数据,可能导致很长的表扫描。

事务与提交

--txn-size 决定每个事务处理多少行,对性能至关重要:大事务更容易产生锁争用和死锁, 小事务则提交开销更频繁。--commit-each 是另一种思路——每取一批行归档完就提交, 用 --limit 控制事务大小,好处是避免在搜索更多行的漫长过程中一直持有打开的事务。 两者互斥。

批量插入与删除

--bulk-insertLOAD DATA LOCAL INFILE 一次性上传整块行,通常比逐行 INSERT 快得多; --bulk-delete 用单条 DELETE 删掉整块行。为保证数据安全,--bulk-insert 会强制启用批量删除, 这样删除一定要等到插入成功之后才发生——逐行发现就逐行删除、却还没插入目标端是不安全的。

批量删除与插件

如果源端插件的 is_archivable() 有时返回假,只有在你清楚 --bulk-delete 行为时才用它: 插件让 pt-archiver 不归档某行,该行仍然会被批量删除掉。

控制复制延迟

--check-replica-lag 指定要观察的副本(可重复指定多个),--max-lag 给出可接受的延迟上限, --check-interval 决定每次发现延迟时暂停多久(该检查每 100 行做一次)。 延迟超限或副本没在运行(延迟为 NULL)时,工具会睡一会儿再看,直到副本追上才继续取行。 这套机制可能让 --sleep--sleep-coef 变得不必要。PXC 集群另有 --max-flow-ctl, 按集群因 Flow Control 暂停的平均时间百分比来暂停工具。

Percona XtraDB Cluster

pt-archiver 可用于 PXC 5.5.28-23.7 及更新版本,但在集群上归档前要考虑三个限制:

  • 提交出错:pt-archiver 提交事务时不检查错误。PXC 上的提交是可能失败的, 而工具目前既不检查也不重试,一旦发生就会直接死掉。
  • MyISAM 表:归档 MyISAM 表能工作,但发布时 PXC 对 MyISAM 的支持仍是实验性的, 且 PXC、MyISAM 表和 AUTO_INCREMENT 列之间有若干已知 bug。因此必须确保归档不会直接或间接 导致 MyISAM 表使用默认的 AUTO_INCREMENT 值——例如用了 --columns 却没包含 AUTO_INCREMENT 列时,--dest 就会出现这种情况。工具不会替你检查这一点。
  • 非集群选项:某些选项可能不生效。例如集群节点本身不是副本时 --check-replica-lag 不起作用;PXC 的表通常是 InnoDB,而 InnoDB 不支持 INSERT DELAYED,所以 --delayed-insert 也不起作用。其他选项也可能失效而工具并不检查,因此请先在测试集群上验证。

错误处理与退出

pt-archiver 会尝试捕获信号并优雅退出:例如收到 SIGTERM(UNIX 类系统上的 Ctrl-C)时, 它会捕获信号、打印一条关于该信号的消息,然后基本正常地退出。此时它不会执行 --analyze--optimize,因为这两者可能耗时很久;其余代码照常运行, 包括调用插件的 after_finish()。换句话说,被捕获的信号会跳出主归档循环并跳过 optimize/analyze。

除信号之外,还有几种主动停止的方式:--run-time 到点退出;--sentinel 指定的哨兵文件一出现 就停止归档并退出(--stop 创建该文件、--unstop 删除它),适合优雅地停掉 cron 作业。 想知道它究竟为什么退出,用 --why-quit

输出

指定 --progress 时,输出是一行表头加上按间隔打印的状态行, 每行列出当前日期时间、pt-archiver 已运行的秒数以及已归档的行数。

指定 --statistics 时,pt-archiver 输出计时等信息,帮你判断归档过程中哪一部分最耗时:

Started at 2008-07-18T07:18:53, ended at 2008-07-18T07:18:53
Source: D=db,t=table
SELECT 4
INSERT 4
DELETE 4
Action         Count       Time        Pct
commit            10     0.1079      88.27
select             5     0.0047       3.87
deleting           4     0.0028       2.29
inserting          4     0.0028       2.28
other              0     0.0040       3.29

开头两(或三)行是起止时间以及源表和目标表,接下来三行是取回、插入、删除的行数。 其余各行是计数与计时,四列分别为动作名、该动作被计时的总次数、总耗时, 以及占程序总运行时间的百分比,按总耗时降序排列;最后一行是未明确归到任何动作上的剩余时间。 具体有哪些动作随命令行选项而变化。

选项

至少要指定 --dest--file--purge 三者之一。互斥关系:--ignore--replace--txn-size--commit-each--low-priority-insert--delayed-insert--share-lock--for-update--analyze--optimize--no-ascend--no-delete。 COPY 为 yes 的 DSN 键,--dest 中缺失时取 --source 的值。

选项说明
--analyze类型:string。结束后对 --source 和/或 --dest 执行 ANALYZE TABLE。参数是任意字符串:含字母 s 分析源表,含 d 分析目标表,可只给一个也可都给(--analyze=ds
--ascend-first只升序索引的最左列。想用升序索引优化(见 --no-ascend)但不愿承担升序一个大的多列索引的开销时用它:比完全不升序索引有显著性能提升,同时避开升序整个索引的成本
--ask-pass连接 MySQL 时交互式询问密码
--buffer缓冲写往 --file 的输出,仅在事务提交时刷盘(默认每写一行就刷)。实际上通常由操作系统按块刷盘,所以两次提交之间也可能有隐式刷盘。风险是崩溃可能丢数据;作者观察到的性能提升约 5%~15%
--[no]buffer-stdout默认开启 STDOUT 缓冲;使用 tee、kubectl logs 等后处理工具想看到实时进度时可关闭
--bulk-delete用单条 DELETE 批量删除整块行(隐含 --commit-each),删除范围是该块首行到末行(含两端)。常规做法是逐行按主键删除,批量删除可能快得多,但 WHERE 子句复杂时未必更快。该选项把所有 DELETE 处理完全推迟到整块行处理完毕,源端插件的 before_delete 不会被调用,改为稍后调用 before_bulk_delete。注意:插件判定不归档的行仍会被批量删除
--[no]bulk-delete-limit默认:yes。给 --bulk-delete 语句加上 --limit 子句。这是高级选项,不明白原理和原因就不要关;某些场合可用 --no-bulk-delete-limit 省略该子句,但 --limit 仍必须指定
--bulk-insertLOAD DATA LOCAL INFILE 批量插入每一块行(隐含 --bulk-delete --commit-each),可能比逐行 INSERT 快很多。实现方式是为每块行建一个临时文件,把行写进文件而不是逐行插入,整块结束后再上传。为保护数据安全,该选项强制使用批量删除,以保证删除等到插入成功之后才做。可与 --low-priority-insert--replace--ignore 同用,但不能与 --delayed-insert 同用。若 LOAD DATA LOCAL INFILEThe used command is not allowed with this MySQL version 之类错误,参见 DSN 的 L 选项
--channel类型:string。使用复制通道连接服务器时指定通道名。例如副本通过 chan_source_a、chan_source_b 两个通道连接两个源时,SHOW REPLICA STATUS 返回两行,工具无法判断哪个才是目标源,此时用 --channel=chan_source_a 指定在该命令中使用的通道名
-A, --charset类型:string。默认字符集(utf8 时设置 binmode/mysql_enable_utf8 并执行 SET NAMES UTF8)。只识别 MySQL 认识的字符集名,UTF8 可以但 UTF-8 不行。参见 --[no]check-charset
--[no]check-charset默认:yes。确保连接与表的字符集一致。关闭这项检查可能导致文本被错误地从一种字符集转换成另一种(通常是 utf8 转 latin1),造成数据丢失或乱码;只在确实需要做字符集转换时才关
--[no]check-columns默认:yes。确保 --source--dest 列相同。只检查源表的所有列在目标表都存在、反之亦然,不检查列顺序、数据类型等。有任何差异就报错退出;用 --no-check-columns 关闭
--check-interval类型:time;默认:1s。给了 --check-replica-lag 时,每次发现副本延迟就暂停这么长时间。该检查每 100 行执行一次
--check-replica-lag类型:string;可重复指定。暂停归档,直到该 DSN 指定的副本延迟小于 --max-lag。可多次指定以检查多个副本
--check-slave-lag类型:string;可重复指定。已废弃,未来版本会移除,请改用 --check-replica-lag
-c, --columns类型:array。以逗号分隔列出要归档的列:只取这些列、写入文件并插入目标表。指定后工具忽略其他列,除非为了升序索引或删除行需要把某些列加进 SELECT——这些额外列只在内部使用,不写入文件和目标表,但会传给插件。参见 --primary-key-only
--commit-each每取一批行归档完就提交事务并刷 --file(禁用 --txn-size,改用 --limit 控制事务大小),提交发生在取下一批行之前、以及 --sleep 睡眠之前。它不只是把 --limit--txn-size 设成同值的捷径,更重要的是避免在搜索更多行时长时间持有事务:例如用 --limit 1000 --txn-size 1000 从一张超大表开头归档,某次归档掉最后的 999 行后,下一条 SELECT 会把表的剩余部分扫完却再也找不到行,白白长时间持有了一个事务
--config类型:Array。读取逗号分隔的配置文件列表;如指定必须放在命令行第一个选项的位置
-D, --database类型:string。连接到这个数据库
--delayed-insert给 INSERT 或 REPLACE 语句加上 DELAYED 修饰符
--dest类型:DSN。指定归档写入的目标表,参数格式与 --source 相同的 key=val。多数缺失值默认沿用 --source,所以源和目标相同的部分不必重复写(哪些值会被复制可用 --help 查看)
--dry-run打印它将使用的文件名和 SQL 语句,然后退出,什么都不做
--file类型:string。归档写入的文件名,支持 MySQL DATE_FORMAT() 的部分格式码:%d 日(01..31)、%H 时(00..23)、%i 分(00..59)、%m 月(01..12)、%s 秒(00..59)、%Y 四位年,另外还有 %D 库名、%t 表名,例如 --file '/var/log/archive/%Y-%m-%d-%D.%t'。文件内容与 SELECT INTO OUTFILE 格式相同:行以换行符结束、列以制表符分隔、NULL 表示为 \N、特殊字符用 \ 转义,因此可以用 LOAD DATA INFILE 的默认设置直接装载回去。想要列头见 --header;文件默认自动刷盘,见 --buffer
--for-update给 SELECT 语句加上 FOR UPDATE 修饰符
--header--file 指定的文件首行写入列名。如果文件已存在则不写表头,这样即使继续追加输出,文件仍可被 LOAD DATA INFILE 装载
--help显示帮助并退出
--high-priority-select给 SELECT 语句加上 HIGH_PRIORITY 修饰符
-h, --host类型:string。要连接的主机
--ignore让插入 --dest 的语句使用 INSERT IGNORE
--limit类型:int;默认:1。每条语句取回并归档的行数,即限制取数 SELECT 返回的行数,默认一行。调大通常更高效,但如果归档得很稀疏、要跳过很多行就要小心:这可能与其他查询产生更多争用,具体取决于存储引擎、事务隔离级别以及 --for-update 之类的选项
--local给 ANALYZE 和 OPTIMIZE 查询加上 NO_WRITE_TO_BINLOG 修饰符,使其不写入 binlog。详见 --analyze
--low-priority-delete给 DELETE 语句加上 LOW_PRIORITY 修饰符
--low-priority-insert给 INSERT 或 REPLACE 语句加上 LOW_PRIORITY 修饰符
--max-flow-ctl类型:float。类似 --max-lag 但面向 PXC 集群:检查集群因 Flow Control 而暂停的平均时间,超过该选项给出的百分比就让工具暂停。默认不做 Flow Control 检查。需要 PXC 5.6 或更高版本
--max-lag类型:time;默认:1s。--check-replica-lag 指定的副本有延迟时暂停归档。每次准备取下一行前都看一眼副本,若延迟大于该值、或副本没在运行(延迟为 NULL),就睡 --check-interval 秒再看,反复直到副本追上,然后才继续取行归档。这个选项可能让 --sleep--sleep-coef 变得不必要
-s, --mysql_ssl类型:int。创建 SSL MySQL 连接
--no-ascend不使用升序索引优化。默认的升序索引优化会优化重复的 SELECT,让它定位到上一条查询结束的索引位置再往后扫,而不是每次都从表头开始扫;对重复访问来说这通常是好策略,故默认开启。但大的多列索引会让 WHERE 子句复杂到反而更低效,例如四列主键 (a, b, c, d) 需要 WHERE (a > ?) OR (a = ? AND b > ?) OR (a = ? AND b = ? AND c > ?) OR (a = ? AND b = ? AND c = ? AND d >= ?):填充这些占位符消耗内存和 CPU、增加网络流量与解析开销,还可能让 MySQL 更难优化;四列还不算什么,但十列且每列都允许 NULL 就可能是问题了。如果你清楚自己只是成块地从表头删行、不留空洞,那从表头开始其实最高效,就不必升序索引。参见 --ascend-first
--no-delete处理完不删除已归档的行。这会禁止 --no-ascend,因为两者同时启用会造成死循环。即使工具不执行删除,源端 DSN 上插件的 before_delete 方法仍会被调用
--optimize类型:string。结束后对 --source 和/或 --dest 执行 OPTIMIZE TABLE,参数语法同 --analyze
--output-format类型:string。配合 --file 指定输出格式:dump 为默认值,即用制表符分隔字段的 MySQL dump 格式;csv, 分隔并可选地用 " 包围字段,等价于 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
-p, --password类型:string。连接密码(含逗号需用反斜杠转义,如 exam\,ple
--pid类型:string。创建指定的 PID 文件;若文件已存在且其中的 PID 与当前 PID 不同则不启动,但若其中的 PID 已不在运行则覆盖该文件。工具退出时自动删除
--plugin类型:string。作为通用插件的 Perl 模块名。目前只用于统计(见 --statistics),必须提供 new()statistics() 方法。new( src => $src, dst => $dst, opts => $o ) 会拿到源和目标 DSN 及其数据库连接(与连接级插件一样),还有一个 OptionParser 对象 $o 用于读取命令行选项(例如 $o->get('purge'));statistics(\%stats, $time) 拿到归档作业收集的统计哈希引用,以及整个作业的开始时间
-P, --port类型:int。连接端口
--primary-key-only只取主键列,等于用 --columns 指定主键列的捷径。只想清除行时这样更高效:DELETE 只需要主键列,不必为此从服务器取回整行。参见 --purge
--progress类型:int。每 X 行打印一次进度信息:当前时间、已运行时间和已归档行数
--purge只清除而不归档,因此允许省略 --file--dest——行只是被删掉。只想清除行时建议用 --primary-key-only 指定表的主键列,避免毫无必要地从服务器取回所有列
--quick-delete给 DELETE 语句加上 QUICK 修饰符。如 MySQL 文档所述,某些情况下先 DELETE QUICKOPTIMIZE TABLE 可能更快,后者可以用 --optimize 完成
-q, --quiet不打印任何输出,包括 --statistics 的输出,但不抑制 --why-quit 的输出
--replace让插入 --dest 的语句写成 REPLACE
--retries类型:int;默认:1。遇到 InnoDB 锁等待超时或死锁时的重试次数;重试用尽后工具报错退出。在事务型与非事务型存储引擎之间归档时要仔细考虑期望发生什么:对 --dest 的 INSERT 和对 --source 的 DELETE 走两条独立连接,即使在同一台服务器上也并不真正处于同一个事务中;不过工具在代码里实现了简单的分布式事务,提交和回滚应能按预期跨两个连接发生。目前工具不处理 InnoDB 以外的事务型存储引擎的错误
--run-time类型:time。运行多久后退出。可选后缀 s=秒、m=分、h=小时、d=天;不带后缀按秒计
--[no]safe-auto-increment默认:yes。不归档 AUTO_INCREMENT 值最大的那一行:在升序单列 AUTO_INCREMENT 键时追加一个额外的 WHERE 子句,防止工具删掉最新一行,从而避免服务器重启后重用 AUTO_INCREMENT 值。该额外子句里用的是归档/清除作业开始时自增列的最大值,运行期间新插入的行它看不到
--sentinel类型:string;默认:/tmp/pt-archiver-sentinel。该文件存在就停止归档并退出。必要时用它优雅地停掉 cron 作业。参见 --stop
--set-vars类型:Array。以逗号分隔的 变量=值 列表设置 MySQL 变量;默认 wait_timeout=10000,命令行指定的值覆盖默认值;无法设置时打印警告并继续
--share-lock给 SELECT 语句加上 LOCK IN SHARE MODE 修饰符
--skip-foreign-key-checksSET FOREIGN_KEY_CHECKS=0 禁用外键检查
--sleep类型:int。两条取数 SELECT 之间睡多少时间,默认完全不睡。睡之前不会提交事务、也不会--file(用 --txn-size 控制这一点);若指定了 --commit-each,则提交和刷盘发生在睡眠之前
--sleep-coef类型:float。把 --sleep 算成上一条 SELECT 耗时的倍数:按指定系数乘以上一条 SELECT 的查询时间来睡。这是更精细的 SELECT 限流方式——根据 SELECT 实际有多慢,动态决定每次之间睡多久
-S, --socket类型:string。连接使用的 socket 文件
--source类型:DSN。指定从哪张表归档(必填),语法见"DSN 选项"。多数键控制如何连接 MySQL,另有本工具扩展的键:Dti 用于选表;a 指定连接用 USE 设为默认的库;b 为真时用 SQL_LOG_BIN 关闭 binlog;m 指定由外部 Perl 模块提供的可插拔动作。只有表是必填的,其余部分可从环境(如选项文件)读取。i 部分值得特别说明:它告诉工具该扫哪个索引,会体现为取数 SELECT 中的 FORCE INDEXUSE INDEX 提示;不指定时工具自动发现合适的索引,优先选主键,多数情况下这样就很好用。工具记住每条 SELECT 取到的最后一行,用指定索引的列构造 WHERE 子句,让 MySQL 从上次结束处开始下一条 SELECT,而不是每次都可能从表头扫。ab 用于控制语句如何流入 binlog,两者是达成同一目的的不同手段——在复制源上归档数据、在副本上保留数据:b 直接在该连接上关掉 binlog,a 让连接 USE 指定的库,配合副本的 --replicate-ignore-db 阻止副本执行这些 binlog 事件
--statistics收集并打印计时统计。这些统计会提供给 --plugin 指定的插件;除非指定 --quiet,工具退出时会打印它们(格式见"输出")。若同时给了 --why-quit,行为略有变化:即使只是因为没有更多行可归档也会打印退出原因。需要标准的 Time::HiRes 模块(较新的 Perl 中属核心模块)
--stop创建 --sentinel 指定的哨兵文件然后退出,效果是停止所有正在监视同一哨兵文件的运行实例。参见 --unstop
--txn-size类型:int;默认:1。每个事务的行数,为 0 则完全禁用事务。处理完这么多行后,工具提交 --source 以及 --dest(如果有),并刷 --file。该参数对性能至关重要:从正在承担繁重 OLTP 工作的线上服务器归档时,需要在事务大小与提交开销之间取得平衡——大事务带来更多锁争用和死锁的可能,小事务则造成更频繁、开销可观的提交。给个概念:作者写工具时用的小测试集上,取 500 时归档大约每 1000 行 2 秒(在一台安静的桌面机 MySQL 实例上,同时归档到磁盘和另一张表);取 0 禁用事务、转为自动提交后,性能降到每千行 38 秒。如果源或目标不是事务型存储引擎,可以禁用事务以免工具尝试提交
--unstop删除 --sentinel 指定的哨兵文件并继续运行。参见 --stop
-u, --user类型:string。登录用户(若非当前用户)
--version显示版本并退出
--[no]version-check默认:yes。检查 Percona Toolkit、MySQL 等软件的最新版本与已知问题版本(详见版本检查
--where类型:string。限制归档哪些行的 WHERE 子句(必填)。不要带 WHERE 这个词,可能需要加引号以防 shell 解释其内容,例如 --where 'ts < current_date - interval 90 day'。为安全起见该选项是必填的;如果确实不需要过滤条件,就写 --where 1=1
--why-quit除"可归档的行已取完"之外,因任何原因退出时都打印原因。比如把带 --run-time 的 pt-archiver 放进 cron 时,用它确认归档是在超时之前完成的。若同时给了 --statistics,行为略有变化:连"没有更多行"也会打印原因。该输出即使指定了 --quiet 也照样打印,这样放进 cron 后一旦异常退出就能收到邮件

选项文件里的 socket 会被继承

如果 --sourceF DSN 键指定了一个定义了 socket 的默认选项文件,那么除非另外为 --dest 指定 socket,pt-archiver 连接 --dest 时会走那个 socket, 也就是说本该连目标端时可能错连到源端。例如:

bash
pt-archiver --source F=host1.cnf,D=db,t=tbl --dest h=host2

当 pt-archiver 连接 --dest(host2)时,它会通过 host1.cnf 里定义的 --source(host1) 的 socket 去连接。

DSN 选项

DSN 由 键=值 组成,逗号分隔;键区分大小写(Pp 不是同一个键); = 前后不能有空白,值含空白必须加引号。COPY 为 yes 表示 --dest 缺失该键时会复制 --source 的值。

DSN 部分说明
a执行查询时用 USE 切换到的数据库(COPY:no)
Acharset默认字符集(COPY:yes)
b为真时用 SQL_LOG_BIN 关闭 binlog(COPY:no)
Ddatabase包含该表的数据库(COPY:yes)
Fmysql_read_default_file只从给定文件读取默认选项(COPY:yes)
hhost要连接的主机(COPY:yes)
i要使用的索引(COPY:yes)
L显式启用 LOAD DATA LOCAL INFILE(COPY:yes)。有些厂商编译 libmysql 时没加 --enable-local-infile,导致服务器允许 LOCAL INFILE 而客户端一用就抛异常;只要服务器允许 LOAD DATA,客户端就能重新启用它,这个键做的正是这件事。目前没发现打开它会导致错误或行为差异,但为稳妥起见默认不开
m插件模块名(COPY:no)
ppassword连接密码(含逗号需转义)(COPY:yes)
Pport连接端口(COPY:yes)
Smysql_socket连接使用的 socket 文件(COPY:yes)
t归档的源表/目标表(COPY:yes)
uuser登录用户(若非当前用户)(COPY:yes)
smysql_ssl创建 SSL 连接(COPY:yes)

插件扩展

pt-archiver 可以通过挂载外部 Perl 模块来接管部分逻辑和动作。--source--dest 都能用 DSN 的 m 部分指定模块:

bash
pt-archiver --source D=test,t=test1,m=My::Module1 --dest m=My::Module2,t=test2

这会让 pt-archiver 加载 My::Module1My::Module2 两个包、创建它们的实例, 并在归档过程中调用它们。也可以用 --plugin 指定插件。模块必须提供以下接口:

方法说明
new(dbh, db, tbl)构造函数拿到数据库句柄引用、库名和表名。插件在工具打开连接之后、检查参数中给出的表之前创建,因此有机会创建并填充临时表或做其他准备工作
before_begin(cols, allcols)在工具开始逐行迭代归档之前、其余准备工作(检查表结构、设计 SQL 查询等)之后调用。这是工具唯一一次告知插件列名的时机:cols 是用户要求归档的列(默认或由 --columns 指定),allcols 是工具会从源表取回的全部列名——它可能取比用户要求更多的列供自己使用,后续插件函数收到的行都是包含这些追加列的完整行
is_archivable(row)对每一行调用以判断是否可归档,仅适用于 --source;返回真则归档,否则跳过。跳过行会给非唯一索引带来麻烦:工具通常用定位到"上一处理行"的 WHERE 子句作为下一条 SELECT 的起点,若该行因返回假而仍存在,就可能死循环。因此为 --source 指定插件时,工具会把起点从"大于或等于"改成"严格大于",这对主键这类唯一索引没问题,但在非唯一索引上、或只升序索引首列时可能跳过行(留下空洞)。指定 --no-delete 时工具同样会改这个子句,原因也是可能死循环
before_delete(row)每行删除前调用,仅适用于 --source。适合在这里处理依赖关系,例如删掉外键指向待删行的数据,或递归归档所有依赖表。即使给了 --no-delete 也会调用,但给了 --bulk-delete 就不调用
before_bulk_delete(first_row, last_row)批量删除执行前调用,与 before_delete 类似,但参数是待删范围的首行和末行。即使给了 --no-delete 也会调用
before_insert(row)每行插入前调用,仅适用于 --dest。可以用它把行插入多张表,比如配合 ON DUPLICATE KEY UPDATE 在数据仓库里构建汇总表。给了 --bulk-insert 时不调用
before_bulk_insert(first_row, last_row, filename)批量插入执行前调用,与 before_insert 类似,但参数是该范围的首行和末行
custom_sth(row, sql)在插入该行之前、before_insert() 之后调用,允许插件指定不同的 INSERT 语句。返回值(如果有)应是一个 DBI 语句句柄,sql 参数是用于准备默认 INSERT 语句的 SQL 文本;无返回值则使用默认语句句柄。指定 --bulk-insert 时不调用。该方法只对 --dest 的插件生效,插件行为不符合预期时先检查是否指定给了目标端而非源端
custom_sth_bulk(first_row, last_row, sql, filename)指定了 --bulk-insert 时,在批量插入之前、before_bulk_insert() 之后调用,参数不同,返回值等约定与 custom_sth() 类似
after_finish()在工具退出归档循环、提交所有数据库句柄、关闭 --file 并打印最终统计之后,运行 ANALYZE 或 OPTIMIZE(见 --analyze--optimize)之前调用

如果为 --source--dest 都指定了插件,工具按 --source--dest 的顺序构造它们、 调用 before_begin()after_finish()。pt-archiver 假定事务由自己控制,插件不应 提交或回滚数据库句柄——传给插件构造函数的句柄就是工具自己用的那个, 注意 --source--dest 是两个独立的句柄。一个示例模块大致长这样:

perl
package My::Module;

sub new {
  my ( $class, %args ) = @_;
  return bless(\%args, $class);
}

sub before_begin {
  my ( $self, %args ) = @_;
  # Save column names for later
  $self->{cols} = $args{cols};
}

sub is_archivable {
  my ( $self, %args ) = @_;
  # Do some advanced logic with $args{row}
  return 1;
}

sub before_delete {} # Take no action
sub before_insert {} # Take no action
sub custom_sth    {} # Take no action
sub after_finish  {} # Take no action

1;

其他信息

  • 作者:Baron Schwartz(致谢 Andrew O'Brien)

  • 通用说明:已知问题的反馈方式、PTDEBUG 调试安全提示、系统要求基线,见 通用说明

更多细节请阅读 官方文档

Percona Toolkit 中文文档 · 社区维护的第三方学习站