小窥数据库事务(上)

一、前言

在这里插入图片描述

上个星期看了DDIA 的第5章内容,有些东西懂了,有些东西还是没怎么看懂。 不过倒是有一点吸引住了我的兴趣,那就是对于事务的并发读写冲突的问题。 以前其实就有过类似的想法,两个进程或者线程并发读写会对最终的数据造成什么影响呢? 现在了解之后,这不就是数据库中的事务隔离嘛。 好好研究一波,记录在此,希望可以便人便己。


二、事务的ACID

在这里插入图片描述

所谓事务,简单来说就是将应用程序的多个读、写操作捆绑在一起成为一个逻辑操作单元。也就是说,事务中的所有读写是一个执行的整体。整体的意思是整个事务要么成功(提交),要么失败(中止或回滚),如果失败,应用程序可以安全的重试。

提到数据库事务,就不能不提数据库事务的ACID了。 事务的ACID分别是指

  1. 原子性(Atomicity)
  2. 一致性 (Consistency)
  3. 隔离性 (Isolation)
  4. 持久性 (Durability)

下面介绍介绍我对这几个特性的理解:

(1)、原子性。 原子性就是指事务的执行就像一个原子操作一样,不可分割。要么成功执行,要么失败回滚到之前的状态,部分完成的写入操作全部丢弃。

注意,此处的原子性和后面介绍的隔离性的区别,前者并不关心多个操作的并发性,其事务执行失败的原因可能是磁盘损坏、网络中断、进程崩溃等(当然也有可能是因为并发操作引起的事务执行失败)。

(2)、一致性。 一致性非常重要,从某种程度上来说,一致性指的是某种操作对数据产生符合特定的预期要求。,是数据库处于一种应用程序(或者说用户)所期待的的“预期状态”。 比如说,一个人有两个账户,其中一个账户向另一个账户转账,转账操作之前的总额应该和转账之后的总额保持一致。

DDIA中说数据库中事务的这种一致性更多的是应用程序的责任,应用程序(或者说程序员用于)应该正确的定义事务,然后数据库主要起辅助作用,更好的执行上层应用程序定义的事务(利用原子性、隔离性和持久性)。比如说,前面所举的账户转账的例子,上层的应用程序应该正确的定义事务操作,即一个账户减去100元,另一个账户必须增加100元。

(3)、隔离性。 隔离性是本篇文章的重点,它主要针对的是对数据的并发事务操作。这个特性所强调的是,不同的事务在同时执行时,要具有相互隔离的效果,一个事务的执行不能影响到另一个事务。 简单来说,虽然物理上事务可能是并行的,但是逻辑上看起来要像串行(一个一个的执行)的效果一样。

这里先提前说一点,虽然这是事务的特点,但是现实的很多数据库处于性能上的考虑,在具体落地时并没有实现这种真正的串行化。 而是一些稍弱的隔离级别。

(4)、持久性。持久性提供了这样一个承诺,它保证一旦事务提交成功,即使存在硬件故障或数据库崩溃,事务写入的数据也不会消失(数据库可能会提供一些恢复机制)。

要注意的是,这种持久性也并不是完美的持久性,如果所有的恢复机制和备份都over了,那么上帝也只能说“I am sorry”了。


三、事务的隔离性

下面主要具体讨论讨论事务的隔离性。

对于引发竞争条件(读写冲突、写写冲突)的并发读写来说,其造成的影响具体分析起来其实算是比较乱的。还好在数据库领域已经有一些前辈帮我们总结好了一些因为并发冲突造成的影响,如脏读、脏写、不可重复读、更新丢失、幻读等。下面分别进行简要介绍。
 

3.1 并发读写造成的一些影响

3.1.1 脏读

脏读是一个读写冲突,指的是某个事务完成了部分数据的写入,但是还未提交;这时另一个事务看到了这些尚未提交的数据。

