深入理解数据库系统储存引擎概述1

北京根治手足癣医院 http://m.39.net/baidianfeng/a_8833646.html

数据库管理系统有着众多不同的应用场景:有些用于存储临时热数据,有些用于长期存储数据,有些用于对数据进行复杂查询和分析,有些仅允许通过键访问对应的值,有些经过优化用于存储时间序列数据,有些则能高效地存储了较大的Blob。为了理解数据库管理系统的这些差异,下面我们首先从简单的数据库分类开始介绍。

我们还会简要介绍数据库管理系统的体系结构,讨论构成数据库管理系统的各个组件及其职责;接下来,我们将讨论基于不同存储介质的数据库管理系统的不同之处,即基于内存与基于磁盘的数据库管理系统间的不同之处;最后,讨论列式存储和行式存储数据库管理系统的不同之处。

除了根据数据库系统使用的存储介质和存储方式来分类外,事实上还有许多其它的分类法。例如,数据库管理系统还可以分为以下三大类:

在线事务处理(OLTP)数据库:这类数据库能处理大量来自用户的请求和事务,通常这些查询都是预定义的,能迅速完成的。在线分析处理(OLAP)数据库:这类数据库能处理复杂的数据聚合请求,通常用于分析和数据仓库,并且能够处理长期运行的复杂数据查询。混合事务和分析处理(HTAP)数据库:这类数据库同时具有OLTP和OLAP两类数据库的特性。另外,数据库还可以分为:键值存储库,关系数据库,面向文档的存储库,以及图形数据库等。

第一部分,我们将专注于讨论数据库的存储和索引结构,主要讨论一些高层数据组织方法以及数据与索引文件之间的关系。最后,在“缓存,不变性和排序”这一小节中,我们将讨论三种广泛用于开发高效存储结构的技术,以及这些技术是如何影响数据库系统的设计和实现。

数据库管理系统体系架构

数据库管理系统的设计并没有统一的设计蓝图,每个数据库的构建都有或多或少的不同,并且每个组件边界也没有清晰的界定。即使我们能在项目文档中找到这些组件的边界,但在实际代码中看似独立的组件也可能由于性能优化,边缘情况的处理或体系结构原因而被耦合。

图1-1:DBMSArchitecture

图1-1是数据库的常见架构图。通常数据库管理系统使用客户端/服务器模型,数据库系统担当服务器的角色,应用程序担当客户端的角色。客户端的请求,通常以某种查询语言的形式提交,通过传输子系统发送给服务器处理。传输子系统除了完成客户端和服务器之间的通信外,还负责完成数据库集群中节点之间的通信。

服务器端接收到请求后,传输子系统将查询请求传递给查询处理器,查询处理器解析、解释并验证请求,然后执行访问控制检查。解析后的查询请求被传递给查询优化器,查询优化器首先消除查询中不会执行和冗余的部分,然后基于内部统计信息(索引基数,近似的交集大小)和数据存放位置(集群中数据存放的节点,以及节点传输的代价)找到最优的执行方式。优化器以依赖树的形式呈现查询需要的多个相关操作,然后优化并选择合适的访问方法。

查询通常以执行计划(或查询计划)的形式呈现:由一系列必须执行的操作构成。由于对于同一查询存在不同的执行计划,优化器会决定和选择最佳的执行计划。

执行计划由执行引擎处理,并收集本地和远程操作的执行结果。远程执行会涉及向集群中的其他节点读写数据,以及数据复制。

本地查询(直接来自客户机或其他节点)由存储引擎来执行。存储引擎包含了如下组件:

事务管理器(TransactionManager):事务管理器调度事务执行,并确保数据库始终处于逻辑一致的状态。锁管理器(Lockmanager):锁管理器锁定正在执行事务的数据库对象,确保并发操作不会破坏物理数据的完整性。存取方法(存储结构):负责管理磁盘上的数据访问和组织。访问方法包括堆文件,B树或LSM树等形式的存储结构。缓冲区管理器:缓冲区管理器负责内存中缓存数据页的管理。恢复管理器:恢复管理器维护操作日志并在出现故障时恢复系统数据。事务管理器和锁管理器共同负责并发控制:它们保证逻辑和物理数据的完整性,同时确保尽可能高效地执行并发操作。

