代码编织梦想


MySQL45讲

实践篇

35 | join语句怎么优化?

create table t1(id int primary key, a int, b int, index(a));
create table t2 like t1;
drop procedure idata;
delimiter ;;
create procedure idata()
begin
  declare i int;
  set i=1;
  while(i<=1000)do
    insert into t1 values(i, 1001-i, i);
    set i=i+1;
  end while;
  
  set i=1;
  while(i<=1000000)do
    insert into t2 values(i, i, i);
    set i=i+1;
  end while;

end;;
delimiter ;
call idata();
Multi-Range Read(MRR) 优化
select * from t1 where a>=1 and a<=100;

主键索引是一棵 B+ 树,在这棵树上,每次只能根据一个主键 id 查到一行数据。因此,回表是一行行搜索主键索引的。

在这里插入图片描述

如果随着 a 的值递增顺序查询的话,id 的值就变成随机的,那么就会出现随机访问,性能相对较差。

因为大多数的数据都是按照主键递增顺序插入得到的,所以可以认为,如果按照主键的递增顺序查询,对磁盘的读比较接近顺序读,能够提升读性能。(MRR 优化的设计思路)

MRR 优化后的语句的执行流程如下:

  1. 根据索引 a,定位到满足条件的记录,将 id 值放入 read_rnd_buffer 中 ;
  2. 将 read_rnd_buffer 中的 id 进行递增排序;
  3. 排序后的 id 数组,依次到主键 id 索引中查记录,并作为结果返回。

read_rnd_buffer 的大小由 read_rnd_buffer_size 参数控制。如果步骤 1 中,read_rnd_buffer 放满了,就会先执行完步骤 2 和 3,然后清空 read_rnd_buffer。之后继续找索引 a 的下个记录,并继续循环。

如果想要稳定地使用 MRR 优化,需要设置 set optimizer_switch=“mrr_cost_based=off”。(官方文档的说法,是现在的优化器策略,判断消耗的时候,会更倾向于不使用 MRR,把 mrr_cost_based 设置为 off,就是固定使用 MRR 了。)

在这里插入图片描述
在这里插入图片描述

MRR 能够提升性能的核心在于,这条查询语句在索引 a 上做的是一个范围查询(也就是说,这是一个多值查询),可以得到足够多的主键 id。这样通过排序以后,再去主键索引查数据,才能体现出“顺序性”的优势。

Batched Key Access(BKA)

BKA 算法,其实就是对 NLJ 算法的优化。NLJ 算法执行的逻辑是:从驱动表 t1,一行行地取出 a 的值,再到被驱动表 t2 去做 join。也就是说,对于表 t2 来说,每次都是匹配一个值。这时,MRR 的优势就用不上了。

BKA 算法的流程:

在这里插入图片描述

join_buffer 的目的是为了后续将查询到的被驱动表的主键 id 聚集起来,然后放入 read_rnd_buffer 中排序,最后将依次到主键 id 索引中查记录,并作为结果返回。

如果要使用 BKA 优化算法,需要在执行 SQL 语句之前,先做如下设置:

set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

其中,前两个参数的作用是要启用 MRR。这么做的原因是,BKA 算法的优化要依赖于 MRR

BNL 算法的性能问题

由于 InnoDB 对 Bufffer Pool 的 LRU 算法做了优化,即:第一次从磁盘读入内存的数据页,会先放在 old 区域。如果 1 秒之后这个数据页不再被访问了,就不会被移动到 LRU 链表头部,这样对 Buffer Pool 的命中率影响就不大。

但是,如果一个使用 BNL 算法的 join 语句,多次扫描一个冷表(被驱动表),而且这个语句执行时间超过 1 秒,就会在再次扫描冷表的时候,把冷表的数据页移到 LRU 链表头部。

如果这个冷表很大,就会出现一种情况:业务正常访问的数据页,没有机会进入 young 区域。

由于优化机制的存在,一个正常访问的数据页,要进入 young 区域,需要隔 1 秒后再次被访问到。但是,由于 join 语句在循环读磁盘和淘汰内存页,进入 old 区域的数据页,很可能在 1 秒之内就被淘汰 (正常数据很快就被挤到 old 区,没有进入 young 区的机会)。这样,就会导致这个 MySQL 实例的 Buffer Pool 在这段时间内,young 区域的数据页没有被合理地淘汰。

大表 join 操作虽然对 IO 有影响,但是在语句执行结束后,对 IO 的影响也就结束了。但是,对 Buffer Pool 的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率。

为了减少这种影响,可以考虑增大 join_buffer_size 的值,减少对被驱动表的扫描次数。

