数据库事务ACID特性以及隔离等级

数据库事务正确执行的4个基本要素是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

ACID特性
原子性整个事务中的所有操作,要么全部完成,要么全部不完成,不会停滞在中间某一个环节,假若事务在执行过程中发生了错误,那么将会回滚到事务开始执行前的状态,这个事务就像没有被执行过一样。
一致性一个事务可以改变封装的状态。事务必须始终保持系统处于一致的状态,不管任何给定的时间内并发的事务有多少。
隔离性指两个事务之间的隔离程度(稍后会说到)。
持久性在事务完成以后,该事务对数据库所做的更改会持久地保存在数据库中,而且不会被回滚

关于隔离性:

隔离性涉及了多个事务的并发状态,并且隔离性分为几个等级。当多个事务并发状态时,就可能产生数据丢失更新的问题。一般情况可能存在两类丢失更新。

假设某互联网账户存在两种消费形式,刷卡或者互联网消费。一对夫妇同时使用这个账户进行消费,老公使用刷卡消费,老婆使用联网消费,请看如下场景:

场景1:

第一类丢失更新
时刻    事务(1)老公   事务(2)老婆
T1查询余额10000元//do nothing
T2//do nothing查询余额10000元
T3//do nothing网购1000元
T4请客吃饭1000元//do nothing
T5提交事务,余额9000元//do nothing
T6//do nothing取消购买,回滚事务至T2,余额10000元

可以看出,整个事务的过程,老公花掉1000元,而老婆最后却查询剩余10000元,明显不符合事实。此类丢失更新,是由于两个事务,一个提交成功,一个回滚,导致的数据不一致,可以成为第一类丢失更新。(目前大多数数据库已经消灭了这种问题),下面来看另一个场景。

场景2:

第二类事务丢失更新
时刻    事务(1)老公   事务(2)老婆
T1查询余额10000元//do nothing
T2//do nothing查询余额10000元
T3//do nothing网购1000元
T4请客吃饭1000元//do nothing
T5提交事务,消费1000,余额9000元//do nothing
T6//do nothing提交成功,消费1000,余额9000元

 可以看出,整个事务过程有两笔消费分别是老公的和老婆的,但由于是在不同的事务中,事务之间无法互相干涉,所以最后查询出的余额都是9000元,不符合实际情况。这就是第二类丢失更新问题。

因此,为了克服第二类丢失更新带来的问题,也就是事务之间的协助的一致性,数据库标准规范了事务之间的隔离等级,可以在一定程度上减少出现丢失更新带来的问题。

隔离等级:

隔离等级可以在不同程度上减少丢失更新,隔离等级分为4层,分别是:脏读(dirty read)读/写提交(read commit)可重复读写(repeatable read)序列化(serilizable)。下面分别解释这四个事务隔离等级。

1.脏读:脏读是最低的隔离级别,它允许一个事务去读取另一个事务中未提交的数据,参考下列以脏读为隔离级别的例子

脏读
时刻    事务(1)老公   事务(2)老婆备注
T1查询余额10000元//do nothingXXX
T2//do nothing查询余额10000元XXX
T3//do nothing网购1000元,余额9000XXX
T4请客吃饭1000元,余额8000//do nothing事务1读取到事务2,余额为9000,再消费1000,余额为8000.
T5提交事务,消费1000,余额8000元//do nothing余额依旧为8000
T6//do nothing取消购买,回滚事务此时回滚,余额为8000

 

可以清楚地看到, 其中只有1笔成功的消费,但是最后得到的结果是8000,其原因就是因为脏读,在T4时刻,事务1读取了事务2的余额,而在T6时刻,由于老婆回滚事务(类似第一类丢失更新,但是目前已经不存在这种问题,因此,此时的余额为8000)。

2.读写提交:针对以上脏读所带来的的一些问题,,因此SQL提出了第二个隔离级别,也就是读/写提交。读写提交就是一个事务只能读取另一个事务已经提交的数据。例子:

读/写提交
时刻    事务(1)老公   事务(2)老婆备注
T1查询余额10000元//do nothingXXX
T2//do nothing查询余额10000元XXX
T3//do nothing网购1000元,余额9000XXX
T4请客吃饭1000元,余额9000//do nothing事务2的余额还未提交,不能读出,因此余额为9000
T5提交事务,消费1000,余额9000元//do nothing余额依旧为9000
T6//do nothing取消购买,回滚事务此时回滚,余额为9000

可以看出,此例的事务采用读写提交的隔离级别,一共花费1笔,最后余额为9000,在T4时刻,由于事务2还未提交,所以事务1无法读取到事务2的余额。事务1在T5时刻提交,事务2在T6时刻回滚(并不会带来第一类丢失更新问题,上面已经讲到过)。所以余额9000正确。但是也可能带来另外一种问题,如下:

不可重复读
时刻    事务(1)老公   事务(2)老婆备注
T1查询余额10000元//do nothingXXX
T2//do nothing查询余额10000元XXX
T3//do nothing网购1000元,余额9000XXX
T4请客吃饭2000元,余额8000//do nothing事务2的余额还未提交,不能读出,因此余额为10000-2000=8000
T5//do nothing继续网购8000,余额1000余额依旧为9000
T6//do nothing提交订单,提交事务老婆提交事务,余额为更新为1000
T7提交事务发现,余额为1000,不足买单//do nothing事务1可以读取到事务2已经提交的数据,并发现余额为1000

 可以看出,由于读写提交的隔离级别,在T6时刻事务2提交事务,余额更新为1000,但是事务1在T6时刻前不能读取事务2的数据,所以,从事务1的这一列来看,假若自己是老公,查到余额有10000元,于是和朋友去吃饭,结账时被告知花了2000元,但是却被提示余额不足!其实问题就在于他的账户余额的不能重复读取导致的问题,这种场景就叫做不可重(复)读。

3.可重复读:为了克服上面类似的不可重复读带来的问题,于是SQL标准提出了可重复读的隔离级别来解决问题。可重复读是针对数据库同一条记录而言,会使数据库同一条记录按照一个序列化进行操作。不会产生交叉的情况,因此可以保证数据的一致性。但是,在很多场景,数据库需要同时对多条记录进行读写操作,此时会产生下列情况:

幻读
时刻    事务(1)老公   事务(2)老婆备注
T1//do nothing查询消费记录:10条,准备打印XXX
T2开始消费,增加1条消费记录//do nothingXXX
T3提交事务//do nothingXXX
T4//do nothing共打印出11条消费记录事务2,打印得到11条结果,比查询时多了1条,她会思考这一条信息的真实性,这样的场景称为幻读

 在T2-T3时刻,事务1开启一笔消费,并提交,对于事务2,查询到10条结果,但她并不知道事务1的操作,并得到11条记录,于是便质疑多出的一条记录是否多余。产生幻读。

4.序列化:为了克服幻读,SQL标准提出了序列化隔离级别,它是一种让SQL按照顺序读写的方式,能够消除数据库事务之间产生的数据不一致的问题。

最后,各类隔离级别以及可能产生的现象如下表:

      隔离级别 / 现象

脏读不可重读幻读
脏读
读/写提交×
可重复读××
序列化×××

一般而言,从脏读到序列化的隔离级别,系统性能直线下降,因此级别越高,会严重的抑制并发,导致大量线程被挂起,需要大量的时间恢复,一般建议使用读写提交的方式来设置事务,这样不仅有助于高并发,也抑制了脏读的产生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值