Database System Concepts——读书笔记 第十五章 查询过程

发布时间 2023-06-09 07:59:12作者: sahara-随笔

join操作
Nest Loop Join
算法简单来说,就是双重循环,遍历外表(驱动表),对于外表的每一行记录,然后遍历内表,然后判断join条件是否符合,进而确定是否将记录吐出给上一个执行节点。从算法角度来说,这是一个M*N的复杂度。

HashJoin
是针对equal-join场景的优化,基本思想是,将外表数据load到内存,并建立hash表,这样只需要遍历一遍内表,就可以完成join操作,输出匹配的记录。如果数据能全部load到内存当然好,逻辑也简单,一般称这种join为CHJ(Classic Hash Join)。如果数据不能全部load到内存,就需要分批load进内存,然后分批join,下面具体介绍这几种join算法的实现。

On-Disk Hash Join
CHJ的限制条件在于,要求内存能装下整个外表。在MySQL中,Join可以使用的内存通过参数join_buffer_size控制。如果join需要的内存超出了join_buffer_size,那么CHJ将无能为力,只能对外表分成若干段,每个分段逐一进行build过程,然后遍历内表对每个分段再进行一次probe过程。假设外表分成了N片,那么将扫描内表N次。这种方式当然是比较弱的。在MySQL8.0中,如果join需要内存超过了join_buffer_size,build阶段会首先利用hash算将外表进行分区,并产生临时分片写到磁盘上;然后在probe阶段,对于内表使用同样的hash算法进行分区。由于使用分片hash函数相同,那么key相同(join条件相同)必然在同一个分片编号中。接下来,再对外表和内表中相同分片编号的数据进行CHJ的过程,所有分片的CHJ做完,整个join过程就结束了。这种算法的代价是,对外表和内表分别进行了两次读IO,一次写IO。相对于之之前需要N次扫描内表IO,现在的处理方式更好。

Grace Hash Join
主流的数据库Oracle,SQLServer,PostgreSQL早就支持了HashJoin。Join算法都类似,这里介绍下Oracle使用的Grace Hash Join算法。其实整个过程与MySQL的HashJoin类似,主要有一点区别。当出现join_buffer_size不足时,MySQL会对外表进行分片,然后再进行CHJ过程。但是,极端情况下,如果数据分布不均匀,导致大量的数据hash后都分布在一个分桶中,导致分片后,join_buffer_size仍然不够,MySQL的处理方式是一次读分片读若干记录构建hash表,然后probe对应的外表分片。处理完一批后,清理hash表,重复上述过程,直到这个分片的所有数据处理完为止。这个过程与CHJ在join_buffer_size不足时,处理逻辑相同。

GraceHash在遇到这种情况时,会继续分片进行二次Hash,直到内存足够放下一个hash表为止。但是,这里仍然有极端情况,如果输入join条件都相同,那么无论进行多少次Hash,都没法分开,那么这个时候GraceHashJoin也退化成和MySQL的处理方式一样。

hybrid hash join
与GraceHashJoin的区别在于,如果缓存能缓存足够多的分片数据,会尽量缓存,那么就不必像GraceHash那样,严格地将所有分片都先读进内存,然后写到外存,然后再读进内存去走build过程。这个是在内存相对于分片比较充裕的情况下的一种优化,目的是为了减少磁盘的读写IO。目前Oceanbase的HashJoin采用的是这种join方式。

连接操作尽量在内存中执行

充分利用cpu缓存感知算法,避免cache失效。

由于数据驻留在内存中,CPU成本成为瓶颈,最大限度地降低CPU成本可以带来显著的好处。传统的数据库查询处理器充当执行查询计划的解释器。然而,由于解释的原因,会产生很大的开销:例如,为了访问记录的属性,查询执行引擎可能会重复查找关系元数据,以找到记录中属性的偏移量,因为相同的代码必须适用于所有关系。由于对操作处理的每个记录执行函数调用,也会产生大量开销。
为了避免由于解释造成的开销,现代主存数据库将查询计划编译为机器代码或中间级别的字节码。例如,编译器可以在编译时计算属性的偏移量,并生成偏移量为常数的代码。编译器还可以以最小化函数调用的方式组合多个函数的代码。通过这些以及其他相关优化,已发现编译代码的执行速度比解释代码快,高达10倍。

MySQL8.0 新特性 Hash Join