MySQL的join你真的了解吗!!!

本文详细介绍了MySQL中JOIN的执行流程、不同类型的JOIN用法以及如何优化。重点讨论了BlockNested-LoopJoin的执行过程,强调了索引、驱动表选择和join_buffer大小对性能的影响,并提出了使用临时表和优化JOIN语句的策略。此外,还提到了MySQL8.0引入的Hash Join特性,以提高查询效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.测试用例数据库信息

本文章采用的数据库结构,以及MySQL版本:5.7

t1 表,有一个主键id,字段a,字段b。 (此表建立了一个索引a) 数据大约1000条
在这里插入图片描述
t2 表,有一个主键id,字段a,字段b。 (此表也建立了一个索引a) 数据大约100w条
在这里插入图片描述


2.join的常见用法

1. inner jon 内连接
内连接又叫等值连接,此时的inner可以省略。获取两个表中有匹配关系的记录,即两表取交集

select  *
from t1 as t
join t2 as tt  on t.id=tt.id

2. left join 左连接
以左表为基础,获取匹配关系的记录,如果右表中没有匹配项,NULL表示

select  *
from t1 as t
left join t2 as tt  on t.id=tt.id

3. right join 右连接
以右表为基础,获取匹配关系的记录,如果左表中没有匹配项,NULL表示

select  *
from t1 as t
right join t2 as tt  on t.id=tt.id

4. straight_join
straight_join 让 MySQL 使用固定的连接方式执行查询,这样优化器只会按照我们指定的方式去 join。其中,t1 是驱动表,t2 是被驱动表。

select  *
from t1 as t
straight_join t2 as tt  on t.id=tt.id

3.join语句的执行流程

执行如下SQL语句,此时t1 是驱动表,t2 是被驱动表。

select * from t1 straight_join t2 on (t1.a=t2.a);

使用explain查看执行详情
在这里插入图片描述
图中你可以看到驱动表t1进行了全表扫描,t2表使用了索引a。

在这里插入图片描述
这个语句执行流程如下

1.从表 t1 中读入一行数据 R;

2.从数据行 R 中,取出 a 字段到表 t2 里去查找;

3.取出表 t2 中满足条件的行,跟 R 组成一行,作为结果集的一部分;

4.重复执行步骤 1 到 3,直到表 t1 的末尾循环结束。

那么此流程一共扫描了多少行?

1. t1表是全表扫描因此扫描了1000行。

2. 因为t2表走的是树搜索过程。由于我们构造的数据都是一一对应的,因此每次的搜索过程都只扫描一行,也是总共扫描 1000 行;

3. 整个执行流程,总扫描行数是 2000行。

那么,如果t2表没有使用索引它的流程是怎样呢?


4. Simple Nested-Loop Join 和Block Nested-Loop Join

1.Simple Nested-Loop Join (在5.5版本之前会使用)

当我们执行如下SQL时,t1表还是进行全表扫描,但是t2表上b字段没有索引

select * from t1 straight_join t2 on (t1.a=t2.b);

他的执行流程和上面还是一样,还是从t1表一行行取出来去t2表中找,那么他扫描了多少行?

1. t1表是全表扫描因此扫描了1000行。

2. 因为t2表走的是全表扫描,也就是扫描 100w 行;

3. 整个执行流程,总扫描行数是 1000 * 100w = 10亿行。

这个很笨重的算法叫做Simple Nested-Loop Join,不过MySQL并没有使用这个算法,而是使用了Block Nested-Loop Join算法。

2.Block Nested-Loop Join (5.5版本以后)

当我们执行如下SQL时,t1表还是进行全表扫描,但是t2表上b字段没有索引

select * from t1 straight_join t2 on (t1.a=t2.b);

在这里插入图片描述

他的执行流程和上面还是一样,还是从t1表一行行取出来去t2表中找,那么他扫描了多少行?

1.把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是 select *,因此是把整个表 t1 放入了内存;

