You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3升级:并发插入MariaDB时出现JPA死锁问题

Spring Boot 3.1升级后JPA批量插入死锁问题排查与解决

不少开发者在从Spring Boot 2.x升级到3.x(搭配Hibernate 6.x)后,遇到过类似的批量插入死锁问题,尤其是MySQL/MariaDB环境下,之前稳定的代码突然出现高频死锁,以下是针对这类问题的分析和解决思路:

可能的原因

  • Hibernate 6批量插入逻辑变更:Spring Boot 3.x默认适配Hibernate 6,它的JDBC批量处理实现(比如MutationExecutorSingleBatched)和Hibernate 5有明显差异,批量插入时锁的获取顺序、时机都可能变化,容易引发循环等待型死锁。
  • 索引锁竞争:你提到的表有外键索引和状态索引,InnoDB在插入时会对涉及的索引加意向锁/排他锁,如果多个并行事务的插入操作涉及的索引键顺序不一致,就会形成死锁的必要条件——循环等待。
  • 事务Flush时机调整:Spring Boot 3对JPA事务的默认Flush策略可能有微调,自动Flush的触发更频繁,导致多个事务同时执行批量插入操作,加剧锁竞争。

可行的解决办法

  • 调整Hibernate批量配置
    • 降低批量大小:在application.properties中设置spring.jpa.properties.hibernate.jdbc.batch_size=10,减少单次批量操作涉及的数据量,缩小锁的覆盖范围。
    • 开启有序插入:添加spring.jpa.properties.hibernate.order_inserts=true,让Hibernate按固定顺序(比如主键、索引字段)排序插入语句,确保所有事务的锁获取顺序一致,从根源避免循环等待。
    • 禁用版本数据批量处理:如果你的实体没有使用乐观锁,可以设置spring.jpa.properties.hibernate.jdbc.batch_versioned_data=false。
  • 优化表索引
    • 检查外键关联的表是否有长时间持锁的操作,避免外键触发的连锁锁竞争;如果状态索引的基数极低(比如只有2-3种状态),可以临时移除该索引测试,确认是否是索引导致的锁冲突。
  • 添加事务重试机制
    • 死锁属于临时性异常,重试通常可以解决。用Spring Retry实现重试逻辑:
      @Retryable(value = {LockAcquisitionException.class, SQLTransactionRollbackException.class}, maxAttempts = 3, backoff = @Backoff(delay = 100))
      @Transactional
      public PurchaseOrder savePurchaseOrder(PurchaseOrder order) {
          return purchaseOrderRepository.save(order);
      }
      
  • 调整测试并发策略
    • 如果死锁仅在测试环境出现,检查测试用例是否有大量并行插入该表的场景,降低测试并发度,或者对冲突的测试用例设置顺序执行,减少竞争。

异常栈信息

Caused by: org.hibernate.exception.LockAcquisitionException: could not execute batch [(conn=1247) Deadlock found when trying to get lock; try restarting transaction] [insert into `order_purchase_order` (`buyer_purchase_order_comment`,`created_at`,`current_state`,`last_modified_at`,`order_id`,`state_history`,`supplier_dialect_id`,`supplier_id`,`supplier_id_name`,`supplier_name`,`supplier_sales_order_reference`,`id`) values (?,?,?,?,?,?,?,?,?,?,?,?)]
    at org.hibernate.dialect.MySQLDialect.lambda$buildSQLExceptionConversionDelegate$3(MySQLDialect.java:1178)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:56)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:108)
    at org.hibernate.engine.jdbc.batch.internal.BatchImpl.lambda$performExecution$2(BatchImpl.java:289)
    at org.hibernate.engine.jdbc.mutation.internal.PreparedStatementGroupSingleTable.forEachStatement(PreparedStatementGroupSingleTable.java:59)
    at org.hibernate.engine.jdbc.batch.internal.BatchImpl.performExecution(BatchImpl.java:262)
    at org.hibernate.engine.jdbc.batch.internal.BatchImpl.execute(BatchImpl.java:241)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.getBatch(JdbcCoordinatorImpl.java:173)
    at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleBatched.resolveBatch(MutationExecutorSingleBatched.java:47)
    at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleBatched.performBatchedOperations(MutationExecutorSingleBatched.java:60)
    at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55)
    at org.hibernate.persister.entity.mutation.InsertCoordinator.doStaticInserts(InsertCoordinator.java:170)
    at org.hibernate.persister.entity.mutation.InsertCoordinator.coordinateInsert(InsertCoordinator.java:112)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2670)
    at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:102)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:618)
    at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:489)
    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:486)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:358)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1412)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:485)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2296)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1961)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:169)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:267)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:561)
    ... 24 common frames omitted
Caused by: java.sql.BatchUpdateException: (conn=1247) Deadlock found when trying to get lock; try restarting transaction
    at org.mariadb.jdbc.export.ExceptionFactory.createBatchUpdate(ExceptionFactory.java:215)
    at org.mariadb.jdbc.client.impl.StandardClient.executePipeline(StandardClient.java:637)
    at org.mariadb.jdbc.ClientPreparedStatement.executeBatchPipeline(ClientPreparedStatement.java:196)
    at org.mariadb.jdbc.ClientPreparedStatement.executeInternalPreparedBatch(ClientPreparedStatement.java:128)
    at org.mariadb.jdbc.ClientPreparedStatement.executeBatch(ClientPreparedStatement.java:466)
    at com.zaxxer.hikari.pool.ProxyStatement.executeBatch(ProxyStatement.java:127)
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeBatch(HikariProxyPreparedStatement.java)
    at org.hibernate.engine.jdbc.batch.internal.BatchImpl.lambda$performExecution$2(BatchImpl.java:275)
    ... 51 common frames omitted
Caused by: java.sql.SQLTransactionRollbackException: (conn=1247) Deadlock found when trying to get lock; try restarting transaction
    at org.mariadb.jdbc.export.ExceptionFactory.createException(ExceptionFactory.java:296)
    at org.mariadb.jdbc.export.ExceptionFactory.create(ExceptionFactory.java:370)
    at org.mariadb.jdbc.message.ClientMessage.readPacket(ClientMessage.java:134)
    at org.mariadb.jdbc.client.impl.StandardClient.readPacket(StandardClient.java:872)
    at org.mariadb.jdbc.client.impl.StandardClient.readResults(StandardClient.java:811)
    at org.mariadb.jdbc.client.impl.StandardClient.readResponse(StandardClient.java:730)
    at org.mariadb.jdbc.client.impl.StandardClient.executePipeline(StandardClient.java:571)
    ... 57 common frames omitted

内容的提问来源于stack exchange,提问作者Matthias

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 03:12:09