http://mp.weixin.qq.com/s?__biz=MzA5ODM5MDU3MA==&mid=400086920&idx=1&sn=b8174184059e2886d4fcb0245543e156&scene=0&key=b410d3164f5f798ec9345d1981cb0938e01659103862237026c920ed43ff06fc67caa821809f7359fdec3f1a6da95423&ascene=0&uin=Mjk1ODMyNTYyMg%3D%3D&devicetype=iMac+MacBookPro11%2C4+OSX+OSX+10.11.1+build(15B42)&version=11020201&pass_ticket=DNAkDcaJZdqMxI32anakljZSgGo5lVDR7hirGheK4N4dBO9M00Bn6PXSkVsezAPJ

ACID 是软件领域使用最广泛的技术之一,它是关系数据库的基石,是企业级中间件不可或缺的部分,但通常通过黑盒的方式提供。但是在许多情况下,这种古老的事务方式已经不能够适应现代大规模系统和NoSQL数据库的需要了,现代系统要求更高的性能要求,更大的数据量,更高的可用性。在这种情况下,传统的事务模型被定制的事务或者半事务模型所取代,而在这些模型中事务性并不像以往那样被看重。

在本文中我们会讨论一下key-value数据库的无锁事务操作,这种技术可以广泛应用于任何一种数据库系统。在GridDynamics中,我们就用这种技术在Oracle Coherence上实现了一个轻量级的非标准的事务机制。在第一部分我们会通过几个重要的用例来了解两种简单的方法,在第二部分我们会研究更多更通用的方法,比如说PostgreSQL的MVCC实现。

原子性缓存切换,读提交隔离

让我们从一个简单易于实现的方法开始,这个方法适用于读远多于写的系统。比如说电子商务系统中每天要进行的数据更新,一些管理性操作例如无效货品的修复以及缓存更新。

最简单的例子是把所有数据都加载进缓存里,然后通过一个代理接口来执行诸如 get() 和 put() 这样的操作。这个接口会与两个缓存打交道,A和B,按照以下逻辑运行(图 1):

  • 任何时候只能有一个缓存处于可用状态,代理接口会把所有的请求路由给它(图1.1)。

  • 更新数据的时候把新数据加载到目前不可用的缓存中(图1.2)。

  • 更新进程切换标志哪个缓存可用的标记(图1.3),代理接口开始把新的读请求分发到新标记为可用的缓存。

  • 缓存切换阶段的事务可以依据不用的持久性和隔离性要求来分别处理。如果允许“不可重复读” ,那么切换很简单,老数据会被立刻清理掉。否则,代理接口会维护一个仍未结束的事务列表,并把属于这个列表中的每一个请求都路由到原来的缓存中。只有当列表中的所有事物都提交或者放弃之后老数据才会被清空。

Fig.1 Cache Switch

相同的技术也可用于部分更新。依据存储方式的不同也有多种实现方法,我们来看一个有三个缓存简单例子。这个例子中的框架遇上一个类似,但是代理接口按照以下逻辑运行(图 2):

  • 用户请求被路由到主缓存("PRIMARY"缓存)(图 2.1)

  • 新增数据和更新数据加载进2号缓存(“NEW”缓存),删除项的key放入3号缓存("DELETE"缓存)(图2.2)

  • 提交进程(特指写事务)切换全局标示,这个标示会告诉代理接口先去"NEW"和"DELETE"缓存去查找所请求的数据,如果在这两个区域中没有发现再去"PRIMARY"缓存查找(图2.3)。换句话说,在这一步所有的请求都被改派到了更新过的数据中查找。

  • 提交进程将 NEW 和 DELETE 区域的变化传递给PRIMARY。也即在PRIMARY缓存区以非原子的方式更新、增加、删除数据项(图2.4)。

  • 最后,所有的提交进程把全局标识切换回来,所有的请求仍然路由到 PRIMARY 缓存区域(图2.5)。

  • 在第4步,可以把老数据拷贝到另一个缓存区,这样就可以支持回滚操作。即使是全量更新也可以用这种方法。

