• 105434

    文章

  • 803

    评论

  • 12

    友链

  • 比来新加了换肤功能,大年夜家多来走走吧~~~~
  • 爱好这个网站的同伙可以加一下QQ群,我们一路交换技巧。

mysql事物默许隔离级别下乐不雅锁(CAS)重试数据版本不更新的成绩

撸了本年阿里、腾讯和美团的面试,我有一个重要发明.......>>

异常信息

Ccom.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

成绩产生缘由

乐不雅锁修改数据的时辰,  数据版本号(version)曾经被修改了,招致修改掉败. 停止重试修改时, 每次从数据库读取出来的数据不是数据库最新版本, 招致无穷次重试, 直到该事物超时主动加入

应用乐不雅锁(CAS)机制去更新一笔记录, 从数据库查询一条数据, 当查出来数据版本号是1, 在修改数据的时辰, 把数据版本号作为修改条件之一,​update `table_name` set version=`2` where version=`1`​ , 假设修改掉败, 那么该数据曾经被更新过了, 重新读取数据, 停止重试,  而Mysql数据库中, 开启事物以后, 在以后事物中, 屡次查询的值, 会是同一个, 即修改掉败, 停止重新读数据, 取得的数据版本号是该次事物中第一次读的时辰的版本号 (即数据库事物隔离级别中的可反复读, 防止脏读景象(屡次读取值不分歧),  类似于开启一个事物的时辰, 每次读取, 会把查询的数据复制到事物空间, 以后事物读数据库的时辰, 不会读表中的实际数据, 而是读事物空间的数据.  由于这个缘由, 版本号一直不会跟新, 所以会一向修改掉败.

成绩代码表示


`[@Override](https://my.oschina.net/u/1162528)`

`@Transactional(rollbackFor = Exception.**class**) // 开端事物`

`**public** Tuple2<TradeVO, WalletVO> earn(EarnDTO earnDTO) {`

`**return** retryEarn(earnDTO);// remaind 重试在事物外部`

`}`

`// 重试机制`

`**private** Tuple2<TradeVO, WalletVO> retryEarn(EarnDTO earnDTO) {`

`Tuple2<TradeVO, WalletVO> res = self. realEarn(earnDTO);`

`**while** (Objects._isNull_(res)) { // 假设修改掉败停止重试`

`res = realEarn(earnDTO);`

`}`

`**return** res;`

`}`

`// 实际的修改`

`**private** Tuple2<TradeVO, WalletVO> realEarn(EarnDTO earnDTO) {`

`// 成绩/problem`

`// 这一行在事物中, 总是会去读取在以后事物空间中的数据`

`// 重试的机制下, CAS版本号总是不会被更新, 所以会无穷制次数的更新, 直到超时, 或许锁等待超时`

`Wallet wallet = **baseMapper**.selectOne(QueryUtils._eq_(Wallet::getUserId, userId))`

`(update(wallet)){`

`return new Tuple2(); // 修改成功前往修改信息`

`}`

`**return** **null**; // 修改掉败前往null`

`}`

处理办法

处理思路

既然找到缘由了, 那么从缘由动手, 让每次查询的时辰, 数据的版本号可以或许停止更新就好了

在事物以外停止重试

在​earn()​办法外停止重试, 而不是该办法外部, 每次重试的时辰就是开启一个新的事物, 那么就不会出现数据版本号没法跟新的成绩了


`[@Override](https://my.oschina.net/u/1162528)`

`@Transactional(rollbackFor = Exception.**class**) // 开端事物`

`**public** Tuple2<TradeVO, WalletVO> earn(EarnDTO earnDTO) {`

`**return** realEarn(earnDTO);`

`}`

`// 实际的修改`

`**private** Tuple2<TradeVO, WalletVO> realEarn(EarnDTO earnDTO) {`

`// 成绩/problem`

`// 这一行在事物中, 总是会去读取在以后事物空间中的数据`

`// 重试的机制下, CAS版本号总是不会被更新, 所以会无穷制次数的更新, 直到超时, 或许锁等待超时`

`Wallet wallet = **baseMapper**.selectOne(QueryUtils._eq_(Wallet::getUserId, userId))`

`(update(wallet)){`

`return new Tuple2(); // 修改成功前往修改信息`

`}`

`**return** **null**; // 修改掉败前往null`

`}`

`// 重试机制`

`// 在事物以外停止重试`

`**public** Tuple2<TradeVO, WalletVO> retryEarn(EarnDTO earnDTO) {`

`Tuple2<TradeVO, WalletVO> res = earn(earnDTO)`

`**while** (Objects._isNull_(res)) { // 假设修改掉败停止重试`

`res = earn(earnDTO);`

`}`

`**return** res;`

`}`

把查询SQL语句隔离事物以外

应用@Transaction的事物传播属性​propagation = Propagation.**REQUIRES_NEW**​把查询SQL语句隔离以外, 每次查询的时辰, 就会拿到最新的数据版本号, 如许就不会招致无穷次重试了

须要留意代码外部用this.办法停止调用是不会失效的, 由于数据事物是编织在AOP中的, 必须Spring代理对象才能使得​**REQUIRES_NEW**​属性失效

`@Override`

`@Transactional(rollbackFor = Exception.**class)**`

`**public** Tuple2<TradeVO, WalletVO> earn(EarnDTO earnDTO) {`

`**return** retryEarn(earnDTO);`

`}`

`**private** Tuple2<TradeVO, WalletVO> retryEarn(EarnDTO earnDTO) {`

`Tuple2<TradeVO, WalletVO> res = self. realEarn(earnDTO);`

`**while** (Objects._isNull_(res)) {`

`res = realEarn(earnDTO);`

`}`

`**return** res;`

`}`

`**private** Tuple2<TradeVO, WalletVO> realEarn(EarnDTO earnDTO) {`

`// 留意这里是self. 而不是 this.`

`Wallet wallet = self.getWallet();`

`(update(wallet)){`

`return new Tuple2();`

`}`

`**return** **null**;`

`}`

`// 开启一个新的自力事物去读取数据, 防止数据库产生幻读`

`@Override`

`@Transactional(readOnly = **true**, propagation = Propagation.**_REQUIRES_NEW_**)`

`**public** Wallet getWallet(String userId) {`

`**return** **baseMapper**.selectOne(QueryUtils._eq_(Wallet::getUserId, userId));`

`}`


695856371Web网页设计师②群 | 爱好本站的同伙可以收藏本站,或许参加我们大年夜家一路来交换技巧!

0条评论

Loading...


自定义皮肤 主体内容背景
翻开付出宝扫码付款购买视频教程
碰到成绩接洽客服QQ:419400980
注册梁钟霖小我博客