资讯动态

Spring事务管理器原理与实战:从@Transaction失效到源码级排查

发布时间:2026/10/15 1:16:26 来源:尧图企业网站定制
1. 项目概述为什么Spring事务管理器不是“加个注解就完事”的魔法你是不是也遇到过这样的场景在Service方法上打了个Transactional测试时数据改得挺顺一上生产环境订单重复扣款、库存超卖、日志写一半就中断——查日志发现事务根本没生效或者该回滚的时候没回滚不该回滚的时候反而回滚了。我带过的几个刚转Java的开发同学头三个月几乎都栽在这个坑里他们把事务管理器当成一个开关以为开了就万事大吉结果调试三天才发现事务传播行为配置错了、异常类型被吞了、数据库连接池没配对、甚至JDK代理和CGLIB代理混用导致切面根本没织入。这根本不是Spring的问题而是我们对事务管理器底层机制缺乏系统性认知。Spring事务管理器PlatformTransactionManager本质上是一个抽象协调层它不直接操作数据库而是统一调度底层真实事务资源比如JDBC的Connection、JPA的EntityManager、JTA的全局事务协调器把“开启事务→执行业务→提交/回滚”这一整套流程标准化、可插拔化。它解决的核心矛盾是业务代码不想关心“我用的是MySQL还是PostgreSQL”也不想操心“这个方法调用另一个服务时要不要挂起当前事务”更不想为每种数据访问技术写一套事务控制逻辑。所以Spring没让你直接调connection.setAutoCommit(false)而是提供TransactionStatus、TransactionDefinition、TransactionCallback这些语义清晰的接口把“事务是什么”和“事务怎么管”彻底解耦。这个系列第15讲专攻事务管理器不是为了教你怎么写Transactional(propagation Propagation.REQUIRED)而是带你钻进源码缝里看清楚当DataSourceTransactionManager.doBegin()被调用时它到底从连接池里捞出哪个Connection为什么Transactional在private方法上无效为什么同一个类内方法调用会绕过AOP代理为什么rollbackFor Exception.class反而让事务不回滚这些都不是玄学而是有明确的执行链路和约束条件。如果你正在维护一个日均百万订单的电商结算模块或者负责一个跨多个微服务的数据一致性方案那么事务管理器的配置粒度、传播行为组合、异常分类策略每一个细节都可能决定系统是稳定运行还是雪崩式故障。这篇文章就是为你准备的——不讲概念复读只讲实操中踩过的坑、压测时暴露的边界、上线后必须盯的日志指标。2. 核心设计思路拆解事务管理器不是单点组件而是一套协同作战体系2.1 为什么必须分层从JDBC原生事务到Spring抽象的演进逻辑先看一段最原始的JDBC事务代码Connection conn dataSource.getConnection(); try { conn.setAutoCommit(false); // 执行SQL1 // 执行SQL2 conn.commit(); } catch (Exception e) { conn.rollback(); } finally { conn.close(); }这段代码问题极多连接没做连接池复用、异常类型没细分SQLException和RuntimeException处理方式不同、事务边界硬编码在业务逻辑里、无法嵌套调用、更别说跨数据源了。Spring事务管理器的诞生正是为了解决这些“硬编码耦合”问题。它的设计哲学非常清晰把事务生命周期控制权从开发者手里收上来交给一个可配置、可替换、可监控的中心化管理者。这个管理者不是孤立存在的它和三个核心组件构成铁三角关系事务定义TransactionDefinition描述“我要什么样的事务”比如隔离级别是READ_COMMITTED还是SERIALIZABLE传播行为是REQUIRED还是REQUIRES_NEW超时时间设多少。它相当于事务的“需求说明书”。事务状态TransactionStatus记录“当前事务进行到哪一步了”比如是否已激活、是否已标记为回滚、是否是新创建的事务。它相当于事务的“实时仪表盘”。事务同步TransactionSynchronization定义“事务成功/失败时要联动做什么”比如清空二级缓存、发送MQ消息、更新ES索引。它相当于事务的“事件监听器”。这三个组件共同支撑起PlatformTransactionManager接口而具体实现类如DataSourceTransactionManager、JpaTransactionManager只负责对接底层资源。这种分层不是为了炫技而是为了解决真实工程问题某次大促前我们把MySQL主库换成TiDB只需更换事务管理器实现类所有Transactional标注的业务方法完全不用改——因为业务代码只依赖PlatformTransactionManager这个抽象不关心底层是JDBC还是分布式事务协调器。提示很多团队误以为换数据库就要重写事务逻辑根源在于没理解Spring事务的抽象分层。当你看到EnableTransactionManagement这个注解时它真正开启的不是“事务功能”而是整个事务抽象层的自动装配机制。2.2 事务管理器选型不是越高级越好而是匹配业务场景的精准手术刀Spring提供了多种事务管理器实现但90%的单体应用其实只需要DataSourceTransactionManager。选择错误会导致灾难性后果——比如在纯JDBC项目里强行引入JtaTransactionManager不仅增加复杂度还会因JTA协调器缺失导致启动失败。管理器类型适用场景关键依赖风险提示DataSourceTransactionManager单数据源MySQL/Oracle/PostgreSQLDataSourceBean最常用性能最优但无法跨库JpaTransactionManagerJPA/Hibernate项目EntityManagerFactory自动处理一级缓存同步但需注意PersistenceContext作用域JtaTransactionManager多数据源XA协议JTA实现如Atomikos、Bitronix配置复杂性能损耗约30%仅在强一致性要求下使用ChainedTransactionManager多数据源非XA场景多个子管理器按顺序执行失败时只能回滚已执行的存在数据不一致风险我们曾在一个物流轨迹系统中踩过坑初期用ChainedTransactionManager管理MySQL订单和MongoDB轨迹认为“先写MySQL再写MongoDB失败就回滚MySQL”很稳妥。结果压测时发现当MongoDB写入超时触发回滚MySQL事务已提交最终出现“订单已生成但轨迹丢失”的脏数据。后来改成基于本地消息表的最终一致性方案事务管理器回归单数据源模式系统稳定性提升40%。这个教训说明事务管理器选型不是技术炫技而是对业务一致性的承诺等级评估。如果你的业务能接受“订单支付成功后5秒内轨迹可见”那就不该用JTA如果必须保证“支付和扣库存原子性”那DataSourceTransactionManager配合SELECT FOR UPDATE才是正解。2.3 代理机制深度解析为什么Transactional必须通过代理生效Transactional失效是最高频的线上问题根源全在Spring的AOP代理机制。Spring事务本质是通过动态代理在目标方法前后织入事务控制逻辑而代理生效有严格前提代理对象必须被Spring容器管理new UserServiceImpl()创建的对象不会被代理必须通过Autowired注入调用必须经过代理对象this.method()是内部调用绕过代理service.method()才走代理方法必须是publicCGLIB代理不支持private/protected方法JDK代理仅支持接口方法异常必须未被捕获try-catch吞掉异常事务管理器收不到回滚信号。我们做过一个实验在OrderService中定义public void createOrder()和private void deductStock()前者加Transactional后者调用JDBC扣库存。结果发现即使deductStock抛出RuntimeException事务也不会回滚——因为deductStock根本不在代理链路上。解决方案不是给private方法加注解无效而是将扣库存逻辑抽成独立的StockService通过Autowired注入后调用。注意Spring Boot 2.6默认禁用CGLIB代理spring.aop.proxy-target-classfalse这意味着没有接口的类只能用JDK代理。如果你的Service类没实现接口又没显式开启CGLIBTransactional会静默失效。检查ApplicationContext中TransactionInterceptor的advised属性能快速定位代理是否生效。3. 核心细节与实操要点从配置到源码的关键参数解析3.1 事务传播行为8种行为背后的业务语义映射传播行为Propagation定义了“当前方法被调用时事务该如何参与”。很多人死记硬背REQUIRED/REQUIRES_NEW却不知道它们对应的真实业务场景PROPAGATION_REQUIRED默认“加入现有事务没有就新建”适用场景同一业务流程的连续操作如“创建订单→扣减库存→生成物流单”。所有操作必须在同一个事务里要么全成功要么全回滚。PROPAGATION_REQUIRES_NEW“挂起当前事务新建独立事务”适用场景日志记录、审计跟踪等“尽力而为”操作。比如支付成功后发短信短信网关超时不能影响支付主流程。PROPAGATION_NESTED“嵌套事务支持savepoint回滚”适用场景复杂业务中需要局部回滚。例如批量导入用户某条数据格式错误只回滚这条而不影响其他成功数据。注意仅MySQL 5.7和Oracle支持H2不支持。我们曾在一个金融对账系统中用错传播行为对账任务包含“查询交易流水→比对金额→生成差异报告”三步全部用REQUIRED。结果某次网络抖动导致查询超时整个事务回滚已生成的报告丢失。后来将“生成报告”改为REQUIRES_NEW即使查询失败报告仍能保存运维人员可人工介入处理。实操技巧在IDEA中按住Ctrl点击Propagation枚举能看到每个值的JavaDoc详细说明。重点关注SUPPORTS无事务也能运行和NOT_SUPPORTED强制不使用事务——它们常用于查询类方法避免不必要的事务开销。3.2 隔离级别数据库锁机制与Spring抽象的映射关系隔离级别Isolation解决并发事务间的干扰问题。Spring定义了5个级别但实际效果取决于数据库支持Spring级别数据库对应典型问题生产建议ISOLATION_DEFAULT数据库默认级别MySQL默认REPEATABLE_READPostgreSQL默认READ_COMMITTED强烈推荐避免硬编码导致环境差异ISOLATION_READ_UNCOMMITTEDREAD UNCOMMITTED脏读、不可重复读、幻读禁用仅测试用ISOLATION_READ_COMMITTEDREAD COMMITTED不可重复读、幻读互联网高并发场景首选平衡性能与一致性ISOLATION_REPEATABLE_READREPEATABLE READ幻读MySQL默认适合强一致性要求场景ISOLATION_SERIALIZABLESERIALIZABLE无并发问题但性能极差仅金融核心账务等极端场景关键洞察Spring的隔离级别只是“建议”最终由数据库执行。比如你在MySQL上设置ISOLATION_SERIALIZABLEInnoDB会自动升级为间隙锁Gap Lock导致大量行锁等待。我们压测时发现将隔离级别从REPEATABLE_READ降到READ_COMMITTEDTPS提升2.3倍锁等待时间下降90%。注意ISOLATION_REPEATABLE_READ在MySQL中通过MVCC实现但SELECT ... FOR UPDATE仍会加临键锁Next-Key Lock。这意味着即使隔离级别相同加锁行为也受SQL类型影响——这是很多“明明配了隔离级别却还死锁”的根本原因。3.3 异常回滚策略为什么rollbackForException.class反而是陷阱事务回滚的触发条件常被误解。默认情况下Spring只对RuntimeException及其子类、Error进行回滚对Checked Exception如IOException、SQLException不回滚。很多人想“所有异常都回滚”于是写Transactional(rollbackFor Exception.class) public void transferMoney() { ... }这看似合理实则埋下隐患。Exception.class包含SQLException而JDBC操作抛出的SQLException往往表示连接超时、网络中断等外部故障。此时回滚事务并不能解决问题——数据库连接已断事务早已失效。更糟的是如果上层捕获了SQLException并重试可能造成重复扣款。正确的策略是按异常语义分类处理RuntimeException程序逻辑错误如空指针、数组越界必须回滚BusinessException自定义业务规则拒绝如余额不足应由业务层处理通常不回滚SQLException数据库层面异常需结合错误码判断。如MySQL错误码1205死锁应重试1040连接数超限应降级。我们在线上系统中定义了标准异常体系// 必须回滚的运行时异常 public class FatalException extends RuntimeException { ... } // 业务异常不回滚由上层处理 public class BusinessException extends Exception { ... } // 数据库异常按错误码路由 public class DatabaseException extends SQLException { private final int mysqlErrorCode; }然后在事务配置中精准指定Transactional( rollbackFor FatalException.class, noRollbackFor BusinessException.class )这样既保证数据一致性又避免无意义的回滚重试。4. 实操过程与核心环节实现从零搭建可验证的事务管理器4.1 基础环境搭建5分钟构建可调试的事务演示项目我们用Spring Boot 3.2 H2内存数据库构建最小可验证环境所有代码可直接运行pom.xml关键依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !-- H2数据库支持事务调试 -- dependency groupIdcom.h2database/groupId artifactIdh2/artifactId /dependency !-- 启用事务管理 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency /dependenciesapplication.yml配置spring: datasource: url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSE driver-class-name: org.h2.Driver username: sa password: h2: console: enabled: true # 开启H2控制台地址 http://localhost:8080/h2-console sql: init: mode: always schema-locations: classpath:schema.sqlschema.sql建表语句CREATE TABLE account ( id BIGINT PRIMARY KEY, name VARCHAR(50), balance DECIMAL(10,2) ); INSERT INTO account VALUES (1, Alice, 1000.00), (2, Bob, 500.00);关键配置类替代EnableTransactionManagementConfiguration EnableTransactionManagement public class TransactionConfig { Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { // 使用DataSourceTransactionManager适配H2 return new DataSourceTransactionManager(dataSource); } Bean public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) { return new TransactionTemplate(transactionManager); } }提示H2数据库支持SET DATABASE_EVENT_LISTENER可监听事务提交/回滚事件。在调试时添加EventListener监听TransactionEvent能直观看到事务生命周期。4.2 核心事务方法实现手写TransactionTemplate与声明式事务对比我们实现两个转账方法对比底层机制方式一声明式事务TransactionalService public class AccountService { Autowired private JdbcTemplate jdbcTemplate; Transactional(rollbackFor RuntimeException.class) public void transferWithAnnotation(Long fromId, Long toId, BigDecimal amount) { // 步骤1扣减转出账户 jdbcTemplate.update( UPDATE account SET balance balance - ? WHERE id ?, amount, fromId ); // 模拟网络延迟 try { Thread.sleep(100); } catch (InterruptedException e) {} // 步骤2增加转入账户 jdbcTemplate.update( UPDATE account SET balance balance ? WHERE id ?, amount, toId ); // 步骤3故意抛异常触发回滚 if (amount.compareTo(new BigDecimal(100)) 0) { throw new RuntimeException(金额超限); } } }方式二编程式事务TransactionTemplateService public class AccountService { Autowired private JdbcTemplate jdbcTemplate; Autowired private TransactionTemplate transactionTemplate; public void transferWithTemplate(Long fromId, Long toId, BigDecimal amount) { transactionTemplate.execute(status - { try { // 扣减转出账户 jdbcTemplate.update( UPDATE account SET balance balance - ? WHERE id ?, amount, fromId ); // 模拟网络延迟 try { Thread.sleep(100); } catch (InterruptedException e) {} // 增加转入账户 jdbcTemplate.update( UPDATE account SET balance balance ? WHERE id ?, amount, toId ); // 金额校验 if (amount.compareTo(new BigDecimal(100)) 0) { status.setRollbackOnly(); // 显式标记回滚 throw new RuntimeException(金额超限); } return null; } catch (Exception e) { status.setRollbackOnly(); throw e; } }); } }调试验证步骤启动应用访问http://localhost:8080/h2-console输入JDBC URL登录执行SELECT * FROM account确认初始余额调用transferWithAnnotation(1,2,50)观察余额变化调用transferWithAnnotation(1,2,150)观察余额是否回滚在transferWithAnnotation方法第一行打断点Debug模式启动观察TransactionSynchronizationManager中的resourcesMap能看到当前绑定的ConnectionHolder。实操心得用H2调试时在application.yml中添加logging.level.org.springframework.transactionDEBUG控制台会输出详细的事务开启/提交/回滚日志比看代码更直观。4.3 深度源码剖析从Transactional到Connection.setAutoCommit(false)的完整链路我们追踪Transactional的执行路径以DataSourceTransactionManager为例Step 1事务拦截器触发// TransactionInterceptor.invoke() public Object invoke(MethodInvocation invocation) throws Throwable { // 获取事务属性来自Transactional注解 TransactionAttribute txAttr getTransactionAttributeSource() .getTransactionAttribute(invocation.getMethod(), invocation.getThis()); // 获取事务管理器 PlatformTransactionManager tm determineTransactionManager(txAttr); // 创建事务 TransactionInfo txInfo createTransactionIfNecessary(tm, txAttr, invocation.getThis()); Object retVal; try { // 执行目标方法 retVal invocation.proceed(); } catch (Throwable ex) { // 异常时回滚 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { // 清理资源 cleanupTransactionInfo(txInfo); } // 方法正常返回提交事务 commitTransactionAfterReturning(txInfo); return retVal; }Step 2doBegin()获取连接并禁用自动提交// DataSourceTransactionManager.doBegin() protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject (DataSourceTransactionObject) transaction; Connection con null; try { // 1. 从数据源获取连接 if (txObject.getConnectionHolder() null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon obtainDataSource().getConnection(); // 2. 绑定到线程ThreadLocal txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } // 3. 设置连接为手动提交模式 con txObject.getConnectionHolder().getConnection(); if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); con.setAutoCommit(false); // 关键禁用自动提交 } // 4. 设置隔离级别如果需要 prepareTransactionalConnection(con, definition); } catch (SQLException ex) { // 连接获取失败清理资源 if (txObject.isNewConnectionHolder()) { releaseConnection(con, obtainDataSource()); } throw new CannotCreateTransactionException(Could not open JDBC Connection, ex); } }Step 3commit()提交或rollback()回滚// DataSourceTransactionManager.doCommit() protected void doCommit(DefaultTransactionStatus status) { DataSourceTransactionObject txObject (DataSourceTransactionObject) status.getTransaction(); Connection con txObject.getConnectionHolder().getConnection(); try { // 直接调用JDBC commit con.commit(); // 真正的数据库提交 } catch (SQLException ex) { throw new TransactionSystemException(Could not commit JDBC transaction, ex); } } // DataSourceTransactionManager.doRollback() protected void doRollback(DefaultTransactionStatus status) { DataSourceTransactionObject txObject (DataSourceTransactionObject) status.getTransaction(); Connection con txObject.getConnectionHolder().getConnection(); try { con.rollback(); // 真正的数据库回滚 } catch (SQLException ex) { throw new TransactionSystemException(Could not roll back JDBC transaction, ex); } }整个链路清晰表明Transactional只是触发器真正的事务控制权始终在JDBC Connection手中。Spring做的只是“借连接、设开关、发指令”所有数据库特性如MySQL的间隙锁、PostgreSQL的行级锁依然生效。注意ConnectionHolder通过ThreadLocal绑定到当前线程这就是为什么异步线程Async中事务失效——新线程没有继承ConnectionHolder。解决方案是手动传递TransactionSynchronizationManager的状态或改用TransactionTemplate在新线程中开启独立事务。5. 常见问题与排查技巧实录线上事故复盘与速查指南5.1 事务不生效的12种典型场景及根因分析我们整理了线上高频问题按发生概率排序排名现象根本原因快速验证方法解决方案1Transactional方法内调用另一个Transactional方法后者不生效内部调用绕过代理AOP失效在被调用方法首行打日志观察是否执行抽离为独立Service或使用ApplicationContext.getBean()获取代理对象2方法为private/protected事务注解无效JDK代理不支持非public方法CGLIB代理需开启且类无final查看TransactionInterceptor的advised属性是否为空改为public方法或在application.properties中添加spring.aop.proxy-target-classtrue3异常被try-catch捕获且未重新抛出事务管理器收不到异常信号在catch块中添加log.error(事务异常被吞, e)不要吞异常或在catch中调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()4类没有被Spring容器管理new出来的代理对象不存在检查该Bean是否在ApplicationContext中改用Autowired注入或用Configuration声明Bean5数据源配置错误如url写错DataSourceTransactionManager初始化失败事务管理器为null查看启动日志是否有No qualifying bean of type PlatformTransactionManager检查DataSourceBean是否正确配置确保Configuration类被扫描6同一个事务中多次获取Connection连接不一致DataSourceUtils.getConnection()未复用连接在方法中打印Connection.hashCode()确保所有DAO操作都通过Spring的JdbcTemplate或EntityManager避免手动dataSource.getConnection()7事务方法被同类中其他方法调用同问题1代理失效在调用处打日志确认执行路径使用AopContext.currentProxy()获取当前代理对象需开启expose-proxytrue8数据库引擎不支持事务如MyISAMMySQL MyISAM引擎无事务能力执行SHOW CREATE TABLE account查看引擎将表引擎改为InnoDBALTER TABLE account ENGINEInnoDB9事务超时时间过短方法执行超时被强制回滚Transactional(timeout1)设置过短查看日志是否有Transaction timed out根据SQL执行时间设置合理超时如timeout30秒10读写分离架构下事务内执行了读操作路由到从库从库不参与主库事务在事务方法中执行SELECT hostname确认连接库配置读写分离中间件如ShardingSphere忽略事务内读请求11使用了Async异步方法事务不传递新线程无事务上下文在异步方法中打印TransactionSynchronizationManager.isActualTransactionActive()改用TransactionTemplate在异步方法内开启新事务或配置TaskExecutor继承主线程事务上下文12Spring Boot版本升级后事务失效Spring Boot 2.6默认禁用CGLIB代理检查spring.aop.proxy-target-class属性值显式配置spring.aop.proxy-target-classtrue实操心得当怀疑事务失效时第一反应不是改代码而是看日志。在application.yml中添加logging: level: org.springframework.transaction: DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUG日志中会出现Creating new transaction、Initiating transaction commit等关键信息缺失即代表事务未触发。5.2 死锁排查实战从MySQL SHOW ENGINE INNODB STATUS到Spring日志关联死锁是事务管理中最棘手的问题。我们复现了一个经典案例死锁场景事务AUPDATE account SET balancebalance-100 WHERE id1; UPDATE account SET balancebalance100 WHERE id2;事务BUPDATE account SET balancebalance-100 WHERE id2; UPDATE account SET balancebalance100 WHERE id1;排查步骤捕获MySQL死锁日志在MySQL中执行SHOW ENGINE INNODB STATUS\G找到LATEST DETECTED DEADLOCK部分------------------------ LATEST DETECTED DEADLOCK ------------------------ 2023-10-01 10:00:00 0x7f8b1c00a700 *** (1) TRANSACTION: TRANSACTION 12345, ACTIVE 10 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 10, OS thread handle 140221234567680, query id 100 localhost root updating UPDATE account SET balancebalance100 WHERE id2 *** (1) HOLDS THE LOCK(S): RECORD LOCKS space id 123 page no 3 n bits 72 index PRIMARY of table testdb.account trx id 12345 lock_mode X locks rec but not gap *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 123 page no 4 n bits 72 index PRIMARY of table testdb.account trx id 12345 lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 12346, ACTIVE 8 sec starting index read ...关联Spring日志在应用日志中搜索Deadlock found when trying to get lock找到对应时间戳的事务ID如Transaction ID: 12345然后在日志中向上追溯找到该事务执行的SQL和调用栈。定位业务代码根据调用栈找到AccountService.transfer()方法结合SQL分析两个事务以相反顺序更新同一组记录形成环路等待。根治方案统一访问顺序所有涉及多记录更新的业务按主键升序排列操作顺序减少事务粒度将“扣减A账户→增加B账户”拆分为两个独立事务用本地消息表保证最终一致性添加重试机制捕获DeadlockLoserDataAccessException自动重试3次。注意Spring的Retryable注解需配合TransactionTemplate使用否则重试时事务已提交无法回滚。正确姿势是在TransactionTemplate.execute()内捕获死锁异常并重试。5.3 性能瓶颈诊断事务持有连接时间过长的3种检测手段事务长时间不提交会导致连接池耗尽、数据库锁堆积。我们用三种手段交叉验证手段1HikariCP连接池监控在application.yml中启用spring: datasource: hikari: metric-registry: com.codahale.metrics.MetricRegistry # 记录连接获取耗时 connection-timeout: 30000 # 连接最大生命周期 max-lifetime: 1800000然后通过Actuator端点/actuator/metrics/hikaricp.connections.acquire查看连接获取平均耗时若超过1秒说明连接池紧张。手段2MySQL慢查询日志开启long_query_time0.1捕获所有执行超100ms的SQL。重点分析SELECT ... FOR UPDATE语句的执行时间事务内执行的复杂JOIN查询未走索引的WHERE条件。手段3Arthas动态诊断用Arthas attach到JVM执行# 监控事务方法执行时间 trace com.example.service.AccountService transfer --skipJDKMethod false # 查看当前活跃事务 ognl org.springframework.transaction.support.TransactionSynchronizationManagerisActualTransactionActive() # 查看当前线程持有的Connection ognl #dataSource org.springframework.context.ApplicationContextgetBean(dataSource), #conn #dataSource.getConnection(), #conn.getMetaData().getURL()我们曾用Arthas发现一个隐藏问题某个定时任务在事务内调用了HTTP远程接口接口响应时间波动极大200ms~5s导致事务平均持有连接3.2秒。优化后改为先提交事务再异步调用HTTP连接持有时间降至20ms以内。最后分享一个小技巧在事务方法开始和结束处添加StopWatch计时记录TransactionSynchronizationManager.getCurrentTransactionName()将耗时日志推送到ELK可建立事务性能基线。当某类事务平均耗时突增50%立即触发告警——这比等连接池报错再处理提前了至少15分钟。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