Почему консультативные блокировки не могут быть разблокированы во время транзакции с pg-обещанием? - PullRequest
2 голосов
/ 27 февраля 2020

Я наткнулся на следующий код и пытался его отладить, но я не понимаю, почему блокировка не может быть разблокирована в этом сценарии:

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)

Конечно, когда я добавляю await перед вызовом db.tx и использованием t в обратном вызове транзакции, затем он работает как положено.

Это происходит потому, что библиотека pg-promise находится в состоянии транзакции, когда unlock называется? Потому что, когда я вызываю эту последовательность запросов, блокировка разблокируется, как и ожидалось:

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

1 Ответ

2 голосов
/ 27 февраля 2020

Эти операции блокировки являются исключительными для текущего сеанса соединения, поэтому они должны выполняться в одном и том же блоке транзакции:

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]);

});
  • Мы используем метод fun c здесь только для того, чтобы сократить синтаксис.

Это, однако, не выполнит разблокировку, если транзакция завершится неудачей, поэтому, если это вызывает проблемы, вам нужно будет вручную отлавливать ошибки внутри транзакции:

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();

Вы просто должны быть осторожны с этим, чтобы не пропустить соединения;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...