数据库之隔离级别,脏读幻读,事务特性,封锁
1 数据库事务
1.1 隔离级别
1.1.1 默认隔离级别
ISOLATION_DEFAULT: 默认隔离级别 ,这是一个PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别,
oracle默认的是.:READ_COMMITTED ,mysql默认的是:REPEATABLE_READ
1.1.2 读未提交
ISOLATION_READ_UNCOMMITTED:读未提交,这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。
1.1.3 读已提交
ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
1.1.4 可重复读
ISOLATION_REPEATABLE_READ:可重复读 ,这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免不可重复读的情况产生。
1.1.5 序列化
ISOLATION_SERIALIZABLE:序列化,这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
除了防止脏读,不可重复读外,还避免了幻像读。
1.2 事务的四个特性
事务的四个特性(ACID)
1.2.1 原子性
原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令之前的数据状态
1.2.2 一致性
一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定
1.2.3 隔离性
隔离性(Isolation):是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即:要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行
1.2.4 持久性
持久性(Durability):当事务正确完成后,它对于数据的改变时永久性的
1.3 事务关键词
1.3.1 定义(脏读,不可重复读,虚读)
脏读:指一个事务读取了一个未提交事务的数据,即当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
说明:事务1更新了记录,但没有提交,事务2读取了更新后的行,然后事务T1回滚,现在T2读取无效。不可重复读:在一个事务内读取表中的某一行数据,多次读取结果不同,一个事务读取到了另一个事务提交后(update)的数据。
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
说明:事务1读取记录时,事务2更新了记录并提交,事务1再次读取时可以看到事务2修改后的记录;虚读(幻读):在一个事务内读取了别的事务插入的数据,导致前后读取不一致(insert)
指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
说明:事务1读取记录时事务2增加了记录并提交,事务1再次读取时可以看到事务2新增的记录;
1.3.2 不可重复读与幻读的区别
不可重复读的重点是修改:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
在一个事务中前后两次读取的结果并不一致,导致了不可重复读。
例如:在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
con1 = getConnection();
select salary from employee empId ="Mary";
在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.
con2 = getConnection();
update employee set salary = 2000;
con2.commit();
在事务1中,Mary 再次读取自己的工资时,工资变为了2000
//con1
select salary from employee empId ="Mary";
幻读的重点在于新增或者删除: 同样的条件, 第1次和第2次读出来的记录数不一样
例如:目前工资为1000的员工有10人。事务1,读取所有工资为1000的员工。
con2 = getConnection();
Insert into employee(empId,salary) values("Lili",1000);
con2.commit();
事务1再次读取所有工资为1000的员工
select * from employee where salary =1000;
共读取到了11条记录,这就产生了幻读。
从总的结果来看,似乎不可重复读和幻读都表现为两次读取的结果不一致。
但如果你从控制的角度来看, 两者的区别就比较大。 对于前者, 只需要锁住满足条件的记录。 对于后者, 要锁住满足条件及其相近的记录。
2 数据库封锁
2.1 封锁概念
封锁是事务T在对某个数据对象例如表、记录等操作之前,先向系统发出请求,对其加锁。加锁后事务T就对该数据有了一定的控制,在事务T释放它的锁之前,其他事务不能更新此数据对象
封锁分类,基本的封锁类型有两种:排他锁(exclusive locks,简称X锁)和共享锁(share locks,简称S锁)
- 排他锁又称为写锁,若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁为止,这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。
- 共享锁又称为读锁,若事务T对事务对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到事务T释放A上的S锁,这就保证了其他事务可以读A,但在事务T释放A上的S锁之前
不能对A做任何修改。
排他锁与共享锁的控制方式如下所示:
第一行是T2,左第一列是T1,Y = Yes,相容的请求,N=No,不相容的请求
| T1 \ T2 | X | S | - |
|---|---|---|---|
| X | N | N | Y |
| S | N | Y | Y |
| - | Y | Y | Y |
点击此处了解MySQL中行表锁,共享排他锁,悲观乐观锁,记录间隙意向锁讲解
2.2 封锁协议
2.2.1 定义
封锁协议:
- 一级封锁协议
一级封锁协议是指事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放,事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK),一级封锁协议可防止丢失修改,并保证事务T是可恢复的 - 二级封锁协议
二级封锁协议是指在一级封锁协议基础上增加事务T在读取数据R之前必须先对其加S锁,读完后即可释放S锁,二级封锁协议除了防止丢失修改,还可以进一步防止读“脏”数据 - 三级封锁协议
三级封锁协议是指在一级封锁协议的基础上增加事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放,三级封锁协议除了防止丢失修改和读“脏”数据外,还进一步保证了不可重复读(这里面是锁模式转换)
不同级别的封锁协议和一致性保证
| X锁 | X锁 | S锁 | S锁 | 一致性保证 | 一致性保证 | 一致性保证 | |
|---|---|---|---|---|---|---|---|
| 操作结束释放 | 事务结束释放 | 操作结束释放 | 事务结束释放 | 不丢失修改 | 不读“脏”数据 | 可重复读 | |
| 一级封锁协议 | √ | √ | |||||
| 二级封锁协议 | √ | √ | √ | √ | |||
| 三级封锁协议 | √ | √ | √ | √ | √ |
2.2.2 锁升级降级
锁的升级和降级是现实数据库管理系统(如Oracle, SQL Server, MySQL InnoDB)中实现并发控制的核心机制
锁升级 (Lock Upgrade): 将一个已经持有的较弱的锁转换为一个更强的锁
锁降级 (Lock Downgrade) : 将一个已经持有的较强的锁转换为一个更弱的锁
如果T1持有了读锁,在没释放时,可以继续加x锁
2.2.2.1 对同一个数据项
如果事务T1已经对数据行R持有了S锁。在释放S锁之前,它能否再对同一个数据行R申请X锁呢
答案:绝大多数数据库管理系统(DBMS)不允许这样做。
原因如下:
锁升级(Lock Upgrade):这个操作的本质是将共享锁(S锁)升级为排他锁(X锁),这是一个非常常见的需求。如果一个事务已经持有一个数据项上的S锁,那么它有权将这个S锁升级为X锁,前提是没有其他事务也持有该数据项上的S锁
潜在的死锁风险:虽然同一个事务对自己持有的锁进行升级听起来很安全,但数据库必须考虑更复杂的并发场景。
假设:
- T1 持有了数据行R的S锁,T2 也持有了数据行R的S锁。
- 现在T1请求将S锁升级为X锁,但X锁与S锁互斥,因此T1的升级请求必须等待T2释放它的S锁。
- 同时,T2可能也正在请求升级为X锁,那么T2也在等待T1释放S锁。
这就形成了一个死锁(Deadlock):T1等T2,T2等T1。
为了避免这种死锁,数据库通常有两种处理方式:
隐式锁升级:许多系统(如MySQL InnoDB)支持一种更智能的方式。当事务持有S锁并试图修改数据时,数据库会自动、原子性地进行锁升级(从S锁到X锁),但这个升级请求可能会被阻塞,直到所有其他的S锁都被释放。- 显式锁升级:有些数据库提供显式的命令(如 SELECT ... FOR UPDATE 语句)。在查询时就直接请求一个X锁(或一个可以升级的锁),而不是先请求S锁再升级。
2.2.2.2 对不同数据项
问题:事务T1已经对数据行R1持有了S锁。在释放S锁之前,它能否对另一个数据行R2申请X锁?
答案:完全可以,这是允许的。
原因如下:
锁的粒度(
Lock Granularity):锁通常是施加在特定的数据对象上的(如一行记录、一个页、一个表)。对一个对象的锁不会影响对其他无关对象的锁操作。
两阶段锁协议(2PL):只要事务处于“扩展阶段”(Growing Phase),它就可以继续申请新的锁,无论锁的类型和对象是什么。申请对R2的X锁与它已经持有的对R1的S锁完全不冲突。
提高并发度:允许这种情况可以大大提高系统的并发处理能力。一个事务可以同时读取一些数据(持有S锁)和修改另一些数据(持有X锁)。
2.3 活锁和死锁
2.3.1 活锁
2.3.1.1 定义
如果事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的锁之后系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后系统又批准了T4的请求....T2有可能永远等待,这就是活锁的情形
活锁产生的原因:当一系列封锁不能按照其先后顺序执行时,可能导致一些事务无限期地等待某个封锁,从而导致活锁。
2.3.1.2 解决方法
解决活锁的简单方法是采用先来先服务的策略。当多个事务请求封锁同一数据对象时,封锁子系统按请求封锁的先后次序对事务排队,数据对象上的锁一旦释放就批准申请队列中的第一个事务获得锁
2.3.2 死锁
2.3.2.1 定义
满足死锁四要素(互斥、占有且等待、非抢占、循环等待)
如果在事务T1封锁了数据R1,T2封锁了数据R2,然后T1又请求封锁R2,因T2已封锁了R2,于是T1等待T2释放R2上的锁;接着T2又申请封锁R1,因T1已封锁了R1,T2也只能等待T1释放R1上的锁
在数据库中解决死锁方法有俩类,一类方法是采取一定措施来预防死锁的发生,另一类方法是允许发生死锁,采用一定手段定期诊断系统中有无死锁,若有则接触之
2.3.2.2 死锁的预防
在数据库中,产生死锁的原因是俩个或多个事务都已封锁了一些数据对象,然后又都请求已被其他事务封锁的数据对象和锁,从而出现死锁等待。防止死锁的发生其实就是要破坏产生死锁的条件,预防死锁通常有俩种方法:
- 一次封锁法
一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行。一次封锁法虽然可以有效地防止死锁的发生,但也存在问题。- 一次就将以后要用到的全部数据加锁,势必扩大了封锁的范围,从而降低了系统的并发度;
- 数据库中数据是不断变化的,原来不要求封锁的数据在执行过程中可能会变成封锁对象,所以很难事先精确地确定每个事务所要封锁的数据对象,为此只能扩大封锁范围,将事务在执行过程中可能要封锁的数据对象全部加锁,这就进一步降低了并发度。
- 顺序封锁法
顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。顺序封锁法可以有效地防止死锁,但也同样存在问题。- 数据库系统中封锁的数据对象极多,并且随数据的插入、删除等操作而不断地变化,要维护这样的资源的封锁顺序非常困难,成本很高。
- 事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序去施加封锁。
2.3.2.3 死锁的诊断与解除
数据库系统中诊断死锁的方法与操作系统类似,一般使用超时法或事务等待图法:
- 超时法
如果一个事务的等待时间超过了规定的时限,就认为发生了死锁。超时法实现简单,但其不足也很明显。- 有可能误判死锁,事务因为其他原因使等待时间超过时限,系统会误认为发生了死锁。
- 时限若设置得太长,死锁发生后不能及时发现。
- 等待图法
事务等待图是一个有向图G=(T,U)。T为结点的集合,每个结点表示正运行的事务;U为边的集合,每条边表示事务等待的情况。若T1等待T2,则T1,T2之间划一条有向边,从T1指向T2。
事务等待图动态地反映了所有事务的等待情况。并发控制子系统周期性地生成事务等待图,并进行检测。如果发现图中存在回路,则表示系统中出现了死锁。
![image]()
DBMS并发控制子系统检测到死锁后,就要设法解除。通常采用的方法是选择一个处理死锁代价最小的事务,将其撤消,释放此事务持有的所有锁,使其他事务得以继续运行。对撤销的事务所执行的数据修改操作必须加以恢复。
2.4 两段锁协议
为了保证并发调度的正确性,DBMS的并发控制机制必须提供一定的手段来保证调度是可串行化的。目前DBMS普遍采用两段锁(Two Phase Locking,简称2PL)协议的方法实现并发调度的可串行性,从而保证调度的正确性。
2.4.1 定义
两段锁协议是指所有事务必须分两个阶段对数据项加锁和解锁:在对任何数据进行读、写操作之前,首先要申请并获得对该数据的封锁;在释放一个封锁之后,事务不再申请和获得任何其他封锁。
“两段”锁的含义是,事务分为两个阶段:
- 第一阶段(扩展阶段 Growing Phase)是
获得封锁,事务可以不断申请新锁,但不能释放任何锁 - 第二阶段(收缩阶段 Shrinking Phase )是
释放封锁,事务可以释放锁,但不能申请任何新锁。
目的:保证事务的隔离性(Isolation),实现可序列化调度,防止并发事务导致的数据不一致(如脏读、不可重复读)
2.4.2 两段锁协议和一次封锁法的异同点
- 相同点
一次封锁法要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行,因此一次封锁法遵守两段锁协议。 - 不同点
两段锁协议并不要求事务必须一次将所有要使用的数据全部加锁,遵守两段锁协议的事务可能发生死锁。
2.5 封锁的粒度
2.5.1 封锁粒度
封锁对象的大小称为封锁粒度(Granularity),封锁对象可以是逻辑单元,也可以是物理单元。
- 封锁粒度与系统的关系
封锁粒度与系统的并发度和并发控制的开销密切相关。- 封锁的粒度越大,数据库所能够封锁的数据单元就越少,并发度就越小,系统开销也越小;
- 封锁的粒度越小,并发度较高,但系统开销也就越大。
- 粒度的选择
在一个系统中同时支持多种封锁粒度供不同的事务选择的封锁方法称为多粒度封锁。选择封锁粒度时应该同时考虑封锁开销和并发度两个因素,适当选择封锁粒度以求得最优的效果。- 需要处理大量元组的事务可以以关系为封锁粒度;
- 需要处理多个关系的大量元组的事务可以以数据库为封锁粒度;
- 对于处理少量元组的用户事务,以元组为封锁粒度比较合适。
2.5.2 多粒度封锁
2.5.2.1 结构
多粒度树的根结点是整个数据库,表示最大的数据粒度。叶结点表示最小的数据粒度。

