数据库事务的一致性和原子性浅析

本文参考自知乎

Oracle事务的概念:事务用户保证数据的一致性,它是由一组dml语句组成,这组dml语句要么全部执行成功,要么全部执行失败。

1、事务一致性

举个例子:假如你去银行转1000元给你的朋友,所有的操作都完成之后,并且提示你转账成功(假设银行是立即转账,不存在延时的情况),你发现你的账户上减少了1000元,但是你打电话给你的朋友确认时,而你的朋友的账户却没有因此增加1000元,那么我们认为这时候的数据就是不一致的状态!!!

在数据库的实现的应用场景中,一致性可以分为数据库外部的一致性和数据库内部的一致性:

i、外部的一致性:由外部的应用编码来实现,即银行的应用在进行转账的操作时,必须在同一事务内部调用对账户A和账户B的操作。如果在这个阶段出现错误,这不是数据库本身能解决的,也不属于我们要讨论的范围。

ii、数据库内部的一致性:在同一个事物内部的一组操作必须全部成功(或者全部失败)。这就是事物处理的原子性

 

2、事务原子性

上面说了事务的原子性是保证:事务内的一组操作全部成功(或者全部失败),为了实现原子性,就需要通过日志:将所有对数据的操作都写入日志,如果事务中的一部分操作已经成功,但后面部分操作,因为系统断电,操作系统崩溃等问题而没有成功执行,那么就要通过回溯日志,将前面已经成功执行的操作撤销,从而达到"全部执行失败"的效果。

 

3、体现事务原子性和数据库一致性和持久性的常见场景

数据库崩溃后重启,此时数据库处于不一致的状态,此时数据库必须做crash recovery操作,大致步骤如下:

a、通过日志REDO(重演所有执行成功但是未写入到磁盘的操作)

b、再对到数据库崩溃前没有执行完成的事务进行UNDO(撤销所有执行了一部分,但是有一部份还没有执行完成,且尚未提交的操作,保证事务的原子性)

c、crash recovery结束后,数据库恢复了一致性,可以继续工作

 

4、多线程下的事务存在的问题

在单线程下,事务的原子性,能保证数据库的一致性,但是在某些情况下,事务的原子性并不能保证数据库的一致性。具体场景如下:

问题:事务一将100元转到帐号A,那么他先读取帐号A,然后再在帐号A上加上100,但是在这个过程中间,事务二也修改了帐号A,给它加了100元,那么最后的结果应该是加了200元。但是当事务一最终完成后,帐号A只加了100,因为事务二的修改结果被事务一覆盖掉了。

为了保证数据的一致性,引入隔离性,既保证每一个事务看到的数据是一致的,确保一个事务在处理数据的同时,没有其他事务对自己正在处理的数据进行干扰,就好像其他事务都是不存在的一样,即事务在并发执行后的状态,和串行执行后的状态时一样的,实现隔离性主要通过加锁的方式。

下面是通过"锁"解决事务在多线程下的数据不一致性问题:

a、悲观锁

即事务将当前操作所有涉及到的对象加锁,操作完成后释放给其他对象使用,为了尽可能的提高性能,发明了各种粒度(数据库级/表级/行级)/各种性质(共享锁/排它锁/共享意向锁/共享排它锁/共享排他意向锁......)的锁,想具体了解锁,请参考Oracle锁机制。为了解决死锁问题,又发明了两阶段锁协议/死锁检测等一系列技术

b、乐观锁

即不同事务看到通一对象(一般是数据行)的不同历史版本,如果有两个事务同时修改了同一数据行,那么在较晚提交的事务提交使进行冲突检测。实现也有两种:一种是通过UNDO的方式获取数据行的历史版本,另一种是简单的在内存中保存数据行的不同历史版本,通过时间戳来区分

猜你喜欢

转载自blog.csdn.net/dhfzhishi/article/details/80073488