常见的数据库隔离级别以及企业里常用的是什么方案

基础知识:

MySQL 默认隔离级别 RR(可重复读),依靠MVCC 多版本并发控制避免普通幻读;如果是当前读(select ... for update 锁查询),依靠间隙锁 + 临键锁彻底解决幻读。只有最高隔离级别:串行化 (Serializable) 完全杜绝所有幻读。

  • 脏读
    • 事务 B:更新 money=500,未 commit
    • 事务 A:查询读到 money=500(读到未提交数据)
    • 事务 B:执行 rollback 回滚,数据变回 1000
    • 问题:A 拿到的 500 是脏数据,完全错误
  • 不可重复读(已有行的值被修改)
    • 事务 A:第一次查询 money=1000
    • 事务 B:更新 money=600,执行 commit 提交
    • 事务 A:再次查询,money=600
    • 问题:A 同一个事务,两次读同一行结果不一样,不可重复读取
  • 幻读(新增 / 删除了行)
    • 事务 A:select * from account where money < 2000,查到 1 条数据
    • 事务 B:插入一条 money=800 的新数据,commit
    • 事务 A:相同条件再次查询,查到 2 条数据
    • 问题:A 明明没操作,凭空多出一条数据,称为幻行

四种隔离级别:

1.读未提交(存在脏读、不可重复读、幻读)

2.读已提交(存在不可重复读、幻读)

3.可重复读(存在幻读)

4.串行化(三个都不存在)

mysql innodb默认可重复读,但是大厂为了提升并发性能,常设计为读已提交,由业务层都低处理重复读、幻读问题

MySQL RC 关闭间隙锁,只有记录锁,并发高,但无法靠数据库隔离级别规避上面不可重复读和幻读,全部交给业务层兜底,下面分通用方案 + 完整订单扣库存实战案例。

业务层通用兜底方案(大厂主流):

1.悲观锁方案(for update)

RC 下 select ... for update 加行排他锁,锁定当前行,其他事务阻塞更新/删除,规避不可重复读;

缺点:并发高容易锁等待,适合库存、订单扣减这类短事务。

2.乐观锁(版本号 version / 时间戳)

无锁,靠版本号控制更新,更新时校验版本不一致直接失败重试,互联网最常用。

3.唯一约束防幻读插入

幻读根源是并发插入,给关键字段加唯一索引,重复插入直接抛唯一冲突,捕获异常重试。

4.分布式锁(Redis/Zookeeper)

全局锁住资源,同一商品/订单同一时间只允许一个事务操作,彻底杜绝并发读写冲突。

5.最终一致性兜底(消息队列+对账)

业务操作+事务消息,定时对账补偿,即使并发出现数据不一致,后台定时任务修复。

6.避免长事务

RC 冲突概率和事务时长正相关,所有更新逻辑尽量缩小事务范围。

乐观锁示例:

  1. 查询当前商品库存、版本号
    sql
    SELECT stock, version FROM product_stock WHERE product_id = 1001;
  2. 判断库存是否充足,不足直接返回失败;
  3. 执行扣减,带上版本号做条件更新:
    sql
    UPDATE product_stock
    SET stock = stock - 1, version = version + 1
    WHERE product_id = 1001 AND version = #{old_version};
  4. 判断更新影响行数:
  • row > 0:扣减成功,提交事务;
  • row = 0:说明中间被其他事务修改(不可重复读导致版本过期),回滚,重试 2~3 次。
    解决了什么
  • 不可重复读:版本号校验拦截中间更新;
  • 库存超卖:多并发只会有一个事务更新成功;
  • 幻读插入: product_id 唯一索引,不会重复创建库存行。

数据库唯一索引

插入防幻读

悲观锁示例:

sql

BEGIN;

-- 加行排他锁,锁定该行,其他事务阻塞读写

SELECT stock FROM product_stock WHERE product_id = 1001 FOR UPDATE;

if (stock < 1) {

ROLLBACK;

return "库存不足";

}

UPDATE product_stock SET stock = stock - 1 WHERE product_id = 1001;

COMMIT;

加锁后同一商品同一时间只能一个事务操作,不会出现两次读取库存不一致(不可重复读)。

分布式锁示例:

单纯数据库锁扛不住秒杀流量,Redis分布式锁前置拦截:

java

// 1. 先抢商品分布式锁

String lockKey = "lock:stock:1001";

boolean lock = redisTemplate.tryLock(lockKey, 3, TimeUnit.SECONDS);

if (!lock) return "抢购拥挤,请重试";

try {

// 2. 数据库RC事务扣库存(乐观锁)

boolean success = deductStockWithVersion(1001);

if (!success) throw new Exception("库存变动,重试");

} finally {

// 释放锁

redisTemplate.unlock(lockKey);

}

全局锁保证同一商品同时只有一个业务流程操作,从源头避免并发读写冲突,彻底屏蔽不可重复读、幻读。

无论乐观锁/分布式锁,网络、DB超时都可能出现不一致,大厂必加定时对账任务:

  1. 订单表已支付订单汇总扣减库存总量;
  2. 和商品实时库存做差值校验;
  3. 差值不为0则自动补发库存/生成异常工单人工处理;
    这是最终兜底,隔离级别带来的一致性问题全部可以靠对账抹平。
相关推荐
Database_Cool_2 小时前
数据库慢查询优化首选方案:阿里云 RDS 性能洞察+自动诊断
数据库·人工智能·阿里云
YOU OU2 小时前
Redis初识
数据库·redis·缓存
长孙豪翔2 小时前
在.net中读写config文件的各种方法
java·数据库·.net
深盾科技_Virbox3 小时前
加密狗授权能力选型:从授权模型到全生命周期管理
java·网络·数据库
峥无3 小时前
深入理解MySQL事务与MVCC机制
数据库·mysql
行思理4 小时前
MongoDB 大数据备份,新手教程
数据库·mongodb
-To be number.wan4 小时前
数据库系统 | 规范化理论
数据库·学习
城数派5 小时前
1950-2026年中国0.1°逐月平均气温栅格数据集
数据库·信息可视化
livemetee5 小时前
【关于redis高性能,高可用处理】
数据库·redis·缓存