MySQL的join关键字详解

MySQL的join关键字详解,第1张

最频繁使用的和重要的联接是INNER JOIN。它也被称为一个等值连接。

INNER JOIN通过结合基于联接谓词两个表(表1和表2)列值创建一个新的结果表。查询比较表1中的每一行与表2中的每一行,找到所有满足联接谓词的行。 当联接谓词被满足时,对于每个匹配的一对A和B的列值被组合成一个结果行。

INNER JOIN基本语法如下:

SELECT table1.column1, table2.column2...

FROM table1

INNER JOIN table2

ON table1.common_field = table2.common_field

select store_id,address from address inner join store on address.address_id=store.address_id

SQL LEFT JOIN返回所有行左表,即使存在左表中没有匹配。这意味着,如果在ON子句在右边的表匹配0(零)个记录,则连接将仍然在结果返回一行,但用NULL显示在右表中的每一列中。

这意味着,一个左连接从左表中返回所有值,再加上如果右表没有匹配就使用NULL值代替返回。

LEFT JOIN的基本语法如下:

SELECT table1.column1, table2.column2...

FROM table1

LEFT JOIN table2

ON table1.common_field = table2.common_field

在这里,给定的条件可能是根据您的需要任意给定表达式。

select address,store_id from address left join store on address.address_id=store.address_id limit 20

SQL RIGHT JOIN返回右表所有行,即使在左表中没有匹配。这意味着,如果ON子句匹配左表0(零)记录, 连接仍返回一行结果,但用NULL代替左表中的每一列。

这意味着,一个右连接返回右表所有的值,再加上如果没有匹配左表时使用NULL值。

RIGHT JOIN的基本语法如下:

SELECT table1.column1, table2.column2...

FROM table1

RIGHT JOIN table2

ON table1.common_field = table2.common_field

示例

select address,store_id from store right join address on address.address_id=store.address_id limit 10

从这一个开始,MySQL不提供正式的一步到位的关键字,效果全靠自己模拟。

左连接是得到A表中去除B表内容的剩下的部分,也就是A表独有的一部分。可以看做是在左外连接的结果中将双方共有的部分去掉得到的。

select address,store_id from address left join store on address.address_id=store.address_id where store_id is null limit 10

同理,右连接是在右外连接的结果中得到B表独有的那一部分

示例

select address,store_id from store right join address on address.address_id=store.address_id where store.store_id is null limit 10

全连接顾名思义是获得AB两表全部的数据,oracle提供了 full join关键字完成这一功能,但是MySQL没有。不过MySQL中可以借助union达到这个效果,union的作用是合并两个查询的结果。

两表的全连接中除去重合的部分,即两张表分别的特有部分的合集。

1. Nested-Loop Join 翻译过来就是嵌套循环连接,简称 NLJ。

这种是 MySQL 里最简单、最容易理解的表关联算法。

比如,拿语句 select * from p1 join p2 using(r1) 来说,

先从表 p1 里拿出来一条记录 ROW1,完了再用 ROW1 遍历表 p2 里的每一条记录,并且字段 r1 来做匹配是否相同,以便输出;再次循环刚才的过程,直到两表的记录数对比完成为止。

2. Block Nested-Loop Join ,块嵌套循环,简称 BNLJ

那 BNLJ 比 NLJ 来说,中间多了一块 BUFFER 来缓存外表的对应记录从而减少了外表的循环次数,也就减少了内表的匹配次数。还是那上面的例子来说,假设 join_buffer_size 刚好能容纳外表的对应 JOIN KEY 记录,那对表 p2 匹配次数就由 1000 次减少到 1 次,性能直接提升了 1000 倍。

3. 最近 MySQL 8.0.18 发布,终于推出了新的 JOIN 算法 — HASH JOIN。

MySQL 的 HASH JOIN 也是用了 JOIN BUFFER 来做缓存,但是和 BNLJ 不同的是,它在 JOIN BUFFER 中以外表为基础建立一张哈希表,内表通过哈希算法来跟哈希表进行匹配,hash join 也就是进一步减少内表的匹配次数。当然官方并没有说明详细的算法描述,以上仅代表个人臆想。那还是针对以上的 SQL,我们来看下执行计划。

