MYSQL中JOIN的算法与性能调优:NLJ /SNLJ / BNL / MRR / BKA

目录

1.JOIN的算法

1.1 JOIN的基础算法有哪些

1.1.1什么是NLJ

1.1.2 什么是SNLJ(拓展)

1.1.3 什么是BNL

1.2 MYSQL选择不同算法的情况

1.3 不同JOIN算法的调优 

1.4 优化的具体实现与未调优带来的问题

2.JOIN算法的进一步优化

2.1JOIN优化涉及的算法有哪些

2.1.1什么是MRR

2.1.2什么是BKA

2.1.3如何优化BNL

3.总结

理解此篇内容需要的知识点:什么是驱动表与被驱动表 / 怎么判断JOIN时使用了什么算法 / BP(buffer_pull) 的原理 / MYSQL对LRU算法的优化

1.JOIN的算法


MYSQL中JOIN的算法与性能调优:NLJ /SNLJ / BNL / MRR / BKA_第1张图片

 

1.1 JOIN的基础算法有哪些

  • MySQL的基础算法有两种:

        “Index Nested-Loop Join”,简称NLJ

        "Block Nested-Loop Join",简称BNL。


1.1.1什么是NLJ

如果被驱动表的关联字段没索引,使用NLJ算法性能会比较低。

mysql会选择Block Nested-Loop Join 算法。

  • JOIN查询时,驱动表走全表扫描中取出每一行数据,逐行遍历取出的条件字段,到被驱动表中走索引的树搜索进行比对。查找每一个等值。
  • 这个结论的前提是ON的条件是可以使用被驱动表的索引

NLJ算法的复杂度:

  • 首先扫描驱动表的所有行(N行),复杂度为N,然后逐行再去被驱动表进行匹配。在逐条去被驱动表搜索时,要先搜索条件索引行,再搜索主键索引行,每次搜索一棵树近似复杂度是以2为底的M的对数,记为log2M,所以在被驱动表上查一行的时间复杂度是 2*log2M。总复杂度为 N + N*2*log2M


1.1.2 什么是SNLJ(拓展)

想要了解BNL的优势,首先了解一下Simple Nested-Loop Join (SNLJ)算法

MYSQL作为优化使用了BNL算法,并没有使用这个

  • 驱动表全表扫描取出所有字段,逐行匹配被驱动表,而被驱动表未使用到索引,每次匹配页都进行一次全表扫描。判断次数与BNL是一样的都为N*M,但是这种算法每次匹配都会导致被驱动表全表扫描,扫描行数大大增加。


1.1.3 什么是BNL

BNL算法使用了join_buffer作为优化,
其中由参数join_buffer_size设定join_buffer的大小,默认值是256k

    分情况讨论

  • JOIN查询时,驱动表全表扫描把数据取出 ,放入内存join_buffer中(select什么就把什么放入内存中,select * 就把一整张表放入)(以无序数组的方式组织的),被驱动表也全表扫描取出每一行数据
  • 如果join_buffer不中能存放驱动表需要的所有数据
    • 那就分段放,每次join buffer被存满就先进行判断,放入结果集后。清空join_buffer再复用。因为驱动表被分入多段,导致被驱动表被扫描了多次,被驱动表扫描行数会增加,但是判断次数还是不变的
  • 如果join_buffer中能存放驱动表需要的所有数据
    • 进行扫描行数是两个表行数之和,内存中进行笛卡尔积的比对,但是NLJ算法是在内存中进行匹配,虽然时间复杂度一样,但是速度是很快的

BNL算法的复杂度:

    分情况讨论

  • join_buffer中需要存放驱动表需要的所有数据。两个表都做一次全表扫描,所以总的扫描行数是M+N;内存中的判断次数是M*N。判断是内存操作,速度上会快很多,性能也更好。
  • join_buffer不中能存放驱动表需要的所有数据。扫描行数是 N+λ*N*M;内存判断 N*M次。需要分段放入join_buffer。判断次数为(第一段 * 被驱动表行数)+ (第二段 * 被驱动表行数)是不变的,但是被驱动表扫描的行数会增加。驱动表数据量越多,分段越多,被驱动表全表扫描的次数就越多。
    λ不是常数,是一个(0,1)范围的数字


1.2 MYSQL选择不同算法的情况

        MYSQL在使用JOIN关键字时优化器选择使用哪种算法,取决于在JOIN时ON的条件字段是否可以使用到被驱动表的索引。

        当JOIN的条件字段可以使用被驱动表的索引时使用NLJ算法

        当JOIN的条件字段不能使用被驱动表的索引时使用BNL算法 


1.3 不同JOIN算法的调优 

影响NLJ算法的主要问题是驱动表的行数
影响BNL算法的主要问题是驱动表行数与join_buffer_size的大小

对于NLJ与BNL两种算法的调优,已经显而易见。

NLJ:

影响NLJ算法的主要问题是驱动表的行数,所以当explain后Extra字段显示了Using Where就是采用了NLJ算法,调优角度应该在JOIN时选择小表作为驱动表,来控制NLJ算法的复杂度。

BNL:

BNL算法优化涉及到的join_buffer参数的问题

  • 内存判断次数就是两表行数相乘,是不受选择哪个表作为驱动表影响的。
  • 而扫描行数,当两表的行数固定时,就要考虑分段带来的影响,把join_buffer_size改大,一次可以放入的行越多,分成的段数也就越少,对被驱动表的全表扫描次数就越少。

影响BNL算法的主要问题是驱动表行数与join_buffer_size的大小,所以当explain后Extra字段显示了Using join buffer(Block Nested Loop)就是采用了NLJ算法,调优角度依然是在JOIN时选择小表作为驱动表,与调大join_buffer_size,来控制BNL算法的复杂度。


