Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CAS下ABA问题及优化方案 | 架构师之路

CAS下ABA问题及优化方案 | 架构师之路

作者头像
架构师之路
发布于 2018-03-01 11:26:11
发布于 2018-03-01 11:26:11
1.9K0
举报
文章被收录于专栏:架构师之路架构师之路

一、并发业务场景

库存业务,stock(sid, num),其中:

  • sid为库存id
  • num为库存值

如上图所示,两个并发的查询库存操作,同时从数据库都得到了库存是5。

接下来用户发生了并发的库存扣减动作:

如上图所示:

  • 用户1购买了3个库存,于是库存要设置为2
  • 用户2购买了2个库存,于是库存要设置为3

这两个设置库存的接口并发执行,库存会先变成2,再变成3,导致数据不一致(实际卖出了5件商品,但库存只扣减了2,最后一次设置库存会覆盖和掩盖前一次并发操作)

二、不一致原因分析

出现数据不一致的根本原因,是设置操作发生的时候,没有检查库存与查询出来的库存有没有变化,理论上:

  • 仅库存为5的时候,用户1的库存设置2才能成功
  • 仅库存为5的时候,用户2的库存设置3才能成功

实际执行的时候:

  • 库存为5,用户1的set stock 2确实应该成功
  • 库存变为2了,用户2的set stock 3应该失败掉

三、CAS优化

大家常说的“Compare And Set”(CAS),是一种常见的降低读写锁冲突,保证数据一致性的乐观锁机制。

针对上述库存扣减的例子,CAS升级很容易,将库存设置接口执行的SQL:

update stock set num=$num_new where sid=$sid

升级为:

update stock set num=$num_new where sid=$sid and num=$num_old

即可。

四、什么是ABA问题

CAS乐观锁机制确实能够提升吞吐,并保证一致性,但在极端情况下可能会出现ABA问题。

什么是ABA问题?

考虑如下操作:

  • 并发1(上):获取出数据的初始值是A,后续计划实施CAS乐观锁,期望数据仍是A的时候,修改才能成功
  • 并发2:将数据修改成B
  • 并发3:将数据修改回A
  • 并发1(下):CAS乐观锁,检测发现初始值还是A,进行数据修改

上述并发环境下,并发1在修改数据时,虽然还是A,但已经不是初始条件的A了,中间发生了A变B,B又变A的变化,此A已经非彼A,数据却成功修改,可能导致错误,这就是CAS引发的所谓的ABA问题。

库存操作,出现ABA问题并不会对业务产生影响。

再看一个堆栈操作的例子:

并发1(上):读取栈顶的元素为“A1”

并发2:进行了2次出栈

并发3:又进行了1次出栈

并发1(下):实施CAS乐观锁,发现栈顶还是“A1”,于是修改为A2

此时会出现系统错误,因为此“A1”非彼“A1”

五、ABA问题的优化

ABA问题导致的原因,是CAS过程中只简单进行了“值”的校验,再有些情况下,“值”相同不会引入错误的业务逻辑(例如库存),有些情况下,“值”虽然相同,却已经不是原来的数据了。

优化方向:CAS不能只比对“值”,还必须确保的是原来的数据,才能修改成功。

常见实践:“版本号”的比对,一个数据一个版本,版本变化,即使值相同,也不应该修改成功。

库存的并发读写例子,引入版本号的具体实践如下:

(1)库存表由

stock(sid, num)

升级为

stock(sid, num, version)

(2)查询库存时同时查询版本号

select num from stock where sid=$sid

升级为

select num, version from stock where sid=$sid

假设有并发操作,都会将版本号查询出来

(3)设置库存时,必须版本号相同,并且版本号要修改

旧版本“值”比对CAS

update stock set num=$num_new where sid=$sid and num=$num_old

升级为“版本号”比对CAS

update stock set num=$num_new, version=$version_new

where sid=$sid and version=$version_old

此时假设有并发操作,第一个操作,比对版本号成功,于是把库存和版本号都进行了修改。

