Skip to content

Latest commit

 

History

History
52 lines (28 loc) · 3.87 KB

10.cost.md

File metadata and controls

52 lines (28 loc) · 3.87 KB

4.9 代价

在前面的介绍中,很多的地方都提到了代价,但一直没有说代价是什么,只是说了在不同的算法中哪些更有优势,例如左深为什么有优势、为什么要利用索引的优势、为什么要利用不同的嵌套循环算法等。这些其实都与底层效率有关,主要是 IO 代价。为帮助大家理解代价,接下来介绍两个示例。

示例 1:选择运算的操作代价

  • 线性扫描

    Cost = br block transfers + 1 seek

    Cost = (br/2) block transfers + 1 seek

    说明

    br 指一个表的元组所占据的磁盘块的块数。比如有一张 test 表,它元组只需要磁盘或闪存上的三个 block 就存得下,每个 block 是 2M。

    如果采用线性扫描,假设表的元组的存放顺序是按照组件存放的,也就是如果要读取一张表的内容,并且采用顺序存放,那代价就是一次寻址的代(需要先寻到这张表的第一个块,再去读取它的全部块),再加上读取全块的代价,即 br block transfers + 1 seek。

    假设是在组件上进行索引扫描,由于组件是有序的,就可以利用二分搜索(一次寻址的代价加上读取二分搜索的代价),即 (br/2) block transfers + 1 seek。

  • B+ 树索引扫描

    Cost = hi * (tT + tS) + tS + tT * b

    Cost = (hi + 1) * (tT + tS)

    如果这张表上有 B+ 树索引,这个时候代价计算方式就不一样了。假设是全表扫描,首先需要进行索引表的扫描,按照层高每层读取一个块,每一个块都要进行寻址,也就是读取每一层的块,加上寻道时间。到了叶子节点之后(B+ 树索引上,叶子结点的是连续的),又有一次的寻道时间,这时候再读取 b 个块。

    如果是主键扫描,就不需要连续的去读取叶子节点上的每一个块,只需要多一层的读取,也就是(hi + 1) ,再乘以每一层的这个块的寻道,还有读取的时间。

示例 2:连接顺序的代价

在上节中讲到左深树的形状更有利于连接,因为可以减少中间临时空间。这里的示例也是这个原理,即先将两个较小关系的表进行组合,这样产生的中间临时关系比较小。

img

统计信息

统计信息是在进行代价估计过程中不可缺少的一部分,统计信息通常有以下几项:

  • 表的行数(row count),对于关系 R,行数可以表示为 row_count=|R|。

  • 某一列中 ndv 的数量(number of distinct value),即去重之后元素的数量。

  • 选择率(selectivity),对于某一个过滤条件,被过滤的行的比例。对于关系 R 上的过滤条件(谓词)p ,其选择率可以表示为 sel(p)=|σpR||R|。

  • 基数(cardinality),狭义上的基数是指数据库中某个表的某个列中不重复行的总个数。

这里通过一个简单的例子来帮助大家理解统计信息在代价估计中的意义。在 Scan 操作的时候,应该使用全表扫描还是索引扫描呢?前几节介绍了一些索引扫描在等值比较查询中能够有效的减少 IO 的说法,但这个说法只能针对于单次的操作而言,从整体上对 SQL 的查询效率不一定是最优的。

比如 SQL 语句 SELECT * FROM tab WHERE id=2022 ,假设在 tab 表上 year 字段基本都是 2022,那么针对 id=2022 这个过滤条件被过滤的行是很少的,因为这整张表上的每一个 tuple,在年份这个字段上基本都是 2022,就没有过滤的意义。这时候如果还进行索引扫描,只会单纯多了一个回表的代价,还不如全表扫描,而且它的投影(SELECT)也是针对于全部的元组的,所以这时候就不需要走索引扫描。