封锁协议

在运用 X 锁和 S 锁对数据对象加锁时,还需要约定一些规则 ,例如何时申请 X 锁或 S 锁、持锁时间、何时释放等。这些规则被称为封锁协议。对封锁方式规定不同的规则,就形成了各种不同的封锁协议。不同的封锁协议,在不同的程度上为并发操作的正确调度提供一定的保证。

一级封锁协议

事务在修改记录之前必须要添加 X 锁,直到事务结束(提交或者 ROLLBACK)才释放。一级封锁协议可以解决丢失修改。但是一级封锁协议在读取记录时不会加锁,所以会出现不可重复读和读取脏数据。

丢失修改:不加锁修改后,该记录又被其他事务修改。

  • 读:不添加锁;

  • 写:加 X 锁 -> 修改记录 -> 成功或失败 -> 释放 X 锁;

二级封锁协议

二级封锁协议在一级封锁协议的基础上,在事务读取记录之前添加 S 锁,在读取结束后释放 S 锁。因为事务读取之前添加了 S 锁,所以可以防止读取脏数据,但是由于事务读取结束就释放了 S 锁,所以不能保证可重复读。

  • 读:读取前添加 S 锁 -> 读取记录 -> 读取结束后释放 S 锁;
  • 写:同一级封锁协议;

三级封锁协议

和二级封锁协议类似,事务读取记录之前必须添加 S 锁,但是三级封锁协议在事务结束后才释放 S 锁,这进一步防止了不可重复读。

  • 读:读取前添加 S 锁 -> 读取记录 -> 事务完成 -> 释放 S 锁;
  • 写:同一级封锁协议;

不同隔离级别对应的封锁协议

  • RU(读未提交):遵循一级封锁协议;

  • RC(读已提交):遵循二级封锁协议;

  • RR(可重复读):遵循二级封锁协议,使用 MVCC 机制实现可重复读;

  • Serializable(串行化):遵循三级封锁协议;

一段锁协议

一段锁协议要求事务开始时一次性申请所有的锁,之后不再申请任何锁。如果其中的某个锁不可用,则整个申请就不成功,事务就不会执行。在事务的尾端,一次性释放所有的锁。

一次性锁协议不会产生死锁的问题,但是事务的并发度很低。

两段锁协议

MySQL 遵循的是两段锁协议,将整个事务分为加锁阶段和解锁阶段。

  • 加锁阶段:该阶段的事务只能操作数据和加锁。任何读操作之前都需要申请获得 S 锁,任何写操作之前需要申请 X 锁。如果加锁不成功,则事务等待,直到加锁成功后继续执行。
  • 解锁阶段:当事务释放第一个锁,就进入了解锁阶段。在该阶段,事务只能操作数据和解锁,但是不能再加锁。

因为两段锁协议保证事务可以在结尾之前解锁,所以事务可以具有较高的并发度,并且保证事务的并发调度是串行化。但是两段锁协议的缺点是没有解决死锁的问题,因为在加锁阶段没有顺序要求,如果两个事务分别申请了 A、B 锁,接着又申请了对方的锁,此时进入死锁状态。

image-20220926164917586

参考

  1. 数据库的一级、二级、三级封锁协议
  2. InnoDB 中的事务隔离级别和锁的关系
  3. 两阶段锁协议
  4. MySQL隔离级别和封锁协议