【问题标题】:Why can't advisory locks be unlocked while in a transaction with pg-promise?为什么在使用 pg-promise 的事务中不能解锁咨询锁?
【发布时间】:2020-02-28 09:21:46
【问题描述】:

我遇到了以下代码并试图调试它,但我不明白为什么在这种情况下无法解锁锁:

const db = pgp(opts)

await db.one('SELECT pg_try_advisory_lock(1) AS lock') // returns true, and I can see the lock in the DB

// no await 
db.tx(async t => {
      const fingerprintIds = fingerprints.map(item => item.id)
      sql = `UPDATE fingerprint SET blah=1 WHERE id IN ($(fingerprintIds:list)) RETURNING id`
      const updatedFingerprintIds = await db.query(sql, { fingerprintIds }) // yes, it is not using the transaction object 

      // some other database calls
    })

result = await db.one('SELECT pg_advisory_unlock(1) AS lock')
// result.lock === false, database logs the error: 'WARNING:  you don't own a lock of type ExclusiveLock'
// lock still stays in the database (until the session ends)

当然,当我在db.tx 调用前面添加await,并在事务回调中使用t 时,它会按预期工作。

这是因为调用unlock 时 pg-promise 库处于事务状态吗?因为当我调用这一系列查询时,锁会按预期解锁:

SELECT pg_try_advisory_lock(1) AS lock;
BEGIN;
SELECT * FROM table;
SELECT pg_advisory_unlock(1) AS lock;

【问题讨论】:

  • 我不知道 pg-promise,但是(会话级)咨询锁独立于事务。
  • Documentation explains 如何以及何时使用任务和事务。你为什么不按照这个模式?上面的代码很糟糕,因为你创建了一个事务上下文,然后实际上并没有使用它。
  • 是的,我意识到 :) 这不是我的代码,但我试图理解这种行为

标签: postgresql postgresql-9.5 pg-promise


【解决方案1】:

这些锁定操作是当前连接会话所独有的,因此它们必须在同一个事务块内执行:

await db.tx(async t => {

    await t.func('pg_try_advisory_lock', [1]);

    // execute all queries here, against 't' context;
    // ...

    await t.func('pg_advisory_unlock', [1]);

});
  • 我们在这里使用func 方法只是为了缩短语法。

但是,如果事务失败,则不会执行解锁,所以如果这是一个问题,那么您将需要手动捕获事务中的错误:

await db.tx(async t => {

    await t.func('pg_try_advisory_lock', [1]);

    let result, err;

    try {
        // execute all queries here, against 't' context;
        // and set the 'result'...

    } catch(e) {
        err = e;
    }

    await t.func('pg_advisory_unlock', [1]);

    if(e) {
        throw e;
    }
    return result;
});

更新

我正在使用咨询锁,因为在这个数据库事务之外还有很多其他事情(非幂等、非数据库)发生,并且由于有多个应用程序实例正在运行,因此锁是防止其他实例同时做同样的工作

我不确定这种类型的锁如何帮助跨多个数据库甚至多个应用程序,因为这些锁只能在同一个会话中工作。

但是,如果您确实需要在事务之外执行这些锁,并且针对相同的连接,那么您将需要手动管理连接:

// Somewhere in the beginning of your chain logic,
// you allocate a manually-managed connection:
const dbm = await db.connect();

// Then your lock happens:
await dbm.func('pg_try_advisory_lock', [1]);


// Then you transaction happens...
await dbm.tx(async t => {
    // transaction queries
});

// Then your unlock query:
await dbm.func('pg_advisory_unlock', [1]);

// Then you release the connection:
dbm.done();

你只需要小心这个,不要泄漏连接;)

【讨论】:

  • 这个例子有点做作,我不能只在事务块中插入锁,因为锁和解锁的调用在调用堆栈中更靠前。我只是想了解为什么在开始执行事务时无法解锁锁,但我可以在原始 sql 中做到这一点
  • @BrDaHa 在BEGIN之前执行锁有什么好处吗?
  • 我正在使用咨询锁,因为在这个数据库事务之外还有很多其他事情(非幂等、非数据库)发生,并且因为应用程序有多个实例运行时,加锁是为了防止其他实例同时做同样的工作
  • @BrDaHa 我不明白。您使用的锁是特定于会话的,这意味着它仅适用于当前连接。它不是可以跨多个数据库实例或多个应用程序使用的东西。
  • 单个数据库,多个应用程序。如果其他应用(不同的会话)没有首先获得锁,则尝试咨询锁确实会返回 false。
猜你喜欢
  • 1970-01-01
  • 2017-05-04
  • 2016-12-15
  • 2015-06-04
  • 2017-11-06
  • 2010-11-18
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多