【mysql 优化 4】嵌套连接优化
与SQL标准相比,table_factor的语法被扩展。后者仅接受table_reference,而不是一对括号内的列表。如果我们将table_reference项目的列表中的每个逗号都视为与内部连接相同,那么这是一个保守的扩展。例如:
SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
等同于:
SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)
ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
在mysql中,cross join等同于inner join语法,它们之间可以互相替换。在SQL标准中,他们并不是等同的。inner join连同 on 语句使用,cross join 则不然。
一般来说,在只包含inner join操作的表达式中可以忽略括号,看看以下的join表达式:
t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
ON t1.a=t2.a
在移除括号,并且组织表达式之后,这个join表达式变成了这样:
(t1 LEFT JOIN t2 ON t1.a=t2.a) LEFT JOIN t3
ON t2.b=t3.b OR t2.b IS NULL
但是,这两个表达式并不等价。看这个,如果t1,t2和t3有以下的特点:
1,t1 包含(1),(2)两行
2,t2包含(1,101)这一行
3,t3包含(101)这一行
在这个实例中,第一个表达式将返回一个结果集,包括行 (1,1,101,101), (2,NULL,NULL,NULL),而第二个表达式会返回(1,1,101,101), (2,NULL,NULL,101):
mysql> SELECT *
FROM t1
LEFT JOIN
(t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
ON t1.a=t2.a;
+------+------+------+------+
| a | a | b | b |
+------+------+------+------+
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | NULL |
+------+------+------+------+
mysql> SELECT *
FROM (t1 LEFT JOIN t2 ON t1.a=t2.a)
LEFT JOIN t3
ON t2.b=t3.b OR t2.b IS NULL;
+------+------+------+------+
| a | a | b | b |
+------+------+------+------+
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | 101 |
+------+------+------+------+
在下面的实例中,内连接和外链接一起使用:
t1 LEFT JOIN (t2, t3) ON t1.a=t2.a
这个表达式不会被转换成如下表达:
t1 LEFT JOIN t2 ON t1.a=t2.a, t3
对于给定的表特点(上述)来说,这两个表达式将会返回两个不同的结果集:
mysql> SELECT *
FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a;
+------+------+------+------+
| a | a | b | b |
+------+------+------+------+
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | NULL |
+------+------+------+------+
mysql> SELECT *
FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3;
+------+------+------+------+
| a | a | b | b |
+------+------+------+------+
| 1 | 1 | 101 | 101 |
| 2 | NULL | NULL | 101 |
+------+------+------+------+
因此,如果我们在一个有外链接的连接表达式中忽略括号的话,我们可能会改变它原始表达式的结果集。
所以,我们不能忽略左外连接操作的右操作数和右连接操作的左操作数中的括号。换句话说,我们不能忽略外链接的内表表达式中的括号。括号对于其他操作数(外表操作数)可以忽略:
(t1,t2) LEFT JOIN t3 ON P(t2.b,t3.b)
对于任何表t1,t2,t3和属性t2.b和t3.b上的任何条件P,等效于此表达式:
t1, t2 LEFT JOIN t3 ON P(t2.b,t3.b)
无论什么时候,一个连接表达式中的连接操作的执行顺序并不是从左往右,我们讨论过嵌套查询,考虑以下查询:
SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a
WHERE t1.a > 1 SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a
WHERE (t2.b=t3.b OR t2.b IS NULL) AND t1.a > 1
这个查询被当作包含以下的连接:
t2 LEFT JOIN t3 ON t2.b=t3.b
t2, t3
在第一个查询中,嵌套查询由左链接操作组成。在第二个查询中,它由一个内连接组成。
在第一个查询中,括号可以被省略,连接表达式的语法结构将规定连接操作的执行顺序。对于第二个查询,括号不能省略,尽管这里的连接表达式可以毫无歧义地被解释。在我们的扩展语法中,第二个查询的(t2,t3)中的括号是必需的,尽管理论上可以在没有它们的情况下解析查询:我们仍然会对查询具有明确的语法结构,因为LEFT JOIN和ON起到表达式(t2,t3)的左和右分隔符。
前面的例子展示了以下重要点:
1,对于仅涉及到内连接(非外链接)的join表达式来说,括号可以被移除,join表达从左到右执行。事实上,表可以以任何顺序执行。
2,一般来说,外连接或外连接与内连接混合也不一样。删除括号可能会改变结果。
具有嵌套外连接的查询以与内连接的查询以相同的流水线方式执行。更准确地说,利用嵌套循环连接算法的变体。回想一下嵌套循环连接执行查询的算法(参见第8.2.1.6节“嵌套循环连接算法”)。假设3个表T1,T2,T3中的连接查询具有此形式:
SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2)
INNER JOIN T3 ON P2(T2,T3)
WHERE P(T1,T2,T3)
在这里,p1(t1,t2) 和 p2(t2,t3)是一样的连接条件p(t1,t2,t3),然后嵌套循环连接算法将会按照以下方式执行这个查询:
FOR each row t1 in T1 {
FOR each row t2 in T2 such that P1(t1,t2) {
FOR each row t3 in T3 such that P2(t2,t3) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
}
}
}
符号t1 || t2 || t3表示通过连接行t1,t2和t3的列构成的行。在以下某些示例中,出现表名的NULL表示在该表的每一列使用NULL的行。例如,t1 || t2 || NULL表示通过连接行t1和t2的列而构成的行,对于t3的每一列,为NULL。这样的一个null行,被称作null补充码
现在,考虑一下的嵌套外链接查询:
SELECT * FROM T1 LEFT JOIN
(T2 LEFT JOIN T3 ON P2(T2,T3))
ON P1(T1,T2)
WHERE P(T1,T2,T3)
在这个查询中,改变嵌套循环模式,将得到如下实例:
FOR each row t1 in T1 {
BOOL f1:=FALSE;
FOR each row t2 in T2 such that P1(t1,t2) {
BOOL f2:=FALSE;
FOR each row t3 in T3 such that P2(t2,t3) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
f2=TRUE;
f1=TRUE;
}
IF (!f2) {
IF P(t1,t2,NULL) {
t:=t1||t2||NULL; OUTPUT t;
}
f1=TRUE;
}
}
IF (!f1) {
IF P(t1,NULL,NULL) {
t:=t1||NULL||NULL; OUTPUT t;
}
}
}
通常,对于外部连接操作中第一个内部表的任何嵌套循环,引入在循环之前关闭的标志,并在循环后检查。当外表中的当前行与表示内部操作数的表匹配时,该标志被打开。如果在循环周期结束时,该标志仍然处于关闭状态,则不会为外部表的当前行找到匹配项。在这种情况下,该行由内部表的列补充NULL值。结果行被传递给输出的最终检查或下一个嵌套循环,但仅当该行满足所有嵌入式外连接的连接条件时。
在该示例中,嵌入由以下表达式表示的外连接表:
(T2 LEFT JOIN T3 ON P2(T2,T3))
对于内连接查询,优化器将选择一个不同的嵌套循环顺序,像这样:
FOR each row t3 in T3 {
FOR each row t2 in T2 such that P2(t2,t3) {
FOR each row t1 in T1 such that P1(t1,t2) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
}
}
}
对于外链接查询来说,优化器会选择这样一个顺序:先外链接循环,后内连接。因此,对于我们的外链接查询来说,只有一个执行嵌套顺序是可能的。例如下列实例,优化器执行了两个不同的嵌套。在这两个嵌套中,T1必须通过外链接循环,因为它使用了outer join。 T2和T3使用了inner join, 所以这个连接必须在内循环中处理。然而,由于是内连接,T2和T3可以以任何顺序执行处理。
SELECT * T1 LEFT JOIN (T2,T3) ON P1(T1,T2) AND P2(T1,T3)
WHERE P(T1,T2,T3)
其中一个顺序为,先T2,然后T3:
FOR each row t1 in T1 {
BOOL f1:=FALSE;
FOR each row t2 in T2 such that P1(t1,t2) {
FOR each row t3 in T3 such that P2(t1,t3) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
f1:=TRUE
}
}
IF (!f1) {
IF P(t1,NULL,NULL) {
t:=t1||NULL||NULL; OUTPUT t;
}
}
}
另外一个顺序为,先T3,然后T2:
FOR each row t1 in T1 {
BOOL f1:=FALSE;
FOR each row t3 in T3 such that P2(t1,t3) {
FOR each row t2 in T2 such that P1(t1,t2) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
f1:=TRUE
}
}
IF (!f1) {
IF P(t1,NULL,NULL) {
t:=t1||NULL||NULL; OUTPUT t;
}
}
}
当我们在讨论内连接的嵌套循环算法时,我们忽略那些可能对于查询性能带来重要影响的细节。我们并没有提及到条件下推。想象我们的WHERE条件 p(T1,T2,T3)可以被连接公式表示:
P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3).
在这个实例中,mysql实际上使用了下列的嵌套循环算法去执行这个内连接查询:
FOR each row t1 in T1 such that C1(t1) {
FOR each row t2 in T2 such that P1(t1,t2) AND C2(t2) {
FOR each row t3 in T3 such that P2(t2,t3) AND C3(t3) {
IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t;
}
}
}
}
您可以看到,每个连接C1(T1),C2(T2),C3(T3)都被从最内循环推出到最外层的循环,可以对其进行评估。如果C1(T1)是非常严格的条件,则该条件下推可能会大大减少从表T1传递到内部循环的行数。因此,查询的执行时间可能会大大改善。 对于具有外部联接的查询,WHERE条件仅在发现外部表中的当前行在内部表中具有匹配之后进行检查。因此,从内部嵌套循环中推送条件的优化不能直接应用于具有外部连接的查询。在这里,我们必须引入条件推倒,当遇到匹配时,标志被打开。
回顾这个外链接的例子:
P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)
在这个例子中,嵌套循环算法被用于保护条件下推的执行,像这样:
FOR each row t1 in T1 such that C1(t1) {
BOOL f1:=FALSE;
FOR each row t2 in T2
such that P1(t1,t2) AND (f1?C2(t2):TRUE) {
BOOL f2:=FALSE;
FOR each row t3 in T3
such that P2(t2,t3) AND (f1&&f2?C3(t3):TRUE) {
IF (f1&&f2?TRUE:(C2(t2) AND C3(t3))) {
t:=t1||t2||t3; OUTPUT t;
}
f2=TRUE;
f1=TRUE;
}
IF (!f2) {
IF (f1?TRUE:C2(t2) && P(t1,t2,NULL)) {
t:=t1||t2||NULL; OUTPUT t;
}
f1=TRUE;
}
}
IF (!f1 && P(t1,NULL,NULL)) {
t:=t1||NULL||NULL; OUTPUT t;
}
}
一般来说,条件下推可以从P1(T1,T2)和P(T2,T3)中提取关键字。在这个实例中,下推也被用做防止检查由相应的外部连接操作生成的NULL补码行的标志。
如果由来自WHERE的条件引发,则禁止通过主键从同一嵌套连接中的一个内部表访问到另一个内部表
【mysql 优化 4】嵌套连接优化的更多相关文章
- MySQL优化二(连接优化和缓存优化)
body { font-family: Helvetica, arial, sans-serif; font-size: 14px; line-height: 1.6; padding-top: 10 ...
- 百度APP移动端网络深度优化实践分享(二):网络连接优化篇
本文由百度技术团队“蔡锐”原创发表于“百度App技术”公众号,原题为<百度App网络深度优化系列<二>连接优化>,感谢原作者的无私分享. 一.前言 在<百度APP移动端网 ...
- mysql优化之连接优化(open-files-limit与table_open_cache)
MySQL打开的文件描述符限制 Can't open file: '.\test\mytable.frm' (errno: 24) OS error code : Too many open file ...
- mysql优化之连接优化
Posted by Money Talks on 2012/02/23 | 第一篇 序章第二篇 连接优化第三篇 索引优化第四篇 查询优化第五篇 到实战中去 连接优化 连接优化主要指客户端连接数据库以及 ...
- MySql数据库3【优化4】连接设置的优化
1.wait_timeout / interactive_timeout 连接超时 服务器关闭连接之前等待活动的秒数.MySQL所支持的最大连接数是有限的,因为每个连接的建立都会消耗内存,因此我们希 ...
- 【mysql 优化 5】左连接和右连接优化
原文地址:8.2.1.8 Left Join and Right Join Optimization mysql以下列方式实现一个A left join B 连接条件: 1,表B设置为依赖于表A和A所 ...
- MySQL实验 内连接优化order by+limit 以及添加索引再次改进
MySQL实验 内连接优化order by+limit 以及添加索引再次改进 在进行子查询优化双参数limit时我萌生了测试更加符合实际生产需要的ORDER BY + LIMIT的想法,或许我们也可以 ...
- mysql笔记03 查询性能优化
查询性能优化 1. 为什么查询速度会慢? 1). 如果把查询看作是一个任务,那么它由一系列子任务组成,每个子任务都会消耗一定的时间.如果要优化查询,实际上要优化其子任务,要么消除其中一些子任务,要么减 ...
- MySQL优化(二):SQL优化
一.SQL优化 1.优化SQL一般步骤 1.1 查看SQL执行频率 SHOW STATUS LIKE 'Com_%'; Com_select:执行SELECT操作的次数,一次查询累加1.其他类似 以下 ...
随机推荐
- Jmeter监控内存及CPU等
在进行性能测试时需要查看内存和CPU等信息来判断系统瓶颈,关于CPU和内存的监控,goole开发了一款专门的jmeter插件,弥补了Jmeter这方面的不足,下面来介绍这款插件-JmeterPlugi ...
- poj 3159 Candies (差分约束)
一个叫差分约束系统的东西.如果每个点定义一个顶标x(v),x(t)-x(s)将对应着s-t的最短路径. 比如说w+a≤b,那么可以画一条a到b的有向边,权值为w,同样地给出b+w2≤c,a+w3≤c. ...
- [机器学习] 简单的机器学习算法和sklearn实现
机器学习基础算法理解和总结 KNN算法 理解 KNN其实是最好理解的算法之一,其实就是依次和空间中的每个点进行距离比较,取距离最近的N个点,看这N个点的类别,那么要判断的点的类别就是这N个点中类别占比 ...
- groupdel - Delete a group
总览 SYNOPSIS groupdel group 描述 DESCRIPTION groupdel 命令会修改系统帐号档,会删除所有指定群组的信息 . 群组名须存在. 你须手动确认一下所有的档案系统 ...
- 连接惠普打印机(通过WIFI)
第一步 找到打印机型号 第二步 到惠普官方网站下载对应驱动 第三步 安装驱动 第四步 安装驱动后选择WIFI连接(IP在打印机显示屏幕上显示,如果输入打印机屏幕IP连接失败:需要获取打印机真正的IP地 ...
- iOS--获取文件目录的方法
很多文章都有写这个问题,我只是为了记录一下,免得总翻书... 1.Documents 目录: 你应该将所有的应用程序数据文件写入到这个目录下.这个目录用于存储用户数据或其它应该定期备份的信息. 2.L ...
- LLDB详解
LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能.平时用Xcode运行程序,实际走的都是LLDB.熟练使用LLDB,可以让你debug事半功倍 L ...
- Unity基础-Input接口
input 底层的设备输入接口,在开发中很少用到 Input.GetKey() // Update is called once per frame void Update () { if (Inpu ...
- Docker系列一:Docker的介绍和安装
Docker介绍 Docker是指容器化技术,用于支持创建和实验Linux Container.借助Docker,你可以将容器当做重量轻.模块化的虚拟机来使用,同时,你还将获得高度的灵活性,从而实现对 ...
- iPhone如何设置自定义铃声?无需连接电脑,轻松几步就搞定!
转载自: https://baijiahao.baidu.com/s?id=1594988016778457969&wfr=spider&for=pc 受够了iPhone自带的千篇一律 ...