首页 > 技术文章 > 第11章:并发控制

sunzhongjie 2020-05-29 18:25 原文

第11章:并发控制

代码是基于SQLServer学习,与MySQL有略微差别!

11.1、并发控制概述

多用户数据库系统

  • 允许多个用户同时使用的数据库系统称为多用户数据库系统。
  • 多用户数据库系统面临着执行多事务的情况。

事务并发执行带来的问题

  • 并发执行的多个事务可能会同时存取同一数据,造成数据的不一致,破坏事务的隔离性和数据库的一致性。
  • DBMS必须提供并发控制机制解决事务并发执行带来的问题。(并发控制器)
  • 并发控制机制是衡量一个DBMS性能的重要标志之一。

并发操作带来的数据不一致性:

1、丢失修改(lost update)
2、不可重复读(non-repeatable read)
3、读“脏”数据(dirty read)

丢失修改

​ 丢失修改是指事务T1与事务T2从数据库中读入同一数据并修改,事务T2的提交结果破坏了事务T1提交的结果,导致事务T1的修改被丢失。

在这里插入图片描述

CREATE TABLE sales
(
	id char(2),
	qty int
)
insert into sales values( 'A1', 10 )	

-- 事务一
begin tran
	declare @sl int
	select @sl = qty from sales where id = 'A1'
	waitfor delay '00:00:15.000'
	update sales set qty = @sl - 1 where id = 'A1'
commit tran
-- 事务二
begin tran
	declare @sl int
	select @sl = qty from sales where id = 'A1'
	waitfor delay '00:00:15.000'
	update sales set qty = @sl - 1 where id = 'A1'
commit tran

不可重复度

1、事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值。

在这里插入图片描述

begin tran
		declare @sl int
		declare @s2 int
		select @sl = qty from sales where id = 'A1'
		print(@sl)
		waitfor delay '00:00:30.000'
		select @s2 = qty from sales where id = 'A1'
		print(@s2)
commit tran
begin tran
		update sales set qty = qty *2 where id = 'A1'
commit tran

2、事务T1按一定条件读取某些数据记录后,事务T2删除了其中部分记录,当事务T1再次按相同条件读取数据时,发现某些记录神密地消失了。
3、事务T1按一定条件读取某些数据记录后,事务T2插入了一些记录,当事务T1再次按相同条件读取数据时,发现多了一些记录。

后两种不可重复读有时也称为幻影现象

读“脏”数据

​ 事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,事务T1由于某种原因被撤消,这时事务T1已修改过的数据恢复原值,事务T2读到的数据就与数据库中的数据不一致,是不正确的数据,又称为“脏”数据。

在这里插入图片描述

事务并发执行会导致数据不一致性

  • 由于并发操作破坏了事务的隔离性

并发控制就是要用正确的方式调度并发操作,使一个事务的执行不受其他事务的干扰,从而避免造成数据的不一致性

并发控制的主要技术

  • 封锁(Locking)
  • 时间戳(Timestamp)
  • 乐观控制法

商用的DBMS一般都采用封锁方法

11.2、封锁(Locking)

11.2.1、封锁概述

什么是封锁?

  • 封锁就是事务T在对某个数据对象(例如表、记录等)操作之前,先向系统发出请求,对其加锁。
  • 如果事务T的加锁请求得到满足,事务T就对该数据对象有了一定的控制。
  • 如果事务T的加锁请求得不到满足,事务T就不能控制该数据对象。
  • 封锁是实现并发控制的一个非常重要的技术

基本封锁类型

  • 排它锁(Exclusive lock,简记为X锁)
  • 共享锁(Share lock,简记为S锁)

排它锁(Exclusive lock,简记为X锁)

  • 排它锁又称为写锁。
  • 若事务T对数据对象A加上X锁,允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。

共享锁(Share lock,简记为S锁)

  • 共享锁又称为读锁。
  • 若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其它事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。

在这里插入图片描述
封锁协议(Locking Protocol)

  • 在运用X锁和S锁对数据对象加锁时,需要约定一些规则(封锁协议):
    • 何时申请X锁或S锁
    • 何时释放X锁或S锁
  • 常用的封锁协议:三级封锁协议。

11.2.2、一级封锁协议

  • 事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。
  • 一级封锁协议可防止丢失修改
  • 在一级封锁协议中,如果是读数据,是不需要加锁的,所以它不能避免不可重复读和读“脏”数据。
  • 在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

