mysql> select * from t1 left join t2 on t1.name=t2.name
说明:
- t1 表为驱动表
- t2 表为被驱动表
- 小表为驱动表
join 语句执行过程中,驱动表是走全表扫描,而被驱动表是走树搜索。
当name 在t2 表中有索引时: 通过Index Nested-Loop Join 算法,执行过程:
- 从 t1 表中读入一行数据 R;
- 从数据行 R 中,取出 name 字段到表 t2 里去查找;
- 通过name 字段值去查找 t2 表中满足的值,通过嵌套循环的方式去匹配数据,取出 t2 中满足条件的行,跟 R 组成一行,作为结果集的一部分;
- 循环1到3步骤,直到t1 表数据循环完。
当name 在t2 表中没有索引时: 通过 Simple Nested-Loop Join 算法,执行过程: 简单的说就是 假如 t1 和 t2 表中 各有100 条数据,则循环的次数就是 100 * 100 = 10000 次。当然mysql 没有使用这种算法,而使用的是 Block Nested-Loop Join 算法去计算的。
通过Block Nested-Loop Join 算法 执行过程:
- 把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是 select *,因此是把整个表 t1 放入了内存;
- 扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回。
所以会在内存中计算100 * 100 = 10000 次。 两个算法是一样的,不过Block Nested-Loop Join算法是在内存中计算,所以速度上会快很多,性能也更好。
join_buffer
join_buffer 的大小是由参数 join_buffer_size 设定的,默认值是 256k。如果放不下表 t1的所有数据话,策略很简单,就是分段放。 执行过程如下:
- 扫描表 t1,顺序读取数据行放入 join_buffer 中,假如放完第 88 行 join_buffer 满了,继续第 2 步;
- 扫描表 t2,把 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回;
- 清空 join_buffer;
- 继续扫描表 t1,顺序读取最后行数据放入 join_buffer 中,继续执行第 2 步。
结论:join_buffer_size 越大,一次可以放入的行越多,分成的段数也就越少,对被驱动表的全表扫描次数就越少。
要不要使用join呢?
- 如果可以使用 Index Nested-Loop Join 算法,也就是说可以用上被驱动表上的索引,其实是没问题的;
- 如果使用 Block Nested-Loop Join 算法,扫描行数就会过多。尤其是在大表上的 join操作,这样可能要扫描被驱动表很多次,会占用大量的系统资源。所以这种 join 尽量不要用。
如果使用join呢? 应该选择大表做驱动表还是选择小表做驱动表?
- 如果是 Index Nested-Loop Join 算法,应该选择小表做驱动表;
- 如果是 Block Nested-Loop Join 算法:在 join_buffer_size 足够大的时候,是一样的;在 join_buffer_size 不够大的时候(这种情况更常见),应该选择小表做驱动表。
所以,这个问题的结论就是,总是应该使用小表做驱动表。
小结:
- 如果可以使用被驱动表的索引,join 语句还是有其优势的;
- 不能使用被驱动表的索引,只能使用 Block Nested-Loop Join 算法,这样的语句就尽量不要使用;
- 在使用 join 的时候,应该让小表做驱动表。
Join 算法总结 不论是Index Nested-Loop Join 还是 Block Nested-Loop Join 都是在Simple Nested-Loop Join的算法的基础上进行优化,这里 Index Nested-Loop Join 和Nested-Loop Join 算法是分别对Join过程中循环匹配次数和IO 次数两个角度进行优化。 Index Nested-Loop Join 是通过索引的机制减少内层表的循环匹配次数达到优化效果,而Block Nested-Loop Join 是通过一次缓存多条数据批量匹配的方式来减少外层表的IO次数,同时也减少了内层表的扫表次数,通过 理解join 的算法原理我们可以得出以下表连接查询的优化思路。 1、永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量) 2、为匹配的条件增加索引(减少内层表的循环匹配次数) 3、增大join buffer size的大小(一次缓存的数据越多,那么内层包的扫表次数就越少) 4、减少不必要的字段查询(字段越少,join buffer 所缓存的数据就越多)
|