基于内存与磁盘的数据库管理系统

内存数据库管理系统的数据主要存储在内存中,磁盘被用来做数据恢复和日志记录。基于磁盘的数据库管理系统将大部分数据存储在磁盘上,内存被用来缓存磁盘数据或存储临时数据。这两种类型的数据管理系统都会在一定程度上使用磁盘纯粹数据,但是基于内存的数据库管理系统几乎所有内容都存储在内存中。

内存访问的速度要比磁盘访问快几个数量级,因此将内存作为主要存储介质仍然有很大的优势。随着内存价格的降低,在经济上这样做的可行性越来越高。但是内存RAM的价格仍然要比持久存储设备(如SSD和HDD硬盘)高很多。

基于内存的数据库系统与基于磁盘的数据库系统不仅仅只是存储介质的不同,存储介质的不同也导致了它们在数据结构,数据组织,使用的优化技术方面的不同。由于基于内存的数据库系统是用内存作为主要储存介质,内存具有较高度性能,较低的访问成本和有更细的访问粒度,和基于磁盘的数据库系统相比更易于开发。操作系统抽象了内存管理,允许我们分配和释放任意大小的内存块。而以磁盘为存储介质,我们必须要处理数据引用,数据的序列化,缓存的管理,已经碎片管理。

内存RAM的易失性和成本是制约基于内存的数据库系统增长的主要因数,软件错误、崩溃、硬件故障和断电都可能导致数据丢失。虽然有很多方法防止数据的丢失,如不间断电源和带电源的RAM,但是这些都需要额外的硬件和运维支持。在实际中,磁盘还是更易于维护,在价格上也更有优势。随着非易失性内存(NVM)技术的发展,未来这以情况可能会有所改变。

基于内存存储的持久性

基于内存的数据库系统在磁盘上维护备份以提供持久性,防止易失性数据的丢失。也有一些数据库系统只在内存中存储数据,不保证数据的持久性,但这不在我们的讨论范围之内。

在基于内存的数据库系统中,任何数据修改操作被认定为完成之前,系统都必须将其结果写入到一个连续的日志文件。另外在数据库系统启动或崩溃恢复时,为了避免重新执行整个日志文件的操作,系统会在磁盘上维护了一个数据备份。磁盘备份数据的修改操作通常以异步的方式进行(相对于客户端请求),并通过使用批量操作来减少I/O的数量。通过这种方式,数据库内存中的数据便可以从备份和日志中进行恢复。

日志记录通常批量应用到备份上。当日志记录被应用后,备份便保存了在某个时间点上数据库数据的快照,在这之前的日志记录就可以删除了。这个过程被称为创建检查点。它通过保持几乎最新数据备份和一些日志记录的方式来减少数据恢复的时间,同时也备份的更新也不会阻塞客户端请求。

注意:即使是基于磁盘的数据库使用大量的页面缓存,它和基于内存的数据库还是有着本质的区别。即使是这些页面缓存在内存中,基于磁盘的数据库在数据的序列化,数据在磁盘上的不同布局也会带来额外的性能开销,无法做到和基于内存数据库同等程度的优化。

基于磁盘的数据库会使用专门的存储结构,这些存储结构利于磁盘数据的快速定位访问。而在内存中指针可以比较快地跳转,并且内存访问要比磁盘访问快很多。因此,基于磁盘数据库的存储结构通常会选择一种具有更大宽度和更小高度的树(后面“基于磁盘的存储的树”部分会具体讨论);而基于内存数据库在数据结构上具有更大的选择空间,并实现一些在磁盘上无法实现的优化。另外,在磁盘上存储大小可变的数据也需要特别注意,而在内存中通常只需要使用指针指向所引用的数据即可。

对于一些使用场景而言,假设整个数据集常驻内存是可行的。这些数据集受到现实世界的制约,在数据量和大小上都是有限的。例如,学校学生的记录,公司客户的记录或者小型商店的库存等。

行式存储和列式存储的数据管理系统

大多数数据库系统都是用于存储一组数据记录,这些数据记录保存为由行和列构成的表中。通常同一列具有相同的数据类型,称为字段。例如,我们定义一个包含用户记录的表,所有的名称都具有相同的类型,并且属于同一列。逻辑上属于同一记录(通常由键标识)的值的集合则构成一行。

