Skip to content

pt-index-usage

从查询日志读取查询,用 EXPLAIN 分析索引使用情况,找出未被使用的索引。

语法

bash
pt-index-usage [OPTIONS] [FILES]
bash
# 分析 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 建议:

bash
pt-index-usage /var/log/mysql/slow.log --host localhost

场景:只分析某个业务库

日志里混了多个库的查询,只关心 appdb 的索引使用,缩小盘点与 EXPLAIN 范围:

bash
pt-index-usage /var/log/mysql/slow.log --host localhost --databases appdb

场景:只分析几张表

针对写入变慢、怀疑索引冗余的某几张表做定点分析:

bash
pt-index-usage /var/log/mysql/slow.log --host localhost \
  --tables appdb.orders,appdb.order_items

场景:排除系统库干扰

排除 mysqlsys、performance_schema、information_schema 这些系统库,只看业务库的索引:

bash
pt-index-usage /var/log/mysql/slow.log --host localhost \
  --ignore-databases mysql,sys,performance_schema,information_schema

场景:落库供事后查询分析

不想只看一次性的 DROP 报告,把索引/查询/表的使用明细存进数据库,事后用 SQL 反复分析(会向目标库写数据,生产环境谨慎):

bash
pt-index-usage /var/log/mysql/slow.log --host localhost \
  --no-report \
  --save-results-database h=localhost,D=percona \
  --create-save-results-database

场景:连唯一索引也一并建议删除

默认只建议删非唯一索引;当确认某些唯一索引也是多余(如已有其他更合适的唯一键)时,放宽到 all 一并列出(务必逐条确认,别误删唯一约束):

bash
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 的建表语句(注释即官方原文), 以及官方随附的索引使用情况分析查询(默认会创建为视图):

sql
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)
)
sql
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。只建议删除这些类型的未用索引:primaryuniquenon-uniqueall。默认不建议删主键/唯一索引。每种类型打印单独的 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 写入,生产环境慎用。结果表含 indexesqueriestablesindex_usageindex_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 部分说明
Acharset默认字符集
Ddatabase要连接的数据库
Fmysql_read_default_file只从给定文件读取默认选项
hhost要连接的主机
ppassword连接密码(含逗号需转义)
Pport连接端口
Smysql_socket连接使用的 socket 文件
uuser登录用户(若非当前用户)
smysql_ssl创建 SSL 连接

其他信息

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

更多细节请阅读 官方文档

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