申请封锁时应该按自上而下的次序进行,释放封锁时则应该按自下而上的次序进行。
2.5.2.2 显式封锁和隐式封锁
多粒度封锁协议允许多粒度树中的每个结点被独立地加锁。对一个结点加锁意味着这个结点的所有后裔结点也被加以同样类型的锁。因此,在多粒度封锁中一个数据对象可能以两种方式封锁,显式封锁和隐式封锁。
- 显式封锁
显式封锁是应事务的要求直接加到数据对象上的封锁。 - 隐式封锁
隐式封锁是该数据对象没有独立加锁,是由于其上级结点加锁而使该数据对象加上了锁。
2.5.2.3 检查封锁方法
系统检查封锁冲突时不仅要检查显式封锁还要检查隐式封锁:
- 对某个数据对象加锁,系统要检查该数据对象上有无显式封锁与之冲突;
- 检查其所有上级结点,看本事务的显式封锁是否与该数据对象上的隐式封锁冲突;
- 检查其所有下级结点,看上面的显式封锁是否与本事务的隐式封锁冲突。
2.5.3 意向锁
引进意向锁是为了提高封锁子系统的效率,封锁子系统支持多种封锁粒度。原因是在多粒度封锁方法中一个数据对象可能以两种方式加锁——显式封锁和隐式封锁。因此系统在对某一数据对象加锁时不仅要检查该数据对象上有无(显式和隐式)封锁与之冲突,还要检查其所有上级结点和所有下级结点,看申请的封锁是否与这些结点上的(显式和隐式)封锁冲突,这样的检查方法效率很低,为此引进了意向锁。
意向锁的含义是:对任一结点加锁时,必须先对它的上层结点加意向锁。
引进意向锁后,系统对某一数据对象加锁时,不必逐个检查与下一级结点的封锁冲突。
如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁;对一结点加锁时,必须先对它的上层结点加意向锁。三种常用的意向锁包括意向共享锁(Intent Share Lock,简称IS锁);意向排它锁(Intent Exclusive Lock,简称IX锁);共享意向排它锁(Share Intent Exclusive Lock,简称SIX锁)。
- IS锁
如果对一个数据对象加IS锁,表示它的后裔结点拟(意向)加S锁。 - IX锁
如果对一个数据对象加IX锁,表示它的后裔结点拟(意向)加X锁。 - SIX锁
如果对一个数据对象加SIX锁,表示对它加S锁,再加IX锁,即SIX=S+IX。
意向锁相互兼容
| T1 \ T2 | S | X | IS | IX | SIX | - |
|---|---|---|---|---|---|---|
| S | Y | N | Y | N | N | Y |
| X | N | N | N | N | N | Y |
| IS | Y | N | Y | Y | Y | Y |
| IX | N | N | Y | Y | N | Y |
| SIX | N | N | Y | N | N | Y |
| - | Y | Y | Y | Y | Y | Y |
申请封锁时应该按自上而下的次序进行,释放封锁时则应该按自下而上的次序进行。具有意向锁的多粒度封锁方法提高了系统的并发度,减少了加锁和解锁的开销。


浙公网安备 33010602011771号