BNL 算法对系统的影响主要包括三个方面:

  1. 可能会多次扫描被驱动表,占用磁盘 IO 资源;
  2. 判断 join 条件需要执行 M*N 次对比(M、N 分别是两张表的行数),如果是大表就会占用非常多的 CPU 资源;
  3. 可能会导致 Buffer Pool 的热数据被淘汰,影响内存命中率。

执行语句之前,需要通过理论分析和查看 explain 结果的方式,确认是否要使用 BNL 算法。如果确认优化器会使用 BNL 算法,就需要做优化。优化的常见做法是,给被驱动表的 join 字段加上索引,把 BNL 算法转成 BKA 算法。

BNL 转 BKA

一些情况下,可以直接在被驱动表上建索引,这时就可以直接转成 BKA 算法。

有时候会碰到一些不适合在被驱动表上建索引的情况。比如下面这个语句:

select * from t1 join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=2000;

经过 where 条件过滤后,需要参与 join 的只有 2000 行数据。如果这条语句同时是一个低频的 SQL 语句,那么再为这个语句在表 t2 的字段 b 上创建一个索引就很浪费了。

如果使用 BNL 算法来 join,这个语句的执行流程如下:

  1. 把表 t1 的所有字段取出来,存入 join_buffer 中。这个表只有 1000 行,join_buffer_size 默认值是 256k,可以完全存入。
  2. 扫描表 t2,取出每一行数据跟 join_buffer 中的数据进行对比,如果不满足 t1.b=t2.b,则跳过;如果满足 t1.b=t2.b, 再判断其他条件,也就是是否满足 t2.b 处于[1,2000]的条件,如果是,就作为结果集的一部分返回,否则跳过。

对于表 t2 的每一行,判断 join 是否满足的时候,都需要遍历 join_buffer 中的所有行。因此判断等值条件的次数是 1000*100 万 =10 亿次,这个判断的工作量很大。

疑问:在表 t2 的字段 b 上创建索引会浪费资源,但是不创建索引的话这个语句的等值条件要判断 10 亿次,如何优化?

可以考虑使用临时表。 使用临时表的大致思路是:

  1. 把表 t2 中满足条件的数据放在临时表 tmp_t 中;
  2. 为了让 join 使用 BKA 算法,给临时表 tmp_t 的字段 b 加上索引;
  3. 让表 t1 和 tmp_t 做 join 操作。
create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb;
insert into temp_t select * from t2 where b>=1 and b<=2000;
select * from t1 join temp_t on (t1.b=temp_t.b);

在这里插入图片描述

不论是在原表上加索引,还是用有索引的临时表,思路都是让 join 语句能够用上被驱动表上的索引,来触发 BKA 算法,提升查询性能。

扩展 -hash join

如果 join_buffer 里面维护的不是一个无序数组,而是一个哈希表的话,那么就不是 10 亿次判断,而是 100 万次 hash 查找。

MySQL 的优化器和执行器一直被诟病的一个原因:不支持哈希 join。并且,MySQL 官方的 roadmap,也是迟迟没有把这个优化排上议程。

这个优化思路,可以实现在业务端。实现流程大致如下:

  1. select * from t1;取得表 t1 的全部 1000 行数据,在业务端存入一个 hash 结构,比如 C++ 里的
    set、PHP 的数组这样的数据结构。
  2. select * from t2 where b>=1 and b<=2000; 获取表 t2 中满足条件的 2000 行数据。
  3. 把这 2000 行数据,一行一行地取到业务端,到 hash 结构的数据表中寻找匹配的数据。满足匹配的条件的这行数据,就作为结果集的一行。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/TQ20160412/article/details/122239935

mysql系列(四):join的优化和如何查询%name%或like %name%_陈永佳的博客-爱代码爱编程

JOIN优化 博主今天为大家带来JOIN的优化以及如何查询%name%或like %name%更为有效,只有少部分,有待补充提高,如有更好地建议,欢饮讨论! 前言: 为什么要优化JOIN!为什么谈到如何查询

从顺序随机i/o原理来讨论mysql mrr nlj bnl bka-爱代码爱编程

从顺序随机I/O原理来讨论MYSQL MRR NLJ BNL BKA 本文只讨论innodb存储引擎,并且有部分观点为作者观点,如果有误请指出。 一、机械磁盘原理     机械盘由动臂,盘片,读写磁头,主轴组成,磁头是固定不能动的,要读取相应的扇区只能通过盘片的     旋转。     每一个盘片为双面,每一个面上分布

