CMU15-445数据库系统:连表算法

本文已收录于 CMU数据库系统学习笔记 系列,共计 11 篇,本篇是第 8 篇

作业进度提醒

视频课程(中字幕)、课件

视频:https://www.bilibili.com/video/BV1tL4y1J7kJ/?spm_id_from=333.788

课件:

Join Algorithms

问:为什么会有JOIN?

答:这是数据库范式化的产物,我们将数据拆分,以此来减少重复和冗余信息。

本节我们讨论一次只将两张数据表进行JOIN连接的操作,这在当今的数据库系统中都已经实现了。虽然有很多的商业数据库也实现了同时将三张表、甚至N张表进行JOIN连接的操作,但这并不是常见的,我们将会在高级课程15-721中讲述。所以本课还是主要讨论 一次只join 两个 tables 的过程。

JOIN的输出(JOIN算子输出什么?)

下面这张图的处理方式是提前物化,其优点是上层需要筛选哪列就不用回表再查询了,非常方便,要哪列就用哪列。

输出数据本身:

延迟物化:延迟物化的缺点是上级想找到S.cdate就需要重新去表中搜索。

JOIN和笛卡尔积

JOIN是数据库中最常用的算子,与JOIN相似的还有一个算子是笛卡尔积。但是笛卡尔积十分耗时,因为它会生成很大的中间表。比如表A的记录数是10条,表B时10条,做笛卡尔积会出来10×10 = 100条数据。笛卡尔积就像是没有匹配条件的JOIN操作。

I/O Cost Analysis

数据库之所以设计 JOIN Algorithm 的目的就是因为数据库中的数据量通常较大,无法一次性载入内存,使用 JOIN Algorithm 可以减少磁盘 I/O,因此我们衡量 JOIN Algorithm 好坏的标准,就是 I/O 的效率。

一般来讲,JOIN算法有三类,本节要介绍的 Join Algorithms 罗列如下:

嵌套循环连接(所有数据库系统都支持)

Simple——STUPID 方案

循环嵌套连接就是需要one by one的连接后比较,即 r and s match。

下面这张图,M代表左表的页数,m代表行数。右边也同样。

R作为外部表,S作为内部表。这样的话一个消费级SSD每次IO仅需要0.1ms的情况下还需要1.3小时才能完成JOIN操作。

S作为外部表,R作为内部表。这样计算下来就只需要1.1小时了。

所以JOIN操作一般将小表放在坐标作为外部表,大表放在右边作为内部表来布局。

答:这里所说的小表指的是表的宽度而不是长度。

这种方法其实也很蠢。每次计算都需要把右边换入的内存中。上面我们计算的就是从硬盘换入内存的时间,这通常是不可忍受的。但如果是在内存中直接做运算就会快很多很多。

Block——对循环嵌套连接做改进,将多个tuple打包进page。

比如左表中选取两个tuple作为page,右表中也是两个tuple作为page。先这这两个page中的4个tuple两两比较,如果没有找到就直接抛弃右边的page,往下顺移。请注意:page和block其实是一个意思,这个叫做什么是无所谓的。

问:那page内部不还是一样要两两比较吗?

答:但是page可以在内存中遍历啊,这与从硬盘中不断地换入tuple到内存是不一样的。

同时缓存一个R中的一个page和S中一个page。这样做可以减少遍历页数,从左表需要遍历m次变成了M个page。

算法:

改进后的消耗:

改进:如果我们的内存池很大,如何利用它?

如果我们开B个page大小的内存,如果一个page是16kb(以MySQL为例)那就是 16kb*B个大小的内存池。如果你忘了page是什么,请回顾我们在存储引擎章节所学的内容 –> 数据库page

给1个page作为输出缓存,再给一个page作为内表(右表)缓存是,剩下的B-2个page全给外表(左表)缓存。

IO开销:

如果左表整个都能塞进内存池中

总结:其实就是利用内存,尽量的放在内存中,这样IO开销就会小。但是需要注意:内存中的运算开销也会增大,但是内存跟硬盘换入相比的开销可以忽略不计。

使用索引的循环嵌套连接

对右表做索引,通过索引的方式在B+Tree中做查找。使用所以可以大大的减少开销,提高查询速度。如果忘了B+Tree的知识,移步复习 –> B+Tree——数据库中广泛应用的数据结构

IO开销:

排序归并连接(建立在上节课所讨论的排序知识上面

先给两个表按 JOIN key 排序,再归并。

IO开销:

关于排序算法回顾上节讲的内容 –> CMU15-445数据库系统:排序与聚集

带入我们之前的例子:

劣势:不过我们发现了这个算法最差的情况——索引全部都是一样的值。这样就退化成 stupid 模式了。

优势:如果已经排好序的话,只需要1500次IO就可以了。

随着B+Tree的增长,扫描B+Tree的时间会越来越长。对这种一对一的查询,其实不需要B+Tree那样的扫描特性。什么样的索引更适合点查询?——Hash

Hash Join(最重要、最快的算法)

如何利用hash表?

步骤:

  1. 先扫描R表,构造哈希表。
  2. S的每一行都去hash中查询。(也就是用R表创建hash表,用S表去使用hash表查询)

哈希表的key和value放什么?

key放置连接点。value还是有提前物化和延迟物化两种思路。可以存放R表中的所有字段;也可以存放R表中的部分字段,然后后续需要的话再去page中查询。关于提前物化延迟物化问题,点这里复习。

Bloom过滤器

B去hash表中查询的时候,要先经过Bloom过滤器。如果bloom过滤器没有发现hash表中的匹配,就直接干掉,不让继续查询了。

Bloom过滤器是什么?

布隆过滤器可以告诉我们 “某样东西一定不存在或者可能存在”,也就是说布隆过滤器说这个数不存在则一定不存,布隆过滤器说这个数存在可能不存在(误判)。

https://www.cnblogs.com/zhaodongge/p/15017574.html

https://blog.csdn.net/qq_41125219/article/details/119982158

配置最合适的Bloom过滤器参数:https://krisives.github.io/bloom-calculator/

Grace Hash Join——当两个 table 都无法放入内存时

前提假设R表和S表没有办法放入内存中。

解决方案:对R表和S表用同一个hash函数做哈希。分成的区域叫 桶 或者 分区。——这个叫什么名字无所谓。

这样做,只要R表和S表能连接的表一定是在同一个桶里

如果这个桶也无法放入内存中呢?

说明这个桶还是大,那就不断的递归分桶,知道能够将桶放入内存中。

加入下面这第二个表无法放入内存,就将其再次哈希,直到可以将桶放入内存中。

S表中的1’表原本是与R表中的第二个桶比较的,但因为S表第二个桶被拆分为 1′ 1” 1”’了,所以S表第二个桶也要用h2函数哈希。

IO开销:

  • Partitioning Phase:
    • Read + Write both tables
    • 2(M+N) I/Os
  • Probing Phase
    • Read both tables
    • M+N I/Os

如果 DBMS 已经知道 tables 大小,则可以使用 static hash table,否则需要使用 dynamic hash table。回顾哈希表 –> CMU15-445数据库系统:哈希表

总结

Algorithm IO Cost Example
Simple Nested Loop Join M+(m×N) 1.3 hours
Block Nested Loop Join M+(M×N) 50 secs
Index Nested Loop Join M+(m×C) 20 secs
Sort-Merge Join M+N+(sort cost) 0.59 secs
Hash Join 3(M + N)3(M+N) 0.45 secs

 

作者: 高志远

高志远,24岁,男生

发表评论

邮箱地址不会被公开。