当前位置: 首页 > 后端技术 > Java

mysql更新不成功,事务问题弄清楚了吗?

时间:2023-04-01 16:02:23 Java

作者:文安石来源:https://my.oschina.net/floor/...提问一个忙(mo)忙(yu)的下午,小航同学突然骂道,“TM,见鬼了,版本没有改变,但更新不成功。”只见他,满头大汗,双手紧握拳头,一脸狰狞,似乎还要再来一击,连忙道:“失败没关系,再试一次就好了,乐观锁通常需要重试。”他略带鄙夷地说:代码有重试逻辑,我加了个log,发现版本没变,但是更新不成功。作为一个对技术没什么追求的人,他的话一下子勾起了我的好奇心,然后很诚恳的说,能不能看看代码?小航没有说话,只是双手做了一个请的手势。仔细看了一下,代码的大致逻辑是这样的:@Transactional(timeout=36000,rollbackFor=Throwable.class)publicvoidupdateGoodNum(Stringid,Integernum)throwsException{//1.selectnumasdbnum,versionasdbversionfromtwhereid=#{id}//2.updatetsetnum=dbnum-num,version=dbversion+1//whereid=#{id}andversion=dbversion;//如果更新失败,重试1、2部分,共3次}我轻轻叹了口气,在mysql连接工具中执行了如下语句。把截图发给小航后,我摆出一副高手的样子说:我们测试环境的隔离级别是RR(REPEATABLE-READ),不能在事务中重试!小航尴尬的说:哥,什么是隔离级别?为什么不?怎么改?Isolationlevel隔离级别说明READUNCOMMITTED未提交读,会造成脏读,违反持久化DREADCOMMITTED读已提交数据,会造成幻读违反一致性CREPEATABLEREAD(RR)可重复读,默认隔离级别,事务中select语句会读取交易开始前的快照,当然你也可以读取交易的更新内容。SERIALIZABLE不会使用mysql的mvcc机制,而是会在每次select请求下获取读锁,在每次更新操作下尝试获取写锁。锁更新操作是读取当前值。那么在RR隔离级别下,为什么在一个事务中无法重试呢?表格模拟,为什么不呢?开始事务前,表t对应id=1,version=1事务Abegin事务Bbegin1selectversionfromtwhereid=1;--getversion=1updatetsetversion=2whereid=1;commit;2updatetsetXXwhereid=1andversion=1;//更新失败,update读取当前version=23selectversionfromtwhereid=1;//获取version=14commit注意:事务中select是读取快照,update是读取电流。简单的说,去别的事务,版本修改了2。事务A看到的还是事务开始前的值,即版本为1。解决方法在RR隔离级别下,重试被移出交易。即每次Retry重新打开一个事务汇总逻辑如下///如果更新失败,则重试updateGoodNum共3次publicAFAcadeImpl{@AutowiredAServiceaservice;publicvoidupdateGoodsNum(){inti=0;while(!aservice.updateGoodNum(id,num)&&i++<3);}}publicAServiceimplimplementAService{@Transactional(timeout=36000,rollbackFor=Throwable.class)publicbooleanupdateGoodNum(Stringid,Integernum)throwsException{//1.selectnumasdbnum,versionasdbversionfromtwhereid=#{id}//2.updatetsetnum=dbnum-num,version=dbversion+1//whereid=#{id}andversion=dbversion;/}}近期热点文章推荐:1.1000+Java面试题及答案(2021最新版)2.终于从开源项目拿到IntelliJIDEA激活码了,好贴!3、阿里Mock工具正式开源,秒杀市面上所有Mock工具!4、SpringCloud2020.0.0正式发布,全新颠覆版本!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!