并发控制中的锁机制
并发控制中的锁机制
复习定位
数据库管理系统中——锁是实现并发控制的主要手段。当一个事务需要修改数据时——它对该数据加锁——防止其他事务同时修改。共享锁(S锁)给读操作用——多个事务可以同时加S锁。排他锁(X锁)给写入/修改/删除用——互斥。不同的封锁协议(一级/二级/三级)通过增加锁的持有时长来实现不同隔离级别的效果。
锁的基本类型
共享锁(S锁)——事务对数据加S锁后可以读该数据——但不可以修改。其他事务同时也可以加S锁——读读不互斥——提高并发性。
排他锁(X锁)——一旦事务对数据加X锁——既可以读又可以写。其他事务不能对该数据加任何锁(包括S锁也不能)——直到释放。
兼容规则:
| T1已持有 | T2请求S锁 | T2请求X锁 |
|---|---|---|
| S锁 | 兼容 | 冲突 |
| X锁 | 冲突 | 冲突 |
三级封锁协议
一级封锁协议——修改数据前必须加X锁——直到事务结束(COMMIT或ROLLBACK)释放——防止丢失更新(两个事务不能同时修改)。读数据不加锁——所以可能读到其他事务未提交的数据(脏读)。
二级封锁协议——一级基础上+读数据前加S锁——读完立即释放(不等事务结束)——防止脏读(因为读加锁了——如果事务A在读——事务B不能修改——所以A只能读到B提交后的数据)。但不可重复读可能发生——因为S锁读后立即释放——B在A两次读之间可以修改数据。
三级封锁协议——一级基础上+读数据前加S锁并且保持到事务结束——防止不可重复读(因为同一行被S锁保护到事务结束——B不能在此期间修改它——所以A的第二次读和第一次读一致)。
活锁与死锁
活锁——优先权高的进程总是先于优先权低的进程获得锁——导致低优先级的进程永远等待。解决——按先来后到(公平调度)。
死锁——两个或多个事务各自持有对方需要的锁并向对方等待释放——形成循环等待——导致所有涉及事务无法继续。解决——检测死锁(等待图是否有环)——选一个代价最小的事务回滚。
两段锁协议
两段锁——将所有封锁操作分为两个阶段:
- 扩展阶段:事务只能加锁——不能释放锁。
- 收缩阶段:事务只能释放锁——不能再请求新锁。
两段锁不是强制要求事务在开始时一次性获得所有锁——而是在第一阶段可以申请新锁——直到某一时刻开始释放第一把锁——即进入收缩阶段——不能再请求新的锁。遵守两段锁协议是并发事务可串行化的充分条件——但不必要。
复习检查
S锁和X锁的区别——多个S锁可以并存——但X锁和所有S锁、X锁互斥——为什么数据库允许读读不互斥但并不允许进行写并发的编排?
一级封锁协议为什么不能避免脏读——因为读数据不加锁——你可以读到未提交的修改数据。而
脏读是读到了一个可能被回滚(rollback)的未提交修改。活锁和死锁的区别——活锁是"等不到"——但可以通过调度策略避免;死锁是"互相等"——必须检测和回滚一个事务打破循环。
两段锁协议的可串行化保证——两段锁协议为什么确保了并发执行的可串行化——因为所有事务都在收缩阶段开始前获得了全部锁——之后不能再加锁保证了事务已"完成数据的请求",它们在扩展阶段获得锁的顺序加上收缩阶段的释放顺序可以帮助调度器为冲突事务序列化地排列。
如果某事务在释放了一把锁之后进入了收缩阶段——它还需要再去读另一——不能发出加锁请求——违背了两段锁的规则——这会导致怎样(与其他事务的时间隔交错产生的不可重复读或不可重复读)?