2.扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回。

由于 join_buffer 是以无序数组的方式组织的,因此对表 t2 中的每一行,都要做 100w 次判断,总共需要在内存中做的判断次数是:1000*100w=10 亿次 。因此,从时间复杂度上来说,这两个算法是一样的。但是,Block Nested-Loop Join 算法的这 10 亿次判断是内存操作,速度上会快很多,性能也更好。


5.使用Block Nested-Loop Join的注意事项

1.可能会多次扫描被驱动表,占用磁盘 IO 资源;
当我们在使用Block Nested-Loop Join 算法时,我们驱动表会放入join_buffer中,但是会把被驱动表的数据一行一行的取出来跟 join_buffer 中的数据做对比。如果被驱动表有1亿条数据,被驱动表就要扫描1亿次,非常占用io资源。

2.可能会导致 Buffer Pool 的热数据被淘汰,影响内存命中率。

join_buffer 是由 join_buffer_size参数控制的,所以 join_buffer 也有一定大小。如果我们join_buffer放慢了怎么办?

现在假如一个场景? join_buffer每次只能放入t1表60%的数据。

1.扫描表 t1,顺序读取数据行放入 join_buffer 中,在放入60%数据后join_buffer 满了,
2. 扫描表 t2,把 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回;
3. 清空 join_buffer;继续扫描表 t1,顺序读取最后的 12 行数据放入 join_buffer 中
4. 扫描表 t2,把 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回;

在上述例子中,我们发现t1表扫描了两次。t2表也扫描了两次。根据我们MySQL的LRU算法

处于 old 区域的数据页,每次被访问的时候都要做下面这个判断:

1.若这个数据页在 LRU 链表中存在的时间超过了 1 秒,就把它移动到链表头部;

2.如果这个数据页在 LRU 链表中存在的时间短于 1 秒,位置保持不变。1 秒这个时间,是由参数 innodb_old_blocks_time 控制的。其默认值是 1000,单位毫秒。

由于优化机制的存在,一个正常访问的数据页,要进入 young 区域,需要隔 1 秒后再次被访问到。在上述例子中,如果我们join_buffer更小,被驱动表就会被多次扫描,而且这个语句执行时间超过 1 秒,就会在再次扫描t2表的时候,把t2表的数据页移到 LRU 链表头部。这时我们Buffer Pool 的热数据就会被淘汰,影响内存命中率。


6.join语句到底需要怎么优化

执行SQL语句

EXPLAIN select * from t1 join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=400000;

t1和t2表都进行了全表扫描,t1表扫描1000行,t2表扫描了970748行
在这里插入图片描述
执行这个SQL语句,我们耗时了46.857s
在这里插入图片描述
执行这条SQL语句时,由于b字段上没有建立索引,导致t2表需要进行全表扫描。如果建立索引又非常消耗系统资源,不建立索引的情况下。怎么进行优化
这时候,我们可以考虑使用临时表。使用临时表的大致思路是:

1.把表 t2 中满足条件的数据放在临时表 tmp_t 中;

2.给临时表 tmp_t 的字段 b 加上索引;

3.让表 t1 和 tmp_t 做 join 操作。

create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb;  创建临时表, 建立索引
insert into temp_t select * from t2 where b>=1 and b<=2000; 插入数据
select * from t1 join temp_t on (t1.b=temp_t.b); 

注意事项:当我们使用范围查询时。 Mysql发现通过索引扫描的行记录数超过全表的25% 时,优化器可能会放弃走索引,自动变成全表扫描。

在这里插入图片描述
执行这个SQL语句,我们耗时了0.318s。


7. MySQL中join 后 and和where的区别

select * from t1 left join t2 on(t1.a=t2.a) and (t1.b=t2.b); /*Q1*/
select * from t1 left join t2 on(t1.a=t2.a) where (t1.b=t2.b);/*Q2*/

