关于更新丢失的处理
预备知识
《关于隔离性》
概述
在《关于隔离性》中,我们提到了更新丢失的问题。我们先来简单回顾下,如果多个用户按照如下流程并行地对数据库中的某个字段进行递增,就会出现更新丢失。
-- x为程序变量
begin;
x = select value from test;
x += 1;
update test set value = x;
commit;
我们提到了一些数据的Repeatable Read和Serializable隔离级别可以解决更新丢失的问题。我们还提到,这样实现递增的方式是非常扯淡的,通过一些其他的技术手段完全可以避免更新丢失,下面我们就来看看这些方法。
原子写操作
如果数据库支持原子写操作,那么最简单的方式就是使用如下SQL语句:
update test set value = value + 1;
所谓数据库支持原子操作,是指对于上述SQL,数据库总是获取最新的value值(本地读),然后对其值进行递增。
显示加锁
第二种方式是对需要修改的行显示加互斥锁,这样就将并行事务转为串行事务,具体流程如下:
-- x为程序变量
begin;
x = select value from test for update;
x += 1;
update test set value = x;
commit;
这种方式只是在原有流程的查询阶段加上了for update,这样事务就变为了串行。
Compare And Set
Compare And Set(CAS)是用于实现自旋锁的手段,这里也可以用来解决数据库的更新丢失,实现思路非常简单。
begin;
old_value = select value from test for update;
new_value = old_value + 1;
update test set value = new_value where value = old_value;
commit;
这种方式,一旦有其他事务对value进行了更新操作,那么就一定不满足value = old_value这个条件。CAS还有一种更通用的做法,给表添加一个版本列,然后采用如下流程:
begin;
(x,old_version) = select value, version from test for update;
x += 1;
update test set value = x, version = version + 1 where version = old_version;
commit;