MySQL实战45讲 ~ 34 - 37

JOIN ta tb id pk, a key, b   t1 是驱动表,t2 是被驱动表

Index Nested-Loop Join: select * from t1 straight_join t2 on (t1.a=t2.a)   

驱动表是走全表扫描,应该选择小表来做驱动表,而被驱动表是走树搜索,先遍历表 t1,然后根据从表 t1 中取出的每行数据中的 a 值,去表 t2 中查找满足条件的记录,用被驱动表的索引

如果不用Join,自己查,遍历生成语句再差,相当于语句多了,交互多了,还要自己拼接结果

Simple Nested-Loop Join:select * from t1 straight_join t2 on (t1.a=t2.b);  性能太差,全表扫描指数次

Block Nested-Loop Join:优化上面的算法,将驱动表读入线程内存 join_buffer 中,放不下就分段放,扫描被驱动表,把被驱动表中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回

在判断要不要使用 join 语句时,就是看 explain 结果里面,Extra 字段里面有没有出现“Block Nested Loop”字样,有最好不用

select * from t2 straight_join t1 on (t1.b=t2.b) where t2.id<=50;  t2有限制条件,查出来的数据少,算小表,小表驱动

select t1.b,t2.* from t1 straight_join t2 on (t1.b=t2.b) where t2.id<=100;  两个表记录只有100,t2查全部字段,放入Buffer的更多,选择t1 作为驱动表

在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表

 

join优化:

select * from t1 where a>=1 and a<=100;

Multi-Range Read 优化 (MRR)。这个优化的主要目的是尽量使用顺序读盘,根据索引 a,定位到满足条件的记录,将 id 值放入 read_rnd_buffer 中,将 read_rnd_buffer 中的 id 进行递增排序,排序后的 id 数组,依次到主键 id 索引中查记录,并作为结果返回,Extra 字段多了 Using MRR,MRR 能够提升性能的核心在于,这条查询语句在索引 a 上做的是一个范围查询(也就是说,这是一个多值查询),可以得到足够多的主键 id

回表是指,InnoDB 在普通索引 a 上查到主键 id 的值后,再根据一个个主键 id 的值到主键索引上去查整行数据的过程

Batched Key Access(BKA) :驱动表数据取出一部分放入join_buffer,批量进行后续操作

Block Nested-Loop Join算法中,大表 join 操作虽然对 IO 有影响,但是在语句执行结束后,对 IO 的影响也就结束了。但是,对 Buffer Pool 的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率,缓解方法,join_buffer_size 的值,减少对被驱动表的扫描次数,BNL 算法对系统的影响主要包括三个方面:可能会多次扫描被驱动表,占用磁盘 IO 资源;判断 join 条件需要执行 M*N 次对比(M、N 分别是两张表的行数),如果是大表就会占用非常多的 CPU 资源;可能会导致 Buffer Pool 的热数据被淘汰,影响内存命中率

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

 

temporary table 临时表仅对当前session可见,showtable和其他会话均不可见,创建session结束表自动删除,不同session创建表名不冲突,被用在复杂查询的优化过程中

分库分表通过代理路由,如果查询条件里面没有用到分区字段,只能到所有的分区中去查找满足条件的所有行,然后统一做 order by 的操作,可以在代理proxy层做排序合并,也可以将结果汇总到一个 MySQL 临时表中进行处理

MySQL 在记录 binlog 的时候,会把主库执行这个语句的线程 id 写到 binlog 中。这样,在备库的应用线程就能够知道执行每个语句的主库线程 id,并利用这个线程 id 来构造临时表的 table_def_key

 

排序的时候用到了 sort buffer,在使用 join 语句的时候用到了 join buffer

(select 1000 as f) union (select id from t1 order by id desc limit 2);    Extra 字段,表示在对子查询的结果集做 union 的时候,使用了临时表 (Using temporary)

创建一个内存临时表包含字段f,执行第一句,将1000放入内存临时表,执行第二段,尝试放入,不存在则放入,最后返回内存临时表并删除,如果是union all,则不需要去重也不需要内存临时表,Extra 字段显示的是 Using index

select id%10 as m, count(*) as c from t1 group by m;

创建内存临时表,扫描表 t1 的索引 a依次计算存入,遍历完成后,再根据字段 m 做排序,得到结果集返回给客户端,如果内存临时空间不足则转为磁盘临时表,磁盘临时表默认使用的引擎是 InnoDB

group by 优化方法 -- 索引:不论是使用内存临时表还是磁盘临时表,group by 逻辑都需要构造一个带唯一索引的表,执行代价都是比较高的。如果表的数据量比较大,上面这个 group by 语句执行起来就会很慢,MySQL 5.7 版本支持了 generated column 机制,用来实现列数据的关联更新。你可以用下面的方法创建一个列 z,然后在 z 列上创建一个索引

如果对 group by 语句的结果没有排序要求,要在语句后面加 order by null;尽量让 group by 过程用上表的索引,确认方法是 explain 结果里没有 Using temporary 和 Using filesort;如果 group by 需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大 tmp_table_size 参数,来避免用到磁盘临时表;如果数据量实在太大,使用 SQL_BIG_RESULT 这个提示,来告诉优化器直接使用排序算法得到 group by 的结果。

MySQL实战45讲 ~ 34 - 37

上一篇:MySQL-NDB7.6集群软件升级


下一篇:oracle如何在所有procedure里搜索某些关键字, 存储过程