语句 Q1 返回的数据集是 6 行,表 t1 中即使没有满足匹配条件的记录,查询结果中也会返回一行,并将表 b 的各个字段值填成 NULL。

语句 Q2 返回的是 4 行。从逻辑上可以这么理解,最后的两行,由于表 t2 中没有匹配的字段,结果集里面t2.b 的值是空,不满足
where 部分的条件判断,因此不能作为结果集的一部分。

为了理解这个问题,我需要再和你交代一个背景知识点:在 MySQL 里,NULL 跟任何值执行等值判断和不等值判断的结果,都是 NULL。这里包括, select NULL = NULL 的结果,也是返回 NULL。
因此,语句 Q2 里面t1.b=t2.b 就表示,查询结果里面不会包含t2.b 是 NULL 的行,这样这个 left join 的语义就是“找到这两个表里面,t1、t2 对应相同的行。对于表 t1 中存在,而表 t2 中匹配不到的行,就放弃”。

8. MySQL的 hash join

1.如果我们把驱动表1000条数据存人map,key是字段对应的值。value就是这行数据的值。
2.在把100w条数据读出来,循环遍历这100w条数据。
3.在从100w条数据的每一行拿出对应key字段的值,如果key拿出来有值,就把这行数据结果存入map并返回。

这样我们10亿次比对就会变成100次hash查找

所以在MySQL8.0之后版本,MySQL就加入了hash join。如下是我使用MySQL8.0版本进行查询,耗时0.610s。
在这里插入图片描述


9.join总结

1. 应该使用小表作为驱动表

2. 被驱动表关键字段应该建立索引

3.被驱动表无法使用索引、数据量很大时,因适当调整join_buffer大小。避免 Buffer Pool 的热数据被淘汰,影响内存命中率。

4.MySQL8.0之后版本,已经支持hash join,没有升级的小伙伴赶紧升级把!!!


在这里插入图片描述
阿根廷加油,梅老板牛逼!!!


