引言
- 事务是数据库的重要概念;
- 事务处理是数据库的核心机制之一;
- 本文总结数据库事务的相关概念;
- 事务的概念可参考这篇文章
事务的性质
- 事务是数据库并发控制的基本单元;
- 事务是一个操作序列,不可分割,要么都执行,要么都不执行;
- 事务开始前和结束后,数据库中的数据应当保持一致性;
- 对数据库修改的多个事务彼此隔离,每个事务不应当影响或依赖其他事务;
数据库事务的四个特性及含义
性质 | 含义 |
---|---|
原子性(atomicity) | 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节; 事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样; 可以维护一个日志(或快照)记录事务执行前数据库的状态,如果事务执行失败,则回滚到事务执行前的状态; 转账时,不可能发生转出账户扣款了,转入账户未到账的情况; |
一致性(consistency) | 在事务开始之前和事务结束以后,数据库的完整性没有被破坏; 转账时,账户总金额不能多也不能少,保持一致性; |
隔离性(isolation) | 数据库允许多个并发事务同时对齐数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致; 事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable),下面详细讲了; |
持久性(durability) | 事务处理结束后,对数据的修改就是永久的,即便系统出现故障,即真正持久化到数据库中; |
事务执行进度
- begin:开始执行事务;
- aborted:事务执行一半终止了;
- roll back:事务已经回滚;
- commited:事务已提交,即执行成功,已经持久化到数据库中;
事务并发产生的问题
- 脏读:
- 一个事务读取了另一个事务操作但未提交的数据;
- 事务T1更新了数据还未提交,这时事务T2来读取相同的数据,则T2读到的数据其实是错误的数据,即脏数据,如果T1回滚的话,T2读到的数据是不可靠的;
- 基于脏数据所作的操作是不可能正确的;
- 丢失更新:
- 事务T1读取了数据,并执行了一些操作,然后更新数据,事务T2也做相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引起错误;
- 用排他锁解决;
- 不可重复读:
- 一个事务中的多个相同的查询返回了不同数据;
- 一个事务的两次读取中,读取相同的资源得到不同的值,当事务T2在事务T1的两次读取之间更新数据,则会发生此种错误(重点在修改);
- 用共享锁解决;
- 幻读:
- 事务并发执行时,其中一个事务对另一个事务中操作的结果集的影响;
- 事务T1对一定范围内执行操作,T2对相同的范围内执行不兼容的操作,这时会发生幻读;
- T1删除符合条件C1的所有数据,T2又插入了一些符合条件C1的数据,则在T1中再次查找符合条件C1的数据还是可以查到,这对T1来说好像是幻觉一样,这时的读取操作称为幻读(重点在新增或删除);
- 用排它锁解决;
事务隔离级别
- 下面是数据库事务从高到低的四个隔离级别;
级别名称 | 含义 |
---|---|
可串行化(Serializable) | 提供严格的事务隔离; 它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行; |
可重复读(Repeatable Read) | 事务T1读取一个数据块,另外,事务T2在事务T1未commit的情况下,试图写同样的数据块,此时,T2的写操作会被阻塞,直到T1commit后,写操作才被commit进入数据库; |
已提交读(Read committed) | 事务T1读取一个数据块,另外,事务T2在事务T1未commit的情况下,成功对同样的数据块进行写操作并commit,事务T1再读这个数据块时,结果和前一次不同; 即允许事务T1在两次读操作期间,其他事务对相同的数据块进行写操作; |
未提交读(Read uncommitted) | 事务T1在事务T2对于一个数据块的写操作未commit的情况下,可以读取T2修改后的结果; |
- 数据库事务隔离级别分别存在的并发问题
隔离级别 | 是否存在脏读 | 是否存在不可重复读 | 是否存在幻读 |
---|---|---|---|
Read uncommitted | 存在 | 存在 | 存在 |
Read committed | 不存在 | 存在 | 存在 |
Repeatable Read | 不存在 | 不存在 | 存在 |
Serializable | 不存在 | 不存在 | 不存在 |
- 以上所有的隔离级别都不允许脏写(一个数据项已经被另外一个尚未提交或中止的事务写入,则不允许对该数据项执行写操作);
- 数据库隔离界别越高,并发性能越低;
事务并发的处理方法
- 共享锁(S锁):
- 获准共享锁的事务职能读取数据,不能修改数据;
- 如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁;
- 排他锁(X锁):
- 获准排他锁的事务既能读取数据,也能修改数据;
- 如果事务T对数据A加上排他锁后,则其他事务不能在对A加任何类型的封锁;
死锁及其解决方法
- 死锁实例1:
- 现有事务A和B;
- A在b上拥有排它锁,B正在申请b上的共享锁,故B需要等待A释放b上的排它锁;
- B在a上拥有共享锁,A正在申请a上的排它锁,故A需要等待B释放a上的共享锁;
- A和B相互等待,形成死锁;
- 死锁实例2:
- 将数据库设置为可重复读的隔离级别;
- 事务A等待事务B执行完毕,以便commit对对象a的更改(B已经读取了a);
- 事务B等待事务A执行完毕,以便commit对对象b的更改(A已经读取了b);
- 死锁的解决:
- 必须有一个事务主动回滚;
- 回滚的事务自动释放了锁;
- 另一个未回滚的事务就可以继续向下执行;