另一种数据库分类的方法是按照数据在磁盘上的存储方式:行式储和列式存储。数据库的表可以进行水平分割存储(属于同一行的值存储在一起)和垂直分割存储(属于同一列的值存储在一起)。图1-2展示了这两种存储的区别:(a)列存储,(b)行存储。

图1-2:列式存储和行式存储的数据布局j

MySQL,PostgreSQL和大多数传统的关系数据库都是使用行存储;开源数据库MonetDB和C-Store则是使用列存储。

行式(Row-Oriented)存储数据库的数据布局

面向行的数据库管理系统将数据存储在记录或行中。它们的布局非常接近于表格数据表示,其中每一行都有相同的字段。例如,面向行的数据库可以有效地存储用户条目,保存姓名、出生日期和

这种存储方式适用于记录由多个字段组成(姓名、出生日期和电话号码),记录可由键唯一标识的情况(ID是一个单调递增的数字)。一条记录中的所有字段值代表单个用户的信息,记录的所有字段常常需要一起读取;在创建记录时(例如,当用户填写注册表单时),用户信息也将被保存一起。另外,记录的每个字段都可以单独修改。

在需要按行访问数据的场景下,面向行存储的优势是非常明显的。整个行的数据存储在一起可以利用空间局部性提高系统的性能。因为持久介质(如磁盘)上的数据通常按块进行访问(即磁盘访问的最小单位是块),单个块上就包含了记录所有列的数据。虽然这对于访问整个用户记录的情况来说有优势,但是这使访问多个用户记录的单个字段(例如,电话号码)的查询更加昂贵,因为其他字段的数据也会被读取进来。

列式(Column-Oriented)存储数据库的数据布局

面向列的数据库管理系统垂直地(按列)划分数据,相同列的值存储在磁盘上是连续的位置。将同一列的数据值存储在一起(不同列的值存储在单独的文件或文件段中,这使得可以高效的执行按列的查询。因为可以一次性读取某列的所有值,而不是读取整个行并丢弃未查询列的数据。列式存非常适合需要做聚合运算的分析类工作,例如查找趋势、计算平均值等。

例如,在逻辑上,股票市场报价数据仍然表示为一张表:

然而,列式存储数据库数据在物理布局上和逻辑上完全不一样,属于相同列的数据被保存在相同的文件位置上:

Symbol:1:DOW;2:DOW;3:SP;4:SP

Date:1:08Aug;2:09Aug;3:08Aug;4:09Aug

Price:1:24,.65;2:24,.16;3:2,.45;4:2,.32

为了重建能用于连接、过滤和多行聚合数据元组,每列上需要保存一些用于关联其它列中字段值的元数据。一种显示的做法是每个值都保存一个键值,但是这会导致数据的重复,并增加存储量;因此,一些列式存储使用隐式标识符(虚拟ID),使用值的位置(偏移量)映射回对应的值。

最近这些年,由于对于在不断增大的数据集上进行复杂分析查询需求的增长,涌现出越来越多的列式存储文件格式,例如ApacheParquet,ApacheORC,RCFile,以及列式存储ApacheKudu,ClickHouse等。

差异和优化

行式存储和列式存储的区别不仅仅只是在数据存储方式上。选择数据存储方式仅仅是实施一些可行优化中的一步。

一次读取同一列的多个数据值可以显著提高缓存利用率和计算效率。利用现代CPU上的矢量化指令,单个CPU指令就能完成多个数据的处理;另外,相同类型的值存储在一起,更有利于数据的压缩。可以针对不同的数据类型选择最有效的压缩算法。

至于是选择使用列式存储还是行式存储,这取决于对数据的访问方式。如果数据需要按照记录的形式读取(即读取大部分或所有列),并且大部分时候需要读取某条几率或某个范围内的记录,行式存储可能会达到更好的性能;如果查询跨越多行,或需要根据某些列进行聚合运算,列式存储是一个值得考虑的方案。




转载请注明:http://www.xcqg58.com/lsqy/lsqy/26847127.html

  • 上一篇文章:
  •   
  • 下一篇文章: 没有了