局部性原理
编写良好的计算机程序倾向于引用的数据项邻近于其他最近用过的数据项,或者邻近于最近自我引用过的数据项
时间局部性:被引用过一次的存储器位置很可能在不远的将来被多次引用
空间局部性:如果一个存储器位置被引用了一次,那么程序很可能在不远的将来引用附近的一个存储器位置
•层次结构中心思想
–对于每个k,位于k层的更快更小的存储设备作为位于k+1层的更大更慢的存储设备的缓存
–层次结构的每一层缓存都来自较低一层的数据对象
•缓存命中
–当程序需要第k+1层的数据对象d时,它会先检查第k层的数据,如果d刚好在第k层,称为缓存命中
•缓存不命中
–当程序需要第k+1层的数据对象d时,它会先检查第k层的数据,如果d不在第k层,称为缓存不命中
•L1高速缓存: CPU芯片上,即芯片上的高速缓存访问速度几乎和寄存器一样快,2-4个时钟周期32KB-256KB
•L2高速缓存: L1高速缓存和主存之间,CPU芯片上访问速度10个时钟周期2MB-4MB
•L3高速缓存: L2高速缓存和主存之间,CPU芯片上访问速度30个时钟周期几MB-几十M
•缓存不命中的种类: 强制性不命中:空的缓存 冲突不命中:缓存放置策略
容量不命中:缓存太小
•通用高速缓存存储器结构 直接映射高速缓存 组相联高速缓存 全相联高速缓存
•考虑一个计算机系统: 每个存储器地址有m位,形成 M=2m个不同地址,高速缓存被组织成S=2s个高速缓存组的数组,每个高速缓存组包括E个高速缓存行,每个高速缓存行由一个B=2b字节数据块组成,有效位指明每个高速缓存行的数据是否有意义,标记位(t = m – b - s)是当前块的存储器地址的位子集,唯一标识存储在该高速缓存行中的块
•根据每个组的高速缓存行数,高速缓存分为不同类
–C/B表示高速缓存所有高速缓存行的数量
–E=1时,直接映射高速缓存
–E=C/B时,全相联高速缓存
–1<E<C/B时,组相联高速缓存
•判断是否包含所需存储字 –组选择 行匹配 字抽取
•如何将SQL数据类型表示成字段?
•如何将元组表示成记录?
•如何在存储块中表示记录或元组的集合?
•如何用块的集合表示和存储关系?
•如果不同的元组可能具有不同的记录大小,或者记录大小不能整除块大小,或两者兼而有之,如何处理记录大小?
•如果因为修改一些字段而导致记录大小发生改变,那么将会发生什么情况?我们如何在记录所在的块内找到存储空间,特别是当记录增大时?
索引结构基础: 一个数据文件可以用来存储一个关系,一个数据文件可能拥有一个或多个索引文件,每个索引文件建立查找键和数据记录之间的关联,查找键的指针指向与查找键具有相同属性值的记录,稠密索引 数据文件的每个记录在索引文件中都设有一个索引项,稀疏索引 数据文件中只有一些记录在索引文件中表示出来 通常为每个数据块在索引文件中设一个索引项
•主索引 能够确定记录在数据文件中的位置,通常在关系的主键上建立主索引
•辅助索引 不能确定记录在数据文件中的位置,通常在其他属性上建立辅助索引
•在顺序文件上构建稠密索引索引文件中的索引块保持键的顺序与文件中的排序顺序一致
利用索引文件,每次查询只需要一次I/O操作就能找到给定键值的记录
•对索引块的有效查找
–索引块数量通常比数据块数量少
–由于键被排序,我们可以使用二分查找法来查找K。若有n个索引块,只需要查找log2n个块
–索引文件可能足够小,以致可以永久地存放在主存中
•稠密索引
–优点:为每个记录都设有一个索引项
–缺点:占用空间相对较大
•稀疏索引
–只为每个存储块设一个键-指针对
–键值是数据块中第一个记录对应值
–优点:节省存储空间
–缺点:查找给定值的记录需要更多时间
稠密索引可以直接回答 “是否存在键值为K的记录?”
稀疏索引需要先在索引中查找键值小于或等于K的最大键值,根据该索引项的指针找到相应的数据块,接下来,必须搜索这个数据块以找到键值为K的记录
重复查找键的索引
–为数据文件建立稠密索引,每一个具有键值K的记录设一索引项,即允许索引文件中出现重复查找键
–为每个键值K只设一个索引项,该索引项的指针指向键值为K的第一个记录。
–在数据文件上构建稀疏索引,键-指针对对应数据文件中每个块的第一个查找键
–在数据文件上构建稀疏索引,键-指针对对应数据文件中每个块的新的、从未在前一存储块中出现过的最小查找键设一个索引项,如果存储块中没有新键值出现,就为该块中唯一的键值设一个索引项,在数据文件上构建稀疏索引,键-指针对对应数据文件中每个块的新的、从未在前一存储块中出现过的最小查找键设一个索引项如果存储块中没有新键值出现,就为该块中唯一的键值设一个索引项
•索引维护的一般原则
–索引文件是顺序文件的一个例子,键-指针对可以看作是按查找键排序的记录。
–数据文件修改过程中用来维护数据文件的那些策略同样适用于索引文件
•辅助索引
–不构建在主键上的索引查找给定一个或多个字段值的记录其与主索引的最大区别在于:辅助索引不决定数据文件中记录的存放位置,而仅能告诉我们记录的当前存放位置辅助索引总是稠密索引,且通常有重复值.查找同样数量的记录,使用辅助索引比使用主索引可能需要多得多的磁盘I/O
•假如某个索引键值在数据文件中出现n次,该键值在辅助索引中就要写n次,更好的选择是:只为指向该键值的所有指针存储一次键值,节省空间,使用一个称为桶的间接层,介于辅助索引文件和数据文件之间,每个查找键K有一个键-指针对,指针指向一个桶文件,维护指向索引键值为K的所有记录的指针
•使用间接的好处: 在不访问数据文件记录的前提下利用桶的指针来帮助回答一些查询
当存在多个条件,并且每个条件都有一个可用的辅助索引时,可以通过在主存中对指针集合求交来找到满足所有条件的指针,然后只需要检索交集中指针指向的记录
•文档查询的关系描述: 把每个属性(即每个词)的索引合成一个,称为倒排索引,使用间接桶 来提高空间利用率
•桶文件的指针:指向文档本身,指向词的一次出现的指针,该指针可以是由文档的第一个块和一个表示该词在文档中出现次数的整数构成的对
指向词的多次出现的指针桶: 桶数组包含更多有关词的出现的信息桶文件可以区分该词在文档中的位置及重要性,尤其是使用HTML、XML等标记语言的文档
•B树的特点: B树能自动地保持与数据文件大小相适应的索引层次,对所使用的存储块空间进行管理,使每个块的充满程度在半满与全满之间,这样的索引不需要溢出块
叶结点中,最后一个指针指向它右边的下一个叶结点存储块,即指向下一个键值大于它的块2) 在叶节点的其它n个指针中,至少有ë(n+1)/2û个指针被使用且指向数据记录;未使用的指针可看作空指针且不指向任何地方。如果第i个指针被使用,则指向具有第i个键值的记录3)内部结点中,所有的n+1个指针都可以用来指向B树中下一层的块。其中至少é(n+1)/2ù个指针被实际使用(但如果是根结点,则不管n多大都只要求至少两个指针被使用)。2) 如果j个指针被使用,那该块中将有j-1个键,设为K1、K2、……、Kj-1。
第一个指针指向B树的一部分,一些键值小于 K1的记录可在这一部分找到。
第二个指针指向B树的另一部分,一些键值大于等于K1且小于K2的记录可在这一部分找到。
以此类推,第j个指针指向B树的又一部分,一些键值大于等于Kj-1的记录可在这一部分找到。2.某些键值远小于K1或者远大于Kj-1的记录可能根本无法通过该块到达,但是可以通过同一层的其他块到达。(兄弟块)
•给定一个B树索引,并且想找出查找键值为K的记录,从根到叶递归查找
基础:若处于叶节点,我们就在其键值中查找。若第i个键是K,则第i个指针可让我们找到所需记录
归纳:若处于某个内部节点,且它的键为K1,K2,…,Kn,根据键值的大小关系来决定下一步该对此节点的哪个子节点进行查找。也就是说,只有一个子节点可使我们找到具有键值K的叶节点。如果K<K1,则为第一个子节点;如果K1<=K<K2,则为第二个子节点,等等。在这一子节点上递归地运用查找过程
•插入时节点的分裂叶结点的分裂
假定N是一个容量为n个键的叶结点,且我们正试图给它插入第(n+1)个键和它相应的指针。
创建一个新结点M,该结点将成为N结点的兄弟,紧挨在N的右边。按键排序顺序的前é(n+1)/2ù个键-指针对保留在结点N中;而其他的键-指针对移到结点M中
结点M和结点N中都有足够数量的键-指针对,即至少有ë(n+1)/2û个这样的键-指针对。
•插入时节点的分裂内结点的分裂
假定N是一个容量为n个键和n+1个指针的内结点,且由于下层结点的分裂,N恰好又被分配给第 (n+2)个指针。执行下列步骤:
创建一个新结点M,该结点将成为N结点的兄弟,紧挨在N的右边。
按键排序顺序的前é(n+1)/2ù个指针保留在结点N中;而将剩余的ë(n+1)/2û个指针移到结点M中前én/2ù个键保留在结点N中.而后ën/2û个键移到结点M中。
•查询: lognN
一般情况下,一个块中可以容纳340个键-指针对,则块的平均充满度为255个指针。
一个根结点有255个子结点,2552个叶结点,在这些叶结点中,有2553个指向元组的指针。
即记录数少于等于2553»1.66´107的关系可以被3层的B-树索引有一个散列函数.它以查找键(散列键)为参数并计算出一个介于0到B-1的整数。B是桶的数目
•桶数组是一个序号从0到B-1的数组中包含B个链表的头。每一个对应于数组中的一个桶。
•如果记录的查找键为K,则将该记录链接到桶号为h(K)的桶列表中存储它,其中h是散列函数。
•散列表的插入
当一个查找键为K的新记录需要被插入时,我们计算h(K)。如果桶号为h(K)的桶还有空间,我们就把该记录存放到此桶的存储块中或在其存储块没有空间时存储到块链上的某个溢出块中如果桶的所有存储块都没有空间,我们就增加一个新的溢出块到该桶的链上,并把新记录存入该块。
•散列表的删除
删除查找键值为K的记录我们找到桶号为h(K)的桶,且从中搜索查找键为K的记录,继而将找到的记录删除。如果我们可以将记录在块中移动,那么删除记录后,我们可选择合并同一链上的存储块
散列方法: 注意它的工作过程重点是动态散列方法可扩展散列表线性散列表
散列方法适用于查找给定值的查询其它索引 (B-Tree等)适用于范围查询
传统索引执行范围查询: 访问每一维的100,000个指针需要查看大约500个磁盘块,加上每个B树的一个中间节点,一共1002次磁盘I/O操作
•缺点:结果元组只占全部数据的1/100利用B树对每一维的处理则涉及到20%的数据
限制: •较大的I/O费用 •无法有效支持多维聚集 •排序的限制
一个n-维空间,在每一维上网格线把空间分成条状不同维的网格线的数目可以不同,相邻网格线之间可以有不同间距 网格只包含桶指针
网格文件在存储高维数据时性能不太好随着维数的增加,桶的数目按指数级增长,增加了管理费用,而且会造成大量空桶 数据偏斜时存在划分不均匀问题
如果数据分布较均匀,维数不高,可以选择网格文件来存储多维数据,并且通过选择网格线,使得: 桶足够少,在主存中维护每一维上网格线的索引。一般的桶只有少量的溢出桶
网格文件支持的一些重要查询
具体点的查找:直接找到适当的桶。部分匹配查询:查找桶矩阵的某一行或某一列的所有桶。范围查询:定义网格的一个矩形网络,且在覆盖该区域的桶中找到的全部都是该查询的答案。最邻近查询:给定一个点P,找到距离P最近的点
•分段散列函数对近邻查询及区域查询无用
•分段散列中数据分布比较均匀,而Grid文件不同位可能会有关联,导致对角线上数据很多
•多键索引•kd树•四叉树•R树
多键索引
•索引的索引,或更一般的树 •树中每一层的结点都是一个属性的索引
KD树: 又叫k维搜索树,一个二叉树,它的内部结点有一个相关联的属性a、一个值V以及指向左右子树的指针,左指针指向a值小于V的部分,而右指针指向a值大于等于V的部分。所有维的属性在内结点的层间循环,叶结点是块,其中存放尽可能多的记录
•Kd树的改进,以更适合于辅存,内结点的多分支,在内结点中设置多个键值-指针对
聚集内结点到块,每个结点仍保持两个子结点,将多个内结点压缩到一个块中
例如:将若干层的所有子结点存入一个块中
四叉树: 每个内结点对应于二维空间中的一个正方形区域,或是k维空间的k维立方体
如果一个正方形中点数不比一个块中能存放的数多,那么我们就把这个正方形看作树的叶结点,并且由存放它的点的块表示;如果正方形中还有太多点,以至于一个块存放不下,那么我们就把这个正方形看作内结点,它的子结点对应于它的四个象限。
R树表示由二维或更高维区域组成的数据,我们把它称为数据区
一个R树的内结点对应于某个内部区域,或称“区域”,它不是普通的数据区
原则上,区域可以是任何形状,虽然实际中它经常为矩形或其他简单形状
•空间数据库、地理信息系统和CAD等:R-Tree、R+-Tree和 R*-Tree
•高维数据索引: R*-Tree、X-Tree、SS-Tree和SR-Tree等
•移动对象数据库 (Moving Object Database): 3D R-tree 、HR-Tree和 TPR-Tree 等
•R+-Tree:不允许MBR互相覆盖,需要分裂区域数据对象,使得一个特定的对象可能包含于多个结点之中
•R*-Tree
新的插入优化目标, R*-Tree在选择插入路径时同时考虑矩形的面积、空白区域和重叠的大小,而R-Tree只考虑面积的大小对叶节点和非叶节点采用不同的分裂策略
Forced re-insert 机制的引入,减少了节点分裂的发生,提高了存储空间利用率
位图索引
关系R有n个元组,标号为1, 2, ……, n;字段F的一个位图索引是一个长度为n的位向量的集合;每一个位向量对应于字段F中可能出现的一个值;若第 i 个记录的字段F值为v,则对应于值 v 的位向量在位置 i 取值为1。否则,该向量的位置 i 取值为0
•类散列索引: 网格文件;分段散列函数
•类树索引:多键索引;Kd树;四叉树;R树
•位图索引: 分段长度编码
查询编译过程
•A 查询分析:构造分析树表达查询及其结构
•B 查询重写:将分析树转化为逻辑查询计划
逻辑查询计划通常是查询的代数表达式
逻辑查询计划将会被转化为一个预期所需时间较小的等价的计划
•C 生成物理查询计划:根据B 步骤生成的逻辑查询计划的每一个操作符选择实现算法,并选择操作符的执行顺序物理计划还包括许多细节,如被查询的关系是如何访问的,及关系是否需要排序等B,C部分被称为查询优化器
•类散列索引: 网格文件 分段散列函数
•类树索引: 多键索引 Kd树 四叉树 R树
•位图索引: 分段长度编码
查询编译过程
•A查询分析:构造分析树表达查询及其结构
•B 查询重写:将分析树转化为逻辑查询计划
–逻辑查询计划通常是查询的代数表达式
–逻辑查询计划将会被转化为一个预期所需时间较小的等价的计划
•C 生成物理查询计划:根据B 步骤生成的逻辑查询计划的每一个操作符选择实现算法,并选择操作符的执行顺序–物理计划还包括许多细节,如被查询的关系是如何访问的,及关系是否需要排序等B,C部分被称为查询优化器
•分析树 (Parser Tree)–由Select、From、Where等组成的语法树
•逻辑查询计划树 (Logical Query Plan Tree)
–由基本关系操作符组成的查询树 –包括基本关系操作符,选择、投影、连接等
•物理查询计划树 (Physical Query Plan Tree)–由物理操作符组成的查询树 –物理操作符
•顺序扫描、索引扫描等 •Hash-join、sort-merge-join等
当描述一个关系的大小时,通常,我们关心包含R的所有元组所需的块的数目,这个块的数目表示为B(R) (简记为B),假设关系R是聚簇的,即R存储在B个块或近似B个块中有时候,我们也需要知道R中元组的数目,将这个数目表示为T(R)(简记为T)。T/B表示一个块中能容纳的R的元组数。有时希望参考出现在关系的一个列中的不同值的数目。如果R是一个关系,它的一个属性是a,那么V(R,a)是R中a对应列上不同值的数目。
如果关系R是聚簇的,那么表-扫描操作符的磁盘I/O数目近似为B。
如果关系R不是聚簇的,那么所需的I/O数通常要高得多。极端情况下,表-扫描所需读的块数与R的元组一样多,即I/O代价为T。
–通常,关系R的一个索引需要的块数比B(R)少许多,扫描整个R比查看整个索引需要更多的I/O
•排序-扫描操作符的磁盘I/O数目
–若R是聚簇的,且能够全部装入内存,则排序-扫描的磁盘I/O为B
–若R是聚簇的,但需两阶段排序,则排序-扫描的代价为3B
–若R不是聚簇的,但能够全部装入内存,则排序-扫描的磁盘I/O为T
–若R不是聚簇的,需两阶段排序,则排序-扫描的代价为T+2B
•一趟算法操作符分为以下三类
一次单个元组,一元操作:这类操作不需要一次在内存中装入整个关系,我们一次可以读一个块,使用内存缓冲区,产生输出
整个关系,一元操作:这类操作需要一次从内存中看到所有或者大部分元组,局限于大小约为M(内存可用缓冲区数量)或更小的关系 整个关系,二元操作:其他所有操作归于此类
简单列出见过的所有元组当考虑R中一个新元组时,将它与迄今为止看到的所有元组比较
问题:时间复杂性太高O(n2)
选择2:更好的数据结构,允许增加新元组并且能够辨别一个给定的元组是否存在,缓慢地依赖于元组数量n增长(log2n)具有大量桶的散列表或者某种形式的二叉查找树
假设关系R的每个不同的元组的一个副本可以放入内存,即B(d(R))<=M
自然连接
嵌套循环连接–在各种算法中,两个操作对象中有一个的元组仅读取一次,而另一个操作对象将重复读取。嵌套循环连接可以用于任何大小的关系,没有必要要求一个关系必须能装入内存中
基于元组的嵌套循环连接 •算法需要的磁盘I/O可能多达T(R)T(S) (关系元组随机分布于磁盘的磁盘块),代价较高
•两种改进: –使用R的连接属性上的索引来查找与给定的S元组匹配的R元组,不必读取整个关系R –更加注重R和S的元组在各个块中的分布方式,并且在执行内存循环时,尽可能多的使用内存,减少磁盘I/O数目
基于块的嵌套循环连接算法•结论:外层循环中使用较小的关系略有优势
嵌套循环连接的分析•给定B(R), B(S)和M,假设S是较小的关系
•外循环:B(S)/(M-1)轮 (B(S)次磁盘I/O)
•内循环:每轮B(R)次磁盘I/O
•总I/O数量:B(S) + B(R) * B(S) / (M-1)
•当B(S)和B(R)相对于M较大时,总I/O数量近似值为B(R) * B(S) / M
两阶段多路归并排序 •TPMMS要求
–子表不能超过M-1个 –要求B/M <= M-1,即B <= M(M-1)
–假设R占用B个块,因为每个子表包含M个块,于是子表的数目为B/M
•TPMMS性能分析
–第一趟:读入关系和输出有序子表(2B次磁盘I/O操作)
–第二趟:再次读入子表(B次磁盘I/O操作)
–总的磁盘I/O数:3B (不考虑输出)或4B(考虑输出)
–块64K,1G内存,M=16K–TPMMS要求关系的块 数B不超过 (16K)2=228,即该关系的大小不超过228*216=244=16TB
•当处理更大的关系时,TPMMS的思想可以递归地使用–将关系分成M(M-1)个片段
–使用TPMMS对其中的每一个片断进行排序,并将排序的结果作为第三趟需要的子表
•利用排序消除重复
–第一趟:依次读入R的M个块到内存并排序,产生若干(N)个排序子表,输出到磁盘
–第二趟:分别从每个排序子表读入一个块到内存,执行去重操作
•考察来自每一块的第一个未考虑的元组。并且按排序的次序在它们中间找到第一个元组,例如t,在输出中生成t的一个副本,而且从不同输入块的前端删除t所有的副本。
•如果一个块已经空了,我们就在它的缓冲区中装入同一子表的下一个块,而且如果那个块中有t,我们也要将其删除。
利用排序进行分组和聚集
同d算法一样,这种两趟算法使用3B(R)次磁盘I/O,且 只要B(R)£M2就可以正常工作。
基于排序的并算法代价分析
R和S的每一个元组被两次读进内存,一次是当子表创建时,第二次是作为子表的一部分。元组还写回磁盘一次,作为新建子表的一部分 因此,磁盘I/O的代价是3(B(R)+B(S))。
因为对每一个子表,我们需要一个缓冲区,所以只要两个关系的子表总数不超过M,算法就能工作既然每一个子表长度是M块,那么两个关系的大小不能超过M2,即B(R)+B(S) £ M2。
基于排序的一个简单连接算法•代价分析第一阶段:对R和S排序,需要使用4(B(R) + B(S))次磁盘I/O第二阶段:merge合并需要B(R)+B(S)次磁盘I/O总代价为 5(B(R) + B(S))
两个关系的大小分别不能超过M2,即B(R),B(S) £ M2
一个更加有效的基于排序的连接•代价分析 第一阶段:对R和S排序,需要使用2(B(R)+B(S))次磁盘I/O第二阶段:merge合并需要B(R) + B(S)次磁盘I/O–总代价为3(B(R)+B(S))两个关系的和不能超过M2,即B(R)+B(S) £ M2。
基于散列的消除重复算法:•散列关系R的代价为2B(R),扫描每个桶,去重的代价为B(R),总代价为3B(R) •算法可行的条件:散列R后产生的每个桶的大小不超过M,即B(R)£M(M-1)»M2 基于散列的分组和聚集算法:•散列关系R的代价为2B(R),扫描每个桶,去重的代价为B(R),总代价为3B(R)
基于散列的并、交、差算法 •计算R和S的交或差
–和计算并时类似,一样创建2(M-1)个桶 –在每对对应桶上运用适当的一趟算法
•一共需要3(B(R) + B(S))次磁盘I/O操作
–散列关系:1次磁盘I/O操作 –输出桶到磁盘:1次磁盘I/O操作
–对每个桶执行的一趟算法:1次磁盘I/O操作
基于散列的连接算法•代价分析 –总代价为3(B(R)+B(S))
–为了使算法可行,我们必须能一趟计算(Ri, Si)的连接。Ri、 Si的大小分别约为B(R)/(M-1)和B(S)/(M-1),一趟算法要求较小的操作对象至多占用M-1个块,–因此,散列连接的两趟算法近似地要求: Min(B(R), B(S))£M2
基于索引的选择
•如果R.a上的索引是聚簇的,取回σa=v(R)所需的磁盘I/O数目大约是B(R)/V(R,a)
•实际的数目会高一些,原因如下
–1. 通常,索引并不是完全保存在内存中,需要一些磁盘I/O支持索引的查找
–2. 即使a=v的所有元组都可以装入b个块,那么他们也可能分布到b+1个块
–3. 尽管R关系的元组是聚簇的,它们不可能都尽可能地填满 块
•如果R.a是非聚簇索引,假设取回的每一个元组在不同的块上,而且我们必须访问T(R)/V(R,a个元组 T(R)/V(R,a)是估计的所需要的磁盘I/O的数目
•常用的缓冲-替换策略
•最近最少使用(LRU: Least Recently Used) 丢出最长时间没有读或写过的块
•先进先出(FIFO: First In, First Out) –当需要一个缓冲区时,被同一个块占用时间最长的缓冲区被清空,并用来装入新的块
•时钟算法 –LRU算法的一个普遍实现的、有效的近似 –将缓冲区看作一个排好的环
•B(R),R的所有元组所占的块数 •T(R),R的所有元组数
•V(R, a),R中a对应列上不同值的个数
•查询分析和优化过程
根据语法将SQL查询转化为语法分析树 将语法分析树转化为逻辑查询计划
利用启发式规则和关系代数原理优化逻辑查询计划 计算和估计选择,投影,连接等操作符的执行代价(中间结果大小),将逻辑计划转化为物理查询计划