7 数据库的存储结构
让我们重新温习第六讲的所学知识。
这个图实际上我们要关注的是蓝色箭头指向的那一层。在这一层之上,我们都是对SQL语句动手,而在这一层之下,我们是转为对文件系统动手。
记得我们在第一讲学过的数据库三层结构吗?
从这个图上看,实际上上面这个图是最上面那张图的抽象。而物理层指的就是数据库文件在操作系统中的存储方式。
我们曾经在数据结构中学过,文件的结构有很多种,比如堆文件、哈希文件、倒排文件。那我们在操作系统中要用什么数据结构存储数据库文件呢?
事实上,在实际开发中,没有一种数据结构能够包治百病。所以选择合适的数据结构来存储数据库文件可以大幅提高查询的效率。在下面的小节中,我们会提到相关知识。
7.1 数据库访问管理-文件组织
经过开头的叙述,相信大多数同学都对本讲有一个大概的把握。我们可以把上述的知识点归结为下面两点:
- 传统的数据模型都以记录为基础。记录的集合构成文件。
- 文件须按照一定的结构组织和存储记录,访问有关的记录须经由一定的存取路径。对数据库的操作最终落实到对文件的操作。
我们首先要先思考一个问题,数据库系统在对底层文件进行访问的时候,有哪些访问类型。
查询文件的全部或相当多的记录
一般把访问15%以上记录的查询都归入这一类。
因为根据统计学规律,如果有15%以上的记录在硬盘上的话,那么我们我们可以理解为这15%几乎遍布所有的物理块,我们必须找 出全部的物理块才能找到这15%的记录。
查询某一特定记录
一般把访问某一条特定元组归为这一类。
查询某些记录
一般把访问15%以下记录的查询都归入这一类
范围查询
范围查询查询出来的元组有时候多有时候少,这取决于你指定的范围。如你要查询学生表里年龄在15到25之间的学生,这时候可能会出现多种情况:查询结果没有该年龄段的学生;查询结果只有一个学生;查询结果有很多学生;所有学生都处于该年龄段。
记录更新
比起查询操作,记录的更新要复杂的多,因为在更新的时候会引起记录位置的调整和相应的修改(比如索引就要改)。
缓冲池和物理块在市面上,磁盘中的数据划分为大小相等的物理块,每个物理块之间要留有间隙,间隙通常放的是初始化信息和下一个物理块的地址信息。
实际上,我们读取磁盘的时候,并不是像1kb1kb那样的字节去读取的,我们是一个物理块一个物理块去读的。所以,我们通常把磁盘叫做块设备。像linux来说,一般一个物理块是1024个字节。现在假如我们有一个关系想要放入文件当中,此时关系中一个元组占一百个字节,那么我一个物理块能放10个元组左右,那么到时候我们读硬盘的时候,就是一块一块读的。比如我们查询的时候就是一块一块找,看这一块有无我们需要的东西。
而对于缓冲池来说,拿MySQL举例,其使用的是InnoDB存储引擎,其以页为单位来管理存储空间。而磁盘Ⅳ/o需要消耗的时间很多,而在内存中进行操作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS会申请占用内存来作为数据缓冲池,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。
这样做的好处是可以让磁盘活动最小化,从而减少与磁盘直接进行工/0 的时间。要知道,这种策略对提升sQL语句的查询性能来说至关重要。如果索引的数据在缓冲池里,那么访问的成本就会降低很多。
虽然数据库基于操作系统之上,其所有数据库文件最终落实到操作系统之中。但是这并不代表他就要使用操作系统的文件管理系统,这是有很多原因的,下面我们列举一下:
- DBMS为了实现其功能,须在文件目录、文件描述块、物理块等部分附加一些信息,而传统的文件系统是不提供这些信息的。
- 传统的文件系统主要面向批处理,而在数据库系统中,常常要求即席访问、动态修改。这就要求文件结构能够适应数据的动态变化,提供快速访问路径。
- 传统的文件基本上是为某一用户或者某类用户服务的,用途比较单一,共享的程度较低;而数据库中文件是供所有数据库用户共享的,甚至有些用途是不可预知的。这就要求数据库的文件结构兼顾多方面的要求,提供多种访问路径。
- 如果采用操作系统的文件管理系统作为DBMS的物理层的实现基础,则DBMS对操作系统的依赖不大,不利于DBMS的移植。
- 传统的文件一旦建立,数据量是比较稳定的;而数据库中文件的数据量变化较大,数据库中的文件结构应能适应这样的变化。
为了改变传统操作系统中的文件管理系统的不足,DBMS提供了多种文件结构。
堆文件
在数据结构中,只要涉及到用new(这里是采用C++说法)开辟内存空间存数据的,都是堆文件。
堆文件就类似堆东西一样,不停的往尾巴放东西,不停的在尾巴后插入数据。但是堆文件不一定是顺序结构,可以是链表结构。在查找的时候我们要通过顺序扫描
,即从头到尾找,而且最坏的情况是你找到最后才找到。
很明显,堆文件对于查询文件的全部或相当多的记录有相当好的效果。
直接文件(哈希文件)
直接文件中,将记录的某一属性用哈希函数(在数据库里面我们以后叫做散列函数)直接映射成记录的地址,被散列的属性称为散列键。
说成大白话就是,你看书看到某一页,你喜欢在这一页加入书签;而哈希文件的原理就是,将你频繁使用那几条元组加上“标记”,然后将这些标记放入散列柜(类似于商城的存储柜)。当你需要查找柜中的元组,你只需要提供对应的标记即可查找到,而无需去”原数据“中顺序扫描浪费时间。
索引文件(堆文件配合B+树索引)
如果我们是要查找某一条元组,为了找出这条元组去堆文件里做顺序扫描来查找,显然效率不高,所以我们一般是在主键上建一个B+树索引,当我们要找某个值的时候,根据叶节点(相当于路标)去查找这个值所在的叶节点。
如果我们要做范围查询,利用B+树索引叶节点上面的链表也可以做双向索引。
索引可以建立多个,甚至是建立复合索引都可以。但是索引并不是建立得越多越好,我们是在建立索引的代价和效率两者之间折中的。
需要知道的是,最经典的MySQL就是采用B+树索引技术。
7.2 数据库访问管理——索引技术
在前面我们曾经挖了一个坑——磁盘的结构。实际上,如果你不是跨专业考研,你都应该学过计算机组成原理,这实际上是计组的知识。
说明:首先,左边有很多盘片,右边是磁头臂,下面有个两个马达,盘片马达驱动磁盘旋转,磁头臂的马达可以驱动磁头臂移动,方便读取磁盘片上的数据。
然而如果你用操作系统中的文件系统存储的话,文件在磁盘上的地址是不确定的(这里指的不是路径,是内存地址),如果是利用FAT
(微软发明的文件配置表)和NTFS
去寻找文件位置,他会依靠之前存取的时候存储的碎片化位置去寻找,这样就会造成寻找文件的效率变低,如果你想把文件存在同一个磁盘片中,而且物理地址连续,那么操作系统是做不到的。
在磁盘中,有个固定操作叫寻道
,他的过程就是将磁力壁定在磁盘片上的某条轨道(磁道)切割磁力线,从而寻找文件的地址。在这个过程中,通过移动磁头臂来寻道。根据摩尔定律,集成电路上可以容纳的晶体管数目在大约每经过18个月便会增加一倍。换言之,处理器的性能每隔两年翻一倍。可对于寻道来说,这是一个机械移动,对于应用在电子设备的摩尔定律显然不适用。也就是说,我们无法通过对硬件的升级来解决效率的问题。所以如果想提高效率问题,一定要把文件的内容根据物理地址按顺序排列好,减少磁头的移动。
每个盘片的盘面被划分成多个狭窄的同心圆环,数据就存储在这样的同心圆环上面,我们将这样的圆环称为磁道 (Track)
。每个盘面可以划分多个磁道,最外圈的磁道是0号磁道,向圆心增长依次为1磁道、2磁道…磁盘的数据存放就是从最外圈开始的。
根据硬盘的规格不同,磁道数可以从几百到成千上万不等。每个磁道可以存储数 Kb 的数据,但是计算机不必要每次都读写这么多数据。因此,再把每个磁道划分为若干个弧段,每个弧段就是一个扇区 (Sector)。扇区是硬盘上存储的物理单位,现在每个扇区可存储 512 字节数据已经成了业界的约定。也就是说,即使计算机只需要某一个字节的数据,但是也得把这个 512 个字节的数据全部读入内存,再选择所需要的那个字节。
磁盘的存取实际由三部分构成:寻道时间
、旋转延迟
和传输时间
。磁盘通过磁头臂的机械移动来寻道,寻道时间要比其他两部分时间大得多,所以为了有效地访问磁盘,我们应该尽可能地一次I/O连续访问较多的数据,减少I/O次数,从而减少寻道和选择延迟的次数。
在早期的DBMS中,常由操作系统来分配数据库所需的物理块,逻辑上相邻的物理块分散到磁盘的不同区域。在连续访问数据库中的数据时,性能严重下降,所以在现代DBMS中,都改由DBMS在初始化时向操作系统一次性申请所需的磁盘空间,让DBMS去分配这些物理块。DBMS主要分配方式主要有以下四种:
- 连续分配法:有点像顺序表,即物理位置上是连续的。
- 链接分配法:有点像链表,也就是逻辑位置上连续。
- 簇集分配法:物理连续逻辑也连续。
- 索引分配法:每个文件都有一个逻辑块号与其物理块地址对照的索引,通过索引,你可以找到文件中任何一块的地址。
在数据库设计中,如果一直频繁地用某个属性来检索某一条元组的话,那我们就可以根据这个属性把对应的元组用簇集的方式去建立,这样的话查找的时候效率非常快。但是,我们建簇集这个属性一定要很少更新,否则维护簇集的代价会很大。