保姆级吃透MySQL事务隔离级别 + 事务传播属性|今天一次性讲明白
前言:
很早之前在苏州面试,面试官问我 MySQL 的事务隔离级别、Spring 的事务传播属性。当时两个概念混在一起,当时还是个刚入行的萌新,完全没有理解这两个概念,直接懵住了,虽然后面也顺利入职。
现在备考软考,再次翻到这块知识点,重新把这部分内容整理一下,如果有对这块不动的小伙伴可以参考:
✅ 事务隔离级别:是 MySQL(数据库层面)管的,控制【多个并发事务之间互相看不看得见对方的数据】
✅ 事务传播属性:是 Spring(框架层面)管的,控制【一个事务方法调用另外一个事务方法时,要不要共用事务、怎么创建事务】二者不是一个层级!一个管并发打架,一个管嵌套调用。这是最核心的分界线。
一、先理清底层:什么是事务、ACID
事务就是一组 SQL,满足 ACID:
- A 原子性:要么全成功,要么全回滚
- C 一致性:事务前后数据合法
- I 隔离性:并发事务之间互相隔离(隔离级别就是用来实现隔离性的)
- D 持久性:提交之后永久落盘
并发事务如果隔离没做好,会产生 3 个经典问题:脏读、不可重复读、幻读,这是隔离级别要解决的目标。
三个并发问题快速回顾(案例)
- 脏读:读到其他事务未提交的数据,对方回滚之后,你读到的数据就是无效脏数据
例:B 修改余额=2000,没提交;A 查询看到2000;B回滚余额变回1000。A读到的2000就是脏读。
- 不可重复读:同一个事务内,两次查询同一行记录,中间被别的事务 update + commit,两次读到的值不一样
例:A 第一次查用户年龄=18;B update改成20并提交;A在同一个事务再次查询,年龄变成20。
- 幻读:同一个事务内,两次范围查询,别的事务 insert / delete + commit,返回的行数变多/变少,像凭空多出来一行幻影数据
例:A 查询
age < 18得到2条;B插入一条age=16并提交;A再次相同查询,拿到3条。
⚠️ 考试&面试重点:理论标准 RR(可重复读)只能解决脏读、不可重复读,理论上不能解决幻读;MySQL InnoDB 的 RR 靠 MVCC + 间隙锁工程上规避了幻读,答题要区分【课本理论】和【MySQL实现】。
二、MySQL 四大事务隔离级别(数据库层面)
隔离级别从低到高,隔离越强,并发性能越差,冲突问题越少。
| 隔离级别 | 英文 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|---|
| 读未提交 | Read Uncommitted | ❌不能解决 | ❌不能解决 | ❌不能解决 | 能读到别人没提交的数据,几乎不会线上用 |
| 读已提交 | Read Committed(RC) | ✅解决 | ❌不能解决 | ❌不能解决 | 只能读到已经commit的数据;Oracle默认级别 |
| 可重复读 | Repeatable Read(RR) | ✅解决 | ✅解决 | 理论❌,MySQL实现✅ | InnoDB 默认隔离级别,面试最常问 |
| 串行化 | Serializable | ✅解决 | ✅解决 | ✅解决 | 事务串行排队执行,无并发,性能极低 |
实际业务案例理解
场景:电商下单,同时很多用户下单扣库存
- 如果隔离级别是 RC(读已提交):可以防止脏读,但是会出现不可重复读。同一个事务里两次查库存,中间别的订单扣减并提交,库存前后数值不一致。
- 如果隔离级别是 RR(可重复读,MySQL默认):同一个事务,多次查询库存,拿到的是快照版本,不受外部已提交update影响,避免不可重复读。
- 如果改成 Serializable:所有读写加锁排队,不会有任何并发问题,但是高流量下单直接卡死,线上几乎不用。
一句话总结隔离级别:多个事务同时跑的时候,它们之间互相“偷看数据”的权限规则。数据库层面控制。
三、划重点:事务传播属性(Spring 层面!和数据库没关系)
很多人当年和我一样踩坑:把传播属性当成数据库的东西,这是致命错误!
传播行为是 Spring 声明式事务 @Transactional 的配置,解决【方法嵌套调用时,事务怎么传递】。
举个业务场景:
orderService.createOrder()加了 @Transactional(主方法,已经开启事务)
里面调用stockService.deductStock(),这个扣库存方法也加了 @Transactional
问题来了:扣库存是复用外层下单的事务?还是新开一个独立事务?如果内层异常,要不要回滚外层订单?
这个选择规则,就是事务传播属性。
Spring 一共7种传播属性,面试高频是前5个,剩下两个冷门。
1. REQUIRED(默认,最常用)
@Transactional(propagation = Propagation.REQUIRED)
规则:如果外层已经有事务,就加入当前事务;外层没有事务,自己新建一个事务。
案例:
- 外层 createOrder 有事务 → deductStock 共用同一个事务
- 内层扣库存抛异常 → 整个事务回滚,订单创建、库存扣减一起回滚,符合下单逻辑。
适用:绝大多数业务,下单、保存主表+子表,要原子绑定。
2. REQUIRES_NEW
REQUIRES_NEW:不管外层有没有事务,内层永远新开一个独立新事务;外层事务暂停,等内层执行完再继续。
案例:
下单主事务,里面调用【记录操作日志】方法,日志就算失败,也不能把订单给回滚。
- 外层下单事务T1,调用日志方法,日志新建独立事务T2
- T2提交/回滚完全不影响T1;T1回滚,也不影响已经提交的T2日志
✅ 典型场景:操作日志、审计记录,日志失败不能干扰主业务。
3. SUPPORTS
SUPPORTS:外层有事务就加入;外层没有事务,就以非事务方式运行(无事务)。
案例:普通查询接口,可事务可不事务。有外层事务就跟着一起,没有就裸读。
注意:查询场景用得多,写操作不推荐。
4. MANDATORY
MANDATORY:必须在已有事务里运行,外层没有事务,直接抛异常。
案例:这个方法只能被别的事务方法调用,禁止单独执行。比如子表插入,不能单独跑。
如果直接单独调用这个方法,Spring直接报错:No existing transaction found for transaction marked with propagation 'mandatory'
5. NOT_SUPPORTED
NOT_SUPPORTED:挂起外层事务,内层方法永远非事务执行。
案例:里面执行一个超长的统计查询,不想占用事务锁、不想拉长主事务耗时。
外层有事务,调用这个方法的时候,外层事务先挂起来,方法裸跑,跑完恢复外层事务。
6. NEVER
NEVER:禁止在事务里运行。如果外层存在事务,直接抛异常。
和MANDATORY反过来:这个方法绝对不能放在事务里面执行。
7. NESTED(嵌套事务)【高频难点,容易和REQUIRES_NEW搞混】
NESTED:外层有事务,则在内层创建保存点(savepoint);外层没有事务,新建事务。
✅ 关键区别对比 REQUIRES_NEW:
- REQUIRES_NEW:两个完全独立事务,互不干扰
- NESTED:内层是外层事务里的保存点,内层异常,只回滚到保存点,外层可以选择继续提交;如果外层事务回滚,内层跟着一起回滚
案例:
下单主事务,包含【扣优惠券】子逻辑。优惠券扣减失败,只回滚优惠券,订单还可以继续走别的流程;但如果外层订单整体报错,优惠券也要跟着回滚。
一句话区分 NESTED vs REQUIRES_NEW
REQUIRES_NEW:分家,各是各的;
NESTED:是一家人,里面有个断点,断点坏了可以只回断点,全家翻车则一起翻车。
四、一张表搞定7种传播属性
| 传播属性 | 规则简述 | 核心场景 |
|---|---|---|
| REQUIRED(默认) | 有就加入,没有就新建 | 90%业务新增、修改,主从逻辑一起回滚 |
| REQUIRES_NEW | 一定开新独立事务,外层暂停 | 日志、审计,子逻辑失败不影响主业务 |
| SUPPORTS | 有事务就加入,无事务就裸跑 | 简单查询 |
| MANDATORY | 必须存在外层事务,否则报错 | 只能被事务方法调用的子业务 |
| NOT_SUPPORTED | 挂起外层事务,内层无事务执行 | 慢查询、大统计,避免长事务 |
| NEVER | 外层有事务直接抛异常 | 禁止事务内执行的逻辑 |
| NESTED | 外层事务内创建savepoint保存点 | 子步骤可局部回滚,但依附外层事务 |
五、面试标准回答模板(当年我缺失的答案,直接背)
面试官问:说下事务隔离级别和事务传播行为?
答:
事务隔离级别是数据库层面(MySQL InnoDB)的配置,用来控制多个并发事务之间数据可见性,解决脏读、不可重复读、幻读问题,一共4级:读未提交、读已提交、可重复读、串行化,MySQL默认是可重复读。事务传播行为是Spring框架@Transactional提供的机制,作用于方法嵌套调用,控制内层方法是复用已有事务、新建事务还是不使用事务,一共7种,最常用是REQUIRED、REQUIRES_NEW、NESTED。
二者层级不一样:隔离级别管【事务和事务并发打架】;传播属性管【一个事务里面调用另一个方法时,事务怎么传递】。
面试官追加:REQUIRES_NEW 和 NESTED 区别?
REQUIRES_NEW 是全新独立事务,内外互不影响;NESTED 是在外层事务上建立保存点,内层局部回滚不影响外层,但是外层回滚,嵌套子事务一起回滚。
六、避坑总结(我当年踩的坑)
- ❌ 错误:传播属性是MySQL的
✅ 正确:传播属性是Spring框架的,数据库根本不知道这个东西 - ❌ 错误:隔离级别控制方法嵌套
✅ 正确:隔离级别只管并发,不管方法调用嵌套 - ❌ 混淆 NESTED 和 REQUIRES_NEW
✅ 记住:独立分家=REQUIRES_NEW;一家人、局部断点=NESTED - ❌ 软考/面试坑:RR理论不能解决幻读,MySQL工程实现通过MVCC+间隙锁解决幻读,答题看场景
原文地址: https://www.cveoy.top/t/topic/qHPO 著作权归作者所有。请勿转载和采集!