<think>嗯,用户问MySQL中的JOIN是不是INNER JOIN的缩写。我需要先确认这一点是否正确。根据我之前的了解,在SQL中,JOIN关键字默认情况下确实是INNER JOIN。但用户可能对不同类型的JOIN不太清楚,或者有其他疑问。 首先,我应该回顾一下SQL中的JOIN类型,包括INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN等。然后确认在MySQL中,当只写JOIN时是否默认是INNER JOIN。根据MySQL的文档,是的,JOIN和INNER JOIN是同义词,用户使用时可以互换。不过可能有些数据库系统有不同的默认行为,但用户问的是MySQL,所以需要明确这一点。 接下来,用户可能实际想知道的是不同JOIN的区别,或者在使用时是否需要显式指定INNER。需要举例说明,比如两个表的JOIN操作,如果没有指定类型,结果是否只返回匹配的行。例如,如果表A和表B进行JOIN,结果是否等同于INNER JOIN,即仅包含两表中匹配的行。 另外,用户可能会有误区,认为JOIN默认是其他类型,比如LEFT JOIN,所以需要澄清。同时,可能需要提醒用户,虽然语法上JOIN等同于INNER JOIN,但明确写出INNER JOIN可能更有利于代码的可读性,尤其是在团队合作中,其他人阅读时不会产生歧义。 还要考虑用户为什么会问这个问题。可能是在查询中遇到了结果不符合预期的情况,或者看到不同的写法,想要确认是否正确。因此,在回答时需要给出明确的结论,并解释默认行为,同时建议最佳实践,比如显式指定JOIN类型以提高代码清晰度。 最后,需要检查是否有其他相关的点需要提及,比如MySQL是否支持FULL OUTER JOIN(实际上MySQL不支持,需要通过UNION实现),或者JOIN时的ON和USING子句的区别,但这些可能超出当前问题的范围,不过如果用户有后续问题,可以进一步展开。 总结来说,回答需要肯定用户的疑问,指出JOINMySQL中确实是INNER JOIN的缩写,同时解释其行为,并给出示例和建议,确保用户理解并能正确应用。</think>--- ### 一、**直接答案** **是的**。在 MySQL 中,直接使用 `JOIN`(不带任何前缀)默认等同于 `INNER JOIN`。两者的行为完全一致,仅返回满足关联条件的匹配行。 --- ### 二、**详细解释** #### 1. **`JOIN` 与 `INNER JOIN` 的关系** - **语法等价性** MySQL 遵循 SQL 标准,`JOIN` 是 `INNER JOIN` 的简写形式。以下两种写法等价: ```sql -- 写法1:显式声明 INNER JOIN SELECT * FROM tableA INNER JOIN tableB ON tableA.id = tableB.id; -- 写法2:直接使用 JOIN(默认 INNER JOIN) SELECT * FROM tableA JOIN tableB ON tableA.id = tableB.id; ``` 两种写法生成的查询结果完全相同。 - **语义行为** 无论使用 `JOIN` 还是 `INNER JOIN`,均会: - 仅返回两个表中**关联条件匹配**的行(`ON` 或 `USING` 子句)。 - 若某行在其中一个表中无匹配项,则该行**不会出现在结果集**中。 #### 2. **对比其他 JOIN 类型** 若需返回不匹配的行,需显式指定其他 JOIN 类型: | **JOIN 类型** | **行为** | |--------------------|--------------------------------------------------------------------------| | `LEFT JOIN` | 返回左表所有行,右表无匹配时填充 `NULL` | | `RIGHT JOIN` | 返回右表所有行,左表无匹配时填充 `NULL` | | `FULL OUTER JOIN` | 返回左右表所有行,无匹配时填充 `NULL`(**MySQL 不支持,需用 `UNION` 模拟**) | | `CROSS JOIN` | 返回两表的笛卡尔积(无关联条件) | --- ### 三、**示例说明** 假设有两个表: - **`users` 表** | id | name | |----|-------| | 1 | Alice | | 2 | Bob | - **`orders` 表** | order_id | user_id | amount | |----------|---------|--------| | 1001 | 1 | 200 | | 1002 | 3 | 150 | #### 1. **使用 `JOIN`/`INNER JOIN`** ```sql SELECT users.name, orders.amount FROM users JOIN orders ON users.id = orders.user_id; ``` **结果**:仅返回匹配的行(Alice 的订单) | name | amount | |-------|--------| | Alice | 200 | #### 2. **使用 `LEFT JOIN`** ```sql SELECT users.name, orders.amount FROM users LEFT JOIN orders ON users.id = orders.user_id; ``` **结果**:返回所有用户,无订单的填充 `NULL` | name | amount | |-------|--------| | Alice | 200 | | Bob | NULL | --- ### 四、**最佳实践建议** 1. **显式声明 JOIN 类型** 即使 `JOIN` 默认是 `INNER JOIN`,建议在代码中显式写出 `INNER JOIN`,以提高可读性。 ```sql -- 推荐写法 SELECT * FROM tableA INNER JOIN tableB ON ...; ``` 2. **避免歧义** 复杂查询中,若涉及多个 JOIN,显式声明类型可减少维护时的理解成本。 3. **注意 MySQL 的限制** MySQL 不支持 `FULL OUTER JOIN`,需通过 `LEFT JOIN + RIGHT JOIN + UNION` 模拟。 --- ### 五、**总结** | **场景** | **写法** | **结果** | |------------------------|--------------------|------------------------------------| | 仅需匹配行 | `JOIN` 或 `INNER JOIN` | 返回两表关联成功的行 | | 需包含左表全部行 | `LEFT JOIN` | 左表全保留,右表无匹配则填充 `NULL` | | 需包含右表全部行 | `RIGHT JOIN` | 右表全保留,左表无匹配则填充 `NULL` |
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值