数据库事务深入分析

来源:互联网 发布:上海清算中心扣钱 知乎 编辑:程序博客网 时间:2024/06/06 01:59

一、数据库事务的四大特性

1、原子性(Atomic)
事务必须是原子工作单元,对数据的修改要么全都执行,要么全不执行。这一性质使得系统在崩溃之后仍能得到保证,当系统发生崩溃时,检查点可根据事务操作日志判断事务在何种状态,从而做出提交or撤销操作;
若深入了解数据库事务的原子性,请阅读我的上一篇博客:浅谈mysql体系结构中的日志系统部分,可清晰了解数据库事务原子性是如何保证的。

2、一致性(Consistent)
一致性要求事务执行完成后,将数据库从一个状态转变为另一个状态,他是一种以一致性规则为基础的逻辑属性,例如在转账操作中,个账户金额必须平衡;

3、隔离性(Insulation)
一个事务的执行不能被其他事务所干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能相互干扰。它要求即使有多个事务并发执行,看上去每个成功事务按穿行调度执行一样,即在一个事务没有结束之前,另外的事务操作不能开始。由于串行调度的性能问题,我们需要进行交错操作的调度,使其效果与串行调度一致,实现机制通过对事务的数据访问对象加适当的锁,从而排斥其他事务对同意数据库对象的并发操作;

4、持久性(Duration)
事务完成之后,他对系统的影响是永久性的,系统发生故障之后也不能改变事务的持久性;

二、深入解析数据库事务并发情况的隔离性

I、数据库多事务并发影响及实例

1、出现脏读

当事务A访问数据库,并且对数据进行修改 而这种修改还没有提交到数据库中,这时,事务B访问数据库,可以看到事务A修改的结果;若此时事务A进行数据回滚,事务B再次查询,得到的结果是事务A回滚之后的数据

以取款事务和柜台转账事务为例:

取款事务包含以下步骤:
(1)某银行客户在银行前台请求取款100元,出纳员先查询账户信息,得知存款余额为1000元。
(2)出纳员判断出存款额超过了取款额,就支付给客户100元,并将账户上的存款余额改为900元。

支票转账事务包含以下步骤:
(1)某出纳员处理一转帐支票,该支票向一帐户汇入100元。出纳员先查询账户信息,得知存款余额为900元。
(2)出纳员将存款余额改为1000元。

图解如下:
这里写图片描述

取款事务在T5时刻把存款余额改为900元,转账事务在T6时刻查询账户的存款余额为900元,取款事务在T7时刻被撤销,支票转账事务在T8时刻把存款余额改为1000元。
由于支票转账事务查询到了取款事务未提交的更新数据,并且在这个查询结果的基础上进行更新操作,如果取款事务最后被撤销,会导致银行客户损失100元。

2、*不可重复读*

当事务A访问数据库,并且对数据进行了修改,紧当事务A的修改已经提交到数据库后,事务B访问数据库,才可以看到事务A修改的结果;

图解如下:
这里写图片描述

取款事务在T5时刻根据在T3时刻的查询结果,把存款余额改为1000-100元,在T7时刻提交事务。转账事务在T8时刻根据在T6时刻的查询结果,把存款余额改为1000+100元。由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失100元。

这也称为第二类丢失更新,即一个事务覆盖另一个事务已提交的更新数据

这种情况是如何出现的?

我们首先就需要了解数据库的锁机制

共享锁【S锁】
又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

排他锁【X锁】
又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

三级封锁协议:

一级封锁协议:事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。 一级封锁协议可以防止丢失修改,并保证事务T是可恢复的。使用一级封锁协议可以解决丢失修改问题。在一级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,它不能保证不可重复读。

二级封锁协议:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后方可释放S锁。 二级封锁协议除防止了丢失修改,还可以进一步防止读“脏”数据。但在二级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读。

三级封锁协议 :一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。 三级封锁协议除防止了丢失修改和不读“脏”数据外,还进一步防止了不可重复读。

当我们将数据库级别设置为Read Committed时使用了一级封锁协议,即读取数据时不加任何锁,修改、删除数据时,加了排它锁

3、可重复读、幻读

事务A读取记录时事务B增加了记录并提交,事务A再次读取时可以看到事务B新增的记录;

很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。
当我们把数据库事务级别设置为Repeatable Read(实际使用了上述提到的二级封锁协议)时,事务执行查询sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。
所以说他只能保证被本事务操作的数据不被其他事务修改,不能保证其他事务提交新的数据

4、可序列化

幻读,不可重复读和脏读都不允许;
因为获得范围锁,且事务是一个接着一个串行执行,则保证了不会发生幻读,但是效率会特别低

II、数据库事务隔离级别
这里写图片描述

III、数据库隔离级别与并发性能的关系
这里写图片描述

IV、设置隔离级别的原则

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、虚读
和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁乐观锁来控制。

参考博客:
http://www.jb51.net/article/50047.htm–表级锁、行级锁的介绍
http://blog.csdn.net/jialinqiang/article/details/8723044

欢迎大家指出或质疑博客中错误观点。谢谢!

转载请标明博客出处

0 0