一、事务模型
关于事务,就是构成单一逻辑工作单元的操作集合。从数据库的角度来看,必须保证事务正确以及完整的执行顺序,在SQL中事务通常用
begin transaction和end transaction语句来界定。
ACID特性
对于数据库维护的事务,通常有以下四个特性:
原子性
事务中的多个数据库操作是一个不可分割的原子单元整体,只有所有的操作执行成功,整个事务才提交;事务中的任何一个数据库操作失败,已经执行的任何操作都必须被撤销,让数据库返回初始状态。
对于原子性的实现,则会采用一个恢复机制,即它可以维护一个日志,日志中会记录磁盘中存储的旧值。如果一个事务不能继续它的执行,数据库系统会从日志中恢复旧值,从而事务处于中止状态。
一致性
事务操作成功后,保证数据不会被破坏,即事务可以从一个状态转变到另一个一致的状态。
假如A账户转账50元到B账户,对应A账户总额减少50,B账户总额增加50,不管操作成功与否,A和B账户的存款总额是不变的。
隔离性
并发事务间不会相互影响。
为满足隔离性,数据库会保证事务的可恢复调度,即对于每对事务T1和T2,如果T2读取了之前由T1所写的数据项,那么T1应该先于T2提交。
举一个反例,如下图,因为T7事务是在T6事务对数据A写操作后对A进行读操作,假如此时T6在T7读操作后发生了回滚,那么T7事务将会发生一个不可恢复的操作。
持久性
一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中。即使在事务提交后,数据库马上崩溃,在数据库重启时,也必须保证能够通过某种机制恢复数据。
对于保证事务的持久性,有以下两点思路:
- 事务做的更新在事务结束前已经写入磁盘
- 有关事务已执行的更新信息已经写入磁盘
事务隔离性级别
以下隔离性级别由高到低逐渐减弱;较低的级别会有较高的并发性能,但安全性能会逐渐降低。
·可串行化(serializable):保证科可串行化调度。
·可重复读(repeatable read):只允许读取已提交数据,且一个事务两次读取期间其他事务不得更新该数据。
·已提交读(read committed):只允许读取已提交数据,但不要求可重复读。
·未提交读(read uncommitted):允许读取未提交数据。
事务并发引发的问题
脏读:一个事务读到另外一个事务还没有提交的数据。
例如:事务T1读取并且修改了一个数据项A,而T2事务在T1还未提交就读取了数据项A。
不可重复读:由于查询时系统中其他事务修改的提交,从而导致一个事务范围内两个相同的查询却返回了不同数据。
例如:事务T1读取了数据项A,然后T2修改了A,接下来T1执行相同的读取操作,结果两次读取的并不相同
幻读:幻读是指当事务不是独立执行时发生的一种现象。
例如:事务T1对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行;同时,事务T2也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户会错认为表中还有没有修改的数据行。
二、基于锁的协议
给数据加锁主要有两种方式:
·共享锁(shared):若事务T1获得数据Q的共享型锁,则T1可读但不可写Q。
·排他锁(exclusive):若事务T1获得数据Q的排他型锁,则T1既可读又可写Q。
任何时候,一个具体数据上可同时拥有(被不同事务持有的)多个共享锁;而排他锁请求只能等待,直到数据上的其他所有共享锁被释放。
申请加锁的事务在并发控制管理器授权加锁之前不能执行下一个动作。
封锁协议
封锁:在整个事务结束时才释放锁。
一般而言,若为了避免不一致性而采用封锁,则死锁一般是不可避免的,如下图例:
T3申请了B数据的共享锁,而T4申请了A数据的排他锁,然后T4在申请B的排他锁以及T3申请A的共享锁时都将陷入死锁,最终某一个事务将会回滚,保证数据的访问继续。
饿死:有可能存在一个事务序列,其中每个事务申请一个数据项Q的共享锁,且在授权加锁后一段时间释放锁,而想对该数据申请排他锁的一个事务T将永远得不到进展的一种情况。
为避免饿死,事务T申请对数据Q加M锁时,并发控制管理器授权加锁的条件是:
1、不存在数据项Q上持有与M型锁冲突的锁的其他事务
2、不存在等待对数据项Q加锁且先于T申请加锁的事务
两阶段封锁协议
两阶段封锁协议可保证串行性,该协议要求事务分两个阶段进行加锁和解锁的申请:
1、增长阶段:事务可获得锁,但不能释放锁
2、缩减阶段:事务可释放锁,但不能获得锁
锁转换:包括锁升级(S锁变X锁)和锁降级(X锁变S锁);锁升级只能发生在事务增长阶段,而锁降级只能发送在事务缩减阶段。
锁转换可有效避免一个事务因分别进行读操作、写操作而重复加锁的情况,转而是将一个共享锁转换为一个排他锁。