并发事务带来哪些问题?(超详细版)

在数据库并发事务处理场景中,若缺乏有效的隔离机制,会引发脏读不可重复读幻读 三类核心数据一致性问题,部分数据库还会伴随丢失更新的衍生问题。这些问题的根源是多个事务对同一批数据的交叉操作,且操作时序未被合理管控。

下面对每类问题进行超详细拆解,包括定义、核心成因、典型场景、危害及解决思路:

一、 脏读(Dirty Read)

1. 核心定义

一个事务读取到了另一个事务 尚未提交 的数据修改,这部分未提交的、可能被回滚的数据被称为 脏数据

2. 核心成因

低隔离级别下,数据库允许事务读取其他事务未持久化的内存修改数据,且不做任何锁或版本控制限制。

3. 典型场景(银行转账)

时间顺序 事务 T1(转账扣钱) 事务 T2(查询余额)
T1 读取账户 A 余额 = 1000 元 -
T2 - 开始执行
T3 执行 UPDATE A SET balance = 500 WHERE id=1(未提交) -
T4 - 读取账户 A 余额 = 500 元
T5 发现转账对象错误,执行 ROLLBACK(余额回滚为 1000 元) -
T6 - 基于 500 元的余额,执行了消费 300 元的操作

4. 危害

事务 T2 基于无效的脏数据做业务决策,会导致业务逻辑错乱,比如上述场景中,账户 A 实际余额 1000 元,但 T2 误以为只有 500 元,后续操作会出现数据偏差。

5. 解决思路

提升事务隔离级别至 读已提交(Read Committed) 及以上,数据库会保证事务只能读取其他事务已提交的数据。

二、 不可重复读(Non-repeatable Read)

1. 核心定义

同一个事务内,多次读取同一行数据,得到的结果不一致。因为在两次读取之间,其他事务提交了对该数据的更新操作。

2. 核心成因

隔离级别为读已提交时,虽然解决了脏读,但允许同一事务内的多次读取,感知到其他事务提交的更新,破坏了事务内数据的一致性。

3. 关键区别(与脏读对比)

  • 脏读读取的是 未提交 的数据;
  • 不可重复读读取的是 已提交 的数据。不可重复读的问题不在于数据 "脏",而在于同一事务内数据读取结果不稳定

4. 典型场景(财务对账)

时间顺序 事务 T1(对账,多次读同一数据) 事务 T2(修改余额)
T1 开始执行,第一次读取账户 A 余额 = 1000 元 -
T2 - 开始执行
T3 - 执行 UPDATE A SET balance = 1500 WHERE id=1
T4 - 提交事务
T5 第二次读取账户 A 余额 = 1500 元 -
T6 T1 内两次读取结果不一致,对账逻辑混乱 -

5. 危害

依赖同一事务内多次读取数据一致性的业务(如对账、统计、计算)会出现结果错误,无法保证业务逻辑的正确性。

6. 解决思路

  1. 提升隔离级别至 可重复读(Repeatable Read) :数据库通过 MVCC(多版本并发控制)行锁,保证同一事务内多次读取同一数据时,只能看到事务启动时的版本。
  2. 手动加锁:对读取的数据加 共享锁,阻止其他事务修改,直到当前事务结束。

三、 幻读(Phantom Read)

1. 核心定义

同一个事务内 ,多次执行 相同的范围查询 ,得到的结果集条数不一致。因为在两次查询之间,其他事务提交了对该范围数据的 插入或删除 操作,就像出现了 "幻觉"。

2. 核心成因

可重复读隔离级别能解决单行数据的不可重复读,但无法管控范围查询下的新增 / 删除操作,因为新增数据的行锁在事务启动时不存在,无法被拦截。

3. 关键区别(与不可重复读对比)

对比维度 不可重复读 幻读
操作类型 针对 单行数据的 UPDATE 针对 范围数据的 INSERT/DELETE
结果变化 数据内容改变 结果集数量改变
解决级别 可重复读即可解决 需串行化级别才能彻底解决

4. 典型场景(库存盘点)