1.4 优化的具体实现与未调优带来的问题

使用JOIN时如何选择驱动表,以及使用BNL算法带来的一系列后续问题

如何选择驱动表:

  • 在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量来决定,哪个表作为驱动表。
  • 通过where条件,可以先过滤掉表中一部分不符合条件的数据,选此表作为驱动表,放入join_buffer更为合适。
    比如下面的SQL,通过where条件可以过滤掉一部分数据,姓名相对于性别来说可以过滤掉更多的数据,所以自然选取a表作为驱动表更合适
    where a.id = b.id and a.name = 'kokopelli' and b.sex = '男'

BNL算法带来的一系列后续问题:

  • BNL算法不仅仅是将驱动表多次全扫时,查询时占用IO资源,比对时占用占用CPU资源,还可能降低BP命中率。这种情况是被驱动表为冷数据,可能导致老年代冷数据进入新生代,使热点数据被换出,使LRU算法出现了预读失效与缓冲池污染,最终导致降低BP命中率降低。并且业务正常访问的数据页,没有机会进入young区域。大表join操作虽然对IO有影响,但是在语句执行结束后,对IO的影响也就结束了。但是,对Buffer Pool的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率。

2.JOIN算法的进一步优化


MYSQL中JOIN的算法与性能调优:NLJ /SNLJ / BNL / MRR / BKA_第2张图片

 

2.1JOIN优化涉及的算法有哪些

MYSQL早期JOIN时候的算法只有NLJ与BNL,5.6之后又对本就性能不错的NLJ算法再次优化,引入了MRR+BKA,使得JOIN的性能进一步提升。

在BNL算法进行优化时使用了到join_buffer内存,这个使用内存的思路,这也使得NLJ算法有了进一步优化的空间。

其中NLJ本就在JOIN中本就问题不大,此次优化属于锦上添花,也就是 MRR+BKA , Mysql5.6开始支持的优化(默认开启)

2.1.1什么是MRR

Multi-Range Read(MRR) ,它的是BKA算法的基石,实现BKA算法需要调用MRR的接口

核心思想:

  • MRR能够提升性能的思想是,如果表存在primary_key,那么数据多数都是按照主键递增顺序insert的,如果按照主键的递增顺序查询,以此来减少范围查询(多值查询)的随机IO,将其转换为近似顺序IO,就能达到对select加速的优化效果。

具体实现:

分情况讨论

  • 扫描二级索引,并收集相关行的主键,来减少范围扫描的随机磁盘访问次数。然后对主键进行快速排序,最后使用主键的顺序从基表检索行。
  • 当一条查询语句在二级索引上做的是一个范围查询(多值查询),能得到足够多的主键id后,优化器将二级索引范围查询到的记录放到read_rnd_buffer缓冲区中 
  • 如果read_rnd_buffer能存放多值查询的全部数据
    则使用快速排序对read_rnd_buffer缓冲区中的内容按照主键进行排序。根据排序后的主键再去聚簇索引访问实际的数据文件
  • 如果read_rnd_buffer不能存放多值查询的全部数据
    扫描二级索引到文件的末尾或者缓冲区已满,则使用快速排序对read_rnd_buffer缓冲区中的内容按照主键进行排序。根据排序后的主键再去聚簇索引访问实际数据文件后,再清空read_rnd_buffer复用上面流程

不使用MRR可能会引发的问题:

  • 并且如果不使用MRR优化的话,需要查询一个个无序的primary_key得到数据,这就有可能导致数据的page页被重复请求,把数据读到BP中,结合MYSQL优化后的LRU算法原理,淘汰新生代热点page页,最终导致BP命中率降低的问题。

MRR算法优化涉及到的read_rnd_buffer参数的问题:

  • read_rnd_buffer read_rnd_buffer_size参数控制
  • set optimizer_switch="mrr_cost_based=off" 固定使用MRR,建议保持官方建议设置为on,让优化器做出决定,优化器一般都是没有问题的。
  • 官方文档的说法,是现在的优化器策略,判断消耗的时候,会更倾向于不使用MRR,想要稳定地使用MRR优化的话,需要设置set optimizer_switch="mrr_cost_based=off",就是固定使用MRR了
  • 当使用到MRR时,在explain结果中,我们可以看到Extra字段多了Using MRR


2.1.2什么是BKA

  • MySQL在5.6版本后开始引入的Batched Key Acess(BKA)
  • 需要启动KBA算法优化的话,在执行SQL语句之前,先设置set optimizer_switch='mrr=on, mrr_cost_based=off ,batched_key_access=on';
    前两句开启MRR算法
  • BNL算法使用到的join_buffer在NLJ算法中并未使用,所以对NLJ优化为的BKA算法引用了join_buffer。将被驱动表需要的字段批量放入join_buffer,提交发给MRR接口,MRR收到后按照算法进行取主键快速排序返回结果集。如果一次join_buffer放不下就多次清空复用


2.1.3如何优化BNL

NLJ算法被MYSQL锦上添花,但是BNL却没有被雪中送炭。对此只能转换思路,自己解决。

  • 因为BNL算法带来的问题较多,对此种情况下的优化建议首先是通过给被驱动表条件字段加索引转换其算法。
  • 退而求其次的,也可以使用临时表或内存临时表,把需要查询的那部分数据创建一个临时表,然后给需要查询的字段创建一个索引,再JOIN两个表,这样就能使用BKA算法,而且不占用太大的空间。
  • 或者像前文提到的一样增大join_buffer_size的参数。

3.总结

        优化JOIN的的核心思想还是使用NLJ,或者将BNL转换为NLJ/BKA。

你可能感兴趣的:(数据库,MYSQL,mysql,数据库,sql,性能优化,算法)