

Hi,大家好!我是白日梦!
今天我要跟你分享的MySQL话题是:“join联表的注意点、有哪些联表查询方式、真题...”
本文是MySQL专题的第 34 篇。
欢迎关注!持续更新中~

你好,看你简历中有写熟悉MySQL数据库啊,要不咱聊聊数据库?
嗯,好啊!


说一下常见的联表查询方式吧
?
嗯~ 如下:
主要有内连接、外连接、交叉连接。
内连接又包括:等值连接、非等值连接、自连接
外连接又含有:左外连接、右外连接、全外连接


嗯!那我现在有这样的两张表。
create table `t1`(`id` int(11) NOT NULL,`a` int(11) default null,`b` int(11) default null,primary key (`id`),key `a` (`a`))engine=innoDB;create table `t2`(`id` int(11) NOT NULL,`a` int(11) default null,`b` int(11) default null,primary key (`id`),key `a` (`a`))engine=innoDB;
其中表t1中有100行数据,表t2中有1000行数据。

你能说一下表t1和t2在进行join操作时,是t1 join t2复杂度更低呢?还是t2 join t1复杂度更低呢?
嗯嗯,具体是小表join大表,或者是谁join谁都无所谓是要根据具体的SQL以及join buffer具体的分析的!

比如我们的SQL是这样的
select *from t1 staight_join t2where t1.a = t2.b
这条SQL的where条件中使用的b列并不存在任何索引。那看起来,貌似这条SQL的执行逻辑应该像下面这样:
Step1:依次扫描t1表中的每一行rx。
Step2:取出rx中的b列的值去t2表中进行全表扫描。
Step3:找到t2表中符合条件的row,和现有的数据组合作为结果
Step4:重复1~3,直到t1中的数据被遍历一遍。
这样看起来无论是t1 join t2,还是t2 join t1,都避免不了在遍历t1的同时也遍历t2,或者在遍历t2的时候遍历t1。最终都要扫描100*1000次磁盘。
But,MySQL其实没有这么做!

MySQL的做法如下:
Step1:扫描驱动表t1全表,将全部的100行数据加载进内存join buffer中。假设t1中有N条数据,那么此次扫描时间复杂度就是N
Step2:依次扫描被驱动t2表(假设有M行)中的每一行,将得到的每一行和join buffer 中的数据行做比较,比较的条件就是where语句中的条件。此次扫描时间复杂度为M
Step3:合并符合条件的数据,作为结果。
Step4:重复1~3,直到t1中的数据被遍历一遍。
还是假设驱动表中有N行,被驱动表中有M行。这样看起来其实扫描数据库的次数不再是N*M或者M*N,而是N+M!
当然还多出了在内存中进行的N*M次使用where条件进行的比较。


嗯。
还有其他情况嘛?
嗯,还有如果我们的SQL join条件如下
select *from t1 staight_join t2where t1.a = t2.a
那这条SQL的执行条件如下:
Step1:遍历表t1,依次从t1中取出一行数据rx
Step2:从rx中取出列的值,使用这个值去t2表的a列这棵二级索引树中进行检索。B+Tree的查询时间复杂度是:log2(M)
Step3:拿着Step2中获取到的id列在t2中进行回表,id也有对应的B+Tree,这次查询的时间复杂度也是log2(M),将查询到的结果组合作为返回值。
Step4:重复1~3
因为这条SQL检索t2时走了索引,所以时间复杂度肯定比上一条SQL更好。
t1肯定是走了全表扫描:100行
t2每次需要两次在B+Tree中检索:2*log2(M)
所以整体的时间复杂度是:N+2*log2(M)
所以在这种情况下,N对整体复杂度的影响最大!所以还是小表驱动大表要更高效一些。


嗯! 是的。
另外刚才有说到的join buffer这个缓存。它里面会存储的驱动表中的数据。默认大小是256k。
比如我们上面的SQL语句是select * from,那么t1中的数据会被全部加载进join buffer中。


