并发控制概述
并发控制概述
复习定位
当多个用户同时修改或读取数据库中的同一数据时——如果不加控制——会导致数据的不一致。并发控制机制保证事务的隔离性——让每个事务认为只有它自己在操作数据库。数据库使用锁和MVCC来实现隔离性——锁保证写之间互斥——MVCC让读不阻塞写、写不阻塞读。
并发操作带来的数据不一致问题
当多个事务并发执行时——
丢失更新——两个事务同时读取同一版本的数据、分别修改并提交——后提交的事务覆盖了先提交的事务的修改——第一个事务的更新丢失。
脏读——一个事务读到另一个事务未提交的数据——另一个事务可能回滚——读到的数据是"脏"的。
不可重复读——一个事务两次读到同一数据但结果不同——因为另一个事务在这期间修改并提交了该数据。
幻读——一个事务按同一条件两次查询——第二次查询结果集中的行数增加了(新插入了符合条件的行)。
隔离级别
SQL标准定义了四种隔离级别来分别处理上述问题:
| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
级别从低到高——隔离性越来越强——并发性能逐渐降低——实际在应用中常见的是READ COMMITTED(大多数数据库默认如PostgreSQL/Oracle)和REPEATABLE READ(MySQL InnoDB默认)。
封锁协议
一级封锁协议——在修改数据时加X锁——直到事务结束(COMMIT或ROLLBACK)才释放。防止丢失更新——因为两个事务不能同时修改同一行。但如果不加S锁——不会阻止脏读。
二级封锁协议——在一级(修改加X锁)基础上——读数据时加S锁——读完立即释放。防止了脏读(因为读加锁后只能读到已提交的数据)。但不可重复读仍可能发生(第二次读之前S锁已释放)。
三级封锁协议——在一级(修改加X锁)基础上——读数据时加S锁并且保持到事务结束(COMMIT/ROLLBACK)。防止了不可重复读——因为同一行的S锁被持有到事务结束——其他事不能在该行上加X锁——保证了两次读取同一值。
两段锁协议
两段锁协议规定所有事务必须分为两个阶段——扩展阶段(只可以加锁、不可以解锁)和收缩阶段(只可以解锁、不可以再加锁)。遵守两段锁协议的事务的并发执行结果是可串行化的。但死锁可能发生——一个事务持有A锁等待B锁、另一个事务持有B锁等待A锁。
复习检查
丢失更新和不一致读(脏读、不可重复读)的区别——丢失更新是写操作的覆盖——脏读是针对未提交数据的读。请用一个具体的并发执行时序列图表示丢失更新的发生过程。
一级封锁协议为什么不能防止脏读——只对写操作加锁——读操作不加锁时读到的值可能是未提交的——对吗?
三级封锁协议保证了可重复读——为什么不能完全防止幻读(在标准SQL范围——不考虑MySQL Next-Key Lock的处理方式)?
两段锁协议与可串行化的关系——可串行化是并发调度的最严格标准——两段锁是保证事务并发调度结果可串行化的充分条件——但不是必要条件——举例事务序列,它们不是两段锁但结果也可串行化。
死锁在并发控制中的处理——两段锁协议更容易导致死锁?如果两个事务各持有一个资源并等待对方的另一个资源——检测到死锁时应该中止哪个事务?