锁是一种解决数据库并发问题的一种设计。
如果一昧的加一种锁,比如全局锁,只要有事务来访问数据,就会申请一个全局锁,虽然从理论上来说,这样是可行的,但毫无疑问,这样的并发管理能力太差,所以需要一些更细粒度化的锁来进行并发控制。
将锁更细粒度化可以分为共享锁(share-mode lock;S-lock)和独占锁(exclusive-mode lock;X-lock),也就是读锁和写锁,事务进行读取的时候,获取数据的读锁,其他事务要修改必须要等待解锁。如果事务进行更新操作的时候,需要获取数据的写锁,只有该事务能对该数据进行读写操作。
只有读锁之间可以相互兼容,其他情况都是不兼容。
S-lock | X-lock | |
---|---|---|
S-lock | 兼容 | 不兼容 |
X-lock | 不兼容 | 不兼容 |
这里通过一个简单的示例来帮助大家理解。
T1:B 转账给 A 50 元
T2:读一下 A 和 B 的余额之和
第一眼看上去感觉结果是合理,假设 A 和 B 原来都只有 100 元,事务 T2 无论在 T1 执行前还是执行后进行操作,读取的结果都是 200 元。
这样 T2 在 T1 中间执行,锁之间的使用是合理的,这种情况下 T2 的结果就会是 150 元。
那怎么处理才能解决上面这种情况呢,很容易想到的就是事务在最后的时刻释放读写锁,这样看上去也很合理,但会出现下面这种情况:
这种情况下,T3 占着 B 的写锁,T4 占着 A 的读锁,这个时候 T3 去申请 A 的写锁,T4 申请 B 的读锁,很明显,这种情况下死锁了。
因此为了避免这种情况,我们引入了两阶段加锁,在获取锁的阶段(growing phase),事务只能不断地获取新锁,在释放锁(shrinking phase)阶段,事务只能不断的释放锁。
但同样也会遇到上述的情况,在 T5 遇到错误的时候,会导致 T6 和 T7 的连锁回滚。为了避免连锁回滚,我们引入更严格的两阶段加锁,写锁必须等事务结束后释放,这样就避免在事务未提交的时候,其他事务不会来进行数据改写,因此避免了连锁回滚。