说说mysql的MRR,ICP,BKA优化。-爱代码爱编程

一)MRR优化 Multi-Range Read优化mysql5.6版本开始支持MRR优化,,目的是为了减少磁盘的随机访问, 并且将随机访问转化为较为顺序的顺序访问,适用于range,ref,eq_ref类型的查询。 MRR优化有以下几个好处: 1 MRR 使数据访问变得较为顺序 ,在查询辅助索引时,首先根据得到的查询结果, 按照主键进行排序,并按照主键排

35讲join语句怎么优化-爱代码爱编程

在上一篇文章中,我和你介绍了join语句的两种算法,分别是Index Nested-Loop Join(NLJ)和Block Nested-Loop Join(BNL)。 我们发现在使用NLJ算法的时候,其实效果还是不错的,比通过应用层拆分成多个语句然后再拼接查询结果更方便,而且性能也不会差。 但是,BNL算法在大表join的时候性能就差多了,比较次数

mysql bnl_Mysql优化,ICP、BNL算法、BKA算法、MMR算法-爱代码爱编程

ICP(Index Condition Pushdown,索引条件下推)是MySQL5.6版本中的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。 出现原因:ICP出现Mysql5.6以前,Mysql查询数据是通过索引查询到主键数据,然后再根据数据行回到Mysql Server层做Using where回表查询检索,这样子把查询回到了Mys

MySQL联接查询算法(NLJ、BNL、BKA、HashJoin)-爱代码爱编程

一、联接过程介绍 为了后面一些测试案例,我们事先创建了两张表,表数据如下: 1 2 3 4 CREATE TABLE t1 (m1 int, n1 char(1)); CREATE TABLE t2 (m2 int, n2 char(1)); INSERT INTO t1 VALUES(1, 'a'), (2, 'b'), (3, 'c'); IN

【mysql】索引性能优化之ICP、MRR、BKA理论加实践-爱代码爱编程

(查询优化)Index Condition Pushdown (指数条件下推)(ICP) ICP是MYSQL使用索引从表中检索行数据的一种优化方式。 目标 减少从基表中读取操作的数量,从而降低I/O操作。 禁用ICP 存储引擎会通过遍历索引定位基表中的行,然后返回给Server 层,在去为这些数据进行WHERE 后的条件过滤。 开启ICP特性

mysql数据库BKA算法详解-爱代码爱编程

BKA算法详解 Batched Key Access理解了 MRR 性能提升的原理,我们就能理解 MySQL 在 5.6 版本后开始引入的 BatchedKey Access(BKA) 算法了。这个 BKA 算法,其实就是对 NLJ 算法的优化。我们再来看看上一篇文章中用到的 NLJ 算法的流程图: 图 4 Index Nested-Loo

MySQL实战:join语句怎么优化?-爱代码爱编程

三种join的方式 Simple Nested-Loop Join 效率最低mysql没有采用 Block Nested-Loop Join show variables like '%join_buffer%' Index Nested-Loop Join CREATE TABLE `t2` ( `id` int(11

如何让join跑得更快?_java序码的博客-爱代码爱编程

JOIN 一直是数据库性能优化的老大难问题,本来挺快的查询,一旦涉及了几个 JOIN,性能就会陡降。而且,参与 JOIN 的表越大越多,性能就越难提上来。 其实,让 JOIN 跑得快的关键是要对 JOIN 分类,分类之后,就能利用各种类型 JOIN 的特征来做性能优化了。 JOIN 分类 有 SQL 开发经验的同学都知道,绝大多数 JOIN 都是等值

如何进行join优化?-爱代码爱编程

定义 JOIN是MySQL用来进行联表操作的,用来匹配两个表的数据,筛选并合并符合我们要求的结果集 常用的联接方式有: 左外连接 LEFT JOIN右外连接 RIGHT JOIN内连接 INNER JOIN 什么是驱

48.mysql优化-配置篇+技巧篇-爱代码爱编程

优化join:joinbuffersize调大。 joinbuffer的大小是由参数joinbuffer_size设定的,默认值是256k。 如果join_buffer放不下表的所有数据,策略很简单,就是分段放。 优化join:在被驱动表上建索引:BNL转BKA 优化join:临时表存储被驱动表符合条件的数据 用临时表的大致思路

数据库监控与调优【十二】—— join语句优化-爱代码爱编程

JOIN语句优化-JOIN种类、算法与原理 JOIN的种类 笛卡尔连接(cross join) -- 举例:通过笛卡尔连接查询两张表的结果集和单查两张表的结果集对比 SELECT count( * ) FROM u