11.MVCC、行锁与事务隔离级别

一、 MVCC 与行锁的场景区分

  • MVCC 相当于行锁的升级版
  • 普通的 SELECT(快照读) :后面没有 UPDATE 等修改操作的情况,底层是用 MVCC 来做隔离控制。
  • 当前读(UPDATE / DELETE / SELECT ... FOR UPDATE :这种情况底层就不用 MVCC 了,用的是来处理。
  • 它们分别用来保证事务的隔离级别(如:读已提交 RC、可重复读 RR)。

二、 快照读与当前读在 RC / RR 下的具体实现

1. 普通 SELECT(快照读,底层用 MVCC)

  • 读已提交(RC) :相当于拍两次(多次)快照。刚开始事务发起查询时拍一张快照;当另外一个事务插入/修改数据后,再查询时会再拍一张快照,所以能读到已提交的数据。
  • 可重复读(RR):事务第一次查询时拍一张快照,后续查询一直复用这张快照,保证同一事务内读到的数据一致。

2. SELECT ... FOR UPDATE(当前读,底层用锁)

  • 可重复读(RR) :需要加间隙锁临键锁
    • 间隙锁:开区间(不包含两头)。
    • 临键锁:一个点。
    • 间隙锁加临键锁组合起来,相当于一个左开右闭的区间,保证这一段间隙里插入不进去值,从而保证当前读下的可重复读(防止幻读)。
  • 读已提交(RC):不加间隙锁,实现读已提交这个隔离级别。

三、 脏读、不可重复读与幻读的区别

  • 脏读:读取到了另一个事务未提交的数据(如果对方回滚,读到的就是脏数据)。
  • 不可重复读 :同一个事务内,多次读取同一条数据,数据的内容被修改或删除了,导致前后读取的不一样。
  • 幻读:读出的记录**条数(行数)**发生了变化,增加或减少了,这叫幻读。

总结口诀(按口述直觉速记)

  • 脏读 ➡️ 读到未提交
  • 不可重复读 ➡️ 记录的内容变了
  • 幻读 ➡️ 记录的**条数(行数)**变了

四、 RC(读已提交)下的典型业务坑点与解决方案

在生产环境中,很多数据库默认采用 RC 隔离级别(并发高、锁开销小)。但在以下场景中,容易产生业务 Bug:

案例 1:财务对账/资产汇总"跨表统计不一致(凭空多钱)"

  • 场景 :计算 总资产 = 银行卡余额 + 余额宝余额
  • 问题:在 RC 下,事务 A 查询银行卡后,用户发生了转账并提交;事务 A 随后查询余额宝读到了最新转入的钱,导致总资产统计重复、凭空多出钱。
  • 解决方案 :针对该特定对账事务,显式将隔离级别设置为 RR(可重复读),确保整笔对账在同一个时间点快照下完成。

案例 2:电商抢购/余额扣减"Check-Then-Act 超卖"

  • 场景 :先 SELECT 检查库存是否 >= 1,然后 UPDATE 扣减。
  • 问题 :在 RC 下普通 SELECT 不加锁,检查通过后、扣减前,其他事务可能抢先扣减并提交,导致库存被扣成负数(超卖)。
  • 解决方案
    • 悲观锁 :查询时改用 SELECT ... FOR UPDATE 物理加锁,强行串行化。
    • 乐观锁 :利用 WHERE version = oldVersion 或状态条件 WHERE stock >= 1

五、 锁机制:悲观锁 vs 乐观锁原理对比

1. 悲观锁(Pessimistic Locking)

  • 核心假定:假设并发冲突概率很高,别人一定会来抢。
  • 做法 :在查询时直接强行上物理锁(如 SELECT ... FOR UPDATE),阻塞其他试图修改该行的事务,直到当前事务提交。
  • 优缺点:绝对安全,但并发性能较低。

2. 乐观锁(Optimistic Locking & CAS 思想)

  • 核心假定:假设并发冲突概率较低,查询时不加任何物理锁。
  • 做法(Version 标志位 / CAS 比较并交换)
    • 数据表中增加 version 字段。
    • 更新时执行:UPDATE product SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = oldVersion;
  • 逻辑判定
    • version 没变:说明在此期间无人动过数据 ➡️ 更新成功。
    • version 变了:说明被他人抢先修改 ➡️ SQL 影响行数为 0,更新失败(由应用层重试或报错)。
  • 优缺点:不上锁、并发性能极高,适合读多写少场景。

六、 事务隔离级别的 3 种控制粒度

隔离级别可以根据业务需要,在不同粒度下进行灵活控制:

  1. 全局粒度 (Global) :影响整个数据库实例(SET GLOBAL ...)。
  2. 会话粒度 (Session) :仅影响当前数据库连接(SET SESSION ...)。
  3. 特定事务粒度 (Transaction) :仅对即将开启的这一个事务 生效,提交后恢复默认。
    • SQL 语法 :在 START TRANSACTION 前执行 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    • Spring 框架语法 :直接在特定方法上使用注解 @Transactional(isolation = Isolation.REPEATABLE_READ)
相关推荐
一开18 分钟前
一个自己开发的 Agent Harness-持久化与恢复篇
后端
一开18 分钟前
一个自己开发的 Agent Harness-模型降级篇
后端
Geek漫游指南23 分钟前
AI 会回答还不够:ProofOps 业务研判平台落地实战
后端
SimonKing37 分钟前
白嫖国产多模态大模型:商汤 SenseNova 接入指南
java·后端·程序员
小江的记录本37 分钟前
【ORM框架】MyBatis核心原理、ORM思想、MyBatis vs JPA
java·数据库·后端·spring·spring cloud·oracle·mybatis
卷无止境1 小时前
FastAPI生产环境密钥管理全解析,从一个.env文件说起
后端·python·fastapi
祀爱1 小时前
C# MQTT 连接服务
后端·c#·.net
卷无止境1 小时前
SigV4与HTTPS,两套完全不同维度的安全机制
后端·python·fastapi
青石路1 小时前
好好的OceanBase官方驱动你不用,非要用第三方驱动,ArrayIndexOutOfBoundsException了吧
java·后端