一、引言
1、若采用封锁技术实现并发控制,事务在访问数据库对象前要在数据库对象上加锁,为提高事务的并发程度,商用DBMS会采用一种多粒度封锁方法
2、事务可访问的数据库对象可以是逻辑单元,包括关系、关系中的元组、关系的属性值或属性值集合、关系上的索引项或整个索引、甚至整个数据库等,也可以是物理单元,比如数据所占用的磁盘块 、索引所占用的磁盘块、磁盘块中的物理记录等
3、多粒度封锁技术根据封锁的数据对象的大小不同化分封锁的粒度,将锁加在不同粒度的数据库对象上。在具体DBMS应用时,实现不同的多粒度封锁模式
二、封锁粒度
1、封锁粒度:封锁单元的大小。如果封锁对象单元小,则称为细粒度,否则称为粗粒度
(1)实际应用中,通常细粒度锁是加在元组上的锁,称为元组锁,也称为行级锁
(2)粗粒度锁是加在关系上的锁,称为关系锁,也称为表级锁
当对关系加上粗粒度锁后,关系中的所有元组,也被隐式地加上了同样的锁,因此,相对粗粒度锁,使用细粒度锁的事务可以只封锁事务实际访问的数据对象,事务的并发程度会更好
2、比如,对于银行业务系统
如果将账户关系表作为封锁对象,整个关系只能被一个事务加锁,由于业务系统中会有并发的许多事务,对账户表中的不同用户的信息进行更新,这些事务都需要账户关系表上的一个排他锁,那么同一时间只能有一个存款或取款业务能进行,系统中事务的并发程度会很低
如果将关系表中的元组作为封锁对象,对不同账户信息进行操作的事务都可以同时对账户信息进行更新
虽然使用细粒度锁使事务的并发程度更好,但由于锁的信息要占用系统的内存空间,并需要DBMS的并发控制机制花费系统时间来管理
3、因此封锁粒度的选择需同时考虑管理锁的开销和事务并发度两个因素,以获得最佳的系统性能
4、为此,有的DBMS实现了多粒度封锁功能,来更好地满足应用需求和提高系统性能
(1)比如,对于需要处理大量元组的事务,可以选择关系为封锁粒度
(2)而对于只处理少量元组的用户事务,选择以元组为封锁粒度更合适
(3)也可允许事务根据操作需要来选择封锁粒度
三、多粒度封锁模式
1、不同的DBMS实现的多粒度封锁模式不同,比如SQL Server提供了如下几种锁
(1)NOLOCK锁:用于SELECT语句,读数据前不用申请数据对象上的锁
(2)TABLOCK锁:在关系表上加共享锁,在读完数据后立即释放锁
(3)HOLDLOCK保持锁:用于TABLOCK后,可将共享锁保留到事务完成,而不是读完数据立即释放锁
(4)UPDLOCK锁:在操作语句中满足条件的指定元组上加更新锁,允许对这些元组进行更新操作,其他事务可以对同一关系表中的其他元组也加更新锁,但不允许对表加共享锁和共享锁
(5)TABLOCKX锁:在关系表上加排他锁
2、该封锁模式对应的封锁策略,可用这个锁相容矩阵来描述
3、SQL Server提供在SELECT、INSERT、UPDATE和DELETE等语句中添加WITH子句来显示地进行封锁操作
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030'
4、下面我们基于创建的学生选课数据库,在SQL Server上通过两个并发事务的执行,来理解多粒度封锁技术的应用。
(1)我们通过在SQL Server的对象资源管理器上打开两个查询窗口来执行两个并发事务,请在事务的并发执行过程中并发调度中每一步的操作结果,分析操作的封锁情况以及多粒度封锁对事务的并发性和数据一致性的影响
(2)在查询窗口1开始执行事务T1,首先对关系表SC表中SNO='202218014030' 的元组进行查询,使用TABLOCK锁得到满足条件的两个元组
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
(3)再在查询窗口2开始执行事务T2,修改学号为'202218014030'的学生的成绩为99,使用UPDLOCK锁
BEGIN TRAN T2
UPDATE SC WITH(UPDLOCK)
SET GRADE=99
WHERE SNO='202218014030';
语句可以执行,说明事务T1读完数据后立刻释放了封锁
(4)返回事务T1继续运行事务,使用NOLOCK锁SC中SNO='202218014030'的元组,查询可执行,事务T1可读到事务T2修改后的数据,看到SC表中SNO='202218014030'的元组的GRADE值被更新,而事务T2没有提交,事务T1读取了脏数据
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(NOLOCK)
WHERE SNO='202218014030';
(5)事务T1继续执行,使用UPDLOCK锁,修改学号为‘202218014032’的学生的成绩为96,语句可以执行,说明事务T2只在SNO='202218014030'的元组上加上了更新锁
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(NOLOCK)
WHERE SNO='202218014030';
UPDATE SC WITH(UPDLOCK)
SET GRADE=96
WHERE SNO='202218014032';
(6)返回事务T2继续运行事务,使用TABLOCK锁,对SC表中SNO='202218014032'的元组进行查询,无结果显示,事务T2处于锁等待状态
BEGIN TRAN T2
UPDATE SC WITH(UPDLOCK)
SET GRADE=99
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014032';
(7)返回事务T1提交事务,再返回事务T2可看到事务T2已执行,读取了事务T1提交后的数据,事务T2不会读取脏数据
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(NOLOCK)
WHERE SNO='202218014030';
UPDATE SC WITH(UPDLOCK)
SET GRADE=96
WHERE SNO='202218014032';
COMMIT TRAN T1;
(8)然后回滚事务T2,对事务T2中修改的元组进行查询,可见对SC表中SNO='202218014030'的元组的修改被撤销了,而已提交的事务T1已读取了事务T2中间修改过的值,读取了脏数据
BEGIN TRAN T2
UPDATE SC WITH(UPDLOCK)
SET GRADE=99
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014032';
ROLLBACK
SELECT *
FROM SC
WHERE SNO='202218014030';
5、通过前面两个并发事务的执行,可以看到
(1)SELECT语句加NOLOCK锁可以使并发操作立即执行,但会读取脏数据
(2)TABLOCK锁是一个短期读锁,可以避免读脏数据
(3)UPDLOCK锁是一个长期写锁 ,是细粒度锁,元组锁
6、下面来看另一组并发事务的执行
(1)在查询窗口1开始执行事务T1,还是首先使用TABLOCK锁,对关系表SC表中SNO='202218014030'的元组进行查询,仍然得到满足条件的3个元组
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
(2)在查询窗口2执行更新语句,也就是一个隐式定义的事务,使用UPDLOCK锁修改修改学号为‘202218014030’的学生的成绩为99,语句可以执行,说明事务T1在读完数据后立刻释放了封锁
UPDATE SC WITH(UPDLOCK)
SET GRADE=99
WHERE SNO='202218014030';
(3)返回事务T1继续运行事务,再次执行相同的查询操作,操作可执行,得到的查询结果与前一次查询结果不一样,说明短期读锁不具有可重复读特性
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
(4)若在事务T1将TABLOCK后加保持锁,再进行查询
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK HOLDLOCK)
WHERE SNO='202218014030';
(5)然后再在查询窗口2执行更新语句, 此时操作不再执行
UPDATE SC WITH(UPDLOCK)
SET GRADE=99
WHERE SNO='202218014030';
UPDATE SC WITH(UPDLOCK)
SET GRADE=0
WHERE SNO='202218014030';
(6)返回查询窗口1,在事务T1中再次执行前面的查询操作,查询结果不变,说明长期读锁具有可重复读特性
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK HOLDLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK HOLDLOCK)
WHERE SNO='202218014030';
(7)待事务T1提交后,查询窗口2中的操作才可执行
BEGIN TRAN T1
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK HOLDLOCK)
WHERE SNO='202218014030';
SELECT SNO,GRADE
FROM SC WITH(TABLOCK HOLDLOCK)
WHERE SNO='202218014030';
COMMIT TRAN T1
7、通过前面两个并发事务的执行可以看到
(1)使用TABLOCK锁,事务可以避免读脏数据,但数据不具有可重复读的特性
(2)使用HOLDLOCK锁,可使TABLOCK锁变为长期读锁,可以保证数据的可重复读特性
8、多粒度封锁的实现
(1)DBMS为实现多粒度封锁,在事务对数据对象进行显示封锁的时候,并发控制机制为了更好地解决元组锁和关系锁之间的冲突,在给元组加锁前,要给元组所在的关系加一个意向锁
(2)意向锁的作用就是标识关系中某些元组正在被锁定或其他用户将要锁定关系中的某些元组
(3)意向锁是由系统隐式进行添加的,不能人为干预
(4)根据事务要对数据对象要进行的读写操作不同,意向锁也有意向共享锁(IS)和意向排他锁(IX)之分
(5)意向锁之间不会产生冲突互相兼容,与共享锁和排他锁间的兼容性,可见给出的锁相容矩阵
四、小结
1、运用多粒度封锁技术可提高并发事务的并发程度
2、用户利用DBMS提供的多粒度封锁模式,根据应用需求,选择封锁粒度和封锁类型,通过显示地为事务中的操作加锁,可控制事务的并发执行
3、但在事务的并发执行过程中,多粒度封锁的灵活应用,在提高事务的并发程度的同时,也会带来数据的不一致问题,用户需要根据事务对并发性和数据一致性的要求合理地使用锁