Skip to content

pt-find

像 GNU find 一样按条件查找 MySQL 表,并对找到的表执行指定动作。

语法

bash
pt-find [OPTIONS] [DATABASES]

默认动作是打印库名和表名。

找出一天前创建、使用 MyISAM 引擎的所有表并打印表名:

bash
pt-find --ctime +1 --engine MyISAM

找出 MyISAM 表并把它们转成 InnoDB:

bash
pt-find --engine MyISAM --exec "ALTER TABLE %D.%N ENGINE=InnoDB"

找出由已不存在的进程按 name_sid_pid 命名约定创建的表,然后删掉它们:

bash
pt-find --connection-id '\D_\d+_(\d+)$' --server-id '\D_(\d+)_\d+$' --exec-plus "DROP TABLE %s"

找出 test 和 junk 两个库里的空表并删除:

bash
pt-find --empty junk test --exec-plus "DROP TABLE %s"

找出总大小超过 5GB 的表:

bash
pt-find --tablesize +5G

列出所有表的数据与索引总大小,并按最大的表排在前面(sort 是另一个程序):

bash
pt-find --printf "%T\t%D.%N\n" | sort -rn

同上,但这次把数据插回数据库留档:

bash
pt-find --noquote --exec "INSERT INTO sysdata.tblsize(db, tbl, size) VALUES('%D', '%N', %T)"

数据风险

--exec--exec-plus 会真的把 SQL 执行掉,上面 DROP TABLE %s 这类用法会直接删表。 所有数据库工具都可能对系统和数据库服务器造成风险,使用前请:阅读工具文档、 查看已知问题、先在非生产服务器上测试、 备份生产服务器并验证备份可用。带破坏性动作之前,建议先用默认的打印动作确认命中的表是否符合预期。

用法示例

以下命令假定已通过 /etc/my.cnf~/.my.cnf 或本机 socket 配置好 MySQL 连接;需显式指定连接时加 -h主机 -P端口 -u用户 -p--defaults-file=/path/my.cnf。命令可直接复制,替换其中的库表名、阈值即可。

场景:排查碎片严重的表

Data_free 很大说明表有大量空洞,是 OPTIMIZE TABLE 或在线改表的候选对象;先 --print 只打印、确认范围后再动手:

bash
pt-find --datafree +1G --print

场景:找半年没改过的冷表

做归档/下线评估时,列出修改时间超过 180 天的表,连同最后修改时间和总大小一起打印:

bash
pt-find --mtime +180 --printf "%D.%N\t%U\t%T\n"

场景:巡检某库是否还有 MyISAM 表

迁移引擎前,只看 appdb 这个库里是否还残留 MyISAM 表(全局 --engine 会扫所有库,这里用 --database 收窄):

bash
pt-find --database appdb --engine MyISAM --print

场景:找行数极少的表

配置表、字典表通常行数很少;反过来拿 --rows -100 快速筛出行数小于 100 的表,定位可疑的小表:

bash
pt-find --rows -100 --print

场景:按命名约定清理临时表

按表名正则 ^tmp_ 找出临时表,务必先用 --print 确认命中名单,确认无误再把 --print 换成 --exec-plus "DROP TABLE %s" 执行删除(删表不可逆,见上方 danger 提示):

bash
pt-find --tblregex '^tmp_' --print

场景:用 OR 圈出超大或超旧的表

想一次性列出"体积超过 10G 一年以上没改过"的表(两个条件满足其一即可),用 --or 切换成 OR 语义:

bash
pt-find --or --tablesize +10G --mtime +365 --print

功能说明

pt-find 寻找通过你指定的测试的 MySQL 表,然后执行你指定的动作。 默认动作是把库名和表名打印到 STDOUT。

它比 GNU find 简单,不允许在命令行上写复杂的表达式。 能用 SHOW TABLES 就用 SHOW TABLES,需要时才用 SHOW TABLE STATUS

三类选项

选项分三种:决定行为或设置的普通选项;决定某张表是否进入查找结果的测试; 以及对找到的表做点什么的动作

pt-find 使用标准的 Getopt::Long 解析选项,所以长选项名前面要写双横线,这一点与 GNU find 不同。 多个测试之间默认是 AND 关系,用 --or 可以改成 OR;由于选项解析不是 pt-find 自己实现的, 无法写出带括号、AND 与 OR 混用的复杂表达式。

测试的取值写法

多数测试是拿 SHOW TABLE STATUS 输出的某一列去比较。数值参数可以写成 +n 表示大于 n、 -n 表示小于 n、n 表示正好等于 n;所有数值选项都可带可选的后缀倍数 kMG (分别是 1024、1048576、1073741824)。除注明为 SQL LIKE 模式的以外,所有模式都是 Perl 正则表达式(见 man perlre),可用 --case-insensitive 让所有正则匹配不区分大小写。

日期和时间都相对同一个瞬间来衡量,即 pt-find 第一次向数据库服务器询问当前时间的那一刻。 所有日期时间运算都在 SQL 里完成,所以"找 5 天前修改过的表"会翻译成 SELECT DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 5 DAY);指定 --day-start 时基准换成 CURRENT_DATE

不过表大小之类的指标并不是某个瞬间的一致快照:MySQL 处理完所有 SHOW 查询需要时间, pt-find 对此无能为力,这些数值就是取到它们的那一刻的值。

动作的执行顺序

--exec-plus 这个动作在其他一切之后发生,除此之外各动作的执行顺序是不确定的。

选项

选项说明
--ask-pass连接 MySQL 时交互式询问密码
--[no]buffer-stdout默认开启 STDOUT 缓冲;使用 tee、kubectl logs 等后处理工具想看到实时进度时可关闭
--case-insensitive让所有正则表达式搜索不区分大小写
-A, --charset类型:string。默认字符集(utf8 时设置 binmode/mysql_enable_utf8 并执行 SET NAMES UTF8
--config类型:Array。读取逗号分隔的配置文件列表;如指定必须放在命令行第一个选项的位置
-D, --database类型:string。连接到这个数据库
--day-start时间(用于 --mmin 等)从今天零点起算,而不是从当前时刻起算
-F, --defaults-file类型:string。只从给定文件读取 mysql 选项,必须给绝对路径
--help显示帮助并退出
-h, --host类型:string。要连接的主机
-s, --mysql_ssl类型:int。创建 SSL MySQL 连接
--or各测试之间用 OR 而不是 AND 组合。默认所有测试之间视为 AND,该选项切换成 OR。由于选项解析不是 pt-find 自己实现的,无法指定带括号以及 OR 与 AND 混用的复杂表达式
-p, --password类型:string。连接密码(含逗号需用反斜杠转义,如 exam\,ple
--pid类型:string。创建指定的 PID 文件;若文件已存在且其中的 PID 与当前 PID 不同则不启动,但若其中的 PID 已不在运行则覆盖该文件。工具退出时自动删除
-P, --port类型:int。连接端口
--[no]quote默认:yes。用 MySQL 标准的反引号包裹 MySQL 标识符名。加引号发生在测试运行之后、动作运行之前(所以正则匹配的是不带反引号的名字,而 --exec 拿到的是带反引号的名字);把库表名当成字符串值拼进 SQL 时需要用 --noquote 关掉
--set-vars类型:Array。以逗号分隔的 变量=值 列表设置 MySQL 变量;默认 wait_timeout=10000,命令行指定的值覆盖默认值;无法设置时打印警告并继续
-S, --socket类型:string。连接使用的 socket 文件
-u, --user类型:string。登录用户(若非当前用户)
--version显示版本并退出
--[no]version-check默认:yes。检查 Percona Toolkit、MySQL 等软件的最新版本与已知问题版本(详见版本检查

本工具还接受额外的命令行参数(要搜索的数据库列表),详见"语法"一节。

测试

数值参数的 +n / -n / n 写法与 k/M/G 后缀见"测试的取值写法"。 如果你需要的测试不在下表中,可以到 https://jira.percona.com/projects/PT 提功能需求。

测试说明
--autoinc类型:string。表的下一个 AUTO_INCREMENT 值为 n,检查 Auto_increment 列
--avgrowlen类型:size。表的平均行长为 n 字节,检查 Avg_row_length 列。可写 NULL 以匹配 Avg_row_length IS NULL
--checksum类型:string。表校验和(checksum)为 n,检查 Checksum 列
--cmin类型:size。表创建于 n 分钟前,检查 Create_time 列
--collation类型:string。表的排序规则匹配该模式,检查 Collation 列
--column-name类型:string。表中有列名匹配该模式
--column-type类型:string。表中有列的类型匹配该类型(不区分大小写)。类型举例:varchar、char、int、smallint、bigint、decimal、year、timestamp、text、enum
--comment类型:string。表注释匹配该模式,检查 Comment 列
--connection-id类型:string。表名中带有已不存在的 MySQL 连接 ID。参数必须是能捕获数字的 Perl 正则,如 (\d+);表名匹配该模式时,捕获到的数字被当作某个进程的连接 ID,若按 SHOW FULL PROCESSLIST 该连接不存在,测试返回真;若该连接 ID 大于 pt-find 自己的连接 ID,为安全起见返回假。用途:使用 MySQL 基于语句的复制时,临时表很麻烦,有人改用带唯一名字的真实表,比如把连接 ID 拼到表名末尾(scratch_table_12345),既保证唯一又能追溯归属,更重要的是连接一旦不存在,就可以认为它是没清理临时表就死掉了,这张表可以删。推荐的参数是 '\D_(\d+)$'——找末尾是一串数字、前面是下划线加一个非数字字符的表(后一个条件避免误伤 baron_scratch_2007_05_07 这种以日期结尾的表)。参见 --server-id
--createopts类型:string。表的创建选项匹配该模式,检查 Create_options 列
--ctime类型:size。表创建于 n 天前,检查 Create_time 列
--datafree类型:size。表有 n 字节空闲空间,检查 Data_free 列。可写 NULL 以匹配 Data_free IS NULL
--datasize类型:size。表数据占用 n 字节,检查 Data_length 列。可写 NULL 以匹配 Data_length IS NULL;注意从 MySQL 8.0 起空表返回 0 而不是 NULL
--dblike类型:string。库名匹配该 SQL LIKE 模式
--dbregex类型:string。库名匹配该模式
--empty表没有行,检查 Rows 列
--engine类型:string。表的存储引擎匹配该模式,检查 Engine 列(更早的 MySQL 版本里是 Type 列)
--function类型:string。函数定义匹配该模式
--indexsize类型:size。表索引占用 n 字节,检查 Index_length 列。可写 NULL 以匹配 Index_length IS NULL
--kmin类型:size。表在 n 分钟前被检查过,检查 Check_time 列
--ktime类型:size。表在 n 天前被检查过,检查 Check_time 列
--mmin类型:size。表最后修改于 n 分钟前,检查 Update_time 列
--mtime类型:size。表最后修改于 n 天前,检查 Update_time 列
--procedure类型:string。存储过程定义匹配该模式
--rowformat类型:string。表的行格式匹配该模式,检查 Row_format 列
--rows类型:size。表有 n 行,检查 Rows 列。可写 NULL 以匹配 Rows IS NULL
--server-id类型:string。表名中含有 server ID。如果你按 --connection-id 说明的命名约定创建临时表,同时还把创建这些表的服务器的 server ID 也写进表名,就可以用这个模式匹配确保表只在创建它的那台服务器上被删除,避免副本上还在用的表被误删(前提是各服务器的 server ID 唯一,而这本来就是复制正常工作的要求)。例如在复制源(server ID 22)上创建了 scratch_table_22_12345,在副本(server ID 23)上看到这张表时,你可能以为没有 12345 号连接就能安全删除;但只要用 --server-id '\D_(\d+)_\d+$' 强制表名必须匹配本机 server ID,这张表在副本上就不会被删
--tablesize类型:size。表占用 n 字节,检查 Data_length 与 Index_length 两列之和
--tbllike类型:string。表名匹配该 SQL LIKE 模式
--tblregex类型:string。表名匹配该模式
--tblversion类型:size。表版本为 n,检查 Version 列
--trigger类型:string。触发器的动作语句匹配该模式
--trigger-table类型:string。--trigger 定义在匹配该模式的表上
--view类型:string。CREATE VIEW 匹配该模式

用 --connection-id 判断"表可以删"需要 PROCESS 权限

如果要这么用,请确保 pt-find 运行所用的账号具有 PROCESS 权限, 否则它只能看到同一用户的连接,可能把仍在使用中的表误判为可以删除。为安全起见,pt-find 会替你检查这一点。

动作

动作说明
--exec类型:string。对找到的每一项执行该 SQL。SQL 中可以使用转义序列和格式化指令(见 --printf
--exec-dsn类型:string。以 键=值 形式指定执行 --exec--exec-plus 的 SQL 时使用的 DSN。未指定的值继承命令行参数
--exec-plus类型:string。把找到的所有项一次性交给该 SQL 执行,与 --exec 不同:没有转义和格式化指令,只有一个特殊占位符 %s 代表库表名列表——找到的表会用逗号连成一串,替换到 %s 所在的位置。例如用它删掉找到的所有表:DROP TABLE %s。相当于 GNU find 的 -exec command {} + 写法
--print打印库名和表名并换行。没有指定其他动作时这是默认动作
--printf类型:string。按格式打印到标准输出,解释 \ 转义与 % 指令。转义是反斜杠开头的字符,如 \n\t,由 Perl 解释,因此 Perl 认识的转义都能用。指令一律按 %s 替换,目前不能附加字段宽度、对齐之类的格式说明。指令列表见下表;% 后面跟一个不在表中的字符时该指令被丢弃(但那个字符照样打印)

--printf 指令

多数指令直接来自 SHOW TABLE STATUS 的列。如果该列为 NULL 或不存在,输出中得到空字符串。

指令数据来源说明
%aAuto_increment
%AAvg_row_length
%cChecksum
%CCreate_time
%DDatabase表所在的库名
%dData_length
%EEngine更早的 MySQL 版本里是 Type
%FData_free
%fInnodb_free从 Comment 字段解析而来
%IIndex_length
%KCheck_time
%LCollation
%MMax_data_length
%NName
%OComment
%PCreate_options
%RRow_format
%SRows
%TTable_lengthData_length 与 Index_length 之和
%UUpdate_time
%VVersion

DSN 选项

DSN 由 键=值 组成,逗号分隔;键区分大小写(Pp 不是同一个键); = 前后不能有空白,值含空白必须加引号。

DSN 部分说明
Acharset默认字符集
Ddatabase默认数据库
Fmysql_read_default_file只从给定文件读取默认选项
hhost要连接的主机
ppassword连接密码(含逗号需转义)
Pport连接端口
Smysql_socket连接使用的 socket 文件
uuser登录用户(若非当前用户)
smysql_ssl创建 SSL 连接

其他信息

  • 作者:Baron Schwartz

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

更多细节请阅读 官方文档

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