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

基础知识:

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则自动补发库存/生成异常工单人工处理;
    这是最终兜底,隔离级别带来的一致性问题全部可以靠对账抹平。
相关推荐
知行合一。。。4 分钟前
RAG--03--Milvus基本用法
数据库·oracle·milvus
TDengine (老段)29 分钟前
TDengine 线程模型 — 网络、调度、执行
大数据·数据库·物联网·制造·时序数据库·tdengine·涛思数据
上学的小垃圾2 小时前
01-数据库系统概述
数据库
二进制漫游记2 小时前
FastAPI项目集成 Qdrant向量数据库+阿里云Embedding完整实战(工具封装+业务调用)
数据库·python·阿里云·embedding
2501_928996222 小时前
GPT-4o换DeepSeek迁移成本多少?中科热备解析API聚合平台技术账本
前端·数据库·人工智能
泡干脆面就番茄4 小时前
MySQL_子查询_分页查询与联合查询详解
数据库·mysql
devpotato5 小时前
缓存与数据库更新顺序不一致问题
java·数据库·redis
天若有情6736 小时前
Node+MySQL小型全栈笔记项目实战课程分享
数据库·笔记·mysql
wxwx_bscxy3226 小时前
基于springboot宠物领养系统的设计与实现
数据库·spring boot·后端·spring·宠物
九月要晚安7 小时前
达梦DW与AFC对比
数据库