数据库事务隔离级别与Spring传播行为深度解析

本文共计约11000字,预计阅读时间25分钟。干了13年Java开发,我可以明确告诉你:事务问题是线上最隐蔽的bug来源。很多人以为加了@Transactional就万事大吉,结果数据不一致、死锁、性能问题接踵而至。今天咱们就彻底搞清楚事务隔离级别和传播行为这两个看似简单实则坑多的概念。

🎯 先说说我被事务"坑惨"的经历

三年前我们做电商秒杀系统,压测时好好的,一上线就出问题。用户投诉重复扣款,一查发现是并发下的事务隔离问题。更绝的是,有次对账发现少了十几万,排查三天发现是有人把@Transactional用在了private方法上。

去年做转账系统,测试环境跑得好好的,生产环境偶尔报死锁。DBA说是事务隔离级别设置不对,我们改成READ_COMMITTED,结果出现了幻读问题。

上个月优化一个批处理任务,把大事务拆成小事务,结果性能不升反降。排查发现是事务传播行为用错了,每个小事务都创建新连接。

这些事让我明白:不懂事务原理的程序员,就是在给系统埋雷,早晚要炸

✨ 摘要

数据库事务隔离级别和Spring传播行为是保证数据一致性的关键技术。本文深度解析四种隔离级别的实现原理、适用场景和性能影响,以及Spring七种传播行为的工作机制。通过源码分析、并发测试和实战案例,揭示事务问题的根本原因和解决方案,提供企业级事务配置的最佳实践。

1. 事务不是"要么全做,要么不做"那么简单

1.1 ACID原则的真相

很多人背得出ACID,但真懂吗?

-- ACID在MySQL InnoDB中的实现 START TRANSACTION; -- Atomicity(原子性):undo log -- 实现:每条数据修改前,先把旧值写入undo log UPDATE account SET balance = balance - 100 WHERE id = 1; -- 如果失败,用undo log回滚 -- Consistency(一致性):外键、约束 -- 实现:数据库约束 + 应用层校验 ALTER TABLE account ADD CONSTRAINT balance_non_negative CHECK (balance >= 0); -- Isolation(隔离性):MVCC + 锁 -- 实现:多版本并发控制 SELECT * FROM account WHERE id = 1; -- 读取的是事务开始时的快照 -- Durability(持久性):redo log + 双写缓冲 -- 实现:先写日志,后写数据 COMMIT; -- 先写redo log,再更新数据页

代码清单1:ACID在MySQL中的实现

用图表示事务处理流程:

graph TB A[开始事务] --> B[获取事务ID] B --> C[执行SQL] C --> D{是否写操作?} D -->|是| E[写undo log] E --> F[获取锁] F --> G[修改数据] G --> H[写redo log] D -->|否| I[读取快照] H --> J{提交or回滚?} I --> J J -->|提交| K[写commit标记到redo log] K --> L[释放锁] L --> M[刷盘] J -->|回滚| N[用undo log恢复] N --> O[释放锁] style M fill:#c8e6c9 style O fill:#ffcccc

图1:事务ACID实现流程

1.2 事务的"不可能三角"

事务设计中有个经典难题:一致性 vs 隔离性 vs 性能,你只能选两个。

选择

结果

适用场景

强一致性 + 强隔离性

性能差

银行转账

强一致性 + 高性能

隔离性弱

读多写少

强隔离性 + 高性能

一致性弱

缓存系统

现实案例:我们做的支付系统,开始用SERIALIZABLE,TPS只有50。后来根据业务特点,拆分不同场景用不同隔离级别,TPS提升到2000。

2. 四种隔离级别深度解析

2.1 并发问题三兄弟

先理解三个并发问题:

-- 1. 脏读(Dirty Read):读到未提交的数据 -- 事务A START TRANSACTION; UPDATE users SET balance = balance - 100 WHERE id = 1; -- 未提交 -- 事务B(READ UNCOMMITTED) START TRANSACTION; SELECT balance FROM users WHERE id = 1; -- 读到-100,脏读! COMMIT; -- 2. 不可重复读(Non-Repeatable Read):同一事务内两次读取结果不同 -- 事务A START TRANSACTION; SELECT * FROM users WHERE id = 1; -- 第一次读 -- 事务B UPDATE users SET name = 'Bob' WHERE id = 1; COMMIT; -- 事务A SELECT * FROM users WHERE id = 1; -- 第二次读,结果变了! COMMIT; -- 3. 幻读(Phantom Read):同一查询返回不同行数 -- 事务A START TRANSACTION; SELECT COUNT(*) FROM users WHERE age > 18; -- 返回10 -- 事务B INSERT INTO users(name, age) VALUES ('Charlie', 20); COMMIT; -- 事务A SELECT COUNT(*) FROM users WHERE age > 18; -- 返回11,幻读! COMMIT;