Fig.2 Partial Cache Switch

从上面的两个例子我们可以看出,专用于读的数据快照避免了数据更新的干扰,大大降低了复杂性。在一个写密集型的环境中就不容易做到这一点了。在下一节我们会讨论一种非常好的方法可以完美的解决这个问题。

MVCC 事务,可重复读隔离

事物间的隔离可以通过给数据项加上版本号来实现。有许多方法能做到这一点,下面我们会介绍一种与PostgreSQL 的事务处理方法非常相似的办法。

正如前面所说,每个事务可以对应于一个部分数据快照。在同一时间,每一个数据项都有他自己的生命周期 - 从加入缓存到移出缓存或者被更新(被新版本所取代)。所以可以通过给每条数据打两个时间戳来实现隔离,每个事物通过开始时间(两个时间戳之一,译者注)来找出在事务开始时处于可见状态的数据。但在实践中常用一个单调递增的计数来代替时间戳:

  • 当新事务开始的时候:

    • 它会获得一个全局唯一且单调递增的事务ID ,也叫 XID。

    • 进程里保存着所有事务的XID.

  • 缓存里的每个数据项有两个额外标记,xmin 和 xmax。按照以下规则赋值:

    • 当数据项被某个事务建立的时候, xmin 设置为该事务的XID ,xmax 无值。

    • 当数据被某个事务移除的时候,xmin 不变,xmax 设置为该事务的XID。数据并没有真的从缓存中清除,只是被标记为已删除。

    • 当数据被某个事务更新的时候,老数据仍然保存在缓存里,xmax 被赋值为事务的XID,同时增加一条新的数据,新数据的 xmin 也赋值为XID 并且xmax 为空。换句话说更新操作等于一次删除加一次增加。

  • 如果以下两个条件成立,那么数据对于某次事务是可见的:

    • xmin 有值并且小于或等于当前事务ID。

    • xmax 为空,或者等于未提交事务(放弃的或者还未完成的)的XID ,或者大于当前事务ID。

  • xmin 和 xmax 可以存储两个位标记,表明事务是否放弃或者提交,这样才能进行上面的检查(xmax 是否等于未提交事务的ID)。

逻辑如下图所示:

Fig.3 PostgeSQL-like MVCC

这种方法的缺点是废弃数据的移除有些繁琐。因为不同事务看到的数据版本不同,决定何时将数据标为不可见或者移除是比较复杂的。不过也有两种以上的方法能够做到,第一种是PostgreSQL中使用的,第二种是Oracle使用的:

  • 所有的版本都存储在同一个key-value空间中,对版本数量没有限制(也即可以储存任意多的版本,译者注)。由一个后台进程来回收老版本数据,这个回收可以按计划调度执行也可以再读或者写的时候触发。

  • 主key-value 空间只储存最新的版本,之前的版本储存在另外的地方,且储存老版本的空间大小是固定的。 最新的版本会指向之前的版本,但是却不能够由此上溯到之前的任意版本, 因为存储老版本数据的区域大小是固定的, 太早的版本会被移除。如果某个事务不能够找到指定版本的数据就会失败。

