由于并发更新,Postgres 无法序列化访问

9 sql postgresql

我遇到了“由于并发更新而无法序列化访问”的问题。我检查了日志,我可以清楚地看到两个事务试图同时更新一行。

我的 sql 查询

UPDATE sessionstore SET valid_until = %s WHERE sid = %s;
Run Code Online (Sandbox Code Playgroud)

如何告诉 postgres “尝试”更新行而不抛出任何异常?

DB1*_*141 9

这里有一个警告,已在评论中提到。您必须使用 REPEATABLE READ 事务隔离或更高级别的隔离。为什么?除非您确实有特定原因,否则通常不需要这样做。

\n\n

如果您使用标准的 READ COMMITTED,您的问题就会消失。但仍然最好使用 SKIP LOCKED 来避免锁等待和冗余更新以及浪费的 WAL 流量。

\n\n

从 Postgres 9.5+ 开始,有一个更好的方法来处理这个问题,如下所示:

\n\n
UPDATE sessionstore\nSET valid_until = %s\nWHERE sid = (\n    SELECT sid FROM sessionstore\n    WHERE sid = %s\n    FOR UPDATE SKIP LOCKED\n);\n
Run Code Online (Sandbox Code Playgroud)\n\n

在 SELECT FOR UPDATE SKIP LOCKED 中获取锁的第一个事务将导致任何冲突事务不选择任何内容,从而导致无操作。按照要求,它不会抛出异常。

\n\n

请参阅此处的“跳过锁定”注释:\n https://www.postgresql.org/docs/current/static/sql-select.html

\n\n

此外,关于保存点的建议不够具体。如果更新因序列化错误以外的原因而失败怎么办?就像真正的僵局一样?您不想\xe2\x80\x99 只想默默地忽略所有错误。但这些通常也是一个坏主意 - 每行更新的异常处理程序或保存点都会产生大量额外开销,特别是在流量较高的情况下。这就是为什么您应该使用 READ COMMITTED 和 SKIP LOCKED 来简化问题,并且任何实际错误都将是真正意外的。

\n


Lau*_*lbe 1

执行此操作的规范方法是在以下操作之前设置一个检查点UPDATE

SAVEPOINT x;
Run Code Online (Sandbox Code Playgroud)

如果更新失败,

ROLLBACK TO SAVEPOINT x;
Run Code Online (Sandbox Code Playgroud)

并且交易可以继续进行。

  • 如果您不想付出代价,为什么要使用 REPEATABLE READ 或 SERIALIZABLE?使用 READ COMMITTED 和 SELECT ... FOR UPDATE,您将永远不会收到序列化错误。 (2认同)
  • 没什么好解释的。如果您使用“READ COMMITTED”隔离级别,则不会出现序列化错误。如果要防止并发事务在读取行后更改该行,请使用“SELECT ... FOR UPDATE”读取它。 (2认同)