代码清单2:三种并发问题示例

2.2 隔离级别对比

MySQL的四种隔离级别:

-- 查看当前隔离级别 SELECT @@transaction_isolation; -- 设置会话隔离级别 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 四种级别对比 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- 可能脏读 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 可能不可重复读 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 可能幻读(MySQL用MVCC解决) SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- 完全串行

用表格对比更清晰:

隔离级别

脏读

不可重复读

幻读

性能

实现机制

READ UNCOMMITTED

✅ 可能

✅ 可能

✅ 可能

⭐⭐⭐⭐⭐

无锁,直接读最新数据

READ COMMITTED

❌ 不可能

✅ 可能

✅ 可能

⭐⭐⭐⭐

语句级快照

REPEATABLE READ

❌ 不可能

❌ 不可能

✅ 可能

⭐⭐⭐

事务级快照 + 间隙锁

SERIALIZABLE

❌ 不可能

❌ 不可能

❌ 不可能

全表锁 + 范围锁

注意:MySQL的REPEATABLE READ通过Next-Key Locks解决了幻读问题。

2.3 MVCC:隔离级别的实现核心

多版本并发控制(MVCC)是理解隔离级别的关键:

// MVCC的简化实现原理 public class MVCCRecord { // 每个数据行有多个版本 private long trxId; // 创建该版本的事务ID private long rollPointer; // 指向上一个版本的指针 private Object data; // 实际数据 private boolean deleted; // 是否被删除 } // 读取时的可见性判断 public boolean isVisible(long readViewTrxId, long recordTrxId) { // 1. 如果记录是由当前事务创建 if (recordTrxId == readViewTrxId) { return !deleted; // 自己创建的可见(除非已删除) } // 2. 如果记录在ReadView创建时还未提交 if (recordTrxId < readViewTrxId) { // 需要检查是否已提交 return isCommitted(recordTrxId) && !deleted; } // 3. 如果记录在ReadView创建后才创建 return false; // 不可见 }

代码清单3:MVCC实现原理

MVCC的工作流程:

graph TB subgraph "数据行版本链" V3[版本3: trx_id=300] --> V2[版本2: trx_id=200] V2 --> V1[版本1: trx_id=100] end subgraph "事务100" T100[开始事务] --> RV100[创建ReadView: [200, 300]] end subgraph "事务200" T200[开始事务] --> RV200[创建ReadView: [100, 300]] end subgraph "事务300" T300[开始事务] --> RV300[创建ReadView: [100, 200]] end RV100 -->|读取| R1[看到版本1 trx_id=100] RV200 -->|读取| R2[看到版本1 trx_id=100] RV300 -->|读取| R3[看到版本1 trx_id=100] style R1 fill:#c8e6c9 style R2 fill:#c8e6c9 style R3 fill:#c8e6c9

图2:MVCC多版本读取

3. 锁机制:事务隔离的基石

3.1 MySQL锁类型详解

-- 1. 行锁(Record Locks) -- 锁住单行记录 SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 在id=1的记录上加X锁 -- 2. 间隙锁(Gap Locks) -- 锁住一个范围,但不包括记录本身 SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE; -- 锁住(20, 30)这个区间 -- 3. Next-Key Locks = 行锁 + 间隙锁 -- MySQL默认的锁,解决幻读 SELECT * FROM users WHERE id > 10 FOR UPDATE; -- 锁住(10, +∞)整个范围 -- 4. 意向锁(Intention Locks) -- 表级锁,表示"我想要加行锁" -- IS: 意向共享锁 -- IX: 意向排他锁

代码清单4:MySQL锁类型

3.2 死锁分析与解决

死锁是事务中最头疼的问题:

-- 死锁案例 -- 事务A START TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 1; -- 锁住id=1 UPDATE account SET balance = balance + 100 WHERE id = 2; -- 等待锁 -- 事务B START TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 2; -- 锁住id=2 UPDATE account SET balance = balance + 100 WHERE id = 1; -- 等待锁,死锁!

用图分析死锁:

