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

Spring Data JDBC执行插入时为何会触发SELECT语句?

问题描述

在Spring应用中遇到了一个令人困惑的死锁问题:无活跃事务的情况下向表中插入数据时触发了死锁。使用的是SimpleJdbcRepository的save方法,并非寻求死锁解决方案,而是想了解触发原因。

猜测可能是save方法启动了事务、执行了至少一条SELECT语句并获取锁,进而导致死锁,但插入操作本不应执行SELECT。此外无法在本地复现该问题,JDBC日志仅显示插入语句。

疑问:

  • Spring Data JDBC为何会执行这些额外的SELECT语句?
  • 如何消除这些语句?

以下是死锁异常的堆栈跟踪:

org.postgresql.util.PSQLException: ERROR: deadlock detected
  Detail: Process 315008 waits for ShareLock on transaction 11699360; blocked by process 315268.
 Process 315268 waits for ShareLock on transaction 11699361; blocked by process 315008.
  Hint: See server log for query details.
  Where: while locking tuple (5,18) in relation "journey"
 SQL statement "SELECT 1 FROM ONLY "my_schema"."user" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR KEY SHARE OF x"
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2676)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2366)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:356)
    at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:496)
    at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:413)
    at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:190)
    at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:152)
    at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
    at org.springframework.jdbc.core.JdbcTemplate.lambda$update$3(JdbcTemplate.java:992)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651)
    at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:991)
    at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:356)
    at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:340)
    at org.springframework.data.jdbc.core.convert.IdGeneratingInsertStrategy.execute(IdGeneratingInsertStrategy.java:67)
    at org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.insert(DefaultDataAccessStrategy.java:117)
    at org.springframework.data.jdbc.core.JdbcAggregateChangeExecutionContext.executeInsertRoot(JdbcAggregateChangeExecutionContext.java:77)
    at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:66)
    at org.springframework.data.jdbc.core.AggregateChangeExecutor.lambda$execute$0(AggregateChangeExecutor.java:52)
    at java.base/java.util.ArrayList.forEach(Unknown Source)
    at org.springframework.data.relational.core.conversion.DefaultAggregateChange.forEachAction(DefaultAggregateChange.java:127)
    at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:52)
    at org.springframework.data.jdbc.core.JdbcAggregateTemplate.store(JdbcAggregateTemplate.java:360)
    at org.springframework.data.jdbc.core.JdbcAggregateTemplate.save(JdbcAggregateTemplate.java:161)
    at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.save(SimpleJdbcRepository.java:78)
原因分析与解决方案

原因分析

这条SELECT ... FOR KEY SHARE语句并非Spring Data JDBC主动执行,而是PostgreSQL为维护外键约束自动触发的:

  • 你插入的journey表存在指向user表的外键,插入journey记录时,PostgreSQL需要验证关联的user记录是否存在,同时为了防止验证后到插入完成前该user记录被删除或修改主键,会自动加上FOR KEY SHARE锁。
  • JDBC日志仅显示插入语句是因为这条SELECT是数据库内部触发的,不属于应用层执行的SQL。
  • Spring Data JDBC的save方法默认会启动隐式事务(JDBC操作本身默认是事务性的),这导致锁等待发生在事务级别,进而引发死锁。

消除额外SELECT语句的方法

  • 移除不必要的外键约束:如果journey表的外键不是业务必需的,直接删除该约束,PostgreSQL就不会触发这条验证用的SELECT。
  • 调整锁获取顺序:确保所有并发插入请求以相同顺序获取user记录的锁(比如按user.id升序处理),避免循环等待导致死锁。
  • 显式控制事务:将插入操作包裹在显式事务中,根据业务场景调整事务隔离级别或锁策略,减少锁冲突概率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 20:11:47