pt-query-digest
分析 MySQL 慢查询日志、通用日志、二进制日志、processlist 与 tcpdump 抓包,把查询按指纹聚合,输出最值得优化的慢查询报告(也可存库做审查与历史趋势)。
语法
pt-query-digest [OPTIONS] [FILES] [DSN]# 从 slow.log 报告最慢的查询(默认行为)
pt-query-digest slow.log# 从 host1 的 processlist 报告最慢的查询
pt-query-digest --processlist h=host1# 先用 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# 把 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 条,快速定位问题:
pt-query-digest --limit 10 slow.log场景:只看最近 24 小时
刚发生性能抖动,只想看最近一天的慢查询,用相对时间过滤:
pt-query-digest --since 24h slow.log场景:找出执行次数最多的高频查询
怀疑是缓存失效或某条 SQL 被打爆,按出现次数(Count)排序找最频繁的查询:
pt-query-digest --order-by Query_time:cnt --limit 20 slow.log场景:只看某个库的慢查询
只想分析 appdb 这个库的慢查询,用 --filter 按 db 过滤(|| "" 防御 db 为空的事件):
pt-query-digest --filter '($event->{db} || "") eq "appdb"' slow.log场景:按表聚合找最热的表
想知道哪些表被查得最多,按表聚合(多表 join 各计一次):
pt-query-digest --group-by tables --limit 20 slow.log场景:输出 JSON 供脚本二次处理
把分析结果导出成 JSON,方便程序解析或入库做告警:
pt-query-digest --output json slow.log > slow-report.json场景:抽取代表样本转成可回放的慢日志
给每类查询各保留 2 条代表样本,转成慢日志格式,用于在测试环境回放压测:
pt-query-digest --sample 2 --no-report --output slowlog slow.log > sample-slow.log场景:分析 binlog 里的写查询
从 binlog 里统计各类写入(INSERT/UPDATE/DELETE)的分布:
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_time、Lock_time 等,慢日志里就能直接看到;有些属性慢日志里没有,而打了 Percona 补丁的服务器还会多出别的属性。熟悉属性是用好 --filter、--ignore-attributes 等选项的前提。
借助 --filter 可由已有属性派生新属性。例如新增一个 Row_ratio 来看 Rows_sent 与 Rows_examined 的比值:
--filter '($event->{Row_ratio} = $event->{Rows_sent} / ($event->{Rows_examined})) && 1'&& 1 是为了确保整段语法恒为真(即便赋值结果为假)。新属性会自动出现在输出里,并可用于 --order-by 等需要属性的选项。
指纹(FINGERPRINTS)
查询指纹是查询的抽象形式,用来把相似查询归到一起:去掉字面量、归一化空白等。例如下面两条查询:
SELECT name, password FROM user WHERE id='12823';
select name, password from user
where id=5;都会指纹化为:
select name, password from user where id=?工具按指纹聚类的逻辑类似 SQL 的 GROUP BY(但注意:多个 --group-by 值定义的是"多份报告"而非多列分组)。例如:
pt-query-digest \
--group-by fingerprint \
--order-by Query_time:sum \
--limit 10 \
slow.log对应的伪 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_2009 与 users_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 | 蒸馏后的查询 |
末尾一行 Rank 为 MISC,汇总因 --limit、--outliers 等未被纳入报告的查询。
单条查询明细
每段以一行标识该查询在 --order-by 排序中的序号、每秒查询数(QPS)、近似并发度(由时间跨度与总 Query_time 推算)、查询 ID(用 --review 时即数据库里 checksum 的十六进制,可用 SELECT ... WHERE checksum=0x... 取回),以及该最差样本在日志里的字节偏移(因慢日志格式异常不一定精确)。
紧接着是该类查询的指标表:
# 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.13Mpct:该指标占整次分析总量的百分比。total:该指标的实际总值。min/max/avg:最小、最大、平均值。95%:第 95 百分位(95% 的值 ≤ 此值)。stddev:标准差,反映值的离散程度。median:中位数。
stddev、median、第 95 百分位都是近似值:为省内存,工具维护 1000 个桶(每个比前一个大 5%,从 .000001 到极大),每见一个值就计入对应桶,故内存固定,误差通常在 5% 左右。
随后是该查询的 users、databases、time range(先显示去重后的计数,再列取值,多个时只列最频繁的几个并附注出现次数)。再往后是 Query_time distribution 对数时间分布图(按 10 的幂划分桶,可用 --report-histogram 改绘图属性,但仅限时间类属性)。接着是 Tables 与 EXPLAIN 段,给出可直接复制执行的 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:
| 段 | 打印内容 |
|---|---|
rusage | ps 报告的 CPU 时间与内存占用 |
date | 当前本地日期时间 |
hostname | 运行 pt-query-digest 的机器主机名 |
files | 读入/解析的输入文件 |
header | 整次分析的摘要概览 |
profile | 概览用的紧凑查询表 |
query_report | 每条唯一查询的明细 |
prepared | 预编译语句 |
rusage、date、files、header 连续指定时会合并在一起;其余段以空行分隔。
审查信息(--review)
用 --review 时,已审查查询的元数据会直接并入报告,出现在执行时间图下方,例如:
# 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(日期时间)、hostname、files(输入文件)、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-by 变 Query_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 部分 | 说明 |
|---|---|---|
A | charset | 默认字符集 |
D | database | 连接 MySQL 时使用的默认数据库 |
F | mysql_read_default_file | 只从给定文件读取默认选项 |
h | host | 要连接的主机 |
p | password | 连接密码(含逗号需转义) |
P | port | 连接端口 |
S | mysql_socket | 连接使用的 socket 文件 |
t | — | --review 或 --history 的表 |
u | user | 登录用户(若非当前用户) |
s | mysql_ssl | 创建 SSL 连接 |
其他信息
作者:Baron Schwartz、Daniel Nichter 和 Brian Fraser
通用说明:已知问题的反馈方式、PTDEBUG 调试安全提示、系统要求基线,见 通用说明。
更多细节请阅读 官方文档。