尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

数据库事务常见陷阱深度剖析:@Transactional失效、脏读、事务嵌套、长事务问题实战处理

数据库事务常见陷阱深度剖析:@Transactional失效、脏读、事务嵌套、长事务问题实战处理 在Java后端开发中Transactional注解几乎是业务开发的标配依靠Spring声明式事务可以简单实现数据库操作的提交与回滚保障业务数据一致性。但线上经常出现非常迷惑的现象明明已经加上事务注解发生异常却没有回滚部分数据已经写入数据库嵌套调用事务方法时回滚行为和预期完全不一样还有长事务导致数据库锁等待、死锁、连接池被打满引发线上故障。很多开发者遇到事务异常第一反应怀疑是数据库问题实际上绝大多数问题来源于Spring AOP代理限制、传播机制理解错误、代码编写不规范、事务范围控制不当。本文结合大量生产踩坑案例梳理事务高频陷阱拆解底层原因提供修复代码与开发规范帮助避开事务各类隐形坑保障业务数据安全。一、Transactional高频失效场景⚠️声明式事务基于Spring AOP动态代理实现一旦破坏代理调用逻辑事务就直接失效这是生产环境最高频问题。1.同类类内部方法直接调用同一个class里面非事务方法直接调用本类加了Transactional的方法不会走代理对象事务完全不生效。错误示例代码Service public class OrderServiceImpl implements OrderService { public void createOrderBiz(){ // 本类内部直接调用不走AOP代理事务失效❗ saveOrder(); } Transactional(rollbackFor Exception.class) public void saveOrder(){ orderMapper.insert(new Order()); int i 1/0; // 抛出异常不会回滚 } }解决方案把事务方法抽取到另外Service或者从Spring上下文获取自身代理对象再调用。2.异常被catch捕获没有向外抛出Spring事务只有捕获到方法抛出的异常才会触发回滚如果try‑catch把异常吃掉框架感知不到异常不会执行回滚。Transactional(rollbackFor Exception.class) public void testCatch(){ try { orderMapper.insert(new Order()); int i 1/0; }catch (Exception e){ log.error(异常,e); // 没有throw抛出异常 → 事务不会回滚❗ } }处理方式catch之后重新throw异常或者手动使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()标记回滚。3.rollbackFor配置遗漏只捕获RuntimeExceptionTransactional默认只对运行时异常RuntimeException回滚如果抛出普通Checked受检异常默认不会回滚。很多开发忘记配置rollbackFor出现业务异常数据错乱。标准写法Transactional(rollbackFor Exception.class)4.方法不是public修饰private、protected、default权限的方法Spring AOP无法生成代理注解直接无效。事务注解只能加在public方法上。二、事务传播机制带来的嵌套坑点传播机制是嵌套事务行为的核心很多业务bug根源就是对PROPAGATION_REQUIRED、REQUIRES_NEW、NESTED理解混淆。REQUIRED默认有就加入当前事务没有就新建事务。子方法异常会导致外层整个事务标记回滚外层抛异常子方法一起回滚。REQUIRES_NEW总是开启全新独立事务挂起外层事务。子事务回滚不影响外层事务外层异常也不会回滚已经提交的子事务适合记录日志、保存操作记录场景。NESTED嵌套事务基于savepoint保存点。子事务回滚只回滚到保存点不影响外层外层回滚全部一起回滚。常见踩坑外层大事务调用子方法希望子失败不影响主业务直接用默认REQUIRED做不到此时需要REQUIRES_NEW。Service public class LogRecordService { // 独立新事务就算主业务失败日志依旧可以保存入库 Transactional(propagation Propagation.REQUIRES_NEW) public void saveOperateLog(OperateLog log){ logMapper.insert(log); } }三、长事务线上隐形杀手长事务指事务开启之后中间夹杂大量业务逻辑、网络调用、文件IO、第三方http请求事务迟迟不提交。长事务带来的严重后果数据库行锁、表锁长时间持有引发大量锁等待、死锁其他业务接口大面积阻塞数据库连接池被占用耗尽新请求拿不到连接服务整体雪崩undo log堆积数据库性能下降错误写法把http远程调用放到Transactional方法内部。Transactional(rollbackFor Exception.class) public void badLongTx(){ orderMapper.insert(order); // ❌事务内部执行第三方http网络慢会拉长整个事务时长 httpApi.callThirdParty(); accountMapper.updateBalance(account); }优化原则事务方法内部只保留数据库操作网络请求、文件处理、复杂计算全部移到事务外面执行。事务尽量短、快。四、隔离级别引发的业务问题MySQL InnoDB默认隔离级别REPEATABLE‑READ可重复读大部分业务不需要修改隔离级别但要理解现象脏读读到其他事务未提交的数据MySQL默认级别不会出现脏读。不可重复读同一个事务内多次读取同一行中间被其他事务修改提交读取结果发生变化。幻读同一范围查询其他事务插入新数据再次查询出现多出的数据。MySQL RR级别通过MVCC间隙锁解决大部分幻读场景。不要随意修改全局隔离级别如需调整仅针对少数特殊业务方法设置。五、生产环境事务编码最佳实践✅事务注解只写在public方法避免本类内部直接调用事务方法。统一配置Transactional(rollbackFor Exception.class)不要使用默认配置。不要在事务方法内部写http请求、RPC调用、大循环复杂计算缩小事务边界。catch异常如果需要回滚要么重新throw要么手动标记回滚。嵌套事务分清传播机制记录日志、审计记录优先使用REQUIRES_NEW。线上开启监控统计事务执行耗时告警超时长事务。尽量避免大事务打包大量数据库操作大业务拆分为多个小事务。六、总结Spring声明式事务看着简单底层依赖AOP代理、事务传播、数据库锁机制稍有不注意就出现数据异常。很多业务线上数据错乱问题根源不是SQL写错而是事务使用方式踩坑。开发中重点规避事务失效、异常吞噬、嵌套传播混淆、长事务四大类问题。坚持小事务原则事务内只做数据库操作尽可能缩短事务生命周期才能保证数据一致性与数据库性能。
返回列表