PostgreSQL异常:DB_Cursor:执行时出错:元组被并发更新

1 投票
2 回答
1065 浏览
提问于 2025-04-18 05:26

在升级过程中,我们的产品脚本会更新一个触发器的存储过程。现在有两个后台程序在运行,它们都可以更新这个存储过程。看起来PostgreSQL没有正确处理这个更新的顺序。具体的错误信息是“DB_Cursor: 执行时异常:元组被同时更新”。在谷歌搜索这个错误时,没有找到完全匹配的结果。这似乎是一个竞争条件的问题。为了避免或防止这样的异常,最好的办法是什么呢?这个问题会导致升级过程失败,必须重启一个或两个后台程序才能重新尝试升级并恢复。请问PostgreSQL有没有已知的问题?我们现在使用的是PostgreSQL 9.2.5。

2 个回答

1

如果你的设计允许多个客户同时进行数据库结构的修改(DDL),那么你需要确保只有一个客户在执行这个操作。你可以通过使用建议锁来实现这一点。

下面是一个伪代码的示例:

function try_upgrade(db) {
  if ( ! is_upgrade_needed(db) ) {
    // we check it before acquiring a lock to speed up a common case of
    // no upgrade available
    return UPGRADE_NOT_NEEDED;
  }

  query_result = db->begin_transaction();
  if ( query_result < 0 ) throw Error("begin failed");

  query_result = db->query(
    "select pg_advisory_xact_lock(?)", MAGIC_NUMBER_UPGRADE_LOCK
  );
  if ( query_result < 0 ) throw Error("pg_advisory_xact_lock failed");

  // another client might have performed upgrade between the previous check
  // and acquiring advisory lock
  if ( ! is_upgrade_needed(db) ) {
    query_result = db->rollback_transaction();
    return UPGRADE_NOT_NEEDED;
  }

  perform_upgrade();

  query_result = db->commit_transaction();
  if ( query_result < 0 ) throw Error("commit failed");

  return UPGRADE_PERFORMED;
}
2

看起来PostgreSQL在升级程序时没有对DDL进行序列化处理。

是的。这在pgsql的邮件列表中时不时会提到,比如最近在这里:

'同时更新元组'错误在授予权限时出现

摘录:

我们确实对表和索引的DDL有这样的锁定,但过去的理论是,对于那些只由单行目录表示的对象,比如函数或角色,这样做不值得麻烦。你不能通过对这样的行进行并发更新来破坏数据库,只会从除了第一个到达的更新外,收到一个“同时更新元组”的错误。

如果你在同时替换函数体,这显然就是你的问题。

而提出的解决方案是:

在此期间,如果你真的需要这些权限能够透明地工作,可以考虑使用应用程序管理的建议锁。

撰写回答