为啥 SELECT FOR UPDATE SKIP LOCKED 时 PostgreSQL 会抛出并发更新错误?

Posted

技术标签:

【中文标题】为啥 SELECT FOR UPDATE SKIP LOCKED 时 PostgreSQL 会抛出并发更新错误?【英文标题】:Why PostgreSQL throws concurrent update error while SELECT FOR UPDATE SKIP LOCKED?为什么 SELECT FOR UPDATE SKIP LOCKED 时 PostgreSQL 会抛出并发更新错误? 【发布时间】:2021-09-25 00:20:45 【问题描述】:

我的 Java 应用程序通过 MyBatis 与 PostgreSQL 交互。

它从多个线程执行这个请求

  select * 
  from v_packet_unread
  limit 1000
  for update skip locked

有时会得到ERROR: could not serialize access due to concurrent update。 我记得这个错误发生在乐观更新的情况下,在这里我只使用 SELECT,甚至没有 UPDATE,并且无法解释发生了什么。

v_packet_unread - 是一个简单的视图,它连接了两个小表(每个 2 列),没有任何隐藏效果(如函数调用的触发器)。

您能帮我找出这种行为的原因以及如何避免这种行为吗?

例外:

2021-07-16 06:31:39.278 [validator-exec-5     ] [ERROR] r.c.p.Operators - Operator called default onErrorDropped
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.  Cause: org.postgresql.util.PSQLException: ERROR: could not serialize access due to concurrent update
### The error may exist in database/schemas/receiver/map/PacketMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select *     from v_packet_unread     limit ? for update skip locked
### Cause: org.postgresql.util.PSQLException: ERROR: could not serialize access due to concurrent update
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.  Cause: org.postgresql.util.PSQLException: ERROR: could not serialize access due to concurrent update
### The error may exist in database/schemas/receiver/map/PacketMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select *     from v_packet_unread     limit ? for update skip locked
### Cause: org.postgresql.util.PSQLException: ERROR: could not serialize access due to concurrent update
    at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:153)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)
    at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:145)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)
    at jdk.proxy2/jdk.proxy2.$Proxy65.selectUnread(Unknown Source)
    at ...
Caused by: org.postgresql.util.PSQLException: ERROR: could not serialize access due to concurrent update
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2553)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2285)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:323)
    at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:481)
    at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:401)
    at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:164)
    at org.postgresql.jdbc.PgPreparedStatement.execute(PgPreparedStatement.java:153)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:64)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
    at org.apache.ibatis.executor.BatchExecutor.doQuery(BatchExecutor.java:92)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)
    ... 25 common frames omitted

版本:

PostgreSQL 12.5 on x86_64-redhat-linux-gnu, 
                compiled by gcc (GCC) 8.3.1 20191121 (Red Hat 8.3.1-5), 64-bit


dependencies:
  org.mybatis:mybatis:3.5.7 
  org.postgresql:postgresql:42.2.20

【问题讨论】:

【参考方案1】:

如果您在隔离级别为REPEATABLE READ 或更高级别的事务中运行,则可能会发生这种情况:如果您尝试锁定自事务启动以来已被不同事务同时修改的行,您将收到序列化错误。

为避免这种情况,请使用默认的READ COMMITTED 隔离级别。

【讨论】:

以上是关于为啥 SELECT FOR UPDATE SKIP LOCKED 时 PostgreSQL 会抛出并发更新错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 SELECT FOR UPDATE?

为啥我们在 Oracle SQL 中需要 SELECT FOR UPDATE?

九Django的锁事务ajax

Django的锁和事务

使用 FOR UPDATE SKIP LOCKED 打开 OUT SYS_REFCURSOR 时出错

Django基础六之ORM中的锁和事务