知识卡片
乐观离线锁默认防不了不一致读,需主动扩大版本检查范围
内容
乐观离线锁最常见的实现是给每条记录关联一个版本号,会话读取记录时把版本号一并带走,提交更新时在UPDATE/DELETE语句的where子句里加上版本号判断——一条SQL同时完成”检查锁”和”更新数据”,返回行数为1代表成功,为0说明记录已被别人改过或删除,此时必须回滚整个系统事务。冲突信息除了版本号,最好再记录”谁在何时最后修改了这条记录”,方便友好地告知用户冲突详情;千万不要用修改时间戳代替版本计数器,因为系统时钟并不可靠,尤其当应用跨多台服务器部署时更是如此。但这套标准实现只防住了”更新丢失”,防不住”不一致读”——书中给了一个具体的账单案例:会话A新建一张账单、查顾客地址算税率;与此同时会话B编辑了这个顾客的地址;因为会话A自始至终没有修改地址记录本身,标准的版本检查压根不会发现这个冲突,最终账单上用的税率就是错的。乐观离线锁并非天生防不了这种情况——解法是让会话A对它”读取但不修改”的地址记录同样纳入版本检查(可以直接把地址加进更新语句的检查范围,或者维护一份”需要版本检查的项目清单”,后者写起来更费事但更能清楚表达意图);如果用重读版本号(而非强制递增)的方式检测不一致读,还必须确保系统事务的隔离级别至少是可重复读,否则较低隔离级别下就得靠强制递增版本号来补救。可迁移启发:乐观并发控制的默认实现只覆盖”我改的东西是否被别人抢先改了”,而”我读了但没改、却依赖其正确性的数据是否被别人动过”是完全独立的一类风险,需要开发者主动识别哪些”只读依赖”也该纳入冲突检测范围,不能指望默认实现自动覆盖。
参考来源
- 位置:《企业应用架构模式》第二部分"模式"之"第16章 离线并发模式"之"16.1 乐观离线锁"(源文件:_epub-src/OEBPS/Text/000190.html)
- 结论依据:原文说明"使用修改时戳代替版本计数是糟糕的方法,因为系统时钟非常不可靠……通常实现乐观离线锁是通过在UPDATE和DELETE语句中加上版本号检查来实现的,但这样不能防止不一致读……乐观离线锁没有理由不能检测不一致读……它应该对地址也进行版本检查……如果通过重读版本号而不是人为的更新来检测不一致读,就要特别注意系统事务的隔离等级……需要有可重复读或更强的隔离等级",直接支撑本卡结论。
- 原始内容:由于税率与住址有关,生成账单时使用的税率就不正确了,但由于账单生成会话不会修改地址信息,因而就不会检测到冲突。