在这里插入图片描述
简单谈谈脏读的这个“脏” 体现在哪儿,在我现在理解,如果事务A还未提交写了一部分的数据,事务B就已经读到了中间的临时数据,如果之后事务A 又重新改变了这部分数据的值(正常修改或者发生失败回滚),那么事务B读到的就是个不确定的值,相对来说也就是个“脏”值。
 

3.1.2 脏写

脏写是一种写写冲突。讲的是如果两个事务A、B同时尝试更改一个对象,但是先写入的事务A还未完全提交,那么就发生了某种程度上的覆盖现象(或者更准备的说是擦除了一部分数据),也就是脏写。
在这里插入图片描述
对于脏写来说,其性质比较恶劣,它有可能违反事务的原子性、一致性和持久性(比如说,上面那张图,如果在Time 4和TIme5 之间发生了事务A的中止,那么A该回滚吗? 如果它回滚的话,就违背了事务B的持久性)。 所以现在几乎所有的数据库都不允许脏写。
 

3.1.3 不可重复读

不可重复度也是一种读写冲突。 它指的是事务A前后两次读取数据出现了不一致的现象,因为事务B在这个中间进行了数据(相同的数据)的修改操作。

下面这段话是我从【3】中摘抄的,是对不可重复读的另一种解释。

在这里插入图片描述

如下图所示。

在这里插入图片描述

不可重复读不同于脏读,前者发生了提交,需要事务A进行两次读取。 对于不可重复读来说,其在一些场合是可以允许存在的,暂时的读到旧数据,过一会刷新就可能得到了最新的数据。 但是对于执行类似备份数据库的任务之时,遇到这种不可重复读的事务还是比较尴尬的,因为观察到的状态可能不一致,将造成无法执行还原。
 

3.1.4 更新丢失

更新丢失应该算是一个写写冲突。 主要指的是下面的并发场景:A要更新x的数据,其首先读取x的值,然后再该值上加1再写回数据库。但是在读取x后,B写入了新的x并成功提交,而A还是在老的x值的基础上加1。这样,B的更新对于T1而言就像被丢弃了一样。

在这里插入图片描述
这里的更新丢失和上面说到的脏写从某种程度上来说很相似,在我看来都是一个事务的写最后覆盖了另一个事务的写。但是**这里的更新丢失更强调的是一种有状态的写,即(读-修改-写),而后者更像是一种无状态的写(直接写)。**还有一个要稍微注意一点的是这里A事务Write的时机,A事务开始的时候是先Read的,而事务B在事务A Write 之前已经完成了提交。这点对有些能防止在脏写的隔离级别但是却不能防止更新丢失具有重要意义。 具体的讨论可以参照【4】。

 

3.1.5 幻读

幻读是一种读写冲突,根据DDIA里的说法,指的是在一个事务中的写入改变了另一个事务查询结果的现象。但我觉得不是很准确,好像不可重复读也符合这个条件啊,╮(╯▽╰)╭?

下面摘抄另一个定义:
在这里插入图片描述
这个我觉得还是挺有道理的。在事务A执行两次select 之间发生了,事务B对数据集的插入(insert)。导致事务A第二次select莫名其妙的多了一些数据,可能导致接下来的行为发生错误。 如下图所示:

在这里插入图片描述

从某种程度上说,幻读应该算是不可重复读的特例如果说非要和不可重复读进行区分的话,我觉得讨论关键点在于具体实施的过程中锁机制的应用范围问题。对于幻读来说,其不可能对还未插入的数据进行加锁,但是不可重复读中的修改操作是可以对所有感兴趣的数据集进行加锁的

内容写得有点多,关于具体的隔离级别请参照小窥数据库事务(下)

参考

【1】、数据密集型应用系统设计(DDIA)
【2】、一文带你理解脏读,幻读,不可重复读与mysql的锁,事务隔离机制
【3】、从0到1理解数据库事务(上):并发问题与隔离级别
【4】、数据库事务的ACID隔离级中P0(脏写)和P4(更新丢失)的区别
【5】、关于幻读,可重复读的真实用例是什么?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值