采用封锁解决并发一致性问题
封锁
封锁粒度
MySQL中提供了两种封锁粒度:行级锁和表级锁。
应该尽量的只锁定需要修改的那部分数据,而不是所有的资源,锁定的数据量越少,发生锁争用的可能性就越小,系统的并发程度就越高。
加锁需要消耗资源,锁的各种操作(包括锁的获取,释放锁,以及检查锁状态)都会增加系统的开销。因此封锁粒度越小,系统的开销就越大。
在选择封锁粒度的时候,需要再锁开销和并发程度之间做一个权衡。
封锁类型
1.读写锁
- 排它锁(Exclusive),简写为X锁,又称为写锁。
- 共享锁(Shared),简写为S锁,又称为读锁。
有以下两个规定:
- 一个事务对数据对象A加了X锁,就可以对A进行读取和修改。加锁期间任何事务不能对A加任何锁。
- 一个事务对数据对象A加了S锁,就可以对A进行读取操作,但是不能进行更新操作。加锁期间其他事务能对A加S锁,但是不能对A加X锁。
锁的兼容关系如下:
--- | X锁 | S锁 |
X锁 | x | x |
S锁 | x | √ |
2.意向锁
使用意向锁(Intension Locks)可以更容易支持多粒度封锁。
在存在行级锁和表级锁的情况下,事务T要想给表A加X锁,就需要先检查是否有其他事务对表A或者表A中任意一行加了锁,那么就需要对表A的每一行都检测一次,这是非常耗时的。
意向锁在原来的X/S锁上引入了IX/IS,IX/IS都是表锁,用来表示一个事务想要在表中的某个数据行上加X锁或S锁,有以下两个规定:
- 一个事务在获得某个数据行对象的S锁之前,必须先获得表的IS锁或者更强的锁。
- 一个事务在获得某个数据行对象的X锁之前,必须先获得表的IX锁。
通过引入意向锁,事务T想对表A加X锁,只需要先检测是否有其他的事务对表A加了X/IX/S/IS锁,如果加了就表示有其他的事务正在使用这个表或者这个表中的某一行的锁,因此事务T加X锁失败。
各种锁的兼容关系如下:
-- | X | IX | S | IS |
X | x | x | x | x |
IX | x | √ | x | x |
S | x | x | √ | √ |
IS | x | √ | √ | √ |
解释如下:
- 任意的IS/IX锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正的加锁。
- S锁只与S锁和IS锁兼容,也就是说事务T想要对数据加S锁,其他的事务可以已经获得对表或者表中的行的S锁。
封锁协议
1.三级封锁协议
一级封锁协议
事务T要修改数据A必须加X锁,直到T结束才能释放锁。
可以解决丢失修改问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。
T1 | T2 |
lock-X(A) | |
read A=20 | |
loak-X(A) | |
wait | |
write A=19 | . |
commit | . |
unlock X(A) | . |
obtain | |
read A=19 | |
write A=21 | |
commit | |
unlock-X(A) |
二级封锁协议
在一级的基础上,要求读取数据A时必须加S锁,读取完成马上释放S锁。
可以解决读脏数据问题,因为如果一个事务在对数据A进行修改,根据1级封锁协议,会加X锁,那么就不能再加S锁了,也就是不会读入数据。
T1 | T2 |
lock-X(A) | |
read A=20 | |
write A=19 | |
lock-S(A) | |
wait | |
rollback | . |
A=20 | . |
unlock-X(A) | . |
obtain | |
read A=20 | |
unlock-S(A) | |
commit |
三级封锁协议
在二级的基础上,要求读取数据A时必须加S锁,直到事务结束了才能释放S锁。
可以解决不可重复读的问题,因为读A时,其他事务不能对A加X锁,从而避免了在读的期间数据发生改变。
T1 | T2 |
lock-S(A) | |
read A=20 | |
lock-X(A) | |
wait | |
read A=20 | . |
commit | . |
unlock-S(A) | . |
obtain | |
read A=20 | |
write A=19 | |
commit | |
unlock-X(A) |
2.两段锁协议
加锁和解锁分为两个阶段进行。
可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。
事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。
lock-X(A).....lock-S(B).....unlock-X(A)......unlock-S(B)
但是不是必要条件,例如以下操作不满足两段锁协议,但是它还是可串行化调度。
lock-X(A).....unlock-X(A).....lock-S(B)......unlock-S(B)
MySQL隐式与显式锁定
MySQL的InnoDB存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
InnoDB也可以使用特定的语句进行显示锁定:
SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;