一、事务的基本概念

所有文章

https://www.cnblogs.com/lay2017/p/12078232.html

 

正文

一、什么是事务?

概念性的东西通常都显得抽象、晦涩,包罗万象但似乎很难一下子抓到要点。为此,我们先来看一个比较典型的例子:银行转账

  市民王先生到银行转账1000元给老李,王先生的账户里现有10000元,老李的账户恰好也有10000元。银行将从王先生账户扣除1000元:10000-1000=9000,然后给老李的账户加上1000元:10000+1000=11000。

  王先生的账户剩余:9000元

  老李的账户剩余:11000元

  我们来看看,这里有两个步骤1)从王先生账户扣钱;2)给老李账户加钱;我们做一个问题假设,银行柜员从王先生的账户扣完钱以后,忘记给老李的账户加钱了。那么会出现什么结果?

  王先生的账户剩余:10000-1000=9000

  老李的账户剩余:10000+0=10000

  很明显,老李没有收到王先生的钱。而王先生也很委屈,已经扣完钱了啊。这反应了一个比较严重的问题,就是当你要处理的事情被分为了很多个步骤,这些步骤可能处理成功,可能失败,甚至可能完全忘了处理。如果都处理成功也没什么问题。如果都处理失败,我们也可以当作什么都没有发生过。唯独有的步骤成功,有的步骤失败的时候比较烦人了。就会出现上面的例子中的问题。

  这时候我们会想,既然都成功或者都失败都行,我们能不能让多个步骤的处理像处理一个步骤一样简单点要么全部成功,要么全部失败呢?如上例子中,要么王先生扣完1000以后也给老李加上10000,要么王先生没扣钱,老李也没加钱,最多老李发现钱没到账会再要求王先生转一次。

  我们可以给这两个步骤取个名字:转账,转账包含了从一个账户扣钱,给另一个账户加钱两个步骤,两个步骤同时成功或者同时失败。

  抛开上面的例子,我们所要处理的其它事情会不会也存在转账这样的问题呢?当然!那么,我们把这些要做的事情像转账一样取个共同的名字,就叫做"事务"。

  到这里,我们给事务下一个定义吧。

事务就是你要做的事情,通常这件事会包含多个步骤。
事务中的多个步骤处理起来就像处理一个步骤一样,要么全部成功,要么全部失败。
事务解决了多个步骤部分成功,部分失败所带来的不一致问题。

所以,事务的诞生是其实是为了解决问题,这种技术手段主要解决了"一致性"问题。

 

二、事务的ACID四个特征

任何存在的真实的亦或者是虚拟的人事物都有其特征,我们通过其特征来识别它与其它人事物之间的区别。

那么,事务这种技术手段存在什么特征呢?

事务的特征,我们简称为ACID。它们分别是什么呢?

A:atomicity 原子性

C:consistency 一致性

I:isolation 隔离性

D:durability 持久性

一致性

我们总说事务具备一致性,但是个人认为它并非事务本身的特性。而是事务这种技术手段,维护了所要处理的对象的一致性。比如说mysql中的数据的一致性,mysql的事务技术维护了mysql内部存储的数据具备一致性。

再来,理解一致性,我们先提一下我们以前学过的"能量守恒定律"。

能量守恒定律是指一个封闭或者孤立的系统中,总的能量是保持不变的。
不会凭空产生,也不会凭空消失,只能从一个物体传递给另一个物体,且能量的形式可以相互转换。

和我们赖以生存的自然界一样,我们程序员所构建的系统也是一个"能量守恒"的世界。我们的代码,我们的数据都是这个世界的一尘一土,不会凭空消失,不会凭空产生。

而事务技术,就是为了让这个程序世界能够像真实世界一样做到"能量守恒"。我们简称其为"事务的一致性"。

似乎从这里感受到了早年看书的时候经常忽略的话,"编程就是对现实世界进行建模",我们可以像上帝一样在这里创造万物,万物生生不息。

原子性

原子是什么?原子是指在化学的定义上一个不可再分割的基本微粒,它是构成一般物质的最小单元。

以上定义指的是一个客观存在的事物由原子构成。那,如果是一个行为,它具备原子特性是什么概念呢?

首先,原子不可分割,是最小的单元。所以,一个行为具备原子特性就不能被再拆分成多个步骤,我们把这个行为看成一个最小单元。步骤全部完成,或全部没开始。还是不好理解?我们举个例子

你一步一步地走路回家,这时候你突然发现在你正要落脚地位置有一坨不可言状地东西。你感到恶心,所以脚到一半就停住了,然后越过它。

很明显,"走一步"这个行为并没有形成原子性。想象一下,如果成为了一个具备原子性的行为,那么即使你的大脑发现有一坨东西,就不能够脚停在半空中,再越过它。你必须踩下去,或者脚收回来(这叫回滚),重新走一步。

这里的原子性只是表达了事务的一种特性,它旨在关注行为整体,而忽略行为的具体步骤。保证事情全部完成,或者全部像没开始一样。

持久性

持久性在定义中是指:事务一旦被提交,它产生的改变就是永久性的。即使发生故障也不应该影响改变的成功与否。

这里注意"提交"这两个事情,我们通俗地说就是一件事完成以后,它产生的影响就是既存事实。不能因为其它因素导致这个事实不存在了。好像还是不好理解,举例说明

