使用Spring Hibernate @Transactional Entity Manager批量插入时避免表锁定
批量插入锁问题解决方案
一、避免读锁定的核心思路
锁的根源是大事务持有锁时间过长,所以核心是缩短锁的持有周期,或者优化锁的范围:
1. 拆分事务到单表插入操作
把原来的全局事务拆成每个单表插入的独立事务,这样每张表插入完成就提交事务,锁会立即释放,不会一直挂到整个批量操作结束。这是最直接有效的方式。
2. 确认数据库锁类型与索引
如果用的是MySQL InnoDB,插入默认是行级锁,但如果表没有主键/唯一索引,会退化为表锁。所以必须确保所有涉及的表都有主键,避免表级锁导致的全表阻塞。
3. 精简事务内操作
不要在事务里做无关的计算、外部调用等耗时操作,让事务只做DB插入,尽可能缩短事务执行时间,减少锁持有时长。
二、是否可以不用事务实现单表独立插入?
完全可以,但要先明确业务需求:
- 如果业务允许部分表插入成功、部分失败(不需要强一致性),那直接去掉全局事务,给每个单表插入单独加事务即可。
- 如果业务要求所有表必须同时成功或失败(强一致性),那不能完全去掉事务,但可以通过拆分事务+补偿机制实现(比如某张表失败后,回滚已成功的表,或者标记异常后续处理)。
代码修改示例
方案1:给单表插入方法加独立事务
// 移除全局方法的@Transactional public BaseImportDtoRoot importData(BaseImportDtoRoot baseDto) throws Exception { try { table1.fninsert(); table2.fninsert(); // ... 剩下8张表的插入调用 } catch(Exception e) { // 这里要注意:部分表可能已经插入成功,需根据业务做补偿(比如删除已插入数据) } } // Table1的插入方法单独加事务,用REQUIRES_NEW确保开启新事务 public class Table1 { @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED) public void fninsert() { MstTable tb1 = modMap.map(MstTableDto, MstTable.class); entityManager.persist(tb1); entityManager.flush(); entityManager.clear(); } } // Table2及其他表同理,都加独立事务
方案2:手动控制事务(更灵活)
如果需要更精细的事务控制,可以手动管理事务生命周期:
@Autowired private PlatformTransactionManager transactionManager; public BaseImportDtoRoot importData(BaseImportDtoRoot baseDto) throws Exception { DefaultTransactionDefinition txDef = new DefaultTransactionDefinition(); txDef.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); // 处理Table1 TransactionStatus txStatus1 = transactionManager.getTransaction(txDef); try { table1.fninsert(); transactionManager.commit(txStatus1); } catch (Exception e) { transactionManager.rollback(txStatus1); throw e; // 或根据业务处理 } // 处理Table2 TransactionStatus txStatus2 = transactionManager.getTransaction(txDef); try { table2.fninsert(); transactionManager.commit(txStatus2); } catch (Exception e) { transactionManager.rollback(txStatus2); // 这里需要考虑是否要回滚Table1的数据,做补偿 throw e; } // ... 其他表的处理逻辑 }
重要提醒
- 一致性问题:拆分事务后,一旦中间某张表插入失败,前面已提交的事务无法自动回滚,必须在业务层做补偿处理(比如记录失败日志,定时清理或重试)。
- 数据库引擎:如果用的是MyISAM这种表锁引擎,必须换成InnoDB才能用行级锁,否则拆分事务也无法解决全表阻塞问题。
- 批量插入优化:如果单条插入性能太低,可以攒一批数据再批量插入,减少事务次数,但要注意批量插入的锁范围,避免一次性锁定过多行。
内容的提问来源于stack exchange,提问作者Ramsha Khan
相关产品推荐
相关产品推荐

