这是目前我在网上找到的一篇一定程度下解惑的相关性文章:
https://www.zhihu.com/question/47007926/answer/2264785785原文搬运到此(侵删)
真实的场景可以注意一下,代码里面,有没有出现连续 2 次 select 的场景。
那为什么会出现连续 2 次 select 的场景,第二次不能复用第一次的结果吗?
这个主要是因为写代码的时候,第一次 select 和第二次 select 很可能不在一个方法里面。要复用,就要在第一个方法里面 return 这个结果,再传给第二个方法。有时候觉得这样写太难看了,干脆就查两次算了,反正小业务不用考虑啥性能的。这个也可以认为是用性能换取代码可读性,代码可维护性。( PS:不过个人还是不喜欢这样查两次,可以再想想办法,在不影响代码可读性上查一次)
另外,讨论幻读和可重复读的问题,很多时候可能都在考虑选 RR 还是 RC 的问题。
选 RR 还是 RC 首要考虑的是业务逻辑上会不会出错。
1.一个判断就是有没有两次 select
2.另外一个就是要注意 RR 虽然能解决可重复读,幻读的问题,但是并不意味着代码就不出错。有点像 java volatile 虽然能保证可见性,但不是说用 volatile 就能解决并发问题这个道理。
举个例子说:现在 RC 级别,一个业务失败了可以记录表等待重试,也可以直接取消掉。定时任务拉取了现在记录表等待重试的记录,结果后面重试又失败了,原因是被直接取消掉了。心想为啥呢,要是被取消了,定时任务也拉不起来呀,这个原因就是拉起来后,中途马上被别人改掉了,重试的时候冲突发生了错误。
这个时候可能心想:哦,原来是读到了别人取消的信号。那简单,直接改成 RR ,保持我这次业务逻辑的正确性(定时任务拉取到,就要能修改),如果是这样做,最后数据库这行记录变成了:取消标志位:true ;执行状态:success 。人傻了。。。
还有一个场景就是,更新余额的时候。像在上上个公司,更新余额的时候,是事务里面,select for update 查出数据,然后减少余额,应用里算出余额的值,最后 update 。这个不管是 RC 还是 RR 级别,都没问题,因为 select for update 锁行了。
像在上个公司,更新库存的时候,开了 RC 级别事务,更新的时候,代码里没有像 select for update 锁行,而是在更新语句中 update 表 set 现在的库存=原来的库存-要扣的数量。这个也没问题。
但是如果你数据库是 RR 级别,没有用 select for update 锁住行,是在应用层算出最后新值,然后再直接 update 这个新值。心里想着,那现在总没问题了吧,RR 级别,可重复读,我这个业务的计算可是正确的。但是实际上也是有问题的,会把别人的覆盖掉。
所以总的来说,RR 和 RC 我一般会先看下有没有两个 select ,也就是明显的幻读,可重复读问题。另一个会看下会不会有可重复读的问题解决了,本次业务能够保证正确性,但是在更大的维度上可能会出错(把别人的覆盖了)
PS:不过上述的考虑我觉得还是有点偏理论偏学生,站在更加实际,现实的角度:
1 、小项目的话,直接默认 RR ,因为没有业务,我没理由改 RC 。改 RC 可能还会有潜在的我不知道的错误(因为毕竟不是默认的),前期需要研发速度,需要尽量简单快速落地
2 、项目变大了,想要 RR 改 RC 。不敢改。项目变大了,稳字优先,稳字当头
3 、想要利用 RR 改 RC 提升性能,没有其他技术手段了吗,非要改这个?
4 、没有其他技术手段了,那就改吧