- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
作者:谢益培 。
关键词:并发、丢失更新 。
预收款账户表上有个累计抵扣金额的字段,该字段的含义是统计商家预收款账户上累计用于抵扣结算成功的金额数。更新时机是,账单结算完成时,更新累计抵扣金额=累计抵扣金额+账单金额.
发现当账单结算完成时,偶尔会发生累计抵扣金额字段值更新不准确的现象。 比如,某商家账户上累计抵扣金额原本为0元,当发生两笔分别为10和8的账单结算完成后,理论上累计抵扣金额应该变为18元,但实际为10元。也就是说,第二次更新把前一次更新内容给覆盖掉了.
该问题为典型的第二类丢失更新问题.
事务在并发情况下,常见如下问题:
第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了。SQL标准中未对此做定义,所有数据库都已解决了第一类丢失更新的问题.
第二类丢失更新:A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失。第二类丢失更新,和不可重复读本质上是同一类并发问题,通常将它看成不可重复读的特例。当两个或多个事务查询相同的记录,然后各自基于查询的结果更新记录时会造成第二类丢失更新问题。每个事务不知道其它事务的存在,最后一个事务对记录所做的更改将覆盖其它事务之前对该记录所做的更改.
发生问题的代码:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ) public void finishDeductTransaction(String customerCode, String entityCode, String currency, ABTransaction abTransaction) { Account account = getAccount(customerCode, entityCode, currency); BigDecimal newValue = account.getCumulativeDeductionAmount().add(transaction.getTransactionAmount()); account.setCumulativeDeductionAmount(newValue); // 持久化 Account update = new Account(); update.setId(account.getId()); update.setCumulativeDeductionAmount(account.getCumulativeDeductionAmount()); accountBalanceInfoMapper.update(update); }
。
上述代码中可以看到,该方法已设置事务隔离级别为可重复读(isolation = Isolation.REPEATABLE_READ,也是MySQL的默认隔离级别)。按照之前对隔离级别规范的理解,可重复读级别是应该能够避免第二类更新丢失的问题的,但为啥还是发生了呢?!于是上网查阅相关资料,得到的结论是:MySQL数据库,设置事务隔离级别为可重复读无法避免发生“第二类丢失更新”问题。从这个案例中也得到一个教训就是,规范标准和所选的产品(组件)实际实现情况,两者需要同时考虑,对于边缘性或存在争议的规范内容要尽可能避免直接使用,最好通过其他机制来保证.
以下整理了针对该问题的常见解决方案并按解决思路进行了分类.
将事务隔离级别改为串行,能解决但并发性能低,还可能导致大量超时和锁竞争.
调整SQL语句,将更新赋值逻辑改为“c=c+x”形式,其中c为要更新的字段,x为增量值。这种方式能确保累加值不会被覆盖。但这种方式需要额外编写特殊的SQL,而且严格意义上讲,存在业务逻辑泄露到持久层的不规范问题.
方法执行时增加分布式锁,来控制同一账户同一时刻只有一个线程可对其进行操作。效果等同将事务级别改为串行,也是排队执行,并发性能也低,只是锁机制不是由数据库实现了而已.
分布式锁的实现方式有多种,比如该项目中有封装好的基于Redis的分布式锁,其注解的使用方式如下:
@CbbSingle(key = " QF:finishDeductTransaction:customerCode " , value = { " #{customerCode} " }) public void finishDeductTransaction(String customerCode, String entityCode, String currency, ABTransaction abTransaction) {
。
通过SQL语句启用数据库排他锁,例如:select * from table where name=’xxx’ for update。通过此sql查询到的数据就会被数据库上排他锁,因此其他事务就无法对该数据及进行修改了.
该方法比上面两种在并发性能方面会好一些,但仍然可能存在锁等待和超时情况发生.
乐观锁的思路是假设并发冲突发生概率较低,开启事务时先不加锁,在更新数据时通过版本比对以及判断影响行数来判断是否更新成功.
其中,版本概念,可以是要更新记录的版本号,或者更新时间等。也可以用旧值条件或校验和等方式; 。
该方法并发性能最好,但一旦发生并发冲突会导致方法执行失败,此时就需要搭配额外的重试或自旋逻辑来闭环。 思路如下:
// 先查询出来要更新的数据 select column1,id,version from table where id= 1001 ; // 进行业务逻辑处理 // 更新这条数据 update table set column1=xx where id= 1001 and version= 查询出来当时的version值 // 判断影响行数 if (records < 1 ) 更新失败...
。
本案例中的问题便是采用这种方法解决的:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ) public void finishDeductTransaction(String customerCode, String entityCode, String currency, ABTransaction abTransaction) { Account account = getAccount(customerCode, entityCode, currency); BigDecimal newValue = account.getCumulativeDeductionAmount().add(transaction.getTransactionAmount()); account.setCumulativeDeductionAmount(newValue); // 持久化 Account update = new Account(); update.setId(account.getId()); update.setVersion(account.getVersion()); update.setCumulativeDeductionAmount(account.getCumulativeDeductionAmount()); int records = accountBalanceInfoMapper.updateByIdAndVersion(update); if (records < 1 ) { throw new SingleThreadException( " 更新时数据版本号已发生改变。原因:发生并发事务 " ); } }
。
最后此篇关于高并发下丢失更新的解决方案的文章就讲到这里了,如果你想了解更多关于高并发下丢失更新的解决方案的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我只是想知道要安装哪个版本的 Visual Studio 2010(专业版或高级版)提示升级项目.. 项目包括:asp.net mvc、数据库和silverlight。 最佳答案 通常,由不同版本的相
几种通过 iproute2 来打通不同节点间容器网络的方式 几种通过 iproute2 来打通不同节点间容器网络的方式 host-gw ipip vxlan 背景 之前由于需
目录 前言 1、TypeHandler 简介 1.1转换步骤 1.2转换规则 2、JSON 转换 3、枚举转换 4、文章小结
目录 前言 1、常见 key-value 2、时效性强 3、计数器相关 4、高实时性 5、排行榜系列 6、文章小结 前言 在笔者 3 年的
目录 前言 四、技术选型 五、后端接口设计 5.1业务系统接口 5.2App 端接口 六、关键逻辑实现 6.1Red
目录 前言 一、需求分析 1.1发送通知 1.2撤回通知 1.3通知消息数 1.4通知消息列表 二、数据模型设计
目录 前言 一、多租户的概念 二、隔离模式 2.1独立数据库模式 2.2共享数据库独立数据架构 2.3共享数据库共享数据架构
导读: 虽然锁在一定程度上能够解决并发问题,但稍有不慎,就可能造成死锁。本文介绍死锁的产生及处理。 死锁的产生和预防 发生死锁的必要条件有4个,分别为互斥条件、不可剥夺条件、请求与保持条件和循环等待条
在浏览网页后,我找不到任何功能来执行此操作,我有可行的个人解决方案。也许它对某人有用。 **使用 Moment 插件转换日期。***moment(currentPersianDate).clone()
是否有一种解决方案可以很好地处理数字(1-10)手写?我试过tesseract,但我得到的只是垃圾。 理想情况下是 OSS,但商业也可以。 最佳答案 OpenCV 现在带有手写数字识别 OCR 示例。
在服务器应用程序上,我们有以下内容:一个称为 JobManager 的单例类。另一个类,Scheduler,不断检查是否需要向 JobManager 添加任何类型的作业。 当需要这样做时,调度程序会执
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 想改进这个问题?将问题更新为 on-topic对于堆栈溢出。 5年前关闭。 Improve this qu
当您尝试从 GitHub 存储库安装某些 R 包时 install_github('rWBclimate', 'ropensci') 如果您遇到以下错误: Installing github repo
问题在以下链接中进行了描述和演示: Paul Stovell WPF: Blurry Text Rendering www.gamedev.net forum Microsoft Connect: W
我正在寻找一种解决方案,使用标准格式 a × 10 b 在科学记数法下格式化 R 中的数字。一些同行评审的科学期刊都要求这样做,并且手动修改图表可能会变得乏味。 下面是 R 标准“E 表示法”的示例,
已编辑解决方案(如下...) 我有一个启动画面,它被打包到它自己的 jar 中。它有效。 我可以通过以下方式从另一个 java 应用程序内部调用 Splash.jar: Desktop.getDesk
什么是创建像 PageFlakes 或 iGoogle 这样的门户网站的好框架/包? ?我们希望创建一个为员工提供 HR 服务的员工/HR 门户,但我们也需要一种足够灵活的产品,以便我们可以使用它来为
我正在寻找一种解决方案,使用标准格式 a × 10 b 在科学记数法下格式化 R 中的数字。一些同行评审的科学期刊都要求这样做,并且手动修改图表可能会变得乏味。 下面是 R 标准“E 表示法”的示例,
如何将 solr 与 heritrix 集成? 我想使用 heritrix 归档一个站点,然后使用 solr 在本地索引和搜索该文件。 谢谢 最佳答案 使用 Solr 进行索引的问题在于它是一个纯文本
完整日历不包含工作时间功能选项(在任何一天的议程 View 中选择第一行和最后一行 - 例如公司不工作)。我做到了类似的事情: viewDisplay: function(view){
我是一名优秀的程序员,十分优秀!