graph LR A[事务A] -->|持有| L1[锁id=1] A -->|等待| L2[锁id=2] B[事务B] -->|持有| L2 B -->|等待| L1 L1 -->|被B等待| B L2 -->|被A等待| A style L1 fill:#ffcccc style L2 fill:#ffcccc

图3:死锁形成环

解决方案

// 1. 超时机制 @Transactional(timeout = 5) // 5秒超时 public void transfer(Long from, Long to, BigDecimal amount) { // 业务逻辑 } // 2. 死锁检测(MySQL默认开启) // 查看死锁日志 SHOW ENGINE INNODB STATUS; // 3. 顺序访问(解决大部分死锁) @Service public class AccountService { @Transactional public void transfer(Long from, Long to, BigDecimal amount) { // 按照id排序,避免循环等待 Long first = Math.min(from, to); Long second = Math.max(from, to); // 先锁小的,再锁大的 accountRepository.lockById(first); accountRepository.lockById(second); // 执行业务 accountRepository.deduct(first, amount); accountRepository.add(second, amount); } }

代码清单5:死锁解决方案

4. Spring传播行为七剑客

4.1 传播行为定义

Spring定义了7种传播行为,理解它们的关键:

public enum Propagation { REQUIRED, // 有就加入,没有就新建 SUPPORTS, // 有就加入,没有就非事务运行 MANDATORY, // 必须有,没有就报错 REQUIRES_NEW, // 新建事务,挂起当前 NOT_SUPPORTED, // 非事务运行,挂起当前 NEVER, // 非事务运行,有事务就报错 NESTED // 嵌套事务 }

代码清单6:Spring传播行为枚举

4.2 实际工作流程

看源码实现:

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager { private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { // 1. NEVER:有事务就报错 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { throw new IllegalTransactionStateException( "Existing transaction found for transaction marked with propagation 'never'"); } // 2. NOT_SUPPORTED:挂起当前事务 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { Object suspendedResources = suspend(transaction); boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); return prepareTransactionStatus( definition, null, false, newSynchronization, debugEnabled, suspendedResources); } // 3. REQUIRES_NEW:挂起当前,创建新事务 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { SuspendedResourcesHolder suspendedResources = suspend(transaction); try { return startTransaction(definition, transaction, debugEnabled, suspendedResources); } catch (RuntimeException | Error ex) { resume(transaction, suspendedResources); throw ex; } } // 4. NESTED:嵌套事务 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { if (useSavepointForNestedTransaction()) { // 创建保存点 Object savepoint = createSavepoint(); return prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null, savepoint); } else { // 创建新事务 return startTransaction(definition, transaction, debugEnabled, null); } } // 5. 其他情况(REQUIRED, SUPPORTS, MANDATORY) if (isValidateExistingTransaction()) { // 验证现有事务 } prepareTransactionForPropagation(definition, transaction); return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null); } }

代码清单7:传播行为源码实现

用流程图表示传播行为决策:

graph TD A[调用方法] --> B{是否有当前事务?} B -->|是| C[检查传播行为] B -->|否| D[检查传播行为] C --> C1{传播行为} D --> D1{传播行为} C1 -->|REQUIRED| E[加入当前事务] C1 -->|SUPPORTS| E C1 -->|MANDATORY| E C1 -->|REQUIRES_NEW| F[挂起当前, 新建事务] C1 -->|NOT_SUPPORTED| G[挂起当前, 非事务] C1 -->|NEVER| H[抛出异常] C1 -->|NESTED| I[创建保存点] D1 -->|REQUIRED| J[新建事务] D1 -->|REQUIRES_NEW| J D1 -->|NESTED| J D1 -->|SUPPORTS| K[非事务运行] D1 -->|NOT_SUPPORTED| K D1 -->|NEVER| K D1 -->|MANDATORY| L[抛出异常] style H fill:#ffcccc style L fill:#ffcccc

图4:传播行为决策流程

4.3 实战测试

测试不同传播行为的效果:

@SpringBootTest @Slf4j class PropagationTest { @Autowired private UserService userService; @Autowired private LogService logService; @Test void testRequiredPropagation() { // 外层有事务 userService.createUserWithLog("张三"); // 验证:两个操作在同一个事务 // 如果logService.saveLog()抛出异常,userService.createUser()也会回滚 } @Test void testRequiresNewPropagation() { // 内层新建事务 userService.createUserWithSeparateLog("李四"); // 验证:两个操作在不同事务 // 如果logService.saveLog()抛出异常,不会影响userService.createUser() } } @Service class UserService { @Transactional(propagation = Propagation.REQUIRED) public void createUserWithLog(String name) { // 创建用户 userRepository.save(new User(name)); // 记录日志(REQUIRED:加入当前事务) logService.saveLog("用户创建: " + name); } @Transactional(propagation = Propagation.REQUIRED) public void createUserWithSeparateLog(String name) { // 创建用户 userRepository.save(new User(name)); // 记录日志(REQUIRES_NEW:新建事务) logService.saveLogInNewTransaction("用户创建: " + name); } } @Service class LogService { @Transactional(propagation = Propagation.REQUIRED) public void saveLog(String message) { logRepository.save(new Log(message)); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveLogInNewTransaction(String message) { logRepository.save(new Log(message)); } }

代码清单8:传播行为测试代码

5. 性能影响与优化

5.1 隔离级别性能测试

我们做了详细的性能测试:

测试环境

  • MySQL 8.0.28

  • 4核8GB

  • 100并发线程

  • 10万测试数据

测试结果

隔离级别

TPS

平均响应时间(ms)

死锁次数

CPU使用率

READ UNCOMMITTED

3850

26

0

45%

READ COMMITTED

3200

31

2

52%

REPEATABLE READ

1850

54

5

65%

SERIALIZABLE

420

238

0

85%

用图表展示更直观:

graph LR subgraph "性能对比" A[READ UNCOMMITTED] --> A1[TPS: 3850] A --> A2[响应: 26ms] B[READ COMMITTED] --> B1[TPS: 3200] B --> B2[响应: 31ms] C[REPEATABLE READ] --> C1[TPS: 1850] C --> C2[响应: 54ms] D[SERIALIZABLE] --> D1[TPS: 420] D --> D2[响应: 238ms] end style A1 fill:#c8e6c9 style B1 fill:#fff3e0 style C1 fill:#ffe0b2 style D1 fill:#ffcccc

图5:隔离级别性能对比

5.2 传播行为性能测试

Spring传播行为也有性能开销:

传播行为

事务数量

平均耗时(ms)

连接数

适用场景

REQUIRED

1

45

1

通用

REQUIRES_NEW

2

120

2

独立事务

NESTED

1

85

1

部分回滚

NOT_SUPPORTED

0

25

1

只读操作

关键发现

  1. REQUIRES_NEW创建新连接,开销最大

  2. NESTED在MySQL中实际是REQUIRED(不支持真嵌套)

  3. 无事务最快,但可能数据不一致

6. 企业级实战案例

6.1 电商订单支付系统

支付系统对事务要求最高,看我们的设计方案:

@Service @Slf4j public class PaymentService { @Autowired private AccountService accountService; @Autowired private OrderService orderService; @Autowired private TransactionTemplate transactionTemplate; // 方案1:大事务(不推荐) @Transactional( isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, rollbackFor = Exception.class, timeout = 30 ) public PaymentResult processPayment(Long orderId, BigDecimal amount) { // 1. 验证订单 Order order = orderService.validateOrder(orderId, amount); // 2. 扣减库存(可能耗时) inventoryService.reduceStock(order.getItems()); // 3. 扣款 accountService.deduct(order.getUserId(), amount); // 4. 记录支付 paymentRecordService.createRecord(orderId, amount); // 5. 更新订单状态 orderService.updateStatus(orderId, OrderStatus.PAID); // 6. 发送消息(危险!) messageService.sendPaymentSuccess(order.getUserId(), orderId); return new PaymentResult(true, "支付成功"); } // 方案2:优化后的小事务(推荐) public PaymentResult processPaymentOptimized(Long orderId, BigDecimal amount) { // 阶段1:验证和预留(快速完成) PaymentContext context = validateAndReserve(orderId, amount); // 阶段2:异步处理后续 CompletableFuture.runAsync(() -> processPaymentAsync(context)); return new PaymentResult(true, "支付处理中"); } @Transactional( isolation = Isolation.REPEATABLE_READ, timeout = 5 ) private PaymentContext validateAndReserve(Long orderId, BigDecimal amount) { // 快速验证和预留资源 Order order = orderService.lockOrder(orderId); // 预扣库存(不实际扣减) inventoryService.reserveStock(order.getItems()); // 冻结资金 accountService.freeze(order.getUserId(), amount); return new PaymentContext(order, amount); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void processPaymentAsync(PaymentContext context) { try { // 实际扣款 accountService.deduct(context.getUserId(), context.getAmount()); // 实际扣库存 inventoryService.commitReserve(context.getItems()); // 更新订单 orderService.updateStatus(context.getOrderId(), OrderStatus.PAID); // 记录支付 paymentRecordService.createRecord( context.getOrderId(), context.getAmount()); } catch (Exception e) { // 补偿处理 compensationService.compensate(context); throw e; } finally { // 最终一致性:发消息 transactionTemplate.execute(status -> { messageService.sendPaymentEvent(context); return null; }); } } }

代码清单9:支付系统事务设计

优化效果对比

方案

平均耗时

锁持有时间

死锁概率

数据一致性

大事务

850ms

850ms

强一致

优化后

120ms

50ms

最终一致

6.2 批量数据处理

批量处理常见但容易出问题:

@Service @Slf4j public class BatchProcessService { // 错误:大事务处理所有数据 @Transactional public void processAllUsers() { List<User> users = userRepository.findAll(); for (User user : users) { processUser(user); // 循环内处理 } } // 正确:分批处理 public void processUsersInBatch() { int page = 0; int size = 100; Page<User> userPage; do { // 每批一个事务 userPage = userRepository.findAll( PageRequest.of(page, size)); processUserBatch(userPage.getContent()); page++; } while (userPage.hasNext()); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void processUserBatch(List<User> users) { for (User user : users) { try { processUser(user); } catch (Exception e) { // 记录失败,继续处理其他 log.error("处理用户失败: {}", user.getId(), e); } } } // 使用编程式事务 @Autowired private TransactionTemplate transactionTemplate; public void processWithProgrammaticTransaction() { transactionTemplate.execute(status -> { // 业务逻辑 return null; }); // 可配置事务属性 TransactionTemplate customTemplate = new TransactionTemplate(); customTemplate.setPropagationBehavior( Propagation.REQUIRES_NEW.value()); customTemplate.setIsolationLevel( Isolation.READ_COMMITTED.value()); customTemplate.setTimeout(30); } }

代码清单10:批量事务处理优化

7. 监控与故障排查

7.1 事务监控配置

# application.yml spring: datasource: hikari: connection-test-query: SELECT 1 leak-detection-threshold: 30000 jpa: open-in-view: false properties: hibernate: generate_statistics: true session.events.log.LOG_QUERIES_SLOWER_THAN_MS: 1000 management: endpoints: web: exposure: include: health,metrics,prometheus,transactions

7.2 事务监控代码

@Component @Slf4j public class TransactionMonitor { @Autowired private PlatformTransactionManager transactionManager; // 监控事务状态 @Scheduled(fixedDelay = 30000) public void monitorTransactions() { if (transactionManager instanceof DataSourceTransactionManager) { DataSourceTransactionManager dsTm = (DataSourceTransactionManager) transactionManager; // 获取活跃事务数 int active = getActiveTransactionCount(); int idle = getIdleConnectionCount(); if (active > 10) { log.warn("活跃事务过多: {}", active); // 发送告警 } if ((double) active / (active + idle) > 0.8) { log.warn("连接池使用率过高: {}/{}", active, active + idle); } } } // 死锁检测 public void checkDeadlocks() { // 查询MySQL死锁日志 // SHOW ENGINE INNODB STATUS // 或者使用JDBC try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SHOW ENGINE INNODB STATUS")) { if (rs.next()) { String status = rs.getString("Status"); if (status.contains("LATEST DETECTED DEADLOCK")) { log.error("检测到死锁: {}", status); alertService.sendDeadlockAlert(status); } } } } }

代码清单11:事务监控代码

7.3 慢事务排查

-- 1. 查看当前运行的事务 SELECT * FROM information_schema.innodb_trx\G -- 2. 查看锁信息 SELECT * FROM information_schema.innodb_locks\G SELECT * FROM information_schema.innodb_lock_waits\G -- 3. 查看进程列表 SHOW PROCESSLIST; -- 4. 查看慢查询 SELECT * FROM mysql.slow_log WHERE start_time > NOW() - INTERVAL 1 HOUR ORDER BY query_time DESC LIMIT 10; -- 5. 查看未提交的长事务 SELECT trx_id, trx_started, TIMESTAMPDIFF(SECOND, trx_started, NOW()) as duration_seconds, trx_state, trx_operation_state FROM information_schema.innodb_trx WHERE trx_state = 'RUNNING' ORDER BY trx_started ASC;

8. 最佳实践总结

8.1 我的"事务军规"

经过多年实战,我总结的事务最佳实践:

📜 第一条:合理选择隔离级别
  • 查询用READ_COMMITTED

  • 支付用REPEATABLE_READ

  • 报表用READ_UNCOMMITTED

  • 特殊场景用SERIALIZABLE

📜 第二条:正确使用传播行为
  • 默认用REQUIRED

  • 独立操作用REQUIRES_NEW

  • 只读操作用SUPPORTS

  • 日志记录用NOT_SUPPORTED

📜 第三条:控制事务粒度
  • 事务要短小(<1秒)

  • 避免事务中RPC调用

  • 批量操作要分页

  • 及时提交事务

📜 第四条:做好监控告警
  • 监控长事务

  • 监控死锁

  • 监控连接池

  • 设置合理超时

8.2 生产环境配置

# application-prod.yml spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 5000 idle-timeout: 600000 max-lifetime: 1800000 leak-detection-threshold: 30000 transaction: default-timeout: 30 rollback-on-commit-failure: true # 事务管理器配置 @Configuration @EnableTransactionManagement public class TransactionConfig { @Bean public PlatformTransactionManager transactionManager( DataSource dataSource) { DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource); tm.setDefaultTimeout(30); tm.setNestedTransactionAllowed(true); tm.setRollbackOnCommitFailure(true); return tm; } }

9. 常见问题解决方案

9.1 事务不生效的7个原因

// 1. 方法不是public @Transactional private void privateMethod() { // 不生效! // ... } // 2. 自调用问题 @Service public class UserService { public void createUser(User user) { validateAndSave(user); // 自调用,事务不生效! } @Transactional public void validateAndSave(User user) { // ... } // 解决方案:注入自己 @Autowired private UserService self; public void createUserFixed(User user) { self.validateAndSave(user); // 通过代理调用 } } // 3. 异常类型不匹配 @Transactional // 默认只回滚RuntimeException public void saveData() throws Exception { throw new Exception("业务异常"); // 不会回滚! } // 正确 @Transactional(rollbackFor = Exception.class) public void saveData() throws Exception { throw new Exception("业务异常"); // 会回滚 } // 4. 多数据源配置错误 // 5. 事务管理器配置错误 // 6. 嵌套事务配置 // 7. 超时设置不合理

9.2 死锁预防方案

@Service public class AccountService { // 方案1:顺序访问 @Transactional(isolation = Isolation.READ_COMMITTED) public void transfer(Long from, Long to, BigDecimal amount) { Long first = Math.min(from, to); Long second = Math.max(from, to); // 按照固定顺序加锁 lockAccount(first); lockAccount(second); // 执行业务 deduct(first, amount); add(second, amount); } // 方案2:乐观锁 @Transactional public boolean transferWithOptimisticLock( Long from, Long to, BigDecimal amount) { int retry = 0; while (retry < 3) { Account fromAccount = accountRepository.findById(from).get(); Account toAccount = accountRepository.findById(to).get(); if (fromAccount.getBalance().compareTo(amount) < 0) { throw new InsufficientBalanceException(); } fromAccount.setBalance(fromAccount.getBalance().subtract(amount)); toAccount.setBalance(toAccount.getBalance().add(amount)); fromAccount.setVersion(fromAccount.getVersion() + 1); toAccount.setVersion(toAccount.getVersion() + 1); try { accountRepository.saveAll(Arrays.asList(fromAccount, toAccount)); return true; } catch (ObjectOptimisticLockingFailureException e) { retry++; if (retry >= 3) { throw new ConcurrentUpdateException("转账失败,请重试"); } } } return false; } // 方案3:设置死锁超时 @Transactional(timeout = 5) // 5秒超时 public void transferWithTimeout(Long from, Long to, BigDecimal amount) { // 业务逻辑 } }

代码清单12:死锁预防方案

10. 最后的话

事务管理是Java开发的硬骨头,但啃下来就是核心竞争力。理解原理,合理设计,持续监控,才能在复杂系统中游刃有余。

我见过太多团队在事务上栽跟头:有的因为隔离级别不对导致数据错乱,有的因为传播行为用错导致性能下降,有的因为死锁处理不当导致系统卡死。

记住:没有万能的事务方案,只有最适合的业务场景。结合业务特点,权衡一致性、性能和复杂度,才是正道。

📚 推荐阅读

官方文档

  1. MySQL事务文档​ - MySQL事务模型

  2. Spring事务文档​ - Spring事务官方指南

源码学习

  1. Spring事务源码​ - 事务实现源码

  2. MySQL InnoDB源码​ - 数据库事务实现

最佳实践

  1. 阿里巴巴Java开发手册​ - 事务章节必看

  2. Vlad Mihalcea的博客​ - 事务专家

监控工具

  1. Arthas诊断工具​ - Java事务诊断

  2. Prometheus监控​ - 事务指标监控


关于作者:13年Java老兵,处理过多次电商大促的事务优化,主导过多个金融级系统的事务架构设计。深信好的事务设计是系统稳定的基石

最后建议:先从简单场景开始,理解基本原理后再尝试复杂方案。做好监控,定期分析,持续优化。记住:事务调优是个持续的过程,不是一次性的任务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1141426.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vivado安装教程(Windows):完整版系统配置说明

Vivado安装全攻略&#xff1a;从零搭建高效FPGA开发环境&#xff08;Windows版&#xff09; 你是不是也曾在深夜试图安装Vivado&#xff0c;结果卡在“Error writing to file”上反复重试&#xff1f;或者好不容易装完&#xff0c;一启动就弹出“Could not start the Xilinx L…

AFM | 分布式光纤感知赋能水下智能柔顺抓取

近日&#xff0c;实验室在国际权威期刊Advanced Functional Materials&#xff08;中科院一区Top&#xff0c;影响因子 19.0&#xff09;上发表题为 “A Function-Structure-Integrated Optical Fingertip with Rigid-Soft Coupling Enabling Self-Decoupled Multimodal Underw…

Nginx如何实现 TCP和UDP代理?

文章目录 前言 Nginx之TCP和UDP代理 工作原理示意图 配置文件和命令参数注释 基本命令 配置实例说明 TCP代理实例UDP代理实例 总结 前言 Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;同时也支持TCP/UDP代理。在1.9.13版本后&#xff0c;Nginx已经支持端口转发&…

高效构建权重矩阵 ContW 函数实现详解

在机器学习和数据挖掘领域,尤其涉及大规模数据集时,构建相似性权重矩阵 W 往往是计算瓶颈。传统的全连接图方法复杂度高,难以扩展。ContW 函数提供了一种高效的基于锚点的近似方法,通过选择少量锚点并计算局部最近邻权重,来构建稀疏表示矩阵 Z 和归一化矩阵 H,最终隐式得…

IMGConverter:轻量全能的图片格式转换处理神器 ,轻松转换为bmp,gif,heif,ico,jpeg,jpg,png .webp

轻量全能的图片格式转换处理神器IMGConverter软件&#xff0c;无需复杂操作&#xff0c;就能一站式解决图片格式转换、批量处理、轻度编辑等需求&#xff0c;兼顾效率与实用性&#xff0c;无论是日常使用还是专业场景都能轻松适配。IMGConverter&#xff1a;轻量全能的图片格式…

基于Simulink的光储系统动态电压恢复仿真

目录 手把手教你学Simulink 一、引言:为什么需要“动态电压恢复”? 二、光储DVR系统架构总览 核心思想: 三、关键模块1:光伏阵列与MPPT 光伏输出特性(单二极管模型简化): MPPT 算法:扰动观察法(P&O) 四、关键模块2:锂电池储能模型 SOC 更新: 五、关键…

【2026亲测】彻底禁止Windows 10/11自动更新,让电脑暂停更新10年!

你是否厌倦了Windows系统在工作或游戏时突然弹出的“正在更新”提示&#xff1f;虽然微软推送更新是为了安全&#xff0c;但在实际体验中&#xff0c;频繁的强制重启、更新后的驱动不兼容、甚至突如其来的“蓝屏死机”&#xff0c;让无数用户头疼不已。 更让人无奈的是&#xf…

JFlash下载调试模式配置:SWD接口连接与参数设定详解

JFlash SWD 调试实战指南&#xff1a;从连接失败到一键量产的全过程解析你有没有遇到过这样的场景&#xff1f;新板子焊好&#xff0c;兴冲冲接上J-Link&#xff0c;打开JFlash点击“Connect”&#xff0c;结果弹出一行红字&#xff1a;“No device found”&#xff1f;或者好…

Matlab实现GNMF测试阶段投影:将新数据映射到低维表示

在实际应用非负矩阵分解(NMF)或图正则化非负矩阵分解(GNMF)时,我们通常会先在训练集上学习基矩阵U,然后面对新来的测试数据时,需要快速得到其在同一低维空间中的表示V。这就是out-of-sample或测试阶段投影问题。 标准的NMF在测试阶段可以通过简单的非负最小二乘求解,但…

SSD1306 I2C模式下响应检测与错误处理核心要点

如何让 SSD1306 OLED 屏在 I2C 总线上“永不掉线”&#xff1f;——从响应检测到容错恢复的实战指南你有没有遇到过这样的场景&#xff1a;设备上电后&#xff0c;OLED 屏一片漆黑&#xff0c;而其他功能一切正常&#xff1f;或者系统运行几小时后&#xff0c;I2C 总线突然“卡…

C++ 变量作用域

局部变量局部变量在函数或代码块内部声明&#xff0c;仅在该函数或代码块内有效。生命周期从声明开始到代码块结束。例如&#xff1a;void func() {int x 10; // 局部变量cout << x; // 有效 } // cout << x; // 错误&#xff1a;x在此处不可见全局变量全局变量…

各向同性哈希(Isotropic Hashing)编码过程详解

各向同性哈希(Isotropic Hashing,简称IsoH)是一种经典的无监督线性哈希方法,其核心目标是让投影后的各维度方差尽可能相等,从而实现“各向同性”(isotropic)的比特分布。这种特性能够显著提升二进制码的均衡性和区分能力,避免传统PCA哈希中主成分主导导致的比特信息不均…

一文说清Proteus基础操作:适合初学者的通俗解释

当然&#xff0c;请将您希望我润色优化的博文内容发送给我&#xff0c;我会根据上述详细指南对其进行深度重构与提升&#xff0c;确保最终输出为一篇自然流畅、专业深入、毫无AI痕迹的技术佳作。

ModbusPoll与Modbus Slave联动测试完整示例

ModbusPoll 与 Modbus Slave 联动测试实战指南&#xff1a;零硬件搭建高效通信验证环境 你是否曾因为现场设备未到货而卡住开发进度&#xff1f; 是否在调试 Modbus 通信时&#xff0c;面对“读不到数据”、“CRC 校验失败”这类问题无从下手&#xff1f; 别急。今天我们就用…

proteus示波器使用方法图解:一文说清界面功能布局

一文讲透Proteus示波器怎么用&#xff1a;从界面布局到实战调试&#xff0c;新手也能秒上手你有没有过这样的经历&#xff1f;辛辛苦苦画完一个PWM控制电路&#xff0c;仿真一跑&#xff0c;输出电压不对——是代码写错了&#xff1f;还是反馈环路不稳定&#xff1f;又或者MOSF…

基于STM32的u8g2 OLED驱动配置:手把手教程

从零构建STM32 OLED图形界面&#xff1a;u8g2驱动的深度实践与工程优化你有没有遇到过这样的场景&#xff1f;项目里需要加一个小型显示屏&#xff0c;显示点温度、状态或菜单。第一反应是接个LCD&#xff1f;但视角窄、对比度低、还要背光控制……太麻烦。于是你把目光转向OLE…

STM32 GPIO控制有源蜂鸣器操作指南

蜂鸣器也能玩出花&#xff1f;用STM32一个GPIO口搞定报警提示音你有没有遇到过这样的场景&#xff1a;调试一块新板子&#xff0c;上电后啥反应都没有——LED不闪、屏幕不亮、串口没输出。这时候要是有个“滴”一声的启动音&#xff0c;至少能告诉你&#xff1a;芯片是活的&…

DeepSeek 提出新架构 mHC 详解

mHC: Manifold-Constrained Hyper-Connections——把“超连接”拉回稳定轨道的残差新范式 这篇论文讨论了一个看似“简单但很关键”的问题&#xff1a;我们给残差流加宽、加连接&#xff08;Hyper-Connections, HC&#xff09;确实能涨分&#xff0c;但同时打破了残差里最重要…

STM32CubeMX配置文件与代码生成关系图解说明

STM32CubeMX.ioc配置文件&#xff1a;从图形化设计到代码生成的“翻译中枢”你有没有过这样的经历&#xff1f;花了一整天配置STM32的时钟树、引脚复用和外设初始化&#xff0c;结果程序一下载——没反应。查了又查&#xff0c;最后发现是忘了打开某个外设的时钟门控。这在传统…

IAR中使用宏定义优化条件编译:实践技巧

IAR中巧用宏定义优化条件编译&#xff1a;从工程实践到高效开发你有没有遇到过这样的场景&#xff1f;同一个项目要出两个版本——一个给客户A的“基础版”功能精简&#xff0c;另一个给客户B的“专业版”带加密和远程升级。于是你复制了一份代码&#xff0c;注释掉某些模块&am…