精通Java事务编程(5)-写倾斜与幻读-灵析社区

攻城狮小明

多个事务并发写相同对象时,会出现脏写、更新丢失两种竞争条件。为避免数据不一致,可:

  • 借助DB内置机制
  • 或通过显式加锁

以执行原子写操作。但这还不是并发写可能导致的全部问题。

2.4.1 值班程序

医院通常会同时要求几个医生待命,前提是至少有一位医生在待命。医生可放弃他们的班次(如若自己生病了),只要至少有一个同事在这天的班中继续工作。

Alice、Bob两位值班医生都病了,所以他们都决定请假。但他们恰在同一时刻点击调班按钮

每笔事务总先检查是否至少有两名医生目前在值班。若是,则有一名医生可安全离开去休班。由于DB使用快照隔离,两次检查都返回2,所以两事务都进入下一阶段:

  • Alice更新自己的记录为休班
  • Bob也更新自己的记录

两个事务都成功提交,最后结果是无医生值班,显然违反了至少有一名医生得值班的业务需求。

2.4.2 写倾斜

这种异常即写倾斜,不是脏写、丢失更新。这俩事务更新的是两个不同对象(Alice 和 Bob 各自值班记录)。这里发生的冲突不是那么明显,但显然也是竞态:若两个事务串行,则第二个医生就不能歇班。异常行为只有在事务并发时才可能。

可将写倾斜视为【广义的丢失更新】。即若两事务读取相同一组对象,然后更新其中一部分:

  • 不同事务,更新不同对象,则可能发生写倾斜
  • 不同事务,更新同一对象,则可能脏写或丢失更新

很多方法可防止丢失更新。但对写倾斜,方案更受限:

  • 由于涉及多对象,单对象的原子操作无效
  • 基于快照隔离来实现自动检测丢失更新也有问题:PostgreSQL可重复读,MySQL/InnoDB 可重复读,Oracle可串行化或SQL Server快照隔离级别中,都不支持自动检测写倾斜。自动防止写倾斜要求真正的可串行化隔离
  • 某些DB支持自定义约束,然后由DB强制执行(如唯一性,外键约束或特定值限制)。但为指定至少有一名医生必须在线,涉及多个对象的约束,大多DB都未内置这种约束,但你可使用触发器或物化视图来实现类似约束
  • 若无法使用可串行化,则次优方案可能是显式锁定事务依赖的行:
BEGIN TRANSACTION;

SELECT * FROM doctors
  WHERE on_call = TRUE
  # 告诉DB锁定返回的所有结果行,以用于更新
  AND shift_id = 1234 FOR UPDATE;

UPDATE doctors
  SET on_call = FALSE
  WHERE name = 'Alice'
  AND shift_id = 1234;
  
COMMIT;

2.4.3 写倾斜case

理解到写倾斜的本质后,容易注意到更多case:

  • 会议室预订系统

不能在同一时间对同一会议室进行多次预订。当有人想要预订时,首先检查是否存在相互冲突的预订(即预订时间范围重叠的同一房间),若无,则创建会议(参阅示例-2)1

例-2 会议室预订系统,避免重复预订(在快照级别隔离下不安全)

BEGIN TRANSACTION;

-- 检查所有现存的与 12:00~13:00 重叠的预定
SELECT COUNT(*) FROM bookings
WHERE room_id = 123 AND
  end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00';

-- 若之前的查询返回 0
INSERT INTO bookings(room_id, start_time, end_time, user_id)
  VALUES (123, '2015-01-01 12:00', '2015-01-01 13:00', 666);

COMMIT;

快照级别隔离无法防止并发用户预订同一会议室。为避免预订冲突,需可串行化隔离级别。

  • 多人游戏

例-1中,使用一个锁来防止丢失更新(即两个玩家不能同时移动同一数字)。但锁不妨碍玩家将两个不同数字移动到棋盘的相同位置或其他违反游戏规则的行为。可能需更多约束,否则很容易发生写倾斜。

  • 抢注用户名

在每个用户拥有唯一用户名的网站上,两个用户可能会尝试同时创建具有相同用户名的帐户。可采用事务检查名称是否被抢占,若无,则使用该名称创建账户。但和之前案例类似,快照隔离下不安全。但唯一约束是简单方案(第二个事务在提交时会因为违反用户名唯一约束而被中止)

  • 防止双重开支

支付或积分服务一般需检查用户的支付数额不超过余额。可通过在用户帐户中插入一个临时支出项目,列出帐户中的所有项目,并检查总和是否为正值。由于写倾斜,可能发生两个支出项目同时插入,两个交易都不超额,但一起会导致余额变为负值。

2.4.4 导致写倾斜的幻读

所有这些案例都遵循类似模式:

阅读量:2016

点赞量:0

收藏量:0