[转载] 为 Key-Value 数据库实现 MVCC 事务的更多相关文章

  1. 【转载】QT MySQL数据库操作总结

    转载自http://blog.chinaunix.net/uid-28194872-id-3631462.html #include <QtSql> QT += sqlQSqlDataba ...

  2. MySQL数据库引擎、事务隔离级别、锁

    MySQL数据库引擎.事务隔离级别.锁 数据库引擎InnoDB和MyISAM有什么区别 大体区别为: MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持.MyISAM类型的表强调的是性能 ...

  3. [转载]Hibernate如何提升数据库查询的性能

    目录(?)[-] 数据库查询性能的提升也是涉及到开发中的各个阶段在开发中选用正确的查询方法无疑是最基础也最简单的 SQL语句的优化 使用正确的查询方法 使用正确的抓取策略 Hibernate的性能优化 ...

  4. SharePoint 2010 数据库xxx的事务日志已满

    接到领导安排,说客户有问题 请求协助解决,对方给我展示的错误日志,如下: 数据库'WSS_Content_xxxx'的事务日志已满.若要查明无法重用日志中的空间的原因,请参阅sy.databases中 ...

  5. 关系型数据库工作原理-事务管理(二)(翻译自Coding-Geek文章)

    本文翻译自Coding-Geek文章:< How does a relational database work>. 原文链接:http://coding-geek.com/how-dat ...

  6. 关系型数据库工作原理-事务管理(一)(翻译自Coding-Geek文章)

    本文翻译自Coding-Geek文章:< How does a relational database work>. 原文链接:http://coding-geek.com/how-dat ...

  7. 【转载】 Sqlserver查看数据库死锁的SQL语句

    在Sqlsever数据库中,有时候操作数据库过程中会进行锁表操作,在锁表操作的过程中,有时候会出现死锁的情况出现,这时候可以使用SQL语句来查询数据库死锁情况,主要通过系统数据库Master数据库来查 ...

  8. JDBC:数据库操作:事务

    事务特征:原子性,一致性,独立性,持久性. 要想操作事务,必须按照以下步骤完成. 1,取消掉自动提交(SET AUTOCOMMIT=0):每次执行数据库更新的时候实际上发出SQL命令之后就已经提交上去 ...

  9. Apache Samza流处理框架介绍——kafka+LevelDB的Key/Value数据库来存储历史消息+?

    转自:http://www.infoq.com/cn/news/2015/02/apache-samza-top-project Apache Samza是一个开源.分布式的流处理框架,它使用开源分布 ...

随机推荐

  1. 深入理解GCD(一)

    虽然 GCD 已经出现过一段时间了,但不是每个人都明了其主要内容.这是可以理解的:并发一直很棘手,而 GCD 是基于 C 的 API ,它们就像一组尖锐的棱角戳进 Objective-C 的平滑世界. ...

  2. Ecshop后台订单列表增加”商品名”检索字段

    近期ecshop网站做活动,统计商品订单量的时候没有按商品名搜索的选项,只能手动查询.这样效率很低下,而且容易出错. 现在为列表增加一个简单的“按商品名搜索”表单项.效果如下图 涉及到2个文件,分别是 ...

  3. Spring+Quartz实现定时执行任务的配置

    1.要想使用Quartz 必须要引入相关的包:以下是我在项目中gradle中的配置: compile 'org.quartz-scheduler:quartz:2.1.1' 2.Scheduler的配 ...

  4. 20150812 Asp.net 父窗体获取子窗体的返回值,更新父窗体文本控件(应用)

    1. 父窗体 **************************************** using System;using System.Collections;using System.C ...

  5. C#获得枚举类型的长度

    enum MyEnum { Value1, Value2, } class Program { static void Main(string[] args) { var e = new MyEnum ...

  6. cetos6.5安装Tomcat

    一. 下载Tomcat 官网下载Tomcat  tar.gz文件 二. 解压tar.gz文件 tar -zxvf tomcat.tar.gz 三. 在catalina.sh最上面添加一下内容 expo ...

  7. java使用Cipher进行签名和验签

    public static void main(String[] args) { try { String plainText = "duwenlei"; KeyPairGener ...

  8. 编写shell管理脚本(一)

    7.1  查看当前linux系统中能够使用的shell程序的列表[root@localhost ~]# cat /etc/shells/bin/sh/bin/bash/sbin/nologin/bin ...

  9. STM32 NVIC

    在stm32中是要配置nvic的.何为nvic,对于我这样的初学者来说,直观感受就是在设置为中断后 还需要配置 中断的优先级nvic就是搞这个的. 那么具体的需要配置些什么那? void NVIC_C ...

  10. .Net验证码实现基础--Draw

    命名空间 using System.Draw; using System.Draw.Drawing2D; 在form等控件的 事件中 添加 paint事件 ///////画各种形状(空心)////// ...