单体应用中长耗时事务方法的替代设计方案咨询
解决方案
1. 拆分事务与长耗时操作(核心思路)
把需要事务保证的数据库操作和耗时的PDF生成/存储拆分开,只在必要的数据库操作块上加事务,避免事务持有时间过长,从根源上减少数据库阻塞。
具体实现步骤:
- 第一步:在独立的事务块中执行
saveData1(),拿到item2.id后立即提交事务,释放数据库锁。 - 第二步:执行非事务的耗时操作
createPDF(item2.id)和savePdf(),这一步不占用数据库事务资源。 - 第三步:开启新的事务块执行
saveData2(),完成最终的数据持久化。
代码示例(Spring环境):
@Autowired private PlatformTransactionManager transactionManager; void sample(Item item) { // 事务1:仅处理saveData1 TransactionStatus status1 = transactionManager.getTransaction(new DefaultTransactionDefinition()); Item2 item2 = null; try { item2 = saveData1(); transactionManager.commit(status1); } catch (Exception e) { transactionManager.rollback(status1); throw e; // 抛出异常终止流程 } // 非事务的耗时操作 createPDF(item2.id); savePdf(); // 事务2:仅处理saveData2 TransactionStatus status2 = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { saveData2(); transactionManager.commit(status2); } catch (Exception e) { transactionManager.rollback(status2); // 补偿操作:删除已生成的PDF,保证数据与文件一致性 deletePdf(); throw e; } }
2. 简化版补偿机制(替代复杂Saga)
因为是单体应用,不需要分布式Saga的复杂架构,针对可能的失败场景做轻量补偿即可:
- 若
saveData2失败,删除已生成的PDF,避免数据与文件状态不一致。 - 若
createPDF或savePdf失败,直接终止流程,同时记录失败日志,后续通过定时任务重试或人工介入处理已提交的saveData1数据。
3. 异步处理长耗时操作(进一步优化)
如果业务允许,把PDF生成和存储放到异步线程中执行,彻底剥离事务与耗时操作:
- 第一步:事务内执行
saveData1()并提交,拿到item2.id。 - 第二步:将
createPDF(item2.id)和savePdf()提交到异步线程池,主线程立即释放资源。 - 第三步:异步任务执行成功后,再触发
saveData2()的事务操作。
注意事项:
- 用带持久化的任务队列(比如结合数据库任务表的Spring
@Async)保证异步任务不丢失。 - 异步任务失败时,同样需要执行补偿逻辑(删除PDF、重试任务等)。
4. 数据库层面辅助优化
- 确保
saveData1和saveData2涉及的表索引合理,减少锁的持有时间。 - PostgreSQL中保持默认的
READ COMMITTED隔离级别,避免不必要的锁升级。
内容的提问来源于stack exchange,提问作者Abdelmouheimen Trabelsi
相关产品推荐
相关产品推荐

