先看看问题场景
某些业务需要更新某一个表的多个字段,很可能更新的结果跟理想值不一致。简单来讲就是执行类似下面的sql会出什么结果?
update test set a = a+1,b=a+1 where id =2
我们来模拟一下上面的sql环境,我们创建一个test表,并使用innodb存储引擎。
create table test (id int,a int,b int,primary key(id));
增加三行数据
insert into test values (1,10,100),(2,20,200),(3,30,300)
进行修改多个字段
update test set a = a+1,b=a+1 where id =2
修改的条件是id=2,为啥修改的结果是(2,21,22),不应该是(2,21,21)吗?是不是很多人以为修改的结果是(2,21,21),是不是感觉数据是乱的,如果数据少,改回去还算省事,关键数据量很多,改的工作量就大了,还容易出错。
原因分析
因为 update 是当前读,读取的是记录数据的最新版本,
update test set a = a+1,b=a+1 where id =2
update a=a+1 因为要做当前读 现在a =20 +1 后 a=21 ,保证最新值,再做 b=a+1
也需要读到a值的最新的值 还要加锁, 现在a的值已经变成21, 再加1就变成22,
那么如果我想按照我们的理想值更新出结果,那要怎么做呢?
问题解决
可以把 a 列暂存在一个临时变量里
selectainto@afromtest wherea=2;
再执行修改
updatetestseta=@a+1,b=@a+1wherea=@a;
结果就会是(2,21,21)了
总结一下
快照读执行select的时候,innodb默认会执行快照读,快照读,也就是读取快照的数据,数据虽然是一致的,但是数据是历史数据。
快照读:只是简单的 select ,不包括 select … lock in share mode, select … for update
当前读
当你执行这几个操作的时候默认会执行当前都会加锁,也就是会读取最新的记录,也就是别的事务提交的数据你也可以看到。
update 执行当前读,然后把返回的数据加锁,之后执行update。
加锁是防止别的事务在这个时候对这条记录做什么,默认加的是排他锁,也就是只允许读,其他都不可以,这样就可以保证数据不会出错了。