
Spring 事务保证数据一致性完整详解一、底层基础数据库原生事务 ACIDSpring 本身不具备事务能力只是对 JDBC 事务、JTA 分布式事务做高层封装与管理真正原子性、隔离性由数据库 InnoDB 引擎实现。ACID 四大特性原子性 Atomic一系列操作要么全成功、要么全回滚依托undo 日志实现回滚。一致性 Consistent事务执行前后业务数据约束完全合法主键、外键、唯一索引、余额非负等是最终目标。隔离性 Isolate多事务并发互不干扰依靠MVCC 锁4 种隔离级别。持久性 Durable提交后数据永久落地依靠redo 崩溃恢复日志。二、Spring 事务两大实现方式1. 编程式事务手动控制TransactionTemplate/PlatformTransactionManager手动开启、提交、回滚灵活但侵入代码。Autowired private TransactionTemplate transactionTemplate; public void biz() { transactionTemplate.execute(status - { // 数据库操作 if(异常条件){ status.setRollbackOnly(); //强制回滚 } return null; }); }2. 声明式事务Transactional主流基于AOP 动态代理实现无侵入分为 JDK 动态代理接口、CGLIB 代理普通类。核心执行流程调用带Transactional方法Spring 生成代理对象拦截方法。代理切面TransactionInterceptor触发事务管理器。获取数据库连接关闭自动提交conn.setAutoCommit(false)。执行业务 SQL。正常走完commit 提交抛出指定异常rollback 回滚。释放连接、恢复自动提交。三、Spring 如何保障一致性分层拆解3.1 原子性保障全部成功 / 全部失败1正常提交流程AOP 拦截方法从连接池拿到 Connection关闭自动提交。执行所有 SQL 写入 InnoDB 缓冲池。方法正常结束切面触发commit()。redo 日志刷盘事务持久化binlog 同步主从。2异常回滚机制最容易踩坑默认仅对RuntimeException、Error自动回滚Checked 受检异常不回滚。// 指定所有异常都回滚 Transactional(rollbackFor Exception.class)手动强制回滚TransactionStatus.setRollbackOnly()回滚原理InnoDB 根据 undo 日志把数据修改全部撤销。经典失效场景直接破坏原子性数据不一致方法内部try-catch吃掉异常切面感知不到异常不会自动回滚。同类内方法自调用AOP 代理失效注解完全无效。多线程异步操作子线程不属于主线程事务互不影响。传播行为配置错误REQUIRES_NEW 新开独立事务。3.2 隔离性保障Spring 对接数据库 4 种隔离级别通过Transactional(isolation Isolation.XXX)设置本质是调用 JDBC 底层设置表格隔离级别脏读不可重复读幻读底层实现READ_UNCOMMITTED允许允许允许无锁控制READ_COMMITTED (MySQL 默认)禁止允许允许MVCC 快照读REPEATABLE_READ (InnoDB 默认)禁止禁止允许MVCCSERIALIZABLE全部禁止全部禁止全部禁止全表行锁MVCC 多版本并发控制不加锁读写通过 undo 版本链 read-view 实现快照读取保证同一事务多次读取数据一致。3.3 持久性保障宕机不丢数据redo 日志事务修改先写 redo 缓冲区定时刷盘数据库崩溃重启后用 redo 重做未刷入磁盘的数据。binlog 二进制日志记录所有修改语句用于主从复制、数据恢复。两阶段提交2PC保证 redo 与 binlog 一致性阶段 1事务 prepare刷 redo 日志阶段 2全部成功提交 binlog正式 commit。 宕机在 prepare 阶段直接回滚在 commit 阶段完成持久化。3.4 一致性最终业务约束合法Spring 不直接管控业务约束依靠两层保证数据库层主键、唯一约束、外键、check 约束非法操作直接抛异常触发事务回滚。业务层事务包裹完整业务逻辑扣款 加钱、主表 子表同时在一个事务内不会出现一方成功一方失败。四、事务传播机制多事务嵌套一致性控制7 种传播属性解决多个事务方法互相调用时共用事务还是新建独立事务REQUIRED默认有事务就加入没有就新建。最常用整体一个事务一荣俱荣一损俱损。REQUIRES_NEW每次新建独立事务外层回滚不影响内层已提交事务。SUPPORTS有事务就用没有就非事务运行。NOT_SUPPORTED强制非事务执行。MANDATORY必须运行在已有事务中否则报错。NEVER禁止存在事务有事务直接抛异常。NESTED嵌套事务基于 savepoint 保存点子事务可单独回滚不影响父事务。五、本地事务 vs 分布式事务跨库一致性1单一库本地事务Spring 声明式即可完全保证单 MySQL 库一个 Connection依靠 InnoDBSpring AOP 事务完全满足 ACID。2分布式场景多库、多微服务、跨服务调用单纯Transactional完全失效会出现局部提交、局部失败数据不一致需要分布式方案2PC 两段提交Seata AT 模式强一致性性能差。TCC手动编写 Confirm/Cancel/Try侵入业务高性能。SAGA长事务补偿回滚最终一致性。本地消息表 / 可靠消息队列最终一致性主流业务选型。六、Spring 事务完整执行时序总结客户端调用业务方法 → 进入 Spring AOP 代理TransactionInterceptor 拦截向 TM 申请事务资源获取 JDBC 连接关闭自动提交执行业务所有 DML无异常执行 conn.commit ()持久化数据抛出指定异常conn.rollback ()undo 日志还原数据释放连接事务结束七、高频面试核心考点Transactional 为什么同类调用失效只有外部调用才经过代理对象内部 this 调用不走 AOP 切面。默认只回滚运行时异常必须手动指定 rollbackForException.class 才能捕获所有异常。Spring 事务只是代理管控连接提交回滚底层一致性完全依赖 InnoDB 日志机制。事务超时timeout 超时后直接抛出异常回滚防止长事务占用连接。只读事务 readonlytrue数据库优化禁止修改操作。Spring Transactional 事务失效场景 复现代码 修复方案环境SpringBoot 2.7/3.x MyBatis/MyBatis-Plus MySQL InnoDB前提数据库引擎必须是 InnoDBMyISAM 不支持事务实体简单示例用户表Data public class User { private Long id; private String name; }MapperMapper public interface UserMapper { int insert(User user); }场景 1同类内部调用this 自调用最常考错误代码Service public class UserService { Autowired private UserMapper userMapper; public void outer() { // this调用不走AOP代理Transactional失效 inner(); } Transactional(rollbackFor Exception.class) public void inner() { User u1 new User(); u1.setName(张三); userMapper.insert(u1); // 抛出异常期望回滚实际不会回滚 throw new RuntimeException(出错); } }测试调用userService.outer()数据库插入成功事务不回滚。原因AOP 事务依靠代理对象this.inner()是原生对象调用没有经过TransactionInterceptor切面拦截事务逻辑完全不执行。修复方案任选其一方案 1自己注入自身推荐Service public class UserService { Autowired private UserMapper userMapper; // 注入代理对象 Autowired private UserService self; public void outer() { self.inner(); // 使用代理调用 } Transactional(rollbackFor Exception.class) public void inner() { User u1 new User(); u1.setName(张三); userMapper.insert(u1); throw new RuntimeException(出错); } }方案 2拆分到不同 Service 方案 3开启expose-proxy使用((UserService) AopContext.currentProxy()).inner()启动类需要配置spring.aop.proxy-target-classtrueaop:aspectj-autoproxy expose-proxytrue/场景 2异常被 try-catch 捕获切面感知不到异常错误代码Service public class UserService { Autowired private UserMapper userMapper; Transactional(rollbackFor Exception.class) public void addUser() { try { User u1 new User(); u1.setName(李四); userMapper.insert(u1); int i 1 / 0; // 算术异常 } catch (Exception e) { e.printStackTrace(); // 吃掉异常没有向外抛出事务不会回滚 } } }现象插入成功数据持久化不会回滚。修复两种方式方式 1catch 后重新抛出catch (Exception e) { throw new RuntimeException(e); }方式 2手动标记回滚不抛出异常也能回滚catch (Exception e) { // 手动告知事务管理器需要回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }场景 3只抛出受检异常Exception非 RuntimeException未配置 rollbackFor错误代码// ❌ 没有 rollbackFor Transactional public void test() throws Exception { User u new User(); u.setName(王五); userMapper.insert(u); throw new Exception(受检异常); }Spring 默认规则仅 RuntimeException / Error 触发自动回滚受检异常Exception、IOException不会回滚修复Transactional(rollbackFor Exception.class)场景 4传播行为配置错误 REQUIRES_NEW 理解误区Service public class UserService { Autowired private UserMapper userMapper; Transactional(rollbackFor Exception.class) public void outer() { User u1 new User(); u1.setName(外层数据); userMapper.insert(u1); try { inner(); } catch (Exception e) { // 捕获内层异常 } // 外层正常结束外层事务提交 } Transactional(propagation Propagation.REQUIRES_NEW, rollbackFor Exception.class) public void inner() { User u2 new User(); u2.setName(内层数据); userMapper.insert(u2); throw new RuntimeException(内层异常); } }现象inner 独立事务异常回滚内层数据无记录outer 事务不受影响外层数据成功入库很多人误以为内层异常会让外层回滚REQUIRES_NEW 互相隔离场景 5方法访问权限不是 public错误代码Service public class UserService { Autowired private UserMapper userMapper; // private / protected / default 包访问权限事务失效 Transactional(rollbackFor Exception.class) private void addUser() { User u new User(); u.setName(赵六); userMapper.insert(u); throw new RuntimeException(); } }原因Spring AOP 只能拦截public方法非 public 不会生成代理增强。 ✅ 修复方法改成 public。场景 6多线程异步场景拓展高频坑Transactional(rollbackFor Exception.class) public void testThread() { new Thread(() - { User u new User(); u.setName(线程数据); userMapper.insert(u); throw new RuntimeException(); }).start(); }现象子线程抛出异常主线程事务不会回滚子线程操作独立连接不属于当前事务。事务和数据库连接绑定 ThreadLocal不同线程连接不同天然无法共享事务。 解决分布式事务方案 Seata AT / 可靠消息不要指望本地事务跨线程。配套测试 ControllerRestController RequestMapping(/tx) public class TxController { Autowired private UserService userService; GetMapping(/test1) public String test1(){ userService.outer(); return ok; } }快速排查事务失效自查清单面试可直接背诵数据库表引擎是否 InnoDBTransactional 是否加在 public 方法是否同类内部 this 调用异常是否被 try-catch 吞掉没有外抛抛出受检异常有没有配置 rollbackFor Exception.class是否多线程操作数据库传播行为 REQUIRES_NEW / NOT_SUPPORTED 是否误用是否使用不同数据源多数据源没配置事务管理器