银行查账的时候发现,A转账给B,B转账给C。这里有两个转账事务产生了。那么如果银行因为停电,A转账给B这个事务却不再是既存事实,银行查账只发现B转账给C这一笔,就会奇怪为什么B的账户了多了一笔钱,而丢失了本该是A转账过来的事务结果?

持久性,代表了一种不可改变的既存事实,也就是我们常说的历史无法改变,只能看向未来。这样看来,如果丢了秦始皇大一统的历史,整个后面的朝代还怎么存在呢?所以,有持久性才能维持世界的一致性。

隔离性

前面的内容里,我们的思维基本上处于思考单线程的状态。而事务在其复杂的使用场景中却不得不考虑并发场景。因此,我们在这里提出"隔离性"。

这里,编一个小故事,噢~不!讲一个小故事,故事的名称叫《老王请客》

老王是一个地中海格子衫的中年男子,他有一个小金库。前几天被老婆发现以后,老王大义凛然地说:"我王某的小金库老婆随时可以取用"。看着老王飘逸的发型,他老婆终究心软了。

第一次

老王决定请朋友吃饭庆祝一下,看了一下小金库,还有1000块钱,于是高兴地跑去餐厅吃饭。

他老婆心血来潮,逛街地时候也看了看小金库,发现有1000块钱,于是决定奖励自己一双鞋子,很快就付款了。

老王吃完饭,付款的时候,发现卡里没钱了,很不幸地加入了洗盘子的队伍。

read uncommitted级别,不考虑并发问题,只要数据被update,那么不管事务是否提交都会被其它事务读取到。
如果事务产生回滚,读取到该事务update以后数据的事务就会出现脏读问题。
一般不会采用这个级别,除非完全不考虑并发问题。

第二次

老王吸取教训,决定只能用卡才能取自己小金库的钱,觉得自己真是太聪明了。

于是,老王决定再次请客,挽回颜面。他谨慎地看了看还有1000块钱,高兴地去了餐厅。

他老婆看到了这一幕,想要戏弄他一下。看了看小金库还有1000块钱,准备再奖励给自己一件小黑裙。但是,她发现没有卡不能取钱。于是气呼呼地等老王回家。

老王吃完并付款成功,这一次终于不用洗盘子了。

他老婆越等越生气,再看了看小金库,发现钱已经被花掉了,小黑裙得不到了。于是决定让老王承担家里洗盘子的工作。

read committed级别,一个事务中update数据,在事务被提交以后才会被其它事务看见。故事中的卡就像是一个排它锁,限制了同时update。
有效地解决了并发地时候出现脏读问题。
但是,也会导致两次读取的数据却不一致的问题,出现同一个事务中不可重复读问题。

第三次

由于上一次戏弄老王失败,他老婆决定先下手为强。看了看小金库又有1000块钱,于是决定带走卡,买回上次留下遗憾的小黑裙。

老王起床发现老婆不在,准备看看小金库,这一次小金库更加被限制的没有卡余额都看不了。老王一脸揪心地坐在电脑前,打消了请客地念头,默默地开始玩游戏。

终于,他老婆高兴地回来了。老王接过卡,一查零头都不剩,默默地去洗碗了。

repeatable read级别,同一个事务中能够重复读取到相同的数据。也因此,repeatable read将比read committed更加地排他。
故事中基本上都是update数据,但是如果产生insert数据的情况,也就是家里突然多出一张卡。那么在同一个事务中就会出现"幻读"的问题。

第四次

老王经历多次失败,决定每次等老婆用完以后再开始考虑是否请客吃饭。至于卡里剩多少钱,听天意了。

这样,就不用因为没钱被留下洗盘子。也不用让老婆等太久而被罚洗盘子。更不用一大早起床,却揪心等待看看自己的小金库还剩多少。

serializable级别意味着事务之间串行化,也很轻易地解决了脏读、不可重复读、幻读地问题。
但是串行化也意味着效率非常低,并发度非常低。通常也不使用这种级别。

 

这里对隔离级别做一个小结

隔离级别分为:1)read uncommitted 2)read committed 3)repeatable read 4)serializable

从左到右级别越高,并发能力越低。所以,一般数据库会折中选择read committed或者repeatable read作为默认的隔离级别。

在二者中做选择也就是说"不可重复读"问题是否需要被考虑,这应该根据业务场景分析。

比如:

  1)我要获取12:00:00这个时间的精准在线人数,而在事务A第一次读取时1000人,这时事务B提交了一次修改变成了1001人,事务A第二次读取的时候变成了1001人,这就影响了精准度。可以选择repeatable read

  2)而,如果我不关心精准度,我只是考虑每天12:00:00获取一下当前的在线人数。那么两次读取不一致并没有什么影响。可以选择read committed

 

总结

到这里,关于事务的基本概念就讲完了。我们从了解什么是事务?到理解ACID的四个特性。

最麻烦的隔离级别也坎坷地通过一个故事的方式理解,不过隔离级别这个东西现在还不需要太纠结它的实现。比如,你思考的时候不停地联系到数据库的隔离级别或者spring框架的隔离级别的实现细节,这可能加重理解它的复杂程度。

后续的文章会继续讨论,加深理解。这里,我们理解在不同隔离级别下,事务之间什么时候能读取到对方update的数据,每个级别还有什么遗留问题需要考虑,并发性如何即可。

最后,我们可以将事务理解为维护"程序世界"数据一致性的技术手段,让系统"能量守恒"。

  

posted @ 2019-12-22 23:06  __lay  阅读(1258)  评论(0编辑  收藏  举报