11.2.3、二级封锁协议

  • 一级封锁协议+事务T在读取数据R前必须先加S锁,读完后不等事务结束即可释放S锁。
  • 二级封锁协议可以防止丢失修改和读“脏”数据。
  • 在二级封锁协议中,由于读完数据后即可释放S锁,所以它不能避免不可重复读。
    在这里插入图片描述
    在这里插入图片描述

11.2.4、三级封锁协议

  • 一级封锁协议 + 事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放
  • 三级封锁协议可避免丢失修改、读“脏”数据和不可重复读。

在这里插入图片描述
在这里插入图片描述

11.2.5、SQL Server的并发机制

测试加排它锁(X锁)能否避免丢失修改:

CREATE TABLE sales
(
	id char(2),
	qty int
)
insert into sales values( 'A1', 10 )	
-- 【事务一】
begin tran
	declare @sl int
	select @sl = qty from sales with(Xlock)
							where id = 'A1'
	waitfor delay '00:00:10.000'
	update sales set qty = @sl - 1 where id = 'A1'
commit tran

-- 【事务二】
begin tran
	declare @sl int	
	select @sl=qty from sales with(Xlock)
							where id='A1'
	waitfor delay '00:00:10.000'
	update sales set qty = @sl - 1 where id = 'A1'
commit tran
  • 事务隔离
    • 对于编程人员来说,不用手工去设置控制锁,通过设置事务的隔离级别自动管理锁。

在这里插入图片描述

-- 【设置事务的隔离级别】
set transaction isolation level
-- 【事务隔离级别】
read uncommitted
read committed
repeatable read
serializable

测试事务隔离级别为可重复读:

-- 设置为可重复度
set transaction isolation level repeatable read
-- 【事务一】
begin tran
		declare @sl int
		declare @s2 int
		select @sl = qty from sales where id = 'a1'
		print(@sl)
		waitfor delay '00:00:30.000'
		select @s2 = qty from sales where id = 'a1'
		print(@s2)
commit tran
-- 【事务二】
begin tran
		update sales set qty = qty *2 where id = 'a1'
commit tran

11.3、活锁和死锁

活锁

  • 事务T1封锁了数据R
  • 事务T2又请求封锁R,于是T2等待。
  • T3也请求封锁R,当T1释放了R上的封锁之后系统首先批准了T3的请求,T2仍然等待。
  • T4又请求封锁R,当T3释放了R上的封锁之后系统又批准了T4的请求……
  • T2有可能永远等待(饿死)。

如何避免活锁

  • 采用先来先服务的策略:
  • 当多个事务请求封锁同一数据对象时,按请求封锁的先后次序对这些事务排队,该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁。

死锁

在这里插入图片描述
解决死锁的方法:

  1. 采取一定措施预防死锁的发生。
  2. 允许死锁发生,采取一定方法诊断死锁、解除死锁。

死锁的预防

  • 一次封锁法
    • 要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行
  • 一次封锁法存在的问题:
    • 以后要用到的全部数据加锁,势必扩大了封锁的范围,从而降低了系统的并发度。

在这里插入图片描述

  • 顺序封锁法:

    • 预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。
  • 顺序封锁法存在的问题

    • 数据库系统中可封锁的数据对象极其众多,而且还在不断变化,要维护这样极多而且变化的资源的封锁顺序非常困难,成本很高。
    • 事务的封锁请求可以随着事务的执行而动态地决定,很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序去施加封锁。

在这里插入图片描述

死锁的诊断

  • 超时法:
    • 如果一个事务的等待时间超过了规定的时限,就认为发生了死锁
    • 优点:实现简单
    • 缺点
      • 有可能误判死锁。
      • 时限若设置得太长,死锁发生后不能及时发现。

死锁的诊断

  • 等待图法:
    • 用事务等待图动态反映所有事务的等待情况
    • 事务等待图是一个有向图G=(T,U)

在这里插入图片描述

  • 并发控制子系统周期性地(比如每隔1min)检测事务等待图,如果发现图中存在回路,则表示系统中出现了死锁。

死锁的解除

选择一个处理死锁代价最小的事务,将其撤消,释放此事务持有的所有的锁,使其它事务能继续运行下去。

推荐阅读