时间顺序 事务 T1(盘点库存 > 0 的商品) 事务 T2(新增商品)
T1 开始执行,查询 SELECT * FROM goods WHERE stock > 0,得到 10 条数据 -
T2 - 开始执行
T3 - 插入一条 stock = 50 的新商品,提交事务
T4 T1 再次执行相同查询,得到 11 条数据 -
T5 T1 基于第一次的 10 条数据生成盘点报告,与实际 11 条不符 -

5. 特殊情况:"写幻读"

更危险的幻读场景是 写操作的幻觉:事务 T1 先查询范围数据,再基于查询结果执行更新,但其他事务插入了新数据,导致 T1 的更新操作意外修改了新增的数据。

sql

复制代码
-- 事务 T1
BEGIN;
-- 步骤1:查询所有库存>0的商品,此时有10条
SELECT * FROM goods WHERE stock > 0;
-- 步骤2:更新这些商品的价格(此时T2插入了1条新商品)
UPDATE goods SET price = price * 1.1 WHERE stock > 0;
COMMIT;
-- 结果:T1 本意是更新10条商品,实际更新了11条,包含了T2新增的商品

6. 解决思路

  1. 串行化(Serializable)隔离级别 :数据库对范围查询加 表锁间隙锁,阻止其他事务在该范围插入 / 删除数据,事务串行执行,彻底解决幻读,但性能损耗极大。
  2. 间隙锁 + 行锁(Next-Key Lock):MySQL InnoDB 存储引擎在可重复读级别下,会自动为范围查询加间隙锁,阻止其他事务在间隙中插入数据,可缓解大部分幻读场景,但无法完全避免。

四、 衍生问题:丢失更新(Lost Update)

1. 核心定义

两个事务同时修改同一行数据,后提交的事务会覆盖先提交事务的修改,导致先提交的修改丢失。

2. 分类

  • 第一类丢失更新:事务 T1 修改数据后未提交,事务 T2 也修改同一数据并提交,随后 T1 回滚,导致 T2 的修改被回滚丢失。
  • 第二类丢失更新:事务 T1 和 T2 都读取同一数据,T1 先修改提交,T2 后修改提交,T2 的修改覆盖了 T1 的修改。

3. 解决思路

  • 排他锁:修改数据时,阻止其他事务读取和修改该数据;
  • 使用 乐观锁:基于版本号或时间戳,在提交时校验数据是否被修改过,若被修改则放弃更新。

五、 各类并发问题与隔离级别的对应关系

隔离级别 脏读 不可重复读 幻读 丢失更新
读未提交(Read Uncommitted) ✅ 会出现 ✅ 会出现 ✅ 会出现 ✅ 会出现
读已提交(Read Committed) ❌ 不会出现 ✅ 会出现 ✅ 会出现 ✅ 会出现
可重复读(Repeatable Read) ❌ 不会出现 ❌ 不会出现 ⚠️ 部分出现(InnoDB 缓解) ❌ 不会出现
串行化(Serializable) ❌ 不会出现 ❌ 不会出现 ❌ 不会出现 ❌ 不会出现

注:✅ 代表会出现,❌ 代表不会出现,⚠️ 代表部分场景会出现

相关推荐
合方圆~小文2 分钟前
工业摄像头工作原理与核心特性
数据库·人工智能·模块测试
jmxwzy7 分钟前
Redis
数据库·redis·缓存
零叹10 分钟前
Redis热Key——大厂是怎么解决的
数据库·redis·缓存·热key
王五周八11 分钟前
基于 Redis+Redisson 实现分布式高可用编码生成器
数据库·redis·分布式
win x13 分钟前
Redis事务
数据库·redis·缓存
飞翔的小->子>弹->14 分钟前
CMK、CEK
服务器·数据库·oracle
peixiuhui19 分钟前
Iotgateway技术手册-7. 数据库设计
数据库·iotgateway·开源dotnet·arm工控板·开源网关软件·开源数据采集
麦兜*26 分钟前
【Spring Boot】 接口性能优化“十板斧”:从数据库连接到 JVM 调优的全链路提升
java·大数据·数据库·spring boot·后端·spring cloud·性能优化
qq_3344668633 分钟前
U9补丁同步的时候报错
数据库
施嘉伟36 分钟前
KSQL Developer 测试记录
数据库·kingbase