这是 pt-visual-explainp 命令,可以使用我们的多个免费在线工作站之一在 OnWorks 免费托管服务提供商中运行,例如 Ubuntu Online、Fedora Online、Windows 在线模拟器或 MAC OS 在线模拟器
程序:
您的姓名
pt-visual-explain - 将 EXPLAIN 输出格式化为树。
概要
用法:pt-visual-explain [选项] [文件]
pt-visual-explain 将 EXPLAIN 输出转换为查询计划的树表示。
如果给出了 FILE,则从文件中读取输入。 没有 FILE 或 FILE 为 - 时,读取
标准输入。
例子:
pt-视觉-解释
pt-visual-explain -c
mysql -e "explain select * from mysql.user" | pt-视觉-解释
风险
Percona Toolkit 是成熟的,在现实世界中得到验证,并经过良好测试,但所有数据库
工具会给系统和数据库服务器带来风险。 在使用这个工具之前,
请:
· 阅读工具的文档
· 查看工具已知的“BUGS”
· 在非生产服务器上测试该工具
· 备份您的生产服务器并验证备份
商品描述
pt-visual-explain 将 MySQL 的 EXPLAIN 输出反向工程为查询执行计划,
然后将其格式化为左深树 - 与内部表示计划的方式相同
MySQL。 可以手动执行此操作,或直接读取 EXPLAIN 的输出,但它
需要耐心和专业知识。 许多人发现更多的树表示
可以理解。
您可以通过管道输入 pt-visual-explain 或在命令行中指定文件名,
包括神奇的“-”文件名,它将从标准输入中读取。 它可以做两个
输入的东西:解析它看起来像 EXPLAIN 输出,或者连接
到 MySQL 实例并在输入上运行 EXPLAIN。
解析其输入时,pt-visual-explain 理解三种格式:类似这样的表格
显示在 mysql 命令行客户端中,垂直于使用 \G 行创建的
mysql 命令行客户端中的终止符,和制表符分隔。 它忽略任何行
不知道怎么解析。
执行输入时,pt-visual-explain 替换输入中的所有内容,直到
先用'EXPLAIN SELECT' SELECT 关键字,然后执行结果。 你必须
指定“--connect”以将输入作为查询执行。
无论哪种方式,它都会根据结果集构建一棵树并将其打印到标准输出。 为了
以下查询,
从 sakila.film_actor 中选择 * 使用(film_id)加入 sakila.film;
pt-visual-explain 生成这个查询计划:
注册
+- 书签查找
| +- 表
| | 表film_actor
| | possible_keys idx_fk_film_id
| +- 索引查找
| 关键电影演员-> idx_fk_film_id
| possible_keys idx_fk_film_id
| key_len 2
| 参考 sakila.film.film_id
| 第 2 行
+- 表扫描
第 952 行
+- 表
桌膜
可能的键主要
查询计划是左深、深度优先搜索,树的根是输出节点——
执行计划的最后一步。 换句话说,像这样阅读:
1. 表扫描'film' 表,它访问估计952 行。
2. 对于每一行,通过索引查找找到匹配的行
film_actor->idx_fk_film_id 索引与来自 sakila.film.film_id 的值,然后一个
书签查找到 film_actor 表。
有关如何读取 EXPLAIN 输出的更多信息,请参阅
<http://dev.mysql.com/doc/en/explain.html>,以及这个名为“MySQL 查询优化器”的演讲
v. 5.2 中的内部结构和即将推出的功能:来自 MySQL 之一的 Timour Katchaounov
开发商:http://goo.gl/VIWvo>
课程结构
这个程序实际上是一个可运行的模块,而不仅仅是一个普通的 Perl 脚本。 实际上,
其中嵌入了两个模块。 这使单元测试变得容易,但它也使
如果需要,您可以轻松使用解析和树构建功能。
ExplainParser 包接受一个字符串并解析它认为看起来像的任何内容
EXPLAIN 的输出。 概要如下:
需要“pt-visual-explain”;
我的 $p = ExplainParser->new();
我的 $rows = $p->parse("some text");
# $rows 是 hashrefs 的 arrayref。
ExplainTree 包接受一组行并将其变成一棵树。 为了方便,
您还可以将其委托给 ExplainParser 并为您解析文本。 这是
概要:
需要“pt-visual-explain”;
我的 $e = ExplainTree->new();
我的 $tree = $e->parse("some text", \%options);
我的 $output = $e->pretty_print($tree);
打印 $tree;
算法
本节说明将 EXPLAIN 转换为树的算法。 你可能会
如果你想更全面地理解 EXPLAIN,或者试图
弄清楚这是如何工作的,否则这部分可能不会让你的生活
更丰富。
可以通过检查每行的 id、select_type 和 table 列来构建树。
以下是我对他们的了解:
id 列是选择的序号。 这并不表示嵌套; 它
只是来自从 SQL 语句的左侧计算 SELECT。 就像捕捉
正则表达式中的括号。 UNION RESULT 行没有 id,因为它
不是 SELECT。 我记得,源代码实际上将 UNION 称为 fake_lex。
如果两个相邻的行具有相同的 id 值,它们将使用标准的单行连接
扫描多连接方法。
select_type 列告诉 a) 一个新的子范围已经打开 b) 什么样的
该行与前一行的关系 c) 该行代表的操作类型。
· SIMPLE 表示整个查询中没有子查询或联合。
· PRIMARY 表示有,但这是最外层的 SELECT。
· [DEPENDENT] UNION 表示这个结果与前一个结果(不是行;a
结果可能包含不止一行)。
· UNION RESULT 终止一组 UNIONed 结果。
· [DEPENDENT|UNCACHEABLE] SUBQUERY 表示一个新的子范围正在打开。 这是那种
发生在 WHERE 子句、SELECT 列表或诸如此类的子查询中; 它不会回来
所谓的“派生表”。
· DERIVED 是FROM 子句中的子查询。
联接的表都具有相同的 select_type。 例如,如果您加入三个
依赖子查询中的表,它们都会说同样的话:DEPENDENT SUBQUERY。
表列通常指定表名或别名,但也可能表示或者
. 如果它说,该行代表对临时文件的访问
包含 id 为 N 的子查询结果的表。如果它说它是
同样的事情,但它指的是它联合在一起的结果。
最后,顺序很重要。 如果一行的 id 小于它之前的,我认为这意味着
它依赖于它之前的东西以外的东西。 例如,
解释选择
(从sakila.film中选择1个),
(从 sakila.film_actor 中选择 2 个),
(从 sakila.actor 中选择 3 个);
| 身份证 | 选择类型 | 表|
+----+------------+------------+
| 1 | 主要 | 空 |
| 4 | 子查询 | 演员 |
| 3 | 子查询 | 电影演员|
| 2 | 子查询 | 电影 |
如果结果的顺序是 2-3-4,我认为这意味着 3 是 2 的子查询,4 是一个
3 的子查询。实际上,这意味着 4 是最近的最近行的子查询
具有较小的 id,即 1。对于 3 和 2 也是如此。
出于同样的原因,这种结构很难以编程方式构建到树中
通过检查来理解:有前向和后向引用。
是对 selectN 的前向引用,而是对 selectM 和的反向引用
选择N。 这使得递归和其他树构建算法难以正确(注意:
实施后,我现在看到如何处理前向和
向后引用,但我没有动力去改变一些有效的东西)。 考虑
执行以下操作:
选择*从(
从 sakila.actor 中选择 1 作为 actor_1
工会
从 sakila.actor 中选择 1 作为 actor_2
) 作为 der_1
工会
选择*从(
从 sakila.actor 中选择 1 作为 actor_3
联合所有
从 sakila.actor 中选择 1 作为 actor_4
) 作为 der_2;
| 身份证 | 选择类型 | 表|
+--------+--------------+------------+
| 1 | 主要 | |
| 2 | 衍生 | 演员_1 |
| 3 | 联合 | 演员_2 |
| 空 | 联合结果 | |
| 4 | 联合 | |
| 5 | 衍生 | 演员_3 |
| 6 | 联合 | 演员_4 |
| 空 | 联合结果 | |
| 空 | 联合结果 | |
如果它看起来像这样,这将更容易使用(我已将 id 括在
我移动的行):
| 身份证 | 选择类型 | 表|
+--------+--------------+------------+
| [1] | 联合结果 | |
| 1 | 主要 | |
| [2] | 联合结果 | |
| 2 | 衍生 | 演员_1 |
| 3 | 联合 | 演员_2 |
| 4 | 联合 | |
| [5] | 联合结果 | |
| 5 | 衍生 | 演员_3 |
| 6 | 联合 | 演员_4 |
事实上,为什么不对所有的 id 重新编号,让 PRIMARY 行变成 2,依此类推? 那
将使它更容易阅读。 不幸的是,这也会产生以下影响
破坏了 id 列的含义,我认为在
最后一棵树。 此外,虽然它使阅读更容易,但它并没有使它更容易
以编程方式操作; 所以可以让它们按原样编号。
重新排序的目标是更容易找出哪些行是其子行
执行计划中的哪些行。 给定重新排序的列表和表为
或者,很容易找到应该
成为树中的子节点:您只需查找 ID 与
表中的第一个数字。
下一个问题是如何找到应该是 UNION 的子节点的最后一行或
衍生的。 我将从 DERIVED 开始,因为该解决方案使 UNION 变得容易。
考虑 MySQL 如何根据它们在
SQL,从左到右。 由于 DERIVED 表将其内的所有内容都包含在一个范围内,因此
变成临时表,只需要考虑两件事:它的子查询
和联合(如果有的话),以及它在包围它的范围内的下一个兄弟姐妹。 它的孩子
根据定义,所有的 id 都会比它大,所以任何后面的行
id 终止范围。
这是一个例子。 这里的中间派生表有一个子查询和一个 UNION 以使其成为
这个例子稍微复杂一点。
解释选择1
来自(
从 sakila.film limit 1 中选择 film_id
) 作为 der_1
加入 (
选择 film_id, actor_id, (select count(*) from sakila.rental) 作为 r
来自 sakila.film_actor 限制 1
联合所有
从 sakila.film_actor 中选择 1, 1, 1 作为虚拟对象
) 作为 der_2 使用 (film_id)
加入 (
从 sakila.actor limit 1 中选择 actor_id
) 作为 der_3 使用 (actor_id);
这是 EXPLAIN 的输出:
| 身份证 | 选择类型 | 表|
| 1 | 主要 | |
| 1 | 主要 | |
| 1 | 主要 | |
| 6 | 衍生 | 演员 |
| 3 | 衍生 | 电影演员|
| 4 | 子查询 | 出租 |
| 5 | 联合 | 假人 |
| 空 | 联合结果 | |
| 2 | 衍生 | 电影 |
兄弟姐妹的id都是1,我关心的中间那个是derivative3。 (注意 MySQL
不会按照我定义的顺序执行它们,这很好)。 现在注意 MySQL
以相反的顺序打印出我定义的子查询的行:6、3、2。它总是
似乎这样做,并且可能还有其他方法可以找到范围边界
包括寻找下一个最大兄弟的下边界,但这是一个很好的
足够的启发式。 对于非派生子查询,我不得不依赖它,所以我依赖它
这里也。 因此,我决定所有大于或等于 3 的东西都属于
派生范围。
UNION 的规则很简单:它们消耗整个封闭范围,并找到
每个部分的组成部分,您会找到每个部分的开头,如
定义,它的结尾要么在下一个定义之前,要么是
最后一部分,结束是范围的结束。
这很简单,因为 UNION 消耗了整个范围,即整个范围
语句,或 DERIVED 表的范围。 这是因为 UNION 不能是兄弟
另一个 UNION 或一个表,DERIVED 与否。 (如果您不这样做,请尝试编写这样的声明
直观地看到它)。 因此,您可以找到封闭范围的边界,并且
剩下的就简单了。 注意在上面的例子中,UNION 结束了, 哪一个
包括 id 为 4 的行——它包括 3 到 5 之间的每一行。
最后,还有非派生子查询需要处理。 在这种情况下我不能看
在兄弟姐妹处找到范围的结尾,就像我为 DERIVED 所做的那样。 我必须相信 MySQL
执行深度优先。 下面是一个例子:
说明
选择actor_id,
(
选择计数(film_id)
+(从 sakila.film 中选择 count(*))
从 sakila.film 加入 sakila.film_actor using(film_id)
哪里存在(
从 sakila.actor 中选择 *
其中 sakila.actor.actor_id = sakila.film_actor.actor_id
)
)
来自 sakila.actor;
| 身份证 | 选择类型 | 表|
| 1 | 主要 | 演员 |
| 2 | 子查询 | 电影 |
| 2 | 子查询 | 电影演员|
| 4 | 相关子查询 | 演员 |
| 3 | 子查询 | 电影 |
按照顺序,应该像这样构建树:
· 见第 1 行。
· 见第 2 行。它的 id 大于 1,所以它是一个子查询,以及每隔一行
id 大于 2。
· 在此范围内,查看 2 和 2 并加入它们。 参见 4。它的 id 比 2 高,所以
这又是一个子查询; 递归。 之后再看3,也更高; 递归。
但是嵌套子查询不包含 select 3 的唯一原因是因为 select 4 来了
第一的。 换句话说,如果 EXPLAIN 看起来像这样,
| 身份证 | 选择类型 | 表|
| 1 | 主要 | 演员 |
| 2 | 子查询 | 电影 |
| 2 | 子查询 | 电影演员|
| 3 | 子查询 | 电影 |
| 4 | 相关子查询 | 演员 |
我将被迫在看到 select 3 时假设 select 4 是它的子查询,而不是
而不仅仅是成为封闭范围内的下一个兄弟。 如果这是错误的,那么
算法是错误的,我不知道可以做些什么。
UNION 比“整个作用域是一个 UNION”要复杂一些,因为
UNION 本身可能位于仅由第一项指示的封闭范围内
联盟内部。 只有三种封闭作用域:UNION、DERIVED 和
子查询。 UNION 不能包含一个 UNION,一个 DERIVED 有它自己的“范围标记”,但是一个
SUBQUERY 可以完全包围一个 UNION,就像空表 t1 上的这个奇怪的例子:
解释 select * from t1 where not exist(
(从 t11 t1 中选择 t11.i)联合(从 t12 t1 中选择 t12.i));
| 身份证 | 选择类型 | 表| 额外 |
+------+--------------+------------+-------------- ------------------+
| 1 | 主要 | t1 | 未找到 const 行 |
| 2 | 子查询 | 空 | 没有使用表 |
| 3 | 子查询 | 空 | const 表中没有匹配的行 |
| 4 | 联合 | t12 | 未找到 const 行 |
| 空 | 联合结果 | | |
UNION 的向后引用可能使它看起来像 UNION 包含子查询,
但是研究查询可以清楚地表明情况并非如此。 所以当一个 UNION 的第一行
说 SUBQUERY,这是特例。
顺便说一下,我并不完全理解这个查询计划; 有 4 个编号的 SELECT
计划,但查询中只有 3 个。 UNION 周围的括号是有意义的。 删除
他们将使 EXPLAIN 不同。 如果你知道,请告诉我这是如何以及为什么会这样。
有了这些知识,就可以使用递归来转换父子
所有行之间的关系变成一棵树,代表执行计划。
MySQL 按执行顺序打印行,甚至是向前和向后引用。 在
任何给定的范围,行都被处理为左深树。 MySQL不做“浓密”
执行计划。 它从一个表开始,在下一个表中找到匹配的行,然后
一直持续到最后一个表,当它发出一行时。 当它用完时,它会回溯到
它可以找到下一行并重复。 当然有微妙之处,但这是
基本计划。 这就是为什么 MySQL 将所有 RIGHT OUTER JOIN 转换为 LEFT OUTER JOIN 和
不能做完全外部联接。
这意味着在任何给定的范围内,比如说
| 身份证 | 选择类型 | 表|
| 1 | 简单 | tbl1 |
| 1 | 简单 | tbl2 |
| 1 | 简单 | tbl3 |
执行计划看起来像是对这棵树的深度优先遍历:
注册
/ \
加入 tbl3
/ \
表1 表2
JOIN 可能不是 JOIN。 例如,它可能是一个子查询。 这来自
类型列 EXPLAIN。 文档说这是一种“连接类型”,但我认为“访问
type”更准确,因为它是“MySQL 如何访问行”。
pt-visual-explain 装饰树的作用不仅仅是将行转换为节点。
每个节点可能会得到一系列转换,将其变成一个以上的子树
节点。 例如,未标记为“使用索引”的索引扫描必须执行书签查找
进入表格行; 那是一个三节点的子树。 然而,在上述节点排序之后
和范围界定的东西,其余的过程非常简单。
配置
此工具接受额外的命令行参数。 参考“概要”和用法
详细信息。
--询问通行证
连接 MySQL 时提示输入密码。
--字符集
简写:-A; 类型:字符串
默认字符集。 如果值为 utf8,则将 STDOUT 上的 Perl 的 binmode 设置为 utf8,
将 mysql_enable_utf8 选项传递给 DBD::mysql,然后运行 SET NAMES UTF8
连接到 MySQL。 任何其他值在没有 utf8 层的 STDOUT 上设置 binmode,
并在连接到 MySQL 后运行 SET NAMES。
--集群-pk
假设PRIMARY KEY索引访问不需要做书签查找来检索
行。 InnoDB 就是这种情况。
--配置
类型:数组
阅读这个逗号分隔的配置文件列表; 如果指定,这必须是第一个
命令行选项。
- 连接
将输入视为查询,并通过连接到 MySQL 实例获取 EXPLAIN 输出
并在查询上运行 EXPLAIN。 当给出这个选项时,pt-visual-explain 使用
其他特定于连接的选项,例如“--user”以连接到 MySQL
实例。 如果你有一个 .my.cnf 文件,它会读取它,所以你可能不需要指定
任何特定于连接的选项。
- 数据库
简写:-D; 类型:字符串
连接到这个数据库。
--defaults-文件
简写:-F; 类型:字符串
仅从给定文件中读取 mysql 选项。 您必须提供绝对路径名。
- 格式
类型:字符串; 默认值:树
设置输出格式。
默认是一个简洁的漂亮印刷树。 有效值为:
价值意义
==================================================== ===
树 印刷精美的简洁树。
dump Data::Dumper 输出(更多信息请参见 Data::Dumper)。
- 帮帮我
显示帮助并退出。
- 主持人
简写:-h; 类型:字符串
连接到主机。
- 密码
简写形式:-p; 类型:字符串
连接时使用的密码。 如果密码包含逗号,则必须对其进行转义
带反斜杠:“exam\,ple”
--pid
类型:字符串
创建给定的 PID 文件。 如果 PID 文件已经存在并且该工具将不会启动
它包含的 PID 与当前 PID 不同。 但是,如果 PID 文件
存在并且它包含的 PID 不再运行,该工具将覆盖 PID
带有当前 PID 的文件。 工具退出时,PID 文件会自动删除。
- 港口
简写形式:-P; 类型:int
用于连接的端口号。
--设置变量
类型:数组
在这个逗号分隔的“变量=值”对列表中设置 MySQL 变量。
默认情况下,工具集:
等待超时=10000
在命令行上指定的变量会覆盖这些默认值。 例如,
指定“--set-vars wait_timeout=500”会覆盖默认值 10000。
如果无法设置变量,该工具会打印警告并继续。
- 插座
简写:-S; 类型:字符串
用于连接的套接字文件。
- 用户
简写:-u; 类型:字符串
如果不是当前用户,则用于登录的用户。
- 版
显示版本并退出。
DSN 配置
这些 DSN 选项用于创建 DSN。 每个选项都像“option=value”一样给出。
选项区分大小写,因此 P 和 p 不是同一个选项。 不可能有
“=”之前或之后的空格,如果该值包含空格,则必须用引号引起来。
DSN 选项以逗号分隔。 有关完整详细信息,请参阅 percona-toolkit 联机帮助页。
· 一种
dsn:字符集; 副本:是
默认字符集。
·D
dsn:数据库; 副本:是
默认数据库。
F
dsn: mysql_read_default_file; 副本:是
仅从给定文件中读取默认选项
· H
dsn:主机; 副本:是
连接到主机。
·p
dsn:密码; 副本:是
连接时使用的密码。 如果密码包含逗号,则必须对其进行转义
带反斜杠:“exam\,ple”
·P
dsn:端口; 副本:是
用于连接的端口号。
·S
dsn: mysql_socket; 副本:是
用于连接的套接字文件。
·你
dsn:用户; 副本:是
如果不是当前用户,则用于登录的用户。
环境
环境变量“PTDEBUG”启用对 STDERR 的详细调试输出。 启用
调试并将所有输出捕获到文件中,运行该工具,如:
PTDEBUG=1 pt-visual-explain ... > 文件 2>&1
请注意:调试输出量很大,可能会生成几兆字节的输出。
系统 参赛要件
你需要 Perl、DBI、DBD::mysql 和一些应该安装在任何地方的核心包。
相当新版本的 Perl。
使用 onworks.net 服务在线使用 pt-visual-explainp