先构建本篇博客的案列演示表:java
create table a(a1 int primary key, a2 int ,index(a2)); --双字段都有索引 create table c(c1 int primary key, c2 int ,index(c2), c3 int); --双字段都有索引 create table b(b1 int primary key, b2 int); --有主键索引 create table d(d1 int, d2 int); --没有索引 insert into a values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10); insert into b values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10); insert into c values(1,1,1),(2,4,4),(3,6,6),(4,5,5),(5,3,3),(6,3,3),(7,2,2),(8,8,8),(9,5,5),(10,3,3); insert into d values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);
驱动表的概念是指多表关联查询时,第一个被处理的表,使用此表的记录去关联其余表。驱动表的肯定很关键,会直接影响多表链接的关联顺序,也决定了后续关联时的查询性能。mysql
驱动表的选择遵循一个原则:在对最终结果集没影响的前提下,优先选择结果集最小的那张表做为驱动表
。改变驱动表就意味着改变链接顺序,只有在不会改变最终输出结果的前提下才能够对驱动表作优化选择。在外链接状况下,不少时候改变驱动表会对输出结果有影响,好比left join的左边表和right join的右边表,驱动表选择join的左边或者右边最终输出结果颇有可能会不一样。web
用结果集来选择驱动表,那结果集是什么?如何计算结果集?mysql在选择前会根据where里的每一个表的筛选条件,相应的对每一个可做为驱动表的表作个结果记录预估,预估出每一个表的返回记录行数,同时再根据select里查询的字段的字节大小总和作乘积:算法
每行查询字节数 * 预估的行数 = 预估结果集
经过where预估结果行数,遵循如下规则:sql
咱们以上述建立的表为基础,用以下sql做为案列来演示:缓存
select a.*,c.c2 from a join c on a.a2=c.c2 where a.a1>5 and c.c1>5;
经过explain查看其执行计划:
explain显示结果里排在第一行的就是驱动表,此时表a为驱动表。svg
若是将sql修改一下,将select 里的条件c.c2
修改成 c.*
:oop
select a.*,c.* from a join c on a.a2=c.c2 where a.a1>5 and c.c1>5;
经过explain查看其执行计划:
此时驱动表仍是c,按理来讲 c.* 的数据量确定是比 a.*大的,彷佛结果集大小的规则在这里没有起做用。性能
此情形下若是用a做为驱动表,经过索引c2关联到c表,那么还须要再回表查询一次,由于仅仅经过c2获取不到c.*的数据,还须要经过c2上的主键c1再查询一次。而上一个sql查询的是c2,不须要额外查询。同时由于a表只有两个字段,经过a2索引可以直接得到a.*,不须要额外查询。优化
综上所述,虽然使用c表来驱动,结果集大一些,可是可以减小一次额外的回表查询,因此mysql认为使用c表做为驱动来效率更高。
结果集是做为选择驱动表的一个主要因素,但不是惟一因素。
2 . 两表关联查询的内在逻辑是怎样的?
mysql表与表之间的关联查询使用Nested-Loop join算法,顾名思义就是嵌套循环链接,可是根据场景不一样可能有不一样的变种:好比Index Nested-Loop join,Simple Nested-Loop join,Block Nested-Loop join, Betched Key Access join等。
使用索引
关联的状况下,有Index Nested-Loop join
和Batched Key Access join
两种算法;未使用索引
关联的状况下,有Simple Nested-Loop join
和Block Nested-Loop join
两种算法;咱们先来看有索引的情形,使用的是博客刚开始时创建的表,sql以下:
select a.*,c.* from a join c on a.a2=c.c2 where a.a1>4;
经过explain查看其执行计划:
首先根据第一步的逻辑来肯定驱动表a,而后经过a.a1>4,a.来查询一条记录a1=5,将此记录的c2关联到c表,取得c2索引上的主键c1,而后用c1的值再去汇集索引上查询c.*,组成一条完整的结果,放入net buffer,而后再根据条件a.a1>4,a. 取下一条记录,循环此过程。过程图以下:
经过索引关联被驱动表,使用的是Index Nested-Loop join算法,不会使用msyql的join buffer。根据驱动表的筛选条件逐条地和被驱动表的索引作关联,每关联到一条符合的记录,放入net-buffer中,而后继续关联。此缓存区由net_buffer_length参数控制,最小4k,最大16M,默认是1M。 若是net-buffer满了,将其发送给client,清空net-buffer,继续上一过程。
经过上述流程知道,驱动表的每条记录在关联被驱动表时,若是须要用到索引不包含的数据时,就须要回表一次,去汇集索引上查询记录,这是一个随机查询的过程。每条记录就是一次随机查询,性能不是很是高。mysql对这种状况有选择的作了优化,将这种随机查询转换为顺序查询,执行过程以下图:
此时会使用Batched Key Access join 算法,顾名思义,就是批量的key访问链接。
逐条的根据where条件查询驱动表,将符合记录的数据行放入join buffer,而后根据关联的索引获取被驱动表的索引记录,存入read_rnd_buffer。join buffer和read_rnd_buffer都有大小限制,不管哪一个到达上限都会中止此批次的数据处理,等处理完清空数据再执行下一批次。也就是驱动表符合条件的数据可能不可以一次处理完,而要分批次处理。
当达到批次上限后,对read_rnd_buffer里的被驱动表的索引按主键作递增排序,这样在回表查询时就可以作到近似顺序查询:
Multi-Range Read
)的顺序查询示意图。
由于mysql的InnoDB引擎的数据是按汇集索引来排列的,当对非汇集索引按照主键来排序后,再用主键去查询就使得随机查询变为顺序查询,而计算机的顺序查询有预读机制,在读取一页数据时,会向后额外多读取最多1M数据。此时顺序读取就能排上用场。
BKA算法在须要对被驱动表回表的状况下可以优化执行逻辑,若是不须要会表,那么天然不须要BKA算法。
若是要使用 BKA 优化算法的话,你须要在执行 SQL 语句以前先设置:
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
前两个参数的做用是要启用 MRR(Multi-Range Read
)。这么作的缘由是,BKA 算法的优化须要依赖于MRR,官方文档的说法,是如今的优化器策略,判断消耗的时候,会更倾向于不使用 MRR,把 mrr_cost_based
设置为 off,就是固定使用 MRR 了。)
最后再用explain查看开启参数后的执行计划:
上述都是有索引关联被驱动表的状况,接下来咱们看看没有索引关联被驱动表的状况。
没有使用索引关联,那么最简单的Simple Nested-Loop join
,就是根据where条件,从驱动表取一条数据,而后全表扫面被驱动表,将符合条件的记录放入最终结果集中。这样驱动表的每条记录都伴随着被驱动表的一次全表扫描,这就是Simple Nested-Loop join。
固然mysql没有直接使用Simple Nested-Loop join,而是对其作了一个优化,不是逐条的获取驱动表的数据,而是多条的获取,也就是一块一块的获取,取名叫Block Nested-Loop join。每次取一批数据,上限是达到join buffer的大小,而后全表扫面被驱动表,每条数据和join buffer里的全部行作匹配,匹配上放入最终结果集中。这样就极大的减小了扫描被驱动表的次数。
BNL
(Block Nested-Loop join
) 和 BKA
(Batched Key Access join
)的流程有点相似, 可是没有read_rnd_buffer
这个步骤。
示例sql以下:
select a.*, d.* from a join d on a.a2=d.d2 where a.a1>7;
用explain查看其执行计划:
3 . 多表链接如何执行?是先两表链接的结果集而后关联第三张表,仍是一条记录贯穿全局?
其实看链接算法的名称:Nested-Loop join,嵌套循环链接,就知道是多表嵌套的循环链接,而不是先两表关联得出结果,而后再依次关联的形式,其形式相似于下面这样:
for row1 in table1 filtered by where{ for row2 in table2 associated by table1.index1 filtered by where{ for row3 in table3 associated by table2.index2 filtered by where{ put into net-buffer then send to client; } } }
对于不一样的join方式,有下列状况:
Index Nested-Loop join
:
sql以下:
select a.*,b.*,c.* from a join c on a.a2=c.c2 join b on c.c2=b.b2 where b.b1>4;
经过explain查看其执行计划:
其内部执行流程以下:
执行前mysql执行器会肯定好各个表的关联顺序。首先经过where条件,筛选驱动表b的第一条记录b5,而后将用此记录的关联字段b2与第二张表a的索引a2作关联,经过Btree定位索引位置,匹配的索引可能不止一条。当匹配上一条,查看where里是否有a2的过滤条件且条件是否须要索引以外的数据,若是要则回表,用a2索引上的主键去查询数据,而后作判断。经过则用join后的信息再用一样的方式来关联第三章表c。
Block Nested-Loop join
和 Batched Key Access join
: 这两个关联算法和Index Nested-Loop join
算法相似,不过由于他们能使用join buffer,因此他们能够每次从驱动表筛选一批数据,而不是一条。同时每一个join关键字就对应着一个join buffer,也就是驱动表和第二张表用一个join buffer,获得的块结果集与第三章表用一个join buffer。
本篇博客主要就是讲述上述三个问题,如何肯定驱动表,两表关联的执行细节,多表关联的执行流程。
有疑问欢迎留言,共同进步。