pt-index-usage
从查询日志读取查询,用 EXPLAIN 分析索引使用情况,找出未被使用的索引。
语法
pt-index-usage [OPTIONS] [FILES]# 分析 slow.log 中的查询并打印报告
pt-index-usage /path/to/slow.log --host localhost
# 关闭报告,把结果保存到 percona 库供以后分析
pt-index-usage slow.log --no-report --save-results-database percona用法示例
以下命令假定已通过 /etc/my.cnf、~/.my.cnf 或本机 socket 配置好 MySQL 连接;需显式指定连接时加 --host 主机 -P 端口 -u 用户 或 --defaults-file=/path/my.cnf。命令可直接复制,替换其中的日志路径、库表名即可。
本工具的产出是"未使用索引的 DROP 建议",只打印不执行;请人工复核这些索引确实没被用到(慢日志覆盖的时间窗口要足够长、足够代表性)后再手动执行 DROP。
场景:分析慢日志找未用索引
拿到一份有代表性的慢日志,跑一遍看哪些索引一次都没被查询用到,输出即 DROP 建议:
pt-index-usage /var/log/mysql/slow.log --host localhost场景:只分析某个业务库
日志里混了多个库的查询,只关心 appdb 的索引使用,缩小盘点与 EXPLAIN 范围:
pt-index-usage /var/log/mysql/slow.log --host localhost --databases appdb场景:只分析几张表
针对写入变慢、怀疑索引冗余的某几张表做定点分析:
pt-index-usage /var/log/mysql/slow.log --host localhost \
--tables appdb.orders,appdb.order_items场景:排除系统库干扰
排除 mysql、sys、performance_schema、information_schema 这些系统库,只看业务库的索引:
pt-index-usage /var/log/mysql/slow.log --host localhost \
--ignore-databases mysql,sys,performance_schema,information_schema场景:落库供事后查询分析
不想只看一次性的 DROP 报告,把索引/查询/表的使用明细存进数据库,事后用 SQL 反复分析(会向目标库写数据,生产环境谨慎):
pt-index-usage /var/log/mysql/slow.log --host localhost \
--no-report \
--save-results-database h=localhost,D=percona \
--create-save-results-database场景:连唯一索引也一并建议删除
默认只建议删非唯一索引;当确认某些唯一索引也是多余(如已有其他更合适的唯一键)时,放宽到 all 一并列出(务必逐条确认,别误删唯一约束):
pt-index-usage /var/log/mysql/slow.log --host localhost --drop all功能说明
连接 MySQL 服务器,通读查询日志并对每条查询执行 EXPLAIN,最后打印查询未使用索引的报告。
- 日志须为 MySQL 慢查询日志格式;其他格式可用 pt-query-digest 转换。不指定文件时读 STDIN。
- 分两个阶段运行:第一阶段盘点数据库中所有表与索引;第二阶段对日志中每条查询执行 EXPLAIN。 两个阶段使用独立连接(共两个连接)。
- 非 SELECT 查询会尝试转换为大致等价的 SELECT 以便 EXPLAIN——不完美但足够有用。
- 与之前完全相同的查询不再重复 EXPLAIN(假定执行计划相同,直接累加索引使用计数); 但指纹相同、校验值不同的查询会重新 EXPLAIN——字面常量不同可能产生不同计划,这是需要度量的。
- EXPLAIN 之后需要把查询中的别名映射回原始表名(如
tbl1 AS foo的 foo → tbl1), 解析复杂,通常很准确,发现错误请提交可复现的测试用例。 - 无法 EXPLAIN 的查询会把后续所有相同指纹的查询加入黑名单,以减少开销并阻止持续报错。
输出
读完日志中所有事件后,为每个未使用的索引打印 DROP 语句。 日志中从未被任何查询访问过的表上的索引会被跳过,以避免误报。 未指定 --quiet 时还会向标准错误输出无法 EXPLAIN 的语句等警告; 进度报告默认开启(--progress),也输出到标准错误。
输出样例
官方源文档并未给出 DROP 报告的成段样例,但给出了 --save-results-database 落库后的结果形态。 下面逐字照抄其中最能说明分析结果的两段:index_alternatives 的建表语句(注释即官方原文), 以及官方随附的索引使用情况分析查询(默认会创建为视图):
CREATE TABLE IF NOT EXISTS index_alternatives (
query_id CHAR(32) NOT NULL, -- This query used
db VARCHAR(64) NOT NULL, -- this index, but...
tbl VARCHAR(64) NOT NULL, --
idx VARCHAR(64) NOT NULL, --
alt_idx VARCHAR(64) NOT NULL, -- was an alternative
cnt BIGINT UNSIGNED NOT NULL DEFAULT 1,
UNIQUE INDEX (query_id, db, tbl, idx, alt_idx),
INDEX (db, tbl, idx),
INDEX (db, tbl, alt_idx)
)SELECT i.idx, iu.usage_cnt, iu.usage_total,
ia.alt_cnt, ia.alt_total
FROM indexes AS i
LEFT OUTER JOIN (
SELECT db, tbl, idx, COUNT(*) AS usage_cnt,
SUM(cnt) AS usage_total, GROUP_CONCAT(query_id) AS used_by
FROM index_usage
GROUP BY db, tbl, idx
) AS iu ON i.db=iu.db AND i.tbl=iu.tbl AND i.idx = iu.idx
LEFT OUTER JOIN (
SELECT db, tbl, idx, COUNT(*) AS alt_cnt,
SUM(cnt) AS alt_total,
GROUP_CONCAT(query_id) AS alt_queries
FROM index_alternatives
GROUP BY db, tbl, idx
) AS ia ON i.db=ia.db AND i.tbl=ia.tbl AND i.idx = ia.idx;(以上为节选,完整示例见官方文档)
- 建表语句里的注释说明了
index_alternatives的语义:某条查询选择了idx, 而alt_idx是当时的备选索引;cnt是出现次数。 - 第二段查询按表逐个索引列出
usage_cnt(有多少种查询用到它)、usage_total(累计使用次数), 以及alt_cnt/alt_total(作为备选被考虑的情况)。 - 两次
LEFT OUTER JOIN之后usage_cnt为空的索引就是日志中从未被用到的那些, 也正是本节开头所说、报告里会为其打印 DROP 语句的对象。 - 这些结果表由
--save-results-database写入,配合--no-report可以先落库、事后再查询分析。
选项
| 选项 | 说明 |
|---|---|
--ask-pass | 连接 MySQL 时交互式询问密码 |
--[no]buffer-stdout | 默认开启 STDOUT 缓冲;使用 tee、kubectl logs 等后处理工具想看到实时进度时可关闭 |
-A, --charset | 类型:string。默认字符集(utf8 时设置 binmode/mysql_enable_utf8 并执行 SET NAMES UTF8) |
--config | 类型:Array。读取逗号分隔的配置文件列表;如指定必须放在命令行第一个选项的位置 |
--create-save-results-database | --save-results-database 不存在时创建它;已存在时直接使用并按需创建缺表 |
--[no]create-views | 默认:yes。为结果库的示例查询创建视图;--no-create-views 阻止创建 |
-D, --database | 类型:string。连接使用的数据库 |
-d, --databases | 类型:hash。只从这些库(逗号分隔)获取表与索引 |
--databases-regex | 类型:string。只从库名匹配该 Perl 正则的库获取 |
-F, --defaults-file | 类型:string。只从给定文件读取 mysql 选项,必须给绝对路径 |
--drop | 类型:Hash;默认:non-unique。只建议删除这些类型的未用索引:primary、unique、non-unique、all。默认不建议删主键/唯一索引。每种类型打印单独的 ALTER TABLE 语句 |
--empty-save-results-tables | 删除并重建 --save-results-database 中已存在的表,清除上次运行的结果 |
--help | 显示帮助并退出 |
-h, --host | 类型:string。要连接的主机 |
--ignore-databases | 类型:Hash。忽略这些库 |
--ignore-databases-regex | 类型:string。忽略库名匹配该正则的库 |
--ignore-tables | 类型:Hash。忽略这些表(可用库名限定) |
--ignore-tables-regex | 类型:string。忽略表名匹配该正则的表 |
-s, --mysql_ssl | 类型:int。创建 SSL MySQL 连接 |
-p, --password | 类型:string。连接密码(含逗号需转义) |
-P, --port | 类型:int。连接端口 |
--progress | 类型:array;默认:time,30。向 STDERR 打印进度:`percentage |
-q, --quiet | 不打印任何警告,同时禁用 --progress |
--[no]report | 默认:yes。打印 --report-format 指定的报告;配合 --save-results-database 只想稍后查询结果表时用 --no-report |
--report-format | 类型:Array;默认:drop_unused_indexes。当前唯一报告:drop_unused_indexes,打印删除未用索引的 SQL(另见 --drop) |
--save-results-database | 类型:DSN。把索引/查询/表及其使用信息保存到该库的多个表中(表自动创建;库不存在可用 --create-save-results-database 自动创建)。通过 INSERT 写入,生产环境慎用。结果表含 indexes、queries、tables、index_usage、index_alternatives,官方文档附有建表语句与一组示例查询(默认创建为视图),可回答"哪些查询用了多个索引及各占比例""哪些索引互为备选""哪些索引从未被选中(多余)""哪些索引对至少一条查询必不可少"等问题 |
--set-vars | 类型:Array。以逗号分隔的 变量=值 列表设置 MySQL 变量;默认 wait_timeout=10000;无法设置时打印警告并继续 |
-S, --socket | 类型:string。连接使用的 socket 文件 |
-t, --tables | 类型:hash。只从这些表(逗号分隔)获取索引 |
--tables-regex | 类型:string。只从表名匹配该 Perl 正则的表获取 |
-u, --user | 类型:string。登录用户(若非当前用户) |
--version | 显示版本并退出 |
--[no]version-check | 默认:yes。检查 Percona Toolkit、MySQL 等软件的最新版本与已知问题版本(详见版本检查) |
DSN 选项
| 键 | DSN 部分 | 说明 |
|---|---|---|
A | charset | 默认字符集 |
D | database | 要连接的数据库 |
F | mysql_read_default_file | 只从给定文件读取默认选项 |
h | host | 要连接的主机 |
p | password | 连接密码(含逗号需转义) |
P | port | 连接端口 |
S | mysql_socket | 连接使用的 socket 文件 |
u | user | 登录用户(若非当前用户) |
s | mysql_ssl | 创建 SSL 连接 |
其他信息
- 通用说明:已知问题的反馈方式、PTDEBUG 调试安全提示、系统要求基线,见 通用说明。
更多细节请阅读 官方文档。