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. 项目管理:CocoaPods建立私有仓库

    CocoaPods是iOS,Mac下优秀的第三方包管理工具,类似于java的maven,给我们项目管理带来了极大的方便. 个人或公司在开发过程中,会积累很多可以复用的代码包,有些我们不想开源,又想像开 ...

  2. 20145227《Java程序设计》第1次实验报告

    20145227<Java程序设计>第1次实验报告 实验步骤与内容 命令行下Java程序开发 1.打开 cmd ,输入 mkdir 20145227 命令建立实验目录,然后输入 cd 20 ...

  3. ScheduledExecutorService定时周期执行指定的任务

    示例代码 package com.effective.common.concurrent.execute; import java.text.DateFormat; import java.text. ...

  4. PHP获取手机相关信息

    该PHP操作类实现获取手机号手机头信息,取UA,取得手机类型,判断是否是opera,判断是否是m3gate,取得HA,取得手机IP 代码如下: <?php /** * @desc 手机操作类 获 ...

  5. 20150916_001 vba 基础

    一.什么是“宏”.“宏”有什么用 关于“宏”的详细定义,可以参考百度百科的解释(点击查看).我给它一个简单的或许不太严谨的定义: 宏的通俗定义:宏是被某些软件所能识别.理解并执行的特定代码/脚本. 宏 ...

  6. Android activity界面跳转动画

    实现activity界面跳转动画 1.在startActivity方法之后加入: overridePendingTransition(R.anim.pull_in_right, R.anim.pull ...

  7. Unity-Animator深入系列---控制IK

    回到 Animator深入系列总目录 要让代码控制IK,需要先在Animator中打开IK pass 然后,和IK相关的代码需要放到相应的函数中去: void OnAnimatorIK() { Deb ...

  8. 无缝漫游 Seamless Roaming

    点击打开链接 如你在由一个以上AP组成的Wifi 无线网中,拿著一部WindowXP 笔记本电脑,乘著汽车在Wifi网中往来,不断通过无线卡Ping 一个目標,你会发现在无线卡过站时,掉包可以高达半分 ...

  9. 【leetcode❤python】141. Linked List Cycle

    #-*- coding: UTF-8 -*- #Method:快慢指针法,建立虚表头,快指针走两步,慢指针走一步,若存在环,则快指针会追上慢指针# Definition for singly-link ...

  10. Nodejs之socket广播

    nodejs发送udp广播还是蛮简单的,我们先写个服务器用于接收广播数据,代码如下: var dgram = require("dgram"); var server = dgra ...