写事务对数据的修改通常遵循“读-改-写”的模式。在快照隔离的条件下,事务都能“读”到一致的数据,但后续的“改-写”仍可能存在并发问题。更新丢失和写倾斜是其中两种。

更新丢失

并发事务中的一个进行提交时,覆盖了其他事务的提交。例如:

  • 更新计数器、更新余额
  • 修改复杂对象后,写回整个对象

更新丢失场景通常为读取-修改-写回(read-modify-write)过程。当事务并发时,由于隔离性其写回操作不会包含其他事务提交的信息,从而导致覆盖。

写倾斜

并发事务依赖查询的条件进行修改提交,但提交时先决条件已不再成立。例如:

  • 更新值班记录
  • 预定会议室
  • 创建不重复的用户名

写倾斜与更新丢失都是RMW模式,区别在于:写倾斜中并发事务写的是不同对象,而更新丢失写的是同一对象。

如果将一组对象视为整体,则写倾斜和更新丢失现象本质相同,其根本原因在于:由于隔离性,当前事务提交时不包含其他事务已提交的信息

解决方案

任何并发问题都可以通过加锁解决,不同的锁方案带来不同的性能。

对于更新丢失:

  • 通过SELECT FOR UPDATEUPDATE SET value=value+1语句,对待更新记录进行显式加锁
  • 为待更新记录增加版本号信息,通过UPDATE WHERE version=v1语句实现乐观锁。当冲突严重时该方案性能不佳

对于写倾斜:

  • 对先决条件显式加锁
  • 当先决条件并非一条记录时,先将其实体化。例如会议室预定中,构造时间-房间表,则可以对该表记录加锁防止写倾斜。但这实际上将并发控制机制降级为数据模型,不够优雅

MySQL InnoDB实现的可重复读(Read Repeatable)级别隔离无法检测更新丢失和写倾斜问题,需要由应用层负责。