乐观锁和悲观锁的理解

参考解答

1. 首先看一个著名的 丢失更新 问题:

两个用户同时修改一条数据,原数据为:

id        name        age
1         张三         18

程序设计时采用下列sql来完成更新

update stu set name=?, age=? where id=?
时间 执行用户 sql 解析
t1 用户1 select * from stu where id=1 用户1看到的数据为1,张三,18并显示在表单页面,准备更新age 18->20
t2 用户2 select * from stu where id=1 用户2看到的数据为1,张三,18并显示在表单页面,准备更新name 张三->张小三
t3 用户2 update stu set name=张小三, age=18 where id=1 以自己看到的数据,执行更新
t4 用户1 update stu set name=张三, age=20 where id=1 以自己看到的数据,执行更新
t5 用户2 select * from stu where id=1 用户2看到的数据是1,张三,20,用户2对姓名的修改丢失了

简单的讲,就是用户1查询出来的数据被别人修改过了,自己看到的还是旧的数据,以旧数据去更新,就会将别人的修改覆盖掉。

2. 解决方法

解法1: 悲观锁

在t3处改为:

开始事务
select * from stu where id=1 for update; // 查出最新的数据1,张三,18
update stu set name=张小三(用户2要修改的值), age=18(查出来的最新值) where id=1;
提交事务

在t4处改为:

开始事务
select * from stu where id=1 for update; // 查出最新的数据1,张小三,18
update stu set name=张小三(查出来的最新值), age=20(用户1要修改的值) where id=1;
提交事务

注意 事务和select ... for update是必须的

  • 普通的select是非阻塞的,不能保证查询的同时别人对数据做修改
  • select ... for update必须配合事务使用,查询时加锁,事务结束才会释放for update 加的锁

解法2: 乐观锁

修改表结构,添加一个version(版本)列,每次更新时版本列加1,并且where条件中也要加入版本列条件

id        name        age       version
1         张三         18        1

在t3处改为:

最早在t1时查出的数据为:1,张三,18,版本1
update stu set name=张小三(用户2要修改的值), age=18, version=2 where id=1 and version=1;
检查更新的影响行数,结果影响行数为1,更新成功

在t4处改为:

最早在t2时查出的数据为:1,张三,18,版本1
update stu set name=张三(自己拿到的值,已经旧了), age=20(用户1要修改的值), version=2 where id=1 and version=1;
检查更新的影响行数,结果影响行数为0,没有更新到

重新查询数据,重复以上逻辑,直至成功

悲观锁和乐观锁比较

悲观锁的缺点,在于在修改前加锁,并发性比较低。即使冲突的可能性比较小,还要每次都加锁,太悲观了。

乐观锁的思想在于,我每次修改完检查一下,看是否有别人做了修改,如果有,那么我刚才做的修改不算,重新尝试一次吧,如果第二次还是有人修改了,那么再尝试第三次... 以乐观的态度面对修改失败。

对于读多写少的业务场景,可以采用乐观锁。 如果写比较频繁,乐观锁会导致重试较多,不太合适。


results matching ""

    No results matching ""