做者:实验室小陈/大数据开放实验室前端
在上一篇文章《内存数据库解析与主流产品对比(二)》中,咱们从数据组织和索引的角度介绍了内存数据库的特色和几款产品的技术实现。本文将继续解析内存数据库,从并发控制、持久化和查询处理的角度介绍几款技术,带来更多维度、更细致的内存数据库技术讨论。算法
— 数据库管理系统中的并发控制—
1. 内存数据库并发控制的两种策略数据库
a. 多版本的并发控制数组
内存数据库中的并发控制主要采用两类策略:1. 多版本的并发控制;2. 分Partition处理。并发控制机制能够分为乐观和悲观两种类型。悲观并发控制则认为进程竞争资源老是存在的,所以访问时先加锁,访问完再释放;乐观并发控制认为大多数状况不须要竞争资源,只在最后提交前检查是否存在冲突,有冲突就回滚,没有就提交。缓存
乐观并发控制大多数不采用基于锁的技术实现,而且一般是多版本的。多版本意味着每次更新都会产生新的版本,读操做根据可见范围选取合适的老版本,读操做不阻塞写操做,因此并发程度比较高。其缺点是会产生额外开销,例如更新要建立新版本,并且随着版本愈来愈多,还须要额外开销收回老版本。内存数据库多采用乐观的多版本并发控制机制,相比于基于锁的悲观并发控制其优点是开销较小,并且支持并发程度较高的场景;缺点是在有大量写竞争的场景下,事务间冲突几率比较高时,大量事务会失败和回滚。性能优化
b. 分Partition处理网络
内存数据库并发控制的另一类策略是把数据库分红多个Partition,每一个Partition采用串行方式处理事务。优点是单Partition业务的执行没有用于并发控制的额外开销,缺点是存在跨Partition事务时系统的吞吐率会直线降低。所以,若是不能保证全部业务都是单Partition进行,将致使性能不可预测。数据结构
2. 多版本并发控制之 Hekaton架构
Hekaton采用乐观的多版本并发控制。Transaction开始时,系统为事务分配读时间戳,并将Transaction标记为active,而后开始执行事务,在操做过程当中系统记录被读取/扫描/写入的数据。随后,在Pre-commit阶段,先获取一个结束的时间戳,而后验证读和扫描数据的版本是否仍然有效。若是验证经过,就写一个新版本到日志,执行Commit,而后把全部的新版本设置为可见。Commit以后,Post-Processing记录版本时间戳,以后Transaction才真正结束。并发
a. Hekaton 的事务验证
i) Read Stability:Hekaton系统可以保证数据的读稳定性(Read Stability),好比交易开始时读到的每条记录版本,在Commit时仍然可见,从而实现Read Stability。
ii) Phantom Avoidances:Phantom指一个事务在开始和结束时执行相同的条件查询,两次结果不同。出现幻影的缘由是该事务执行过程当中,其余事务对相同数据集进行了增长/删除/更新操做。应该如何避免幻影现象呢?可经过重复扫描,检查所读取的数据是否有新版本,保证记录在事务开始时的版本和在结束时一致。
Hekaton并发控制的好处在于,不须要对Read-Only事务作验证,由于多版本可以保证事务开始时的记录版本在结束时依然存在。对于执行更新的事务,是否作验证由事务的隔离级别决定。例如若是快照隔离级别,就不须要作任何验证;若是要作可重复读,就要作Read Stability;若是是串行化隔离级别,既要保证Read Stability,又要保证Phantom Avoidance。
b. Hekaton的回收策略
Hekaton中的回收任务并不禁独立的线程处理,而是每一个事务本身回收。以下图所示,Transaction ID为250的事务结束时间戳为150且状态为terminated,此时会有一个Write Set获取全部老版本,并判断当前全部active的Transaction的开始时间戳是否大于ID为250的事务结束时间,即150。若是都大于150,说明不可能再基于时间戳早于150的旧版本进行修改,于是由事务回收旧版本,这部分工做是每一个线程在处理Transaction时的额外工做。
3. 多版本并发控制之Hyper
Hyper的并发控制和Hekaton的区别主要有如下三点:1. 直接在记录位置进行更新,经过undo buffer来保存对数据的修改,数据和全部修改被连接在一块儿;2. 验证是根据最近的更新与读的记录进行比较来实现(后续会涉及到);3. 串行化处理commit,对提交的事务进行排序,并依次处理。
在事务验证方面,Hyper的验证须要在日志中记录Read Predictates,包括查询或Range Scan,并且要记录插入、删除和更新的记录。在Hyper模式中,插入/删除/更新经过对应的Undo Buffer获悉被修改过的记录,因此记录改动对于Hyper而言是容易的。
对于每一个Transaction,只须要比较该事务从开始到Commit之间,是否存在其余Transaction对知足搜索条件的数据集进行过增/删/改,从而判断是否存在幻影现象,若是存在,就直接终止事务。
4. 多版本并发控制之HANA和HStore/VoltDB
HANA并行控制方式比较简单,采用悲观的多版本控制,由行级锁保护数据结构,每行由时间戳决定每一个版本的可见范围。每一个Transaction在更新或删除时都须要申请写锁,并且要作死锁检测。
HStore/VoltDB是一个Partition系统,锁的粒度很粗,每一个Partition对应一把锁,所以Transaction在某节点上执行时,须要拿到该节点全部资源。一旦一个事务可能涉及到两个Partition,就须要把两个Partition的锁都拿到。因此Partition系统的优势是单Partition处理速度很是快,缺点是多Partition效率很低。同时,系统对于负载的偏斜很是敏感,若是有热点数据,那么热点数据就构成系统瓶颈。
5. 多版本并发控制之负载预知
假设一个工做负载中,事务须要读和写的数据集能够提早得到,就能够在执行前肯定全部事务的执行顺序。Calvin就是基于这样的假设设计的VLL (Very Lightweight Locking)超轻量级锁数据库原型系统。通用场景的工做负载是没法提早知道读写集合的,但在存储过程业务的应用中,能够提早肯定读写集合,对于这些场景就能够考虑相似Calvin的系统。
—数据库管理系统中的持久化技术—
对于内存数据库而言,和基于磁盘的数据库相同也须要日志和Checkpoint。Checkpoint的目的是恢复能够从最近的检查点开始,而不须要回放全部数据。由于Checkpoint涉及写入磁盘的操做,因此影响性能,所以要尽可能加快相关的处理。
一个不一样是内存数据库的日志和Checkpoint能够不包含索引,在恢复时经过基础数据从新构造索引。内存数据库中的索引在恢复时从新构造,构造完成后也放在内存中而不用落盘,内存索引数据丢失了再重构便可。另一个不一样是内存数据库Checkpoint的数据量更大。面向磁盘的数据库在Checkpoint时,只须要把内存中全部Dirty Page写到磁盘上便可,可是内存数据库Checkpoint要把全部数据所有写到磁盘,数据量不管多大都要全量写一遍,因此内存数据库Checkpoint时写入磁盘的数据远大于基于磁盘的数据库。
Hekaton Checkpoint
对于持久化的性能优化,第一要保证写日志时的高吞吐量和低延迟,第二要考虑恢复时如何快速重构整个数据库。Hekaton的记录和索引存放在内存,全部操做写日志到磁盘。日志只记录数据的更新,而不记录索引的更新。进行Checkpoint时,Hekaton会从日志中恢复,并根据主键范围并行处理。以下图,分三个主键范围:100~19九、200~29九、300~399,绿色表明数据,红色表明删除的记录(单独保存被删除的文件)。在恢复时,Hekaton用并行算法在内存中重构索引和数据,过程当中根据删除记录过滤数据文件,去除被删除的数据,而后从Checkpoint点开始,根据日志回放数据。
其余系统的Checkpoints
1. 采用Logic Logging的系统如H-Store/VoltDB,即不记录具体的数据改动,而是记录执行过的操做、指令。它的优点是记录的日志信息比较少。写日志时,HStore/VoltDB采用COW(Copy-on-Write)模式,即正常状态是单版本,但在写日志时会另外“复制”一个版本,待写完再合并版本。
2. 另外一种是按期把Snapshot写入磁盘(不包括索引),好比Hyper就是基于操做系统Folk功能来提供Snapshot。
— 数据库管理系统中的查询处理—
传统的查询处理采用火山模型,查询树上的每一个节点是一个通用的Operator,优点在于Operator能够任意组合。但Operator拿到的记录只是一个字节数组,还须要调用另外一个方法来解析属性以及属性类型。若是这种设计放到内存数据库中,属性以及类型的解析都是在Runtime而非编译时进行的,会对性能产生影响。
另外对于get-next,若是有百万个数据就要调用百万次,同时get-next一般实现是一个虚函数,经过指针调用,相比直接经过内存地址调用,这些都会影响性能。此外,这样的函数代码在内存中的分布是非连续的,要不断跳转。综上,传统DBMS的查询处理方式在内存数据库当中并不适用,尤为体如今在底层执行时。
内存数据库一般采用编译执行的方式,首先对查询进行解析,而后优化解析后的语句,并生成执行计划,而后根据模板对执行计划进行编译产生可执行的机器代码,随后把机器代码加载到数据库引擎,执行时直接调用。
下图是对不一样查询方式的耗时分析,能够看出编译执行方式中Resource Stall的占比不多。
另一张图解释了目前的CPU架构实现,L2 Cache和主存之间存在Hardware Pre-fetcher。L2 Cache分为指令Cache和Data Cache,指令Cache会由Branch Prediction实现分支预测,Data Cache会由基于Sequential Pattern的Pre-fetcher实现预测。所以,数据库系统的设计须要考虑该架构下如何充分发挥Pre-fetcher功能,让Cache能够不断为 CPU计算单元提供指令和数据,避免出现Cache Stall。
Hekaton编译查询处理
Hekaton的编译采用T-SQL存储过程,编译的中间形式叫作MAT Generator,生成最终的C代码在编译器中执行。它产生的库和通用Operator的区别在于:通用Operator须要在运行时解释数据类型;而Hekaton编译方式是把表的定义和查询编译在一块儿,每一个库只能处理对应的表而不能通用,天然就能拿到数据类型,这样的实现能得到3~4倍的性能提高。
HyPer和MemSQL编译查询处理
HyPer的编译方式是把查询树按照Pipeline的分割点每段编译。而MemSQL采用LLVM作编译,把MPI语言编译成代码。
下图是一个对MemSQL性能的测试。没有采用编译执行时,MemSQL两次执行相同查询的时间都是0.05秒;若是采用编译执行,第一次耗时0.08秒,可是再执行时耗时仅0.02秒。
— 其余内存数据库系统—
除了以前提到的几种内存数据库外,还有其余一些著名的内存DBMS出现。
i) SolidDB:诞生于1992年的混合型数据库系统,同时具有基于磁盘和内存的优化引擎,使用VTRIE(Variable-length Trie)树索引和悲观锁机制进行并发控制,经过Snapshot Checkpoints恢复。
ii) Oracle Times Ten:早期是惠普实验室名为Smallbase的研究项目,在2005年被Oracle收购,如今多做为大型数据库系统的前端内存加速引擎。Oracle Times Ten支持灵活部署,具备独立的DBMS引擎和基于RDBMS的事务缓存;在BI工做时支持Memory Repository,经过Locking进行并发控制;使用行级Latching处理写冲突,采用Write-Ahead Logging和Checkpoint机制提升持久性。
iii) Altibase:于1999年在韩国成立,在电信、金融和制造业应用普遍。Altibase在Page上存储记录,以Page为粒度进行Checkpoint且兼容传统DBMS引擎;支持多版本并发控制,使用预写日志记录和检查点实现持久性和恢复能力,经过Latching-Free对Page的数据进行Checkpoint。
iv) P*Time: 21世纪起源于韩国,2005年出售给SAP,如今是SAP HANA的一部分。P*Time具有极佳的性能处理,对日志记录使用差分编码(XOR),采用混合存储布局,支持大于内存(Larger-than-Memory)的数据库管理系统。
— 本文小结—
每个数据库系统都是针对特定的硬件环境设计,基于磁盘的数据库系统面临CPU单核、内存小、磁盘慢的场景设计。而内存数据库之内存为主存,不须要再重复读写磁盘,磁盘I/O再也不是性能瓶颈,而要解决其余瓶颈,好比:1. Locking/Latching的开销;2. Cache-Line Miss,即若是数据结构定义的不够好或在内存中组织的很差,没法匹配CPU的层级缓存,会致使计算性能变差;3. Pointer Chasing,即须要另外一个指针解释,或者查另外的表才能查到记录地址,也是主要的性能开销。此外,还有Predicate Evaluation计算、数据迁移/存储时的大量拷贝、分布式系统应用与数据库系统的网络通讯等开销。
在本专栏中,咱们介绍了传统基于磁盘的DBMS和内存数据库的特色,并从数据组织、索引、并发控制、语句处理编译、持久化几个方面对内存数据库与基于磁盘数据库的相同和差别性进行了介绍:
1. 数据组织:内存数据库中,把记录分红定长和变长管理,不须要数据连续存储,用指针替代了Page ID + Offset的间接访问;
2. 索引优化:考虑面向内存中的Cach Line优化、快速的内存访问等Latch-Free技术,以及索引的更新不记录日志等;
3. 并发控制:能够采用悲观和乐观的并发控制方式,可是与传统基于磁盘数据库的区别是,内存数据库锁信息和数据绑定,而不用单独的Lock Table管理;
4. 查询处理:内存数据库场景下的顺序访问和随机访问性能差异不大。能够经过编译执行提升查询性能;
5. 持久化:仍然经过WAL(Write-Ahead Logging)作Logging,并采用轻量级的日志,日志记录的内容尽可能少,目的是下降日志写入磁盘延迟。
内存数据库从1970s开始出现经历了理论成熟、投入生产、市场验证等发展环节。随着当前互联网秒杀、移动支付、短视频平台等高并发、大流量、低时延的平台出现,对于数据库性能提出了巨大需求和挑战。同时,内存自己在容量、单位价格友好度上不断提高,以及近期非容失性存储(NVM)的发展,促进了内存数据库的发展,这些因素使得内存数据库在将来有着广阔的市场和落地机会。
注:本文相关内容参照如下资料:
1. Pavlo, Andrew & Curino, Carlo & Zdonik, Stan. (2012). Skew-aware automatic database partitioning in shared-nothing, parallel OLTP systems. Proceedings of the ACM SIGMOD International Conference on Management of Data. DOI: 10.1145/2213836.2213844.
2. Kemper, Alfons & Neumann, Thomas. (2011). HyPer: A hybrid OLTP&OLAP main memory database system based on virtual memory snapshots. Proceedings - International Conference on Data Engineering. 195-206. DOI: 10.1109/ICDE.2011.5767867.
3. Faerber, Frans & Kemper, Alfons & Larson, Per-Åke & Levandoski, Justin & Neumann, Tjomas & Pavlo, Andrew. (2017). Main Memory Database Systems. Foundations and Trends in Databases. 8. 1-130. DOI: 10.1561/1900000058.
4. Sikka, Vishal & Färber, Franz & Lehner, Wolfgang & Cha, Sang & Peh, Thomas & Bornhövd, Christof. (2012). Efficient Transaction Processing in SAP HANA Database –The End of a Column Store Myth. DOI: 10.1145/2213836.2213946.
5. Diaconu, Cristian & Freedman, Craig & Ismert, Erik & Larson, Per-Åke & Mittal, Pravin & Stonecipher, Ryan & Verma, Nitin & Zwilling, Mike. (2013). Hekaton: SQL server's memory-optimized OLTP engine. 1243-1254. DOI: 10.1145/2463676.2463710.