同时存在的第二个并发操作,比对版本号发生了变化,也是库存应该修改失败。

六、总结

  • select&set业务场景,在并发时会出现一致性问题
  • 基于“值”的CAS乐观锁,可能导致ABA问题
  • CAS乐观锁,必须保证修改时的“此数据”就是“彼数据”,应该由“值”比对,优化为“版本号”比对
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构师之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
CAS下的ABA问题及优化方案!技术交流,没必要张口闭口就...(第44讲)
update stock set num=$num_new where sid=$sid
架构师之路
2025/03/11
1230
CAS下的ABA问题及优化方案!技术交流,没必要张口闭口就...(第44讲)
[MySQL]简单理解并发下的CAS比较交换和ABA问题
并发下 , 库存实际上变成了2或者3 , 这就不符合逻辑 ,应该变成0 , 因为卖出了5件商品
唯一Chat
2021/03/15
2.3K0
库存扣减还有这么多方案? | 架构师之路
昨天一篇《库存扣多了,到底怎么整》,核心观点是: 用“设置库存”替代“扣减库存”,以保证幂等性 使用CAS乐观锁,在“设置库存”时加上原始库存的比对,避免数据不一致 文章非常多朋友留言发表观点,“架构师之路”能引发不少同学思考,甚是欣慰。 原以为两个核心观点应该是没有疑义的,结果很多朋友说方案不好,今天交流下部分回复的方案,个人的一些看法。 留言一 是否能使用 update stock set num=num-$count where sid=$sid and stock>=$count; 的方式扣减库存?
架构师之路
2018/03/01
3.8K0
库存扣多了,到底怎么整 | 架构师之路
业务复杂、数据量大、并发量大的业务场景下,典型的互联网架构,一般会分为这么几层: 调用层,一般是处于端上的browser或者APP 站点层,一般是拼装html或者json返回的web-server层 服务层,一般是提供RPC调用接口的service层 数据层,提供固化数据存储的db 对于库存业务,一般有个库存服务,提供库存的查询、扣减、设置等RPC接口: 库存查询,stock-service本质上执行的是 select num from stock where sid=$sid 库存扣减,stoc
架构师之路
2018/03/01
1.9K0
库存扣多了,到底怎么整 | 架构师之路
并发扣款,如何保证一致性?
沈老师,我们有个业务,同一个用户在并发“查询,逻辑计算,扣款”的情况下,余额可能出现不一致,请问有什么优化方法么? 今天和大家聊一聊这个问题。
架构师之路
2023/01/04
9480
并发扣款,如何保证一致性?
数据库中的乐观锁与悲观锁
当我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。
曾高飞
2022/08/15
3970
漫话:如何给女朋友解释什么是乐观锁与悲观锁
最近,五一小长假的放假时间调整了,决定趁着假期出去玩一玩。我和女朋友商量好,我负责制定行程,她负责购买出行用品。相安无事,我正在各家比价中,不知道发生了什么,女朋友买买买竟然不高兴了。
周三不加班
2019/06/04
6600
漫话:如何给女朋友解释什么是乐观锁与悲观锁
抢红包案例分析以及代码实现(三)
乐观锁是一种不会阻塞其他线程并发的机制,它不会使用数据库的锁进行实现,它的设计里面由于不阻塞其他线程,所以并不会引发线程频繁挂起和恢复,这样便能够提高并发能力,也称之为为非阻塞锁。 乐观锁使用的是 CAS原理。
Java团长
2018/12/12
9160
抢红包案例分析以及代码实现(三)
Redis乐观锁解决高并发抢红包的问题【redis】
乐观锁是一种不会阻塞其他线程并发的机制,它不会使用数据库的锁进行实现,它的设计里面由于不阻塞其他线程,所以并不会引发线程频繁挂起和恢复,这样便能够提高并发能力,所以也有人把它称为非阻塞锁,那么它的机制是怎么样的呢?
sinnoo
2022/01/04
1.1K0
Redis乐观锁解决高并发抢红包的问题【redis】
并发扣减库存方案一
在开始分析库存扣减方案之前,首先有几个概念需要明确,因为本篇分析就是在此思想的基础上得出的解决方案. 那就是CAS和幂等,下边逐个做简要解释: 1.CAS CAS全称是Compare And Set,是java最底层的一种操作,jvm提供了unsafe类与物理机内存打交道,其原理 就是"比较赋值",重要的有点事比较和赋值有严格的顺序关系,并且比较成立才会赋值. java并发包中的原子操作类和重入锁都使用的CAS,下面拿AtomicInteger中的一段代码举例分析: 12345678910111213 /*
叔牙
2020/11/19
1.3K0
并发扣减库存方案一
以超卖为例✨各种场景下如何防止并发污染数据?
比如:商品库存扣减、用户余额调整、火车票、机票、演唱会入场票的扣减(类似商品库存扣减)等...
菜菜的后端私房菜
2024/08/13
2781
玩转 Spring Boot 应用篇(解决菜菜店铺商品超卖问题)
玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin)
一猿小讲
2022/05/31
6020
玩转 Spring Boot 应用篇(解决菜菜店铺商品超卖问题)
基于数据库实现的 “乐观锁” 和 “悲观锁”
有一个资源正在被操作的时候,不希望被其它人操作,此时就需要通过加锁来防止这种情况的出现。
软件书桌
2024/04/11
3000
系统重试,导致库存扣多啦,怎么办(两行代码破解)?(第41讲)
update stock set num=num-$reduce where sid=$sid
架构师之路
2025/03/06
960
系统重试,导致库存扣多啦,怎么办(两行代码破解)?(第41讲)
聊聊乐观锁与悲观锁
在MySQL中,悲观锁依赖数据库提供的锁机制来实现。在InnoDB引擎中,使用悲观锁需要先关闭MySQL数据库的自动提交属性,然后通过select ... for update来进行加锁。
@派大星
2024/05/20
1700
聊聊乐观锁与悲观锁
【📕分布式锁通关指南 01】从解决库存超卖开始加锁的初体验
背景非常简单,就是在电商项目中,用户购买商品和数量后后,系统会对商品的库存进行相应数量的扣减。因此,我们模拟这个场景就需要商品表和库存表两张表,但业务并不是这里的重点,需要简化一下,一张简单的商品库存表足以,如下:
别惹CC
2025/01/15
2650
【📕分布式锁通关指南 01】从解决库存超卖开始加锁的初体验
Java篇 | 巧妙的CAS与乐观锁
接下来我们先理解CAS怎么保证安全的修改共享变量,然后查看JDK源码分析其最佳实践,再举例实际企业开发中乐观锁思想的应用。最后总结CAS以及分析其局限性。
程序员小猿
2021/01/19
8590
java 中的CAS与ABA问题
属于悲观锁,有共享资源,需要加锁时,会以独占锁的方式导致其它需要获取锁才能执行的线程挂起,等待持有锁的钱程释放锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁的思想。
山行AI
2019/06/28
1.1K0
Java并发:非阻塞无锁(Lock-Free)算法---基于CAS+volatile实现及ABA问题
非阻塞无锁(Lock-Free)算法用底层的机器指令(例如比较交换-CAS指令)代替锁来确保数据在并发访问中的一致性。
崔认知
2023/06/19
3930
Java并发:非阻塞无锁(Lock-Free)算法---基于CAS+volatile实现及ABA问题
数据库事务与锁机制学习
事务与锁是数据库保证数据一致性和处理高并发的核心技术。可以避免脏数据、处理并发冲突,并设计出高效安全的数据库应用。
IT咸鱼
2025/05/20
1300
数据库事务与锁机制学习
推荐阅读
相关推荐
CAS下的ABA问题及优化方案!技术交流,没必要张口闭口就...(第44讲)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档