【MySQL 文档翻译】理解查询计划
来源:SegmentFault
时间:2023-01-09 17:07:33 426浏览 收藏
知识点掌握了,还需要不断练习才能熟练运用。下面golang学习网给大家带来一个数据库开发实战,手把手教大家学习《【MySQL 文档翻译】理解查询计划》,在实现功能的过程中也带大家重新温习相关知识点,温故而知新,回头看看说不定又有不一样的感悟!
原文地址: 【MySQL 文档翻译】理解查询计划
欢迎访问我的博客: http://blog.duhbb.com/
官方文档
MySQL 官方文档地址: 8.8 Understanding the Query Execution Plan
引言
MySQL 优化器会根据 SQL 语句中的表, 列, 索引和 WHERE 子句中的条件的详细信息, 使用许多技术来有效地执行 SQL 查询. 可以在不读取所有行的情况下对一个巨大的表执行查询; 可以在不比较每个行组合的情况下执行涉及多个表的连接. 优化器选择执行最有效查询的一组操作称为
SELECT * FROM tbl_name WHERE primary_key=1; SELECT * FROM tbl_name WHERE primary_key_part1=1 AND primary_key_part2=2;
eq_ref (直接查询主键或者非空索引)
对于先前表中的每个行组合, 从该表中读取一行. 除了 system 和 const 类型, 这是最好的连接类型. 当连接使用索引的所有部分并且索引是 PRIMARY KEY 或 UNIQUE NOT NULL 索引时就是这种类型.
SELECT * FROM ref_table, other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table, other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
ref (最左前缀或键不是 PRIMARY KEY 或 UNIQUE 索引)
对于先前表中的每个行组合, 从该表中读取具有匹配索引值的所有行. 如果连接仅使用键的最左前缀或键不是 PRIMARY KEY 或 UNIQUE 索引 (换句话说, 如果连接不能基于键值选择单行), 则是
SELECT * FROM ref_table WHERE key_column=expr; SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
连接是使用 FULLTEXT 索引执行的.
ref_or_null(相比
SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;
此连接类型表明使用了索引合并优化. 在这种情况下, key 输出行中的列包含所用索引的列表, 并 key_len 包含所用索引的最长键部分的列表. 有关更多信息, 请参阅第 8.2.1.3 节 索引合并优化.
unique_subquery
此类型可将通过下面的 IN 子查询替换 eq_ref:
value IN (SELECT primary_key FROM single_table WHERE some_expr)
unique_subquery 只是一个索引查找功能, 完全替代子查询以提高效率.
index_subquery
此连接类型类似于 unique_subquery. 它替换 IN 子查询, 但它适用于以下形式的子查询中的非唯一索引:
value IN (SELECT key_column FROM single_table WHERE some_expr)
range
仅检索给定范围内的行, 使用索引选择行. 输出行中的 key 列指示使用了哪个索引. key_len 包含使用的最长的关键部分. 该 ref 列适用 NULL 于这种类型.
range 可以在使用运算符中的任何一个与常量进行比较:
SELECT * FROM tbl_name WHERE key_column = 10; SELECT * FROM tbl_name WHERE key_column BETWEEN 10 and 20; SELECT * FROM tbl_name WHERE key_column IN (10,20,30); SELECT * FROM tbl_name WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
index (完全扫描索引了)
连接类型与 index 相同 ALL, 只是扫描了索引树. 这有两种方式:
- 如果索引是查询的覆盖索引并且可以用于满足表中所需的所有数据, 则仅扫描索引树. 在这种情况下, 该 Extra 列 显示 Using index. 仅索引扫描通常比仅索引扫描更快, ALL 因为索引的大小通常小于表数据.
- 使用从索引中读取以按索引顺序查找数据行来执行全表扫描. Uses index 没有出现在 Extra 列中.
当查询仅使用属于单个索引的列时,MySQL 可以使用此连接类型.
对先前表中的每个行组合进行全表扫描. 如果该表是第一个未被标记为 const 的表 , 这通常不好, 并且在所有其他情况下通常非常糟糕. 通常, 您可以 ALL 通过添加索引来避免基于先前表中的常量值或列值从表中检索行.
哇偶, 好厉害, 感觉这个得掌握一下哦
解释额外信息
输出列 Extra 包含 EXPLAIN 有关 MySQL 如何解析查询的附加信息. 下面的列表解释了可以出现在此列中的值. 每个项目还为 JSON 格式的输出指示哪个属性显示该 Extra 值. 对于其中一些, 有一个特定的属性. 其他显示为 message 属性的文本.
如果您想尽可能快地进行查询, 请注意查看 Extra 值是否包含
SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;
假设
SELECT ... FROM t, LATERAL (derived table that refers to t) AS dt ...
每次 t 顶部查询处理新行时, 派生表的内容都会重新实现以使其保持最新状态.
这表示在处理表查询时服务器执行了多少目录扫描 INFORMATION_SCHEMA, 如第 8.2.3 节 优化 INFORMATION_SCHEMA 查询 中所述. N 的值可以是 0, 1 或 all.
Select tables optimized away
优化器确定 1) 最多应该返回一行, 以及 2) 要生成这一行, 必须读取一组确定性的行. 当在优化阶段可以读取要读取的行时 (例如, 通过读取索引行), 在查询执行期间不需要读取任何表.
当查询被隐式分组 (包含聚合函数但没有 GROUP BY 子句) 时, 第一个条件得到满足. 当每个使用的索引执行一次行查找时, 满足第二个条件. 读取的索引数决定了要读取的行数.
考虑以下隐式分组查询:
SELECT MIN(c1), MIN(c2) FROM t1;
假设 MIN(c1) 可以通过读取一个索引行 MIN(c2) 来检索它, 并且可以通过从不同的索引读取一行来检索它. 也就是说, 对于每一列 c1 和 c2, 都存在一个索引, 其中该列是索引的第一列. 在这种情况下, 通过读取两个确定性行来返回一行.
如果要读取的行不确定, 则 Extra 不会出现此值. 考虑这个查询:
SELECT MIN(c2) FROM t1 WHERE c1
假设这 (c1, c2) 是一个覆盖索引. 使用此索引, c1
SELECT MIN(c2) FROM t1 WHERE c1 = 10;
在这种情况下, 第一个索引行 c1 = 10 包含最小值 c2 . 只需读取一行即可生成返回的行.
对于维护每个表的精确行数的存储引擎 (例如 MyISAM, 但不是 InnoDB), 对于缺少
EXPLAIN SELECT tt.TicketNumber, tt.TimeIn, tt.ProjectReference, tt.EstimatedShipDate, tt.ActualShipDate, tt.ClientID, tt.ServiceCodes, tt.RepetitiveID, tt.CurrentProcess, tt.CurrentDPPerson, tt.RecordVolume, tt.DPPrinted, et.COUNTRY, et_1.COUNTRY, do.CUSTNAME FROM tt, et, et AS et_1, do WHERE tt.SubmitTime IS NULL AND tt.ActualPC = et.EMPLOYID AND tt.AssignedPC = et_1.EMPLOYID AND tt.ClientID = do.CUSTNMBR;
对于此示例, 作出以下假设:
- 被比较的列已声明如下.
表 | 列 | 数据类型 |
---|---|---|
tt | ActualPC | CHAR(10) |
tt | AssignedPC | CHAR(10) |
tt | ClientID | CHAR(10) |
et | EMPLOYID | CHAR(15) |
do | CUSTNMBR | CHAR(15) |
- 这些表具有以下索引.
表 | 索引 |
---|---|
tt | ActualPC |
tt | AssignedPC |
tt | ClientID |
et | EMPLOYID(主键) |
do | CUSTNMBR(主键) |
- 这些 tt.ActualPC 值不是均匀分布的.
最初, 在执行任何优化之前, 该 EXPLAIN 语句会生成以下信息:
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 do ALL PRIMARY NULL NULL NULL 2135 et_1 ALL PRIMARY NULL NULL NULL 74 tt ALL AssignedPC, NULL NULL NULL 3872 ClientID, ActualPC Range checked for each record (index map: 0x23)
因为对于每个表
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
现在
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC, NULL NULL NULL 3872 Using ClientID, where ActualPC do ALL PRIMARY NULL NULL NULL 2135 Range checked for each record (index map: 0x1) et_1 ALL PRIMARY NULL NULL NULL 74 Range checked for each record (index map: 0x1) et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
这并不完美, 但要好得多: 这些 rows 值的乘积小了 74 倍. 这个版本在几秒钟内执行.
可以进行第二次更改以消除
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15), MODIFY ClientID VARCHAR(15);
修改后, EXPLAIN 产生如下所示的输出:
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using ClientID, where ActualPC et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
在这一点上, 查询几乎被尽可能地优化了. 剩下的问题是, 默认情况下, MySQL 假定
mysql> ANALYZE TABLE tt;
使用附加的索引信息, 连接是完美的并 EXPLAIN 产生以下结果:
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC NULL NULL NULL 3872 Using ClientID, where ActualPC et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1 et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
EXPLAIN 输出中的 rows 列是来自 MySQL 连接优化器的有根据的猜测. rows 通过将产品与查询返回的实际行数进行比较, 检查这些数字是否更接近事实. 如果数字完全不同, 您可能会通过
mysql> EXPLAIN SELECT t1.a, t1.a IN (SELECT t2.a FROM t2) FROM t1\G *************************** 1. row *************************** id: 1 select_type: PRIMARY table: t1 type: index possible_keys: NULL key: PRIMARY key_len: 4 ref: NULL rows: 4 filtered: 100.00 Extra: Using index *************************** 2. row *************************** id: 2 select_type: SUBQUERY table: t2 type: index possible_keys: a key: a key_len: 5 ref: NULL rows: 3 filtered: 100.00 Extra: Using index 2 rows in set, 1 warning (0.00 sec) mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Note Code: 1003 Message: /* select#1 */ select ` test` .` t1` .` a` AS ` a` , (` test` .` t1` .` a` ,` test` .` t1` .` a` in ( (/* select#2 */ select ` test` .` t2` .` a` from ` test` .` t2` where 1 having 1 ), (` test` .` t1` .` a` in on where ((` test` .` t1` .` a` = ` materialized-subquery` .` a` ))))) AS ` t1.a IN (SELECT t2.a FROM t2)` from ` test` .` t1` 1 row in set (0.00 sec)
因为显示的语句 SHOW WARNINGS 可能包含特殊标记以提供有关查询重写或优化器操作的信息, 所以该语句不一定是有效的 SQL, 并且不打算执行. 输出还可能包含带有 Message 值的行, 这些值提供有关优化器所采取的操作的附加非 SQL 解释性说明.
以下列表描述了可以出现在由 SHOW WARNINGS 显示的扩展输出中的特殊标记:
获取命名连接的执行计划信息
估计查询性能
在大多数情况下, 您可以通过计算磁盘寻道 (
disk seeks
) 次数来估计查询性能. 对于小型表, 通常可以在一次磁盘查找中找到一行 (因为索引可能已缓存). 对于更大的表, 您可以估计, 使用 B-tree 索引, 您需要这么多次查找才能找到一行:$$ \frac{log(row\\\_count)}{log(index\\\_block\\\_length / 3 * 2 / (index\\\_length + data\\\_pointer\\\_length))} + 1 $$
在 MySQL 中, 索引块通常为 1024 字节, 数据指针通常为 4 字节. 对于一个 500000 行的表, 键值长度为 3 个字节 (大小为 MEDIUMINT), 公式表示 $log(500000)/log(1024/3\*2/(3+4)) + 1= 4$ seeks.
该索引需要大约 $500000 \* 7 \* 3/2 = 5.2MB$ 的存储空间 (假设典型的索引缓冲区填充率为 2/3), 因此您可能在内存中有很多索引, 因此只需要一两次调用读取数据以查找行.
但是, 对于写入, 您需要四个查找请求来查找放置新索引值的位置, 通常需要两次查找来更新索引并写入行.
前面的讨论并不意味着您的应用程序性能会以 $log(N)$ 的速度缓慢下降. 只要一切都被操作系统或 MySQL 服务器缓存, 随着表变大, 事情只会稍微变慢. 在数据变得太大而无法缓存后, 事情开始变得慢得多, 直到您的应用程序仅受磁盘搜索 (增加 log N) 的约束. 为避免这种情况, 请随着数据的增长而增加 key 的缓存大小. 对于 MyISAM 表, 键缓存大小由 key_buffer_size 系统变量控制. 请参见第 5.1.1 节 配置服务器.
笔者的验证 Demo
没有刻意增加 Explain 的使用 Demo, 后续的开发中会找机会验证的.
原文地址: 【MySQL 文档翻译】理解查询计划
欢迎访问我的博客: http://blog.duhbb.com/
好了,本文到此结束,带大家了解了《【MySQL 文档翻译】理解查询计划》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多数据库知识!
-
499 收藏
-
244 收藏
-
235 收藏
-
157 收藏
-
101 收藏
-
475 收藏
-
266 收藏
-
273 收藏
-
283 收藏
-
210 收藏
-
371 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 虚幻的小伙
- 这篇博文真及时,太详细了,真优秀,mark,关注楼主了!希望楼主能多写数据库相关的文章。
- 2023-02-04 13:17:08
-
- 幸福的野狼
- 太全面了,码住,感谢楼主的这篇博文,我会继续支持!
- 2023-02-03 06:07:46
-
- 生动的皮带
- 这篇文章太及时了,细节满满,太给力了,已加入收藏夹了,关注up主了!希望up主能多写数据库相关的文章。
- 2023-01-24 15:19:11
-
- 凶狠的黄豆
- 很有用,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢博主分享技术文章!
- 2023-01-21 06:03:34
-
- 称心的楼房
- 好细啊,已加入收藏夹了,感谢up主的这篇技术贴,我会继续支持!
- 2023-01-13 22:05:17
-
- 现代的金毛
- 很棒,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢楼主分享技术贴!
- 2023-01-11 20:20:38