写事务对数据的修改通常遵循“读-改-写”的模式。在快照隔离的条件下,事务都能“读”到一致的数据,但后续的“改-写”仍可能存在并发问题。更新丢失和写倾斜是其中两种。
更新丢失
并发事务中的一个进行提交时,覆盖了其他事务的提交。例如:
- 更新计数器、更新余额
- 修改复杂对象后,写回整个对象
更新丢失场景通常为读取-修改-写回(read-modify-write)过程。当事务并发时,由于隔离性其写回操作不会包含其他事务提交的信息,从而导致覆盖。
写倾斜
并发事务依赖查询的条件进行修改提交,但提交时先决条件已不再成立。例如:
- 更新值班记录
- 预定会议室
- 创建不重复的用户名
写倾斜与更新丢失都是RMW模式,区别在于:写倾斜中并发事务写的是不同对象,而更新丢失写的是同一对象。
如果将一组对象视为整体,则写倾斜和更新丢失现象本质相同,其根本原因在于:由于隔离性,当前事务提交时不包含其他事务已提交的信息
解决方案
任何并发问题都可以通过加锁解决,不同的锁方案带来不同的性能。
对于更新丢失:
- 通过
SELECT FOR UPDATE
或UPDATE SET value=value+1
语句,对待更新记录进行显式加锁 - 为待更新记录增加版本号信息,通过
UPDATE WHERE version=v1
语句实现乐观锁。当冲突严重时该方案性能不佳
对于写倾斜:
- 对先决条件显式加锁
- 当先决条件并非一条记录时,先将其实体化。例如会议室预定中,构造时间-房间表,则可以对该表记录加锁防止写倾斜。但这实际上将并发控制机制降级为数据模型,不够优雅
MySQL InnoDB实现的可重复读(Read Repeatable)级别隔离无法检测更新丢失和写倾斜问题,需要由应用层负责。