【并发控制】CAS 与版本号机制如何高并发场景保证数据一致性?

🍰 个人主页:不摆烂的小劉
🍞文章有不合理的地方请各位大佬指正。
🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍 收藏⭐️

《redis数据一致性》讨论了加分布式锁几种方式,保证了数据一致性。
本文继续讨论并发场景下的数据一致性问题,以及常见的解决方式——加锁。而常见的锁又分为乐观锁和悲观锁,如何使用?使用时有什么注意事项?

并发场景下的数据一致性问题

1.读异常(脏读、不可重复读、幻读)

由事务间的未提交读、更新或插入操作导致

  1. 脏读
    定义:事务 A 读取到事务 B 未提交的中间数据,若 B 回滚,A 读到的是无效数据;
    典型场景:转账时,收款方在转账事务提交前查询余额,若转账回滚则显示错误金额。
    解决:设置数据库隔离级别为「读已提交」或更高

  2. 不可重复读
    定义:同一事务内两次读取同一数据,结果不同(因其他事务在期间执行了更新操作)
    典型场景:报表统计事务中,两次查询订单总金额时,中间有订单金额被修改。
    解决:提升隔离级别至「可重复读」(通过行锁或 MVCC 保证事务内数据版本不变);对读取数据加共享锁(如 SELECT ... FOR SHARE),阻止其他事务修改。

  3. 幻读:
    定义:同一事务内两次执行相同范围查询(如 WHERE status=0),结果集数量或内容不同(因其他事务在期间执行了插入 / 删除操作)
    典型场景:库存统计时,两次查询「库存 > 0」的商品,中间有新品上架或商品下架。
    解决:使用「可重复读」隔离级别(如 MySQLRR 级别,通过间隙锁锁定数据范围);对查询加范围排它锁(SELECT ... FOR UPDATE),阻止并发插入 / 删除。

2.写异常(丢失更新)

由并发修改未加控制导致。

  1. 丢失更新
    两个事务同时读取同一数据并修改,后提交的事务覆盖先提交的事务的修改,导致前者的更新被「丢失」
    典型场景:多人同时编辑文档、商品库存扣减(如两人同时购买同一商品,库存未正确递减)
    解决:悲观锁,更新前加排它锁(SELECT ... FOR UPDATE),确保同一时间仅一个事务修改;
    乐观锁,通过版本号(version 字段)或 CAS(比较并交换)校验,如 UPDATE ... WHERE id=1 AND version=旧值
    原子操作,将更新逻辑简化为数据库内置函数(如 UPDATE stock SET count=count-1 WHERE count>0

3.应用层竞争(超卖)

由业务逻辑未原子化或分布式系统特性导致

  1. 超卖:
    定义:高并发下,多个请求同时判定资源充足(如库存 > 0),但实际扣减后资源为负(如卖出 101 件库存 100 的商品)。上面只是其中一种超卖的情况,业务逻辑的错误等其他情况导致超卖这里不再讨论
    典型场景:秒杀活动中,多用户同时下单,库存校验与扣减未严格同步
    解决:原子化操作,将库存扣减逻辑写为数据库单行原子语句,如 UPDATE stock SET count=count-1 WHERE count>0; 分布式锁,通过Redis分布式锁保证同一时间仅一个节点处理扣减逻辑

悲观锁

定义:在访问共享资源前会先获取锁,确保同一时间只有一个线程能操作该资源。例Mysql的SELECT ... FOR UPDATE,Java的synchronized

  1. 适合场景:
    高冲突场景:数据争用频繁的场景(如库存扣减、订单处理);需要严格保证数据原子性的操作(如银行转账)
    低并发环境:并发量较小的系统,锁竞争压力小,适合使用悲观锁
  2. 优点:简单直观;适用于对数据准确性要求极高的场景
  3. 缺点:高并发场景下,锁竞争严重,可能导致大量事务阻塞甚至死锁; 加锁和解锁操作会增加系统开销,降低吞吐量

乐观锁

CAS

1.什么是CAS

先检查某个键的值是否符合预期,如果符合则更新为新值

2.CAS原理
  1. 读取当前值V。
  2. 比较V与预期值A:
    若相等,将V更新为B(原子操作)。
    若不等,说明其他线程已修改V,当前线程需重试或放弃

Sql修改库存为例
在这里插入图片描述

3.CAS解决什么问题
  1. 并发修改时的数据一致性问题,替代传统锁(如synchronized)悲观锁,尤其是丢失更新和原子性更新需求
  2. 避免线程因竞争锁而阻塞,减少上下文切换开销
  3. 适用于单变量的原子更新(如计数器、单例模式),典型场景包括:
    无锁计数器:AtomicInteger的incrementAndGet()
    并发容器:ConcurrentHashMap中使用CAS实现无锁数据结构更新
4.CAS带来什么风险
  1. ABA 问题:这是指在 CAS 操作期间,如果一个值 A 被修改为另一个值 B 然后再变回 A,那么 CAS 操作会误以为没有任何变化而继续执行。
  2. 只能保证一个共享变量的原子性:对于多个共享变量的操作,CAS 不能保证原子性。

版本号机制

1.版本号机制原理

添加一个version字段(版本号),记录数据的修改次数,每次更新数据时,需检查当前版本号是否与读取时一致,若一致则允许更新并递增版本号,否则拒绝操作
在这里插入图片描述

tip:其中Mysql的多版本并发控制(MVCC)与其类似,实现:数据库InnoDB为每个事务维护快照,读写互不阻塞。例如:MysqlREAD COMMITTED隔离级别下,写操作自动获取并递增version

2.版本号机制解决什么问题

和CAS类似

  1. 保证数据一致性,替代传统锁(如synchronized)悲观锁
  2. 避免ABA问题,通过版本号递增确保状态变化可追溯
3.版本号机制带来什么风险
  1. 高冲突场景下,大量事务因版本不匹配回滚

参考:
[1]使用select for share,for update的场景及死锁陷阱
[2]全方位探究似懂非懂的 CAS 机制~
[3]深入理解 CAS 原理 | Java
[4]深入理解CAS机制
[5]CAS是什么?彻底搞懂CAS
[6]乐观锁常见的两种实现方式

🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍 收藏⭐️

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值