嗯! 那问题来了,如果驱动表t1中的数据太多了,join buffer一次性存储不下这么大的数据怎么半?
嗯,针对这种情况:解决的策略就是将t1表中的数据分段、分别加载进join buffer中。
比如t1中的100行数据被分成了80行、20行两段分别放入join buffer中进行上面说过的Step操作,但是整个过程中使用where条件比较的次数 (80+20)*1000 是不变的...


嗯! 你继续说
回过头来重新看这个问题,在join buffer不够大时,假设驱动表数据行数为N,需要分成K段,被驱动表有数据行数M。
所以这种分段的情况下,扫描磁盘的次数不再是N+M
而是:N+K*M

得到上面的公式后,我们假设join buffer的大小就是默认的大小256k,t1的大小是2560k,t2的大小是25600k
所以对于t1做驱动表来说:K = 2560/256 = 10段
需要扫描到总条数为:N+K*M
也就是 100+10*1000 = 10100
对于t2做驱动表来说:K = 25600/256 = 100段
需要扫描到总条数为:N+K*M
也就是 1000+100*100 = 11000
所以当join buffer不足够大时,选择小表当作驱动表看起来复杂度要更友好一些。


可!
总结来说就是当join buffer足够大且驱动表没有走索引时,选谁当驱动表复杂度都是一样的,当join buffer小时,选择小表当驱动表复杂度会更低一些。
但是无论如何,都是join buffer越大越好
嗯嗯,是的!另外还有这样一个参数:join_buffer_size 可以控制join buffer的大小。
mysql> show variables like '%join_buffer_size%';+------------------+---------+| Variable_name | Value |+------------------+---------+| join_buffer_size | 8388608 |+------------------+---------+1 row in set (0.08 sec)


回答的还可,开始下一个话题吧!我有两种表如下:
mysql> select * from t1;+----+------+| id | name |+----+------+| 1 | a || 1 | b || 2 | c || 2 | d |+----+------+4 rows in set (0.00 sec)mysql> select * from t2;+----+------+| id | name |+----+------+| 1 | e || 2 | f || 3 | g || 4 | h |+----+------+4 rows in set (0.00 sec)
你看下我这条SQL的返回值是啥?
select *from t1 , t2where t1.id = t2.id;
好(内心:这泥马
)

mysql> select * from t1,t2 where t1.id=t2.id;+----+------+----+------+| id | name | id | name |+----+------+----+------+| 1 | a | 1 | e || 1 | b | 1 | e || 2 | c | 2 | f || 2 | d | 2 | f |+----+------+----+------+4 rows in set (0.00 sec)
(难是不难,你有没有被唬住?
)


嗯,好没有其他问题了,你表现还可以,应该会有下一面的,你还有想问我的嘛?
没有问题了!
感谢您安排的面试

欢迎关注:

参考:
备注《丁奇大佬的MySQL45讲》
推荐阅读:
1、MySQL的修仙之路,图文谈谈如何学MySQL、如何进阶!
2、数据库面经,常见的面试题
6、能谈谈year、date、datetime、time、timestamp的区别吗?
10、了解Flush-List吗?顺便说一下脏页的落盘机制!
11、用 11 张图讲清楚,当你CRUD时BufferPool中发生了什么!以及BufferPool的优化!
16、简述undo log、truncate、以及undo log如何帮你回滚事务?
19、LSN、Checkpoint?谈谈MYSQL的崩溃恢复是怎么回事!
20、MySQL的 bin log有啥用?在哪里?谁写的?怎么配置?
21、bin log有哪些格式?有啥区别?优缺点?线上用哪种格式?
24、自导自演的数据库面试现场--谈谈MySQL的10种文件
25、大型面试现场:一条update sql的执行都经历了什么?
26、大型翻车现场:如何实现记录存在的话就更新,如果记录不存在的话就插入。
27、如何实现记录存在的话就更新,如果记录不存在的话就插入--续
28、面试现场:说说char和varchar的区别你了解多少?
29、沙尘暴也阻挡不了学习的脚步-- 面试官:你竟然不知道MySQL的组提交?
长按二维码、关注白日梦
赠送精美脑图(操作系统、网络、Java基础...)
长按二维码
关注白日梦吧!





