表连接的io cost

2024年 3月 16日 51.4k 0

变量说明

R表(外部表,驱动表),页数:M,记录行数:n

S表(被驱动表),页数:N,记录行数:n

TS:顺序读

TR:随机读

Nested Loop Join

循环嵌套连接:mysql5.x都只支持NL join,这也是mysql经常被吐槽的点

外部循环中的表称为外部表(驱动表),而内部循环中表称为内部表(被驱动表),通常小表(数据行)驱动大表(数据行),是为了降低连接次数

Simple Nested-Loop Join

执行步骤

1.外部表读取一行记录
2.连接被内部表
3.得到匹配结果
4.执行读外部表下一行记录重复连接匹配,直到表的最后一行循环结束
5.合并结果返回

即:驱动表扫描一次+m次扫描被驱动表

注:mysql并未使用简单嵌套查询

Block Nested-Loop Join

执行步骤

1.外部表读入内存join_buffer
2.内部表读取一行与外部表连接
3.得到匹配结果
4.读取外部表下一批记录读入join_buffer,直到所有行都读入内存循环结束
5.合并结果并返回



即 外部表扫描一次,内部表扫描1~多次

注:内部表扫描次数取决于join_buffer大小,如果join_buffer足够大则能一次把外部表读入join_buffer,如果join_buffer不够大则需要多次读入内存即分成多批blocks.

join_buffer_size mysql各版本默认该设置为128 或 256 或512k。以join_buffer_size 2M计算,通常一条记录4004096字节之间,可用读入5125120条记录,这里也可以引出小表的定义:大概1万行内或小于4M大小.

单独客户端查询(手动查询)也可以调整session的join_buffer_size 值

set session join_buffer_size =536870912 --设置512M

Index Nested-Loop Join

执行步骤

1.外部表读取一行记录
2.通过索引连接内部表
3.得到匹配结果
4.执行读外部表下一行记录重复连接匹配,直到表的最后一行循环结束
5.合并结果返回



即:外部表扫描一次+外部表每一行索引查询内部表

注:mysql B-树 通常树深三层,n取值为1000左右,千万级别表3次io操作即可查询

Batched Key Acess(BKA)

Multi-Range Read 步骤

1.索引查找匹配,记录id存入read_rnd_buffer
2.在read_rnd_buffe中id进行排序,规避随机IO访问
3.根据id回表查询
4.返回结果

注:优化器倾向于不使用MRR,需要设置

set optimizer_switch="mrr_cost_based=off"

BKA执行步骤

1.外部表读入内存join_buffer
2.批量的将Key(索引键值)发送到MRR)接口(read_rnd_buffer)
3.MRR通过收到的Key在read_rnd_buffer中,根据其对应的ROWID进行排序
4.根据id顺序回表查询
5.执行读取外部表下批记录连接匹配,直到所有blocks连接循环结束
5.合并结果并返回

Sort-Merge Join

归并排序连接:mysql不支持

执行步骤:

1.对关联2个表进行排序

2.读取表R与表S记录直到所有最后一行记录

3.表R与表S进行关联匹配,返回输出,表S读取下一条记录

4.如不匹配,表R读取下一条记录

5.直到最后一行记录



注:B个缓冲区用于排序

Hash Join

散列连接:mysql8.0开始支持hash join

Basic Hash Join

执行步骤

1.build:扫描外表,选取连接条件通过hash函数划分bucket构建hash table

2.Probe:扫描内表,选取连接条件通过hash函数进行bucket匹配,定位到对应的bucket,进行匹配


Grace Hash Join / Partitioned Hash Join

表在内存中无法存放,需要频繁在磁盘与内存中换进换出

执行步骤

1.build:扫描外表,如果内存放不下hash buket,2次hash 映射hash partition、hash bucket

2.Probe:扫描内表,选取连接条件通过2次hash函数进行bucket匹配,定位到对应的bucket,进行匹配


分析

磁盘的吞吐量为 100M/S ~200M/S之间 ,便于计算取160M/S ,对于16K页为0.1ms 即一次顺序读 TS:0.1ms

IOPS 为:100~150,即一次随机读为 TR:10ms

一条记录为400字节到4K字节之间,对于16K页通常存放4~40条记录,取中位数一页存放20条记录,则表页数:行数=20:1

假设值如下:

TS: 0.1ms

TR: 10 ms

m: 10000 1万条记录

n: 1000000 100万条记录

M: 0.05m 500页

N:0.05 n 5万页



c取1000,百万级别2次 内存io;记一次随机IO 10ms

小表驱动大表:SNLJ cost= 500*0.1ms +(10000 * 50000) *0.1 ms =13.89 小时

大表驱动小表: SNLJ cost= 50000*0.1ms +(1000000 * 500) *0.1 ms =13.89 小时

结论:“小表驱动大表” 成本略低于 “大表驱动小表”



以join_buffer 2M 计算,小表需要4次读入join_buffer,大表需要390次读入join_buffer

小表驱动大表: BNLJ cost=500 * 0.1 ms + (4 * 50000) * 0.1 ms = 20.05秒

大表驱动小表: BNLJ cost=50000 * 0.1 ms + (390 * 500) * 0.1 ms =24.5秒

结论:“小表驱动大表” 成本略低于 “大表驱动小表”



小表驱动大表: INLJ cost= 500 * 0.1ms + (10000 * 1 ) * 10 ms = 100.05 秒

大表驱动小表: INLJ cost= 50000 * 0.1ms + (1000000 * 1 ) * 10 ms = 2.7 小时

结论:“小表驱动大表” 成本远低于 “大表驱动小表”



小表驱动大表: INLJ cost= (500 +50000)* 0.1ms + (42000 ) * 0.1 ms = 15.15秒

结论:sort merge join 不区分大小表

HSJ:cost= (M + N)

HSJ:cost= 3 * (M + N)

小表驱动大表: INLJ cost= (50000 + 500 ) * 0.1ms = 5.05 秒

小表驱动大表: INLJ cost= 3*(50000 + 500 ) * 0.1ms = 15.15 秒

结论:hash join 需要考虑大小表能否放在内存问题,如果内存放不下,需要hash 分区,则成本更高;即还是倾向小表驱动大表

总结

1.hash join (只适用等值连接)性能最优 ,在非等值关联时适用sort merge join

2.hash join 优势这么大,nested loop join 为什么没被淘汰,存在以下几个原因:

a.返回记录行数小,驱动表、被驱动表大且有索引;被驱动表随机读成本远小于表扫描

以磁盘100M/S,iops 100 来计算,2表有条件过滤再关联二次随机读20ms,20ms表扫描才能2M数据,128个16K数据页

b.小表大表关联,有索引,关联连接区分度高

3.优化Nested loop join 的的核心思想还是添加索引,将BNL转换为INLJ/BKA

4.数据已经排序,且存在倾斜,非等值比较 sort merge join更占优

参考

https://15445.courses.cs.cmu.edu/fall2023/notes/11-joins.pdf

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论