Как я могу заставить MySQL получить блокировку таблицы для транзакции? - PullRequest
0 голосов
/ 15 ноября 2018

Я пытаюсь выполнить операцию над таблицей базы данных MySQL, используя механизм хранения InnoDB.Эта операция является операцией типа INSERT-or-UPDATE, в которой у меня есть входящий набор данных, и в таблице уже могут быть некоторые данные, которые необходимо обновить.Например, у меня может быть эта таблица:

test_table
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| value | varchar(255) | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+

... и некоторые примеры данных:

+----+-------+
| id | value |
+----+-------+
|  1 | foo   |
|  2 | bar   |
|  3 | baz   |
+----+-------+

Теперь я хочу «объединить» следующие значения:

2, qux
4, corge

Мой код в конечном итоге завершает выполнение следующих запросов:

BEGIN;
SELECT id, value FROM test WHERE id=2 FOR UPDATE;
UPDATE test SET id=2, value='qux' WHERE id=2;
INSERT INTO test (id, value) VALUES (4, 'corge');
COMMIT;

(Я не совсем уверен, что происходит с SELECT ... FOR UPDATE и UPDATE, потому что я использую MySQLБиблиотека Connector / J для Java и простой вызов метода updateRow для ResultSet. В качестве аргумента, давайте предположим, что вышеприведенные запросы фактически являются тем, что выдается серверу.)

Примечание: приведенная выше таблица является тривиальным примером для иллюстрации моего вопроса.Реальная таблица более сложна, и я не использую PK в качестве поля для сопоставления при выполнении SELECT ... FOR UPDATE.Так что не очевидно, нужно ли вставлять или обновлять запись, просто просматривая входящие данные.НЕОБХОДИМО обратиться к базе данных, чтобы определить, использовать ли INSERT / UPDATE.

Вышеуказанные запросы работают нормально в большинстве случаев.Однако, когда нужно объединить больше записей, строки SELECT ... FOR UPDATE и INSERT могут чередоваться, и я не могу предсказать, будут ли выданы SELECT ... FOR UPDATE или INSERT и в каком порядке.

В результате иногда происходит взаимоблокировка транзакций, поскольку один поток заблокировал часть таблицы для операции UPDATE и ожидает блокировку таблицы (для INSERT, которая требует блокировки наиндекс первичного ключа), в то время как другой поток уже получил блокировку таблицы для первичного ключа (предположительно потому, что он выдал запрос INSERT) и теперь ожидает блокировку строки (или, болееСкорее всего, это блокировка на уровне страницы), которая удерживается первым потоком.

Это единственное место в коде, где обновляется эта таблица, и в настоящее время нет явных блокировок, получаемых в настоящее время.Порядок в UPDATE против INSERT, кажется, корень проблемы.

Есть несколько возможностей, которые я могу придумать, чтобы "исправить" это.

  1. Определите взаимоблокировку (MySQL выдает ошибку) и просто повторите попытку.Это моя текущая реализация, потому что проблема довольно редкая.Это происходит несколько раз в день.
  2. Используйте LOCK TABLES для получения блокировки таблицы до процесса слияния и UNLOCK TABLES после.Очевидно, это не сработает с MariaDB Galera - что, вероятно, в будущем для этого продукта.
  3. Измените код, чтобы всегда выдавать INSERT запросов в первую очередь.Это приведет к тому, что сначала будут получены любые блокировки на уровне таблицы, что позволит избежать тупика.

Проблема с # 3 состоит в том, что для этого потребуется более сложный код в методе, который уже довольно сложен (a "операция «слияние» по своей сути сложна).Этот более сложный код также означает примерно удвоенное количество запросов (SELECT, чтобы определить, существует ли идентификатор строки, а затем еще один SELECT ... FOR UPDATE / UPDATE, чтобы фактически обновить его).Эта таблица находится в разумном количестве споров, поэтому я хотел бы, по возможности, избегать дополнительных запросов.

Есть ли способ заставить MySQL получить блокировку на уровне таблицы без использования LOCK TABLES?То есть таким образом, который будет работать, если мы переедем в Галеру?

1 Ответ

0 голосов
/ 15 ноября 2018

Я думаю, что вы можете сделать то, что вы хотите, получив набор блокировок строк и пробелов:

START TRANSACTION;
SELECT id, value
FROM test
WHERE id in (2, 4) -- list all the IDs you need to UPSERT
FOR UPDATE;
UPDATE test SET value = 'qux' WHERE id = 2;
INSERT INTO test (id, value) VALUES (4, 'corge');
COMMIT;

Запрос SELECT заблокирует уже существующие строки и создаст блокировки пробеловдля строк, которые еще не существуют.Блокировка пробела не позволит другим транзакциям создавать эти строки.

...