常听说MySQL中3表 join 的执行流程并不是前两张表 join 得出结果,再与第三张表进行 join;而是3表嵌套的循环连接。那这个3表嵌套的循环连接具体又是个什么流程呢?与前两张表 join 得出结果再与第三张表进行 join 的执行效率相比如何呢?下面通过一个例子来分析分析。

set optimizer_switch='block_nested_loop=off'

关联字段无索引的情况下强制使用索引嵌套循环连接算法,目的是更好的观察扫描行数。

表结构和数据如下:

示例SQL:

通过 slow log 得知一共扫描 24100 行:

执行计划显示用的索引嵌套循环连接算法:

扫描行数构成:

总行数=100+4000+20000=24100。

从这个结果来看,join 过程像是先 t1 和 t3 join 得出 20 行中间结果,再与 t2 进行 join 得出结果。这结论与我们通常认为的 3表 join 实际上是3表嵌套的循环连接不一样,接着往下看。

查看执行计划成本

mysql>explain format=json select * from t1 join t2 on t1.b=t2.b join t3 on t1.b=t3.b where t1.a<21\G

其他信息:

IO成本= 1*1.0 =1

CPU成本= 100*0.2 =20

t1总成本=21

IO成本= 1*1.0 =1

CPU成本= 200*0.2 =40

t3表总成本= 驱动表扇出*(IO成本+CPU成本) = 20*(1+40) =820

阶段性总成本= 21+820 =841

此处 eval_cost=80,实则为 驱动表扇出*被驱动每次扫描行数*filtered*成本常数 ,即 20*200*10%*0.2 。

简化公式为: eval_cost=rows_produced_per_json*成本常数

IO成本= 4*1.0 =4

CPU成本= 1000*0.2 =200

t2表总成本= 前2表join的扇出*(IO成本+CPU成本) = 400*(4+200) =81600

阶段性总成本= 841+81600 =82441

此处 eval_cost=8000,即 rows_produced_per_json*成本常数 ,即 40000*0.2

根据执行计划成本分析:

这样看,3表 join 流程是:

注意,由于造的数据比较特殊,所以第 3 步得出的中间结果集实际上只有 1行,所以最终 t2 表的查找次数是 20*1=20 ,所以扫描总行数是 20*1000 。所以单看 slow log 中显示的 24100 行,会误认为是先得出 t1 和 t3 join 的结果,再去和 t2 进行 join。

当我调整 t3 的数据,删除20行,再插入20行,使满足 b<21 的数据翻倍,这样“第 3 步得出的中间结果集”变成 2 行:

再来看slow log 中扫描的总行数为44100,t1、t3的扫描行数不变,t2 的扫描行数变为 20*2*1000=40000 :

为什么执行计划中分析得到的是 t2 表查找 400 次呢?

因为执行计划对t1 join t3 的扇出是个估算值,不准确。而 slow log 是真实执行后统计的,是个准确值。

为什么执行计划中,t2表的执行次数是用“t1 join t3 的扇出”表示的?这不是说明 t1 先和 t3 join,结果再和 t2 join?

其实拆解来看,“3表嵌套循环” 和 “前2表 join 的结果和第3张表 join” 两种算法,成本是一样的,而且如果要按3表嵌套循环的方式展示每张表的成本将非常复杂,可读性不强。所以执行计划中这么表示没有问题。

总的来说,对于3表join或者多表join 来说,“3表嵌套循环” 和 “先2表 join,结果和第3张表join” 两种算法,成本是一样的。要注意的一点是3表嵌套循环成本并非如下图写的:n m x,而是 n (m+a x),其中 a 为 t2 满足单个等值条件的平均值。

当被驱动表的关联字段不是唯一索引,或者没有索引,每次扫描行数会大于1时,其扇出误差会非常大。比如在上面的示例中:

t3 实际的扇出只有 20,但优化器估算值是 总扫描行数的 10%,由于t3表的关联字段没有索引,所以每次都要全表扫描200行,总的扫描行数= 20*200 =4000,扇出= 4000*10% =400,比实际的20大了20倍。尤其对于后续表的 join 来说,成本估算会产生更严重的偏差。

如果是 left join,每个被驱动表的 filtered 都会被优化器认定为 100%,误差更大!

通常建议join不超过2表,就是因为优化器估算成本误差大导致选择不好的执行计划,如果要用,一定要记住:关联字段必须要有索引,最好有唯一性或者基数大。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/8502620.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-17
下一篇 2023-04-17

发表评论

登录后才能评论

评论列表(0条)

保存