做者: 实验室小陈 / 大数据开放实验室算法
8月26日,星环邀请来自华东师范大学软件工程学院的博士生导师宫学庆教授带来《数据库前沿技术系列讲座》,分享数据库业内前沿发展和研究热点。现将宫学庆教授的培训第一讲内容:内存数据库的技术发展分享给你们。数据库
— 基于磁盘的数据库管理系统 —编程
传统的数据库管理系统(DBMS)一般是采用基于磁盘的设计,缘由在于早期数据库管理系统设计时受到了硬件资源如单CPU、单核、可用内存小等条件的限制,把整个数据库放到内存里是不现实的,只能放在磁盘上。因为磁盘是一个很是慢的存储设备(相对于CPU的速度),所以学术界和工业界发展出的数据库管理系统在架构上都必须适应当时的硬件条件,沿用至今的Oracle和MySQL等数据库管理系统仍然采用的是这种架构设计。服务器
伴随着技术的发展,内存已经愈来愈便宜,容量也愈来愈大。单台计算机的内存能够配置到几百GB甚至TB级别。对于一个数据库应用来讲,这样的内存配置已经足够将全部的业务数据加载到内存中进行使用。虽然大数据处理的数据量多是PB级别的,但那些数据通常是非结构化的数据。一般来说,结构化数据的规模并不会特别大,例如一个银行10年到20年的交易数据加在一块儿可能只有几十TB。这样规模的结构化数据若是放在基于磁盘的DBMS中,在面对大规模SQL查询和交易处理时,受限于磁盘的I/O性能,不少时候数据库系统会成为整个应用系统的性能瓶颈。数据结构
若是咱们为数据库服务器配置足够大的内存,是否能够仍然采用原来的架构,经过把全部的结构化数据加载到内存缓冲区中,就能够解决数据库系统的性能问题呢?这种方式虽然可以在必定程度上提升数据库系统的性能,但在日志机制和更新数据落盘等方面仍然受限于磁盘的读写速度,远没有发挥出大内存系统的优点。内存数据库管理系统和传统基于磁盘的数据库管理系统在架构设计和内存使用方式上仍是有着明显的区别。多线程
— 缓冲区管理方式 —架构
在传统的数据库管理系统中,数据的主存储介质是磁盘。例如,逻辑上的一张表一般会被映射到磁盘上的一个文件,文件是以数据块(Data Block,也称做Page)的形式存储在磁盘上。对于结构化数据来讲,一条记录会被保存在磁盘上的某个数据块中,能够用数据块ID和Offset/偏移量来表示该条记录的具体位置。这种形式的数据块也被称做 Slotted Page,顾名思义是把数据块划分红不少槽位,而后一个Record放在某一个槽位上。在对某条记录进行处理时,能够经过表明该记录地址的Page ID + Offset从磁盘上获取该记录;随后系统会把存储有该条记录的数据块从磁盘读到缓冲区(Buffer Pool分为多个Frame,每一个Frame能够保存一个磁盘块),再从缓冲区将该条记录读到线程或事务的工做区进行处理;处理结束后将更新的记录写回缓冲区中的数据块,再由数据库管理系统将修改过的数据块写回到磁盘上。并发
基于磁盘的数据库管理系统中的数据访问示例性能
在基于磁盘的数据库管理系统中,处理查询时一般会把整个索引加载到内存,而B+树索引中一个索引节点的大小一般是一个数据块。每一个被索引的key值在索引叶子节点中都有对应的索引项,索引项中包含该key值所对应记录的存储位置(Page ID + Offset);当一个数据块被加载到内存中的缓冲区时,DBMS经过Page Table结构来维护Page ID + Offset的地址与内存缓冲区地址的转换。在访问数据时,先在Page Table中查找是否存在对应的Page ID + Offset,若是没有则说明这条记录仍然在磁盘上,须要先把磁盘上数据块的读进缓冲区,而后再在Page Table中维护好地址映射关系。具体的实现过程是,DBMS首先会在缓冲区中寻找可用的Frame,若是没有就根据缓冲区替换算法选取脏页(Dirty Page)替换出去;假如选中了某个脏页进行替换,则须要对该位置加Latch锁来保证在替换过程当中该位置不会被其余事务访问(Latch后面会介绍)。在脏页写回磁盘后,系统就能够把目标数据块读入到缓冲区中的该位置,再将其在缓冲区中的地址写到Page Table,维护好地址映射关系;在这些操做完成后再将Frame上的Latch锁释放。大数据
传统DBMS中的内存地址映射
对于传统基于磁盘的DBMS而言,即便内存缓冲区足够大,能够将全部数据加载到内存中,但访问数据过程当中的地址映射和转换依然存在,只是省掉了将数据块从磁盘加载到内存的开销。即便数据已经所有被加载到内存,基于磁盘的DBMS性能上与内存数据库相比仍是有很大差距,这是其中一个重要的缘由。
总结来看,基于磁盘的DBMS和内存数据库在实现技术上一个重要区别是:在访问数据时,基于磁盘的DBMS须要经过地址映射将数据在磁盘上的地址转换成在内存中地址,而内存数据库在设计上则是直接使用数据在内存中的地址。
— 事务ACID属性保证 —
在数据库管理系统中,须要保证并发访问场景下事务的ACID属性,即事务的原子性、一致性、隔离性和持久性。事务的ACID属性主要靠数据库管理系统中的两个机制实现,一个是并发控制,另外一个是Logging/Recovery机制。
-
并发控制
传统基于磁盘的DBMS大部分是采用基于锁(Lock)的悲观并发控制,即事务在访问数据时先加锁,用完后再进行解锁,其余事务在访问数据时若是存在冲突则须要等待拥有锁的事务释放锁。传统DBMS通常会在内存中维护一个单独数据结构——Lock Table来存放全部的锁,由Lock Manager模块进行统一管理,这样在内存中锁和缓冲区中的数据是分开存放和管理的。事务在访问数据时先向Lock Manager申请数据所对应的锁,而后再访问数据;执行结束后经过Lock Manager把锁释放,Lock Manager可以保证全部事务申请和释放锁都是遵循严格的两阶段封锁协议(strict 2 phase locking protocol)。同时,并发控制机制所带来的开销与用户的实际业务处理没有直接关系,是用于保证事务一致性和隔离性的额外开销。
内存数据库在访问数据时也须要加锁,但和基于磁盘的DBMS不一样,锁和数据在内存中是存放在一块儿的,一般是将锁信息保存在数据记录Header中。为何基于磁盘的DBMS要单独将锁信息放在Lock Table中,而内存数据库就能够把锁信息和数据存放在一块儿呢?由于在基于磁盘的DBMS中,数据块是有可能被系统从内存缓冲区中替换到磁盘上,若是锁信息和数据放在一块儿,一旦数据块被替换出去,Lock Manager和全部事务都没法得到关于数据的锁信息。因此说对于传统基于磁盘的DBMS来说,锁要单独维护在内存中,且须要始终保持在内存中,不能被替换出去。而对于内存数据库来讲,不存在这样的场景。
实际上,数据库管理系统中有两种锁机制,分别被称为Lock和Latch,目的都是为了保护数据的一致性不被并发访问所破坏。Lock机制是对数据库逻辑内容的保护,通常来讲拥有持续时间长,一般是事务执行的整个过程;而且Lock机制要支持事务的回滚以撤销事务对数据修改。而Latch机制是为了保证内存中特定的数据结构不会由于并发访问而致使错误,好比在多线程编程时有一个共享队列发生插入、删除等操做时,须要Latch保证操做过程当中的队列不受其余线程的干扰。Latch的保持时长与操做有关,本次操做作完就结束,同时也不须要支持对数据修改的回滚。
因此传统DBMS若是要对缓冲区中的一个Page作操做则须要加Latch;若是是修改数据库的内容则须要加Lock,单独放在Lock Table维护和管理。下图是对Lock和Latch的一个简单对比。
Lock和Latch特征对比
-
Logging 和 Recovery
数据库管理系统中,Logging和Recovery机制是日志来保证事务的原子性和持久性的方式。原子性意味着一个事务中的全部操做必须同时成功或者撤销,在执行一半作不下去时,能够按照日志进行回滚;持久性意味着数据若是丢失,能够根据日志来进行恢复。
在传统DBMS的Logging和Recovery中,最重要的概念是WAL(Write-Ahead Log)——预写式日志。WAL是指系统中全部更新操做都有对应的日志,而在日志没有落盘前,对数据的修改不容许落盘。系统中每条日志都有一个LSN号(Log Sequence Number),全部的LSN号单调递增,日志落盘的过程是向磁盘的连续写(顺序写)。但若是系统严格按照一条日志对应一条操做,日志落盘后立刻将操做对数据的更新结果落盘,那么系统性能会受到很大影响。因此,大多数的DBMS会采用Steal + No Force的缓冲区管理策略。Steal是指DBMS能够将未提交事务的更新刷到磁盘,没必要等事务提交时再把更新刷到磁盘,提升了系统刷盘的灵活性和性能;若是在事务未提交时发生crash,因为更新可能已经写到磁盘,这时就须要经过对日志的undo操做进行回滚。No Force是指在事务已经提交后,对数据的更新能够依然存放在内存缓冲区中不写入磁盘,在合并其余事务的更新后再一次性写入磁盘,为系统提供优化空间。但No Force可能带来的风险是:若是事务已经成功提交但更新没有写到磁盘,此时出现crash,则仍然在内存中的数据更新就会丢失,须要根据已经写到磁盘的日志(事务成功提交的前提是其全部日志都必须已经落盘)进行redo操做。
有了WAL和Steal + No Force机制后,就能够给基于磁盘的DBMS提供最大的灵活性,来优化磁盘I/O。但对于内存数据库而言,全部的数据放在内存里,是否还须要这个机制呢?能够明确的一点是,内存数据库仍是须要Logging的,但和基于磁盘的DBMS有所区别,在日志中只记载redo操做所需的信息,不记载undo所需的信息。你们能够想一下这是为何?另外一方面,内存数据库在Logging过程当中不记录关于索引的更新,只记录对于基础表的更新,那Logging过程当中所需写盘的内容就少了不少。而在内存数据库出现故障须要恢复时,首先从磁盘上保存的检查点(Check Point)数据和日志中恢复基础表,而后在内存中从新构造索引。
— 面向磁盘的DBMS性能开销 —
2008年,SIGMOD的一篇论文对面向磁盘的数据库性能开销作了分析,把整个数据库系统的开销作了划分。分析发现:假设一次业务处理的总开销是100%,实际上只有7%不到的资源是在真正处理业务逻辑;34%用于缓冲区管理如缓冲区的加载替换、地址转化等;14%处理Latching;16%处理Locking;而后12%处理Logging;最后16%用于对B树索引的处理。也就是说,机器资源跑满负荷之后,真正用于处理业务逻辑的只有7%。
磁盘数据库系统性能开销
那么是否能够将开销大的部分去掉,来提升业务逻辑的资源占比呢?若是数据库是单用户的,没有并发竞争冲突,那么能够省去Locking和Latching等方面的开销。历史上也有一些单线程的解决方案,例如将数据库分红多个Partition,每一个Partition由一个线程处理等。但这样的方案具备明显缺点:每一个Partition是串行处理,假若有一个长的事务在执行,串行处理将致使后续事务所有被阻塞,直到该事务结束。并且面向磁盘的系统在进行大规模事务处理时瓶颈是磁盘I/O,若是单线程执行,在从磁盘读取数据时CPU将处于空闲状态。但对于内存数据库来讲,全部数据存储在内存,磁盘I/O不是系统主要瓶颈,所以使用的技术与以前有了很大的差异。固然技术在发展过程当中也经历了各类各样的尝试,某些技术的发展不适合于现实背景,慢慢就被人忘记了。
能够看到,基于磁盘的数据库管理系统作了不少额外的管理工做,这些工做虽然不处理业务逻辑,但在保证业务逻辑正确性上不可或缺。对于内存数据库而言,面临的问题是应该作哪些优化来获得最优的性能。和基于磁盘的系统相比,内存数据库主存储是内存,但依然须要磁盘来作Check Point和Logging,故障时要靠磁盘上的检查点数据和日志来恢复整个内存数据库。
— 内存数据库技术历史发展 —
内存数据库的发展大体能够分红三个阶段:1984年到1994年的10年;1994年到2005年的10年;2005年之后到如今。第一个阶段出现了内存相关的处理技术;第二阶段出现了一些内存数据库系统;第三个阶段就是咱们如今面临的场景。
-
1984年 - 1994年
在1984年到1994年间,学术界针对内存数据管理提出了不少假设,好比内存缓冲区能够放进所有数据,能够采用组提交和快速提交优化技术等。同时也提出了面向内存的数据访问方法,再也不像基于磁盘的DBMS同样采用Page ID + Offset方式进行访问,而是在全部数据结构中都直接采用内存地址。还有面向内存的T-tree索引结构以及对系统按功能分红多个处理引擎,有的专门作事务处理,有的专门作恢复,至关于有两个核,一个专门负责事务处理,另外一个负责日志处理。此外还有和Partition相关的主存数据库,把数据库分红不少个Partition,每一个Partition对应一个核(或节点),进程间没有竞争。能够看到,这个期间的数据库技术发展已经在考虑若是数据所有放在内存,能够采用哪些技术。但受限于当时的硬件条件,这些技术并无获得大规模应用。
-
1994年 - 2005年
1994年到2005年间出现了一些商业内存数据库系统,好比贝尔实验室研发的Dali、Oracle Times Ten的前身Smallbase等。同时,也出现了一些面向多核的优化系统如P*-Time(如今是SAP-HANA事务处理引擎)。当时也有一些Lock-free的实现技术被应用于内存数据库系统,即无锁的编程技术和数据结构。
-
前两阶段小结
前两个阶段的技术大体能够分红这样几类:
一、解决Buffer Pool的In-Direction访问:把间接访问替换掉,换成直接的内存地址访问;索引的叶子节点再也不放Page ID 和Offset,而直接是内存地址。
二、Data Partition:切分数据,不作并发访问控制的一类技术。
三、Lock-free和Cache-Conscious:相较于面向磁盘的数据库管理系统把一个索引节点存储在一个数据块中,内存数据库中一个索引节点是一个或几个Cache Line的长度。
四、粗粒度的锁:一次锁一张表或一个Partition,而不是一条记录,但这种技术如今使用较少,由于多核场景访问竞争激烈,粗粒度锁可能致使并发程度下降。(目前使用较少)
五、Functional Partition:把系统按照功能进行切分,每个线程负责特定的功能等。(目前使用较少)
DBMS历史技术总结
— 数据库系统的现代化发展 —
在如今的环境中,硬件条件基本有三个特色:1. 内存大而便宜;2. 多核CPU(从主频提高转变到内核数的提高);3. Multi-Socket即多核多CPU,意味着处理的并发程度能够愈来愈高。这些都是数据库系统研发在当下所面临的状况。
现代硬件环境
对于内存数据库而言,CPU和磁盘I/O再也不是主要瓶颈,所以优化技术目前主要从如下角度来考虑:
-
去掉传统的缓冲区机制:传统的缓冲区机制在内存数据库中并不适用,锁和数据不须要再分两个地方存储,但仍然须要并发控制,须要采用与传统基于锁的悲观并发控制不一样的并发控制策略。
-
尽可能减小运行时开销:磁盘I/O再也不是瓶颈,新的瓶颈在于计算性能和功能调用等方面,须要提升运行时性能。
-
采用编译执行方式:传统数据库多采用火山模型执行引擎,每个Operator都被实现为一个迭代器,提供三个接口:Initial、Get-Next、Closed,从上往下依次调用。这种执行引擎的调用开销在基于磁盘的数据库管理系统中不占主要比重(磁盘I/O是最主要瓶颈),但在内存数据库里可能会构成瓶颈。假设要读取100万条记录,就须要调用100万次,性能会变得难以忍受,这就是内存数据库中大量采用编译执行方式的缘由。直接调用编译后的机器代码,再也不须要运行时的解释和指针调用,性能会有效提高。
-
可扩展的高性能索引构建:虽然内存数据库不从磁盘读数据,但日志依然要写进磁盘,须要考虑日志写速度跟不上的问题。能够减小写日志的内容,例如把undo信息去掉,只写redo信息;只写数据但不写索引更新。若是数据库系统崩溃,从磁盘上加载数据后,能够采用并发的方式从新创建索引。只要基础表在,索引就能够重建,在内存中重建索引的速度也比较快。
-
— 本文小结 —
本篇主要介绍了基于磁盘的数据库管理系统与内存数据库管理系统在几个实现方面存在的主要异同,以及内存数据库从1984年开始到如今的技术发展。后面会继续分享关于内存数据库技术的发展,从数据组织、索引、并发控制、编译查询和持久化角度出发,介绍并对比几款主流内存数据库产品的实现技术。
注:本文部分材料来自于:
1. VLDB 2016会议上的现代主存数据库系统教程(Modern Main-Memory Database Systems Tutorial)
2. CMU(卡耐基梅隆大学)Andy Pavlo教授的高级数据库系统(Advanced Database Systems)课程