在MySQL中进行多表联合查询时,MySQL会通过驱动表的结果集作为基础数据,在被驱动表中匹配对应的数据,匹配成功合并后的临时表再作为驱动表或被驱动表继续与第三张表进行匹配合并,直到所有表都已匹配完毕,最后将结果返回出来。匹配算法:Nested-Loop Join(嵌套循环连接),在MySQL中有三种具体的实现算法:
Simple Nested-Loop Join
:简单嵌套循环连接Index Nested-Loop Join
:索引嵌套循环链接Block Nested-Loop Join
:缓存快嵌套循环链接简单嵌套循环连接实际上就是简单粗暴的嵌套循环,如果驱动表有100条数据,被驱动表有100条数据,那么在匹配时会将驱动表的每一条数据作为匹配条件去被驱动表中逐个比较,实际上就要比较100*100=10000次,可以想象这种比较效率是非常低下的。
索引嵌套循环连接是基于被驱动表的索引进行连接的算法,通过驱动表的匹配条件与被驱动表的索引进行匹配,避免和每条记录比较,从而利用索引的查询减少匹配次数,提高查询的性能。但要注意的是被驱动表的关联条件必须要有索引时才能用到Index Nested-Loop Join
。另外由于用到索引,如果是非聚簇索引并且查询的数据包含了被驱动表的其他字段,则会回到被驱动表再查询一次对应的数据,即回表,多了IO操作。
缓存嵌套循环连接通过一次性缓存多条驱动表数据、参与查询的列到Join Buffer
里,然后拿Join Buffer
里的数据批量与被驱动表中的数据进行比较,从而减少了循环匹配次数。
关于Join Buffer
- Join Buffer会缓存所有参与查询的列,而不是只有Join的匹配列
- 可以调整MySQL的join_buffer_size缓存大小,join_buffer_size的默认值是256K,最大值在MySQL 5.1.22版本前是4G,而之后的版本才能在64位操作系统下申请大于4G的空间
- 要使用Block Nested-Loop Join算法需要开启优化器管理配置的optimizer_switch的设置block_nested_loop为on,默认为on
当查询优化器不使用Index Nested-Loop Join
算法的时候,默认使用Block Nested-Loop Join
算法。
明白联合查询的原理是驱动表与被驱动表通过条件嵌套循环连接匹配后,查询性能优化的思路就是:减少循环比较次数。可以通过以下几个原则来进行优化。
通过上面的分析可以得知,MySQL在联合查询中是用驱动表的数据作为筛选条件在被驱动表中进行匹配,所以假设table1作为驱动表,数据有10000条,table2作为被驱动表的数据有100条,并且被table2中有索引,那么用Index Nested-Loop Join
算法进行匹配时要进行10000次的关联操作。但如果反过来用table2作为驱动表,table1作为被驱动表,只需要进行100次关联即可完成匹配,效率也会大大提高,其他的连接算法也类似。简单说通常情况下要用小表驱动大表。
但是这里的小表和大表是根据查询条件相对而言的,大小的计算是要根据查询条件和具体的字段进行衡量,假如查询条件指定了table1的搜索范围,即table1满足查询条件的行数有90行,那么计算公式为:90乘以参与关联查询字段的大小总和,若结果小于table2满足查询条件后的行数乘以参与关联查询字段的大小,则table1为小表,否则table1为大表。
匹配的条件字段列尽量使用有索引的,争取使用Index Nested-Loop Join
算法进行关联,减少被驱动表的循环次数
当使用Block Nested-Loop Join
算法时,增大join_buffer_size
的大小可以使驱动表一次缓存更多的数据,从而减少总体循环匹配的次数
Block Nested-Loop Join
算法时,字段越少,join Buffer
所缓存的数据就越多,那么循环的次数就越少。Index Nested-Loop Join
算法时,如果可以不回表查询,即只查询索引列,利用覆盖索引则可能提升匹配效率left join
左边的表为驱动表,右边的为被驱动表right join
右边的表为驱动表,左边的为被驱动表join
时,MySQL会自动判断左右两边哪边是小表,哪边是大表。小表作为驱动表,大表作为被驱动表,小表与大表的判断原则为上面讲到的根据行数和参与关联的字段计算得出。in
时,驱动表和被驱动表由MySQL的执行器根据表的大小自动选择exists
时,外部表为驱动表,内部表为被驱动表。无论加什么查询条件都无法改变使用join
连接查询时如果有where
条件,则MySQL执行器会根据查询条件过滤后的结果自动选择驱动表或被驱动表。