上次我们在最后一部分,给出了一个事务操作的模型和内部简单实现的方式,以期让大家能针对事务有一个比较简单的印象。今天,我就尝试针对事务的基本特性做一个简单的概述。当然,因为仍然是概述,所以不会特别深入的去介绍和分析内部的机制,只希望能够将一些难以理解的概念,用举例子的方式协助大家能够更容易的理解事务的基本概念。

谈到事务,我们要做的第一件事,就是理解,什么叫做事务,以及为什么要有事务。

事务这个概念的产生,仍然是为了解决我们在之前提到的打字机问题:
(

http://blog.sina.com.cn/s/blog_693f08470101l1n1.html)

像我们在上文中提到的,计算机只是一个图灵机,所以,如果计算机要实现我们现实的世界的一些操作模式,只能靠模拟的方式进行。上次我们是使用锁来处理一次写请求过程里面对应的多次计算机操作的时候怎么看起来像是一次整体的操作。而在这里我们要解决的问题是,一组用户的多次写入和读取操作,怎么能够被看起来像是一次整体的操作呢?首先,让我用个例子来看看为什么需要这样做。

为了后面叙述方便,让我们先设定一个实际的环境场景:
梅梅给李雷打了一个电话,说最近想买一本英语书,需要一百元,李雷得知梅梅的请求,二话不说就开始操作他的网银给梅梅做了转账付款:

假定李雷账户是pk=1,韩梅梅的账户是pk=2
begintransaction;
{查看李雷是否有一百元}
selectcashfromTwherepk=1;A操作
{确定有足够的钱,减少李雷的钱}
updateTsetcash=cach-100wherepk=1;B操作
{给韩梅梅增加一百元}
updateTsetcach=cash+100wherepk=2;C操作
commit;好,我想我针对事务场景的场景应该描述清楚了。

首先我们来介绍原子性

从业务需求来说,我们必须保证,只要李雷的账户减少了100块,那么这一百块必须能够加到韩梅梅的账户里。如果中间有任何一个操作失败,系统不能够凭空多出一百元(李雷没有减钱,韩梅梅却加了钱),也不能凭空少了一百元(李雷减少了一百元,韩梅梅账户却没增加)。
为了做到这个特性,就必须保证李雷账户减钱与韩梅梅账户加钱这两件事,要么同时成功,要么同时不成功。
这就是事务中的第一个重要的概念:原子性的含义

下面我们来介绍一致性
当看到自己的账户减少了一百块以后,李雷问电话那头的梅梅:“梅梅,我看我这里操作成功了,你那里收到钱了没?”。如果这时候梅梅跟李雷说:”雷~我没看见我账户的钱有变化啊“。
想一想,这时候李雷会是什么感觉?他的心里一定会”咯噔“一下,钱不会是丢了吧?赶紧给支付宝打电话确认怎么回事!

这里的这种情况,就是一致性没有保证而可能出现的一种情况,简单来说,就是其他用户看到了事务三步操作过程中的中间状态信息,这种信息是明显反用户常识的,所以应该尽可能被避免。
因此我做个简单小结,这里我们需要能够保证,当李雷看到自己账户减少了100元的时候,韩梅梅的账户里也必须能够看到增加了一百元。
这种要么同时出现,要么同时消失的保证,就是事务一致性。在看到这里的时候,相信一定会有人来问,这个一致性和原子性,看起来都是要么成功,要么不成功的感觉,那么这两个概念有什么差异呢?

恩,下面我就来尝试解决这个疑惑吧~
区分这俩概念的关键,其实在一个”看”字,一致性约束的是一个用户写入并提交数据之后,其他用户去去读这条记录的时候,要么看到的是李雷还没有给韩梅梅转账的那个状态,要么就是李雷已经成功转账给了韩梅梅的状态,而在这两个状态之间的事务状态,比如李雷减少了100元,但韩梅梅还没加上这一百块的这个中间状态则不能够被其他人看到。而原子性只需要保证操作要么”最终“全部成功,要么”最终“全部失败而已。原子性不约束可见性,只保证操作的逻辑性。
这,就是一致性和原子性保证的差别。

最后,我们来介绍持久性
我想,任何用户都一定不希望下述的这种情况发生:李雷和韩梅梅都已经确认这次转账成功了。然而过了5分钟,韩梅梅想用这充满了感情的100元去买他想要的英语书的时候,却发现钱不翼而飞了!?于是赶紧给支付宝打电话询问,支付宝却回答说:对不起您那账户的数据库服务器重启了,丢失了一部分的数据,您账户里的那个钱正好在丢失范围内,请节哀。
我想这时候应该有(bi….)的动机了吧:)

于是,这里就需要做一个很基础的保证:如果一个事务结束后,已经被其他人看到了,那么就不能够回退到事务之前的状态了。
这就是事务的第三个重要的概念:持久性的含义

好了,我们介绍了三个关键的概念,原子性A,一致性C,持久性D。下课~

可能有看过其他数据库书籍的人会问了:”等等,你这不对吧?事务不是四个特性么?别忽悠我们啊,我们明天面试还得背这些概念呢,要不找不到工作啊~”

好好好,我马上来介绍隔离性。。。

其实,我将隔离性从上面几个属性中剥离出来,是有我自己的考虑的,主要原因是我个人认为,隔离性从逻辑概念上不与上面介绍的ACD平级。为什么这么说呢?我的理由是,如果要模拟现实中发生的各种操作,只需要保证ACD三个属性,就能够完美的完成一次事务操作了,隔离性则明显是多余的。。

”那么,既然多余,去掉不就好了。。还留着他干啥?不知道这个隔离性是最(biiiii…)复杂的概念么,上次面试就因为没说明白这货所以面试被鄙视了呢!”

唉,这还真去不掉,因为去掉了,数据库的事务就慢的受不了了。。所以数据库开山的那批大牛也就不顾逻辑的清晰不清晰了,把这个概念加入了事务的四大要素。

从这件事,我们也就能感受到这个概念对性能的重要性了吧。。甚至可以说,数据库事务性能是否能够提升,关键就看你能接受哪个隔离级别了呢!

咋样?是不是对这个概念肃然起敬了?好,那么我们就来看看隔离性是个什么东西。

首先,明确隔离性要解决的问题域,与上面为了保证一次事务提交逻辑上的正确性不同的,隔离性主要解决的问题是性能,或者更直接点,就是尽可能的降低受到锁影响的事务进程的个数的。

然后,看他解决问题的方法:
在之前我们提到,一致性要保证数据被其他人看到的时候必须是一个完整的数据状态,数据更新过程中的中间状态则不能被看到。
好了,大牛们刚刚说了限定条件,下面立马给自己来了个响亮的自抽。。

隔离性的最低级别,就是读未提交
什么叫读未提交呢?我们来抄维基。。
读未提交,可能出现脏读,当一个事务试图读取另一个还未提交的事务正在修改的某一行数据时,肮脏读取(dirtyreads)就会发生。

简单来说,就是一个事务,需要同时更新两张表的数据,而另一个人去读这两张表的数据的时候,可能能够读到一张表的新值和另外一张表的老值!
这明显就违反了一致性不是:)

其实,从我上面举出的例子,你就能感受到,一致性和隔离性应该被一起考虑,一致性描述的是理想的事务过程应该怎样,而理想的事务因为锁的范围太大以至于性能难以接受,所以就使用隔离性的多个隔离级别来破坏一致性应该给出的保证,以换取更小的锁范围,从而获取更高的性能。

在这一篇,我不会扩展隔离性这个概念,因为如果不联系实际的数据库实现而死记硬背各种隔离级别的概念,我觉得太难理解这个概念了。

因此,我将在后面展开讨论事务和分布式事务的章节再来关注隔离性的四个级别的问题。在这里,如果你回顾本篇的时候,如果能回想起原子,一致,持久这三个特性产生的原因,然后又能理解到隔离性主要解决的问题域,以及从最低级别:读未提交,到最高级别:可序列化,隔离性的本质就是一个锁的范围逐渐扩大,性能逐渐降低,但能提供更强级别的一致性保证的这么一个特征。那么我的目标也就达到了。

随着隔离性概要介绍的完结,数据库系统概述这一章也就随着这篇概述的结束而结束了,在本章,我写作的主要目标就是,用尽可能具象化的例子来对一个数据库系统做了一个概要性的描述。
下面,我会在后面的章节开始深入到分布式存储的一些重要的领域进行更深入的探讨。

enjoy~