MySql get_lock для безопасного параллелизма - PullRequest
0 голосов
/ 23 июня 2019

Написание API в node.js с помощью MySQL DB, и я реализую довольно стандартный шаблон:

If exists then update
else insert

Это, конечно, работает нормально, пока к API не будет отправлено несколько одновременных запросов, и в этот момент If exists по запросу 2 может быть выполнено до вставки запроса 1, что приводит к двум записям вместо одной.

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

Это звучало как хороший пример использования мьютекса / блокировки. Мне нужно, чтобы это было распространено, поскольку API может иметь несколько экземпляров, работающих как часть пула / фермы.

Я придумал следующую реализацию:

try {
     await this.databaseConnection.knexRaw().raw(`SELECT GET_LOCK('lock1',10);`);
     await this.databaseConnection.knexRaw().transaction(async (trx) => {
        const existing = await this.findExisting(id);
        if (existing) {
          await this.update(myThing);
        } else {
          await this.insert(myThing);
        }
     });

  } finally {
    await this.databaseConnection.knexRaw().raw(`SELECT RELEASE_LOCK('lock1');`);
}

Кажется, все это работает нормально, и мои тесты теперь производят только одну вставку. Хотя, кажется, немного грубой силы / руководство. Будучи новичком в MySQL и узле (я пришел из C # и SQL Server фоне), этот подход вменяемый? Есть ли лучший подход?

1 Ответ

2 голосов
/ 23 июня 2019

Это нормально?Субъективно.

Это технически безопасно?Это может быть - GET_LOCK() надежно - но не так, как вы написали.

Вы игнорируете возвращаемое значение GET_LOCK(), которое равно 1, если вы получили блокировку, 0, если тайм-аутистек срок действия, и вы не получили блокировку, и NULL в некоторых случаях сбоя.

Как написано, вы будете ждать 10 секунд, а затем все равно будете выполнять работу, поэтому небезопасно.

Это предполагает, что у вас есть только один мастер MySQL.Это не сработает, если у вас несколько мастеров или Galera, поскольку Galera не реплицирует GET_LOCK() на все узлы.(Кластер Galera - это кластер MySQL / MariaDB / Percona с высокой доступностью для записи мастеров, которые синхронно реплицируются и выдерживают ошибку / изоляцию до (ceil (n / 2) - 1) из n всего узлов).

Было бы лучше найти и заблокировать соответствующие строки, используя SELECT ... FOR UPDATE, который блокирует найденные строки или, в некоторых случаях, пробелгде они были бы, если бы существовали, блокируя другие транзакции, которые пытаются захватить те же самые блокировки, пока вы не выполните откат или фиксацию ... но если это нецелесообразно, использование GET_LOCK() допустимо, с учетом вышеизложенного пункта о возвратезначение.

...