乐观锁 悲观锁 及 幻读(虚读)情境处理
乐观锁:冲突检测
悲观锁:冲突避免
乐观锁是在 这条记录保存到数据库中时进行版本检测,也就是说这条记录 进行的操作是 update 时,乐观锁才会产生效果。
幻读(虚读)三种情况:
1、人员表的身份证字段在做唯一检查的时候满足条件,但在做后续 save操作完成之前,该表新增了一条相同身份证的记录,导致数据库中出现2条身份证相同的记录。
解决:
是否可以通过 悲观锁的 读锁 来解决呢?
//PO中 @Table(indexes = { @Index(name="IDX_BookShelf_Owner_Name",columnList = "owner",unique = true), @Index(name="IDX_BookShelf_Owner_Name",columnList = "name",unique = true), }) //Repository中 @Lock(LockModeType.PESSIMISTIC_READ) public long countByNameAndOwner(String name,String owner);
悲观锁很容易造成死锁。
我们思考下这种场景:
事务A,第一步 检查身份证是否有11111的记录,第二步 如果没有则将身份证修改为22222。
事务B,第一步 检查身份证是否有22222的记录,第二步 如果没有则将身份证修改为11111。
事务A执行检查时,会为所有身份证是否有11111的记录加上读锁,该读锁同时也会阻止 所有通过新增或修改方式让身份证变为11111的写操作。
事务B执行检查时,会为所有身份证是否有22222的记录加上读锁,该读锁同时也会阻止 所有通过新增或修改方式让身份证变为22222的写操作。
如果事务A执行完检查,事务B也执行完检查,则 任何将身份证变为11111 或 22222 的操作都会被阻止。
事务A的第二步在等事务B释放读锁,事务B的第二步在等事务A释放读锁,造成死锁。
那这种幻读究竟怎么处理好呢?
我的做法是 加 唯一索引,然后捕获 commit失败时候的异常,处理后返回给前端。
2、某人银行余额500,存100块。此时从数据库中取出余额记录为500,系统计算500+100=600。
要将600存入数据库前,这个人同时又从银行取走了500,照道理数据库中余额应该是100,但实际保存到数据库的余额是600。
解决:
- 如果通过悲观锁的写锁来处理,处理起来方便,但它会阻止任何 带了读锁的select语句 访问该记录。
- 如果通过乐观锁来处理,处理起来会麻烦些,但效率方面会好一点。
延伸:根据业务确定 你的系统中有多少重要字段需要 进行这种保护。绝大多数情况下,要么这个字段在业务场景下不太可能有这么大的并发,要么这个字段本身没有这么重要。
3、银行系统有一张负债表和一张发放贷款表,负债高于100W的人不允许发放贷款。某个人向银行贷款,检查负债表时,负债没高于100W,银行系统准备往贷款表更新数据了。
但这时候,此人的贷款突然激增超过100W,结果银行系统还是给这个人发放了贷款。
解决:
- 这种情况能用乐观锁处理吗? 不能!因为最后只会往贷款表中更新数据,比对的是贷款表中的记录version,负债表根本不会去比对,所以乐观锁不能发现这个问题。
- 悲观锁可以处理这个问题。在查负债表的时候,加上读锁,防止负债表该记录被修改,就可以解决这个问题。
延伸:这个问题和第2个问题的不同之处在于,第2个问题是要update的那条记录本身被修改导致的问题,而这个问题是其他表的记录被修改导致的问题。
浙公网安备 33010602011771号