Skip to content

pt-query-digest

分析 MySQL 慢查询日志、通用日志、二进制日志、processlist 与 tcpdump 抓包,把查询按指纹聚合,输出最值得优化的慢查询报告(也可存库做审查与历史趋势)。

语法

bash
pt-query-digest [OPTIONS] [FILES] [DSN]
bash
# 从 slow.log 报告最慢的查询(默认行为)
pt-query-digest slow.log
bash
# 从 host1 的 processlist 报告最慢的查询
pt-query-digest --processlist h=host1
bash
# 先用 tcpdump 抓 MySQL 协议包,再用 pt-query-digest 解析报最慢查询
tcpdump -s 65535 -x -nn -q -tttt -i any -c 1000 port 3306 > mysql.tcp.txt
pt-query-digest --type tcpdump mysql.tcp.txt
bash
# 把 slow.log 的查询数据存到 host2 供以后审查与趋势分析(不打印报告)
pt-query-digest --review h=host2 --no-report slow.log

用法示例

以下命令假定已开启慢查询日志并生成了 slow.log;连接 MySQL 的场景用 -h主机 -P端口 -u用户 -p--defaults-file=/path/my.cnf 指定。命令可直接复制,替换其中的日志路径、库表名与时间即可。

场景:只看最慢的 Top 10

慢日志很大时,先看最慢的 10 条,快速定位问题:

bash
pt-query-digest --limit 10 slow.log

场景:只看最近 24 小时

刚发生性能抖动,只想看最近一天的慢查询,用相对时间过滤:

bash
pt-query-digest --since 24h slow.log

场景:找出执行次数最多的高频查询

怀疑是缓存失效或某条 SQL 被打爆,按出现次数(Count)排序找最频繁的查询:

bash
pt-query-digest --order-by Query_time:cnt --limit 20 slow.log

场景:只看某个库的慢查询

只想分析 appdb 这个库的慢查询,用 --filter 按 db 过滤(|| "" 防御 db 为空的事件):

bash
pt-query-digest --filter '($event->{db} || "") eq "appdb"' slow.log

场景:按表聚合找最热的表

想知道哪些表被查得最多,按表聚合(多表 join 各计一次):

bash
pt-query-digest --group-by tables --limit 20 slow.log

场景:输出 JSON 供脚本二次处理

把分析结果导出成 JSON,方便程序解析或入库做告警:

bash
pt-query-digest --output json slow.log > slow-report.json

场景:抽取代表样本转成可回放的慢日志

给每类查询各保留 2 条代表样本,转成慢日志格式,用于在测试环境回放压测:

bash
pt-query-digest --sample 2 --no-report --output slowlog slow.log > sample-slow.log

场景:分析 binlog 里的写查询

从 binlog 里统计各类写入(INSERT/UPDATE/DELETE)的分布:

bash
mysqlbinlog mysql-bin.000123 | pt-query-digest --type binlog

功能说明

pt-query-digest 分析 MySQL 慢日志、通用日志与二进制日志(二进制日志须先用 mysqlbinlog 转成文本,见 --type),也可分析 SHOW PROCESSLIST 与 tcpdump 抓到的 MySQL 协议数据。默认按指纹(fingerprint)把相似查询聚为一类,并按查询时间降序(最慢的在前)报告;不指定文件时读 STDIN;可选 DSN 用于 --since/--until 等需要连接 MySQL 的场景。

查询审查与历史

把"分析查询"变成可常态化的工作,工具提供两个特性:

  • 查询审查(--review:所有不同查询的指纹存进数据库;下次带 --review 运行时,已审查(设过 reviewed_by)的查询不再打印,从而突出需要新审查的查询。
  • 查询历史(--history:把每类查询的指标(查询时间、锁时间等)存进数据库,随每次运行累积,便于跨时间做趋势与性能分析。

属性(ATTRIBUTES)

工具处理的是"事件(event)",即一组键值对,称为属性(attributes),如 Query_timeLock_time 等,慢日志里就能直接看到;有些属性慢日志里没有,而打了 Percona 补丁的服务器还会多出别的属性。熟悉属性是用好 --filter--ignore-attributes 等选项的前提。

借助 --filter 可由已有属性派生新属性。例如新增一个 Row_ratio 来看 Rows_sentRows_examined 的比值:

bash
--filter '($event->{Row_ratio} = $event->{Rows_sent} / ($event->{Rows_examined})) && 1'

&& 1 是为了确保整段语法恒为真(即便赋值结果为假)。新属性会自动出现在输出里,并可用于 --order-by 等需要属性的选项。

指纹(FINGERPRINTS)

查询指纹是查询的抽象形式,用来把相似查询归到一起:去掉字面量、归一化空白等。例如下面两条查询:

sql
SELECT name, password FROM user WHERE id='12823';
select name,   password from user
  where id=5;

都会指纹化为:

sql
select name, password from user where id=?

工具按指纹聚类的逻辑类似 SQL 的 GROUP BY(但注意:多个 --group-by 值定义的是"多份报告"而非多列分组)。例如:

bash
pt-query-digest               \
  --group-by fingerprint    \
  --order-by Query_time:sum \
  --limit 10                \
  slow.log

对应的伪 SQL 为:

sql
SELECT WORST(query BY Query_time), SUM(Query_time), ...
FROM /path/to/slow.log
GROUP BY FINGERPRINT(query)
ORDER BY SUM(Query_time) DESC
LIMIT 10

指纹化会处理许多现实中的特例,例如:把 mysqldump 的所有 SELECT(即便针对不同表)归到一起(pt-table-checksum 的所有查询同理);把多值 INSERT 缩短为单个 VALUES();去掉注释;把 USE 语句里的库名抽象;替换所有字面量(含十六进制、NULL,以及标识符里的数字,如 users_2009users_2010 指纹相同);空白折叠为单空格;整体转小写;把 IN()/VALUES() 里的字面量无论多少都替换为单个占位符;把多个相同的 UNION 查询折叠为一个。

输入类型(--type

  • binlog:先用 mysqlbinlog 把二进制日志转成文本再解析。
  • genlog:解析 MySQL 通用日志;通用日志缺少 Query_time,故默认 --order-by 变为 Query_time:cnt
  • slowlog(默认):解析各种 MySQL 慢日志格式。
  • tcpdump:解析 tcpdump 的输出(不是真正抓包),从网络包里解码 MySQL 客户端协议、抽取查询与响应。期望输入用 -x -n -q -tttt 格式化。tcpdump 只能在 TCP 端口上抓,无法抓 Unix socket 流量;SSL 加密流量无法解码。所有跑在 3306 端口的服务器会被自动识别,多台 3306 服务器的包会当作一台合并分析;非 3306 端口须用 --watch-server 指定。
  • rawlog:非 MySQL 日志,只是每行一条 SQL 的纯文本。没有指标,故许多功能不可用;一个用途是在只有查询清单(如轮询 SHOW PROCESSLIST 得到)时按出现次数排名。

输出

默认 --output 是一份查询分析报告,由 --[no]report 控制是否打印(用 --review/--history 时往往配合 --no-report)。报告为每一类查询输出一段("类"指 --group-by 属性值相同的查询,默认是 fingerprint)。报告排版便于直接粘进邮件,且所有非查询行都以 # 注释开头,可存成 .sql 文件在支持高亮的编辑器里打开。

全局概览段

报告开头先有一段关于整次分析的概览,信息与每类查询相似,但不含代价太高的全局统计,并附带代码自身的执行统计(CPU/内存占用、运行本地日期时间、读入/解析的文件列表)。

响应时间概览(response-time profile)

概览之后是事件响应时间概览,高度汇总随后详细的查询报告,含以下列:

含义
Rank该查询在整次分析集合中的排名
Query ID查询的指纹
Response time总响应时间,以及占总体总时间的比例
Calls该查询被执行次数
R/Call每次执行的平均响应时间
V/M响应时间的方差均值比(variance-to-mean ratio)
Item蒸馏后的查询

末尾一行 RankMISC,汇总因 --limit--outliers 等未被纳入报告的查询。

单条查询明细

每段以一行标识该查询在 --order-by 排序中的序号、每秒查询数(QPS)、近似并发度(由时间跨度与总 Query_time 推算)、查询 ID(用 --review 时即数据库里 checksum 的十六进制,可用 SELECT ... WHERE checksum=0x... 取回),以及该最差样本在日志里的字节偏移(因慢日志格式异常不一定精确)。

紧接着是该类查询的指标表:

text
#           pct   total    min    max     avg     95%  stddev  median
# Count       0       2
# Exec time  13   1105s   552s   554s    553s    554s      2s    553s
# Lock time   0   216us   99us  117us   108us   117us    12us   108us
# Rows sent  20   6.26M  3.13M  3.13M   3.13M   3.13M   12.73   3.13M
# Rows exam   0   6.26M  3.13M  3.13M   3.13M   3.13M   12.73   3.13M
  • pct:该指标占整次分析总量的百分比。
  • total:该指标的实际总值。
  • min/max/avg:最小、最大、平均值。
  • 95%:第 95 百分位(95% 的值 ≤ 此值)。
  • stddev:标准差,反映值的离散程度。
  • median:中位数。

stddevmedian、第 95 百分位都是近似值:为省内存,工具维护 1000 个桶(每个比前一个大 5%,从 .000001 到极大),每见一个值就计入对应桶,故内存固定,误差通常在 5% 左右。

随后是该查询的 users、databases、time range(先显示去重后的计数,再列取值,多个时只列最频繁的几个并附注出现次数)。再往后是 Query_time distribution 对数时间分布图(按 10 的幂划分桶,可用 --report-histogram 改绘图属性,但仅限时间类属性)。接着是 TablesEXPLAIN 段,给出可直接复制执行的 SHOW TABLE STATUS/SHOW CREATE TABLE/EXPLAIN 命令。最后是该类中"最差"样本查询(按 --order-by 排序最差者),其前通常有 # EXPLAIN 注释行;对非 SELECT 查询,工具会尝试转成大致等价的 SELECT 再补在下面。

报告段(--report-format

报告由若干段组成,--report-format 指定打印哪些段及顺序,默认 rusage,date,hostname,files,header,profile,query_report,prepared

打印内容
rusageps 报告的 CPU 时间与内存占用
date当前本地日期时间
hostname运行 pt-query-digest 的机器主机名
files读入/解析的输入文件
header整次分析的摘要概览
profile概览用的紧凑查询表
query_report每条唯一查询的明细
prepared预编译语句

rusagedatefilesheader 连续指定时会合并在一起;其余段以空行分隔。

审查信息(--review

--review 时,已审查查询的元数据会直接并入报告,出现在执行时间图下方,例如:

text
# Review information
#      comments: really bad IN() subquery, fix soon!
#    first_seen: 2008-12-01 11:48:57
#   jira_ticket: 1933
#     last_seen: 2008-12-18 11:49:07
#      priority: high
#   reviewed_by: xaprb
#   reviewed_on: 2008-12-18 15:03:11

--report-all 可让已审查的查询也出现在报告里。

历史对比(--history

--history 把每类查询的指标写入表(默认 percona_schema.query_history,可用 DSN 的 D/t 改写),表名后缀约定见下方列定义(如 Query_time_sum 存该类 Query_time 之和)。建表语句中,列名以 _pct/_avg/_cnt/_sum/_min/_max/_pct_95/_stddev/_median/_rank 结尾的,前缀被解释为事件属性、后缀为要存的指标。自 Percona Toolkit 3.0.11 起 checksum 改用 32 位 MD5,历史表的 checksum 值与旧版不同。

时间线(--timeline

--timeline 输出另一种报告:仍按 --group-by 归类聚合,但按时间顺序打印,每行含时间戳、间隔、次数与值(如 --group-by distill --timeline)。只想看时间线可加 --no-report 抑制默认报告;否则时间线打印在 response-time profile 之前。

选项

选项说明
--ask-pass连接 MySQL 时交互式询问密码
--attribute-aliases类型:array;默认:db|Schema。属性别名列表 属性|别名;事件缺少主属性时取别名的值并删除别名属性,有主属性则删除所有别名属性(避免报告里同时出现 db 与 Schema 两行)
--attribute-value-limit类型:int;默认:0。属性值的合理性上限;因慢日志 bug 导致某属性值过大时,改用该类查询上一次的取值;默认 0 表示关闭
--[no]buffer-stdout默认:yes。开启 STDOUT 缓冲;用 tee、kubectl logs 等后处理工具想看实时进度时关闭
-A, --charset类型:string。默认字符集;utf8 时设置 Perl binmode 为 utf8、向 DBD::mysql 传 mysql_enable_utf8,并在连接后执行 SET NAMES UTF8;其他值仅设 binmode 并执行 SET NAMES
--config类型:Array。读取逗号分隔的配置文件列表;如指定必须放在命令行第一个选项的位置
--[no]continue-on-error默认:yes。解析出错也继续;但累计 100 个错误即停止(多半是工具 bug 或输入非法)
--[no]create-history-table默认:yes。--history 指定的表不存在时按文档给出的结构创建
--[no]create-review-table默认:yes。--review 指定的表不存在时按文档给出的结构创建
--daemonize派生到后台并从 shell 脱离(仅 POSIX 系统)
-D, --database类型:string。连接使用的数据库
-F, --defaults-file类型:string。只从给定文件读取 mysql 选项;必须给绝对路径
--embedded-attributes类型:array。两个 Perl 正则:第一个匹配整个伪属性集合(如藏在注释里的 file: /login.php, line: 493),第二个从其中捕获 属性: 值 对并加入事件;正则中的逗号必须转义
--expected-range类型:array;默认:5,10。报告项数(受 --limit/--outliers 控制)与预期不符时,逐项解释为何被纳入
--explain类型:DSN。用该 DSN 对样本查询执行 EXPLAIN 并把结果纳入报告;仅在 --group-by 含 fingerprint 时生效;含子查询(如派生表 select ... from (...) der)的查询出于安全不 EXPLAIN;结果以 \G 纵向格式打印
--filter类型:string。丢弃该 Perl 代码返回非真值的事件;代码接收 $event(哈希引用)作参数,可为文件(不含 shebang)。命令行给出的过滤会被包进括号 ( filter ),故复杂多行逻辑须放进文件。可用它新建属性
--group-by类型:Array;默认:fingerprint。按事件的哪个属性归类;默认 fingerprint 把相似抽象查询归为一类。可取值含魔术值 tables(按出现的表聚合,多表 join 各计一次)、distill(超级指纹,折叠成 INSERT SELECT table1 table2 之类的动作建议)。每个值对应一份报告,--group-by user,db 是分别按 user 和 db 报告,而非组合
--help显示帮助并退出
--history类型:DSN。把每个查询类的指标(查询时间、锁时间等)存到指定表做趋势分析;默认表 percona_schema.query_history(D/t 可改),除非 --no-create-history-table 否则库表自动创建
-h, --host类型:string。要连接的主机
--ignore-attributes类型:array;默认:arg, cmd, insert_id, ip, port, Thread_id, timestamp, exptime, flags, key, res, val, server_id, offset, end_log_pos, Xid。不聚合这些属性(多为不可/不需聚合的元数据)
--inherit-attributes类型:array;默认:db,ts。事件缺失这些属性时,从最近一次拥有它们的事件继承(如上一事件 db=foo,下一事件无 db 则继承 foo)
--interval类型:float;默认:.1。轮询 processlist 的间隔秒数(配合 --processlist
--iterations类型:int;默认:1。采集-报告循环执行次数;0 表示无限。每次迭代运行 --run-time 时长;--run-time-mode interval 时由 --run-time 决定间隔边界
--limit类型:Array;默认:95%:20。限制输出的百分比或条数:整数=前 N 条最差查询;N%=最差查询的 N%;N%:M=百分比或 M 条先到者为准。值是与 --group-by 对应的逗号分隔数组,缺省项默认前 95%
--log类型:string。daemonize 时把所有输出写到此文件
--max-hostname-length类型:int;默认:10。报告中主机名截断到的长度;0=不截断
--max-line-length类型:int;默认:74。报告行截断到的长度;0=不截断
-s, --mysql_ssl类型:int。创建 SSL MySQL 连接
--order-by类型:Array;默认:Query_time:sum。按 属性:聚合函数 排序事件;聚合有 sum/min/max/cnt。默认 Query_time:sum 即按总执行时间(Exec time)排序;Query_time:max 按最大执行时间;cnt 按出现频率(Count)。解析 genlog 时默认变 Query_time:cnt。属性不存在则回退 Query_time:sum 并提示
--outliers类型:array;默认:Query_time:1:10。按 属性:百分位:次数 报告离群查询:第 2 字段与属性 95 百分位比较,第 3 字段(可选)与 cnt 比较;符合条件的即便超出 --limit 也加入报告(如 Query_time:60:5 表示 95 百分位≥60s 且出现≥5 次)。可为 --group-by 每个值分别指定
--output类型:string;默认:report。结果格式:report(标准报告)、slowlog(慢日志)、json(每查询类一个数组)、json-anon(去示例的 JSON)、secure-slowlog(匿名化查询的慢日志)。整份报告可用 --no-report 关掉,各段用 --report-format 控制
-p, --password类型:string。连接密码(含逗号需转义)
--pid类型:string。创建指定 PID 文件;PID 文件已存在且其中 PID 与当前不同则不启动,若其中 PID 已不在运行则覆盖;工具退出时自动删除
-P, --port类型:int。连接端口
--preserve-embedded-numbers指纹化时保留库/表名中的数字(默认会把 db1.table2 抽象成 db?.table?,此选项使其保持 db1.table2
--processlist类型:DSN。按 --interval 间隔轮询该 DSN 的 processlist 取查询;连接失败每秒尝试重连一次
--progress类型:array;默认:time,30。向 STDERR 打印进度:第一部分 percentage|time|iterations,第二部分为更新频率(百分比/秒/迭代数)
--read-timeout类型:time;默认:0。等待输入事件的最长时间,0=永远等;除 --processlist 外都适用。超时后停止读输入并打印报告(--iterations 为 0 或 >1 则进入下一轮)。需 Perl POSIX 模块
--[no]report默认:yes。为每个 --group-by 属性打印查询分析报告(标准慢日志分析);用 --review/--history 不需要报告时应加 --no-report 以省去昂贵操作
--report-all报告所有查询,包括已审查过的;仅在使用 --review--output 为报告时生效,否则所有查询始终打印
--report-format类型:Array;默认:rusage,date,hostname,files,header,profile,query_report,prepared。打印报告的哪些段及其顺序:rusage(CPU/内存)、date(日期时间)、hostnamefiles(输入文件)、header(全局概览)、profile(概览紧凑表)、query_report(每条查询明细)、prepared(预编译语句)。rusage/date/files/header 连续指定时合并输出
--report-histogram类型:string;默认:Query_time。绘制该属性值的分布图;仅限时间类属性(如 Rows_examined 画出来没意义)
--resume类型:string。把最后文件偏移写入该文件;再次以同一值运行时读取偏移并从此处续解析
--review类型:DSN。把查询类存库供以后审查,且不再报告已审查的类;默认表 percona_schema.query_review(D/t 可改),除非 --no-create-review-table 否则自动创建。依赖 --group-by fingerprint(默认),否则不生效;reviewed_by 被设置后该类不再打印
--run-time类型:time。每个 --iterations 运行多久;默认永远(可 CTRL-C 中断)。因 --iterations 默认 1,只指定它则运行该时长后退出;与 --iterations 配合做采集-报告循环
--run-time-mode类型:string;默认:clock--run-time 的计量基准:clock(真实时钟)、event(日志时间戳决定的日志时间)、interval(把日志时间划分为固定间隔并各出报告,间隔须能整除分/时/日,如 5m 但非 7m)。interval 模式按时间戳所在整点区间计算,不是从时间戳自身起算
--sample类型:int。只保留每类查询的前 N 个样本(按 --group-by 首值,默认按指纹);配合 --output slowlog 打印样本。例:--sample 2 --no-report --output slowlog slow.log
--set-vars类型:Array。以逗号分隔的 变量=值 列表设置 MySQL 变量;默认 wait_timeout=10000;命令行指定的覆盖默认;无法设置时打印警告并继续
--show-all类型:Hash。对这些属性显示全部取值(忽略行宽,仅对 user/host/db 等字符串属性有效),默认只显示单行能放下的值
--since类型:string。只解析比该值更新的查询(自某日期起)。取值可为 N[shmd](相对时间)、YYYY-MM-DD [HH:MM:SS]YYMMDD [HH:MM:SS]、或 MySQL 时间表达式(包进 SELECT UNIX_TIMESTAMP(<expr>),勿再套 UNIX_TIMESTAMP())。若用 MySQL 表达式且未给 --explain/--processlist/--review 的 DSN,则必须在命令行给一个 DSN 以便连接求值。事件假定按时间顺序排列,--since 严格:遇到足够新的事件前全部忽略
-S, --socket类型:string。连接使用的 socket 文件
--timeline显示事件时间线报告:仍按 --group-by 归类聚合,但按时间顺序打印,每行含时间戳、间隔、次数、值。只想看时间线可加 --no-report;否则时间线打印在 response-time profile 之前
--type类型:Array;默认:slowlog。输入类型:binlog(先用 mysqlbinlog 转文本)、genlog(通用日志,无 Query_time,默认 --order-byQuery_time:cnt)、slowlog(各种慢日志格式)、tcpdump(解析 tcpdump 输出而非真正抓包)、rawlog(每行一条 SQL 的纯文本,无指标,很多功能不可用)
--until类型:string。只解析比该值更旧的查询(直到某日期);取值类型同 --since。与 --since 不同,--until 不严格:解析到某事件时间戳≥--until 后,其后全部忽略
-u, --user类型:string。登录用户(若非当前用户)
--variations类型:Array。报告这些属性取值的变化数(通常 arg 表示类中有多少条不同查询),用以判断可缓存性;基于属性值的 CRC32 校验和,只保留 1000 个故为近似值
--version显示版本并退出
--[no]version-check默认:yes。检查 Percona Toolkit、MySQL 等软件的最新版本与已知问题版本(详见版本检查
--[no]vertical-format默认:yes。在报告的 SQL 查询后输出尾随 \G(纵向格式),非原生客户端如 phpMyAdmin 不支持
--watch-server类型:string。解析 tcpdump(--type tcpdump)时要监视的服务器 IP:端口(如 10.0.0.1:3306),其余服务器忽略;不指定则监视所有用 3306 或 "mysql" 的服务器。非标准端口必须指定;标准+非标准混合需分别生成 tcpdump 输出再各自指定

DSN 选项

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

其他信息

  • 作者:Baron Schwartz、Daniel Nichter 和 Brian Fraser

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

更多细节请阅读 官方文档

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