Я пытаюсь выполнить операцию над таблицей базы данных 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
, кажется, корень проблемы.
Есть несколько возможностей, которые я могу придумать, чтобы "исправить" это.
- Определите взаимоблокировку (MySQL выдает ошибку) и просто повторите попытку.Это моя текущая реализация, потому что проблема довольно редкая.Это происходит несколько раз в день.
- Используйте
LOCK TABLES
для получения блокировки таблицы до процесса слияния и UNLOCK TABLES
после.Очевидно, это не сработает с MariaDB Galera - что, вероятно, в будущем для этого продукта. - Измените код, чтобы всегда выдавать
INSERT
запросов в первую очередь.Это приведет к тому, что сначала будут получены любые блокировки на уровне таблицы, что позволит избежать тупика.
Проблема с # 3 состоит в том, что для этого потребуется более сложный код в методе, который уже довольно сложен (a "операция «слияние» по своей сути сложна).Этот более сложный код также означает примерно удвоенное количество запросов (SELECT
, чтобы определить, существует ли идентификатор строки, а затем еще один SELECT ... FOR UPDATE
/ UPDATE
, чтобы фактически обновить его).Эта таблица находится в разумном количестве споров, поэтому я хотел бы, по возможности, избегать дополнительных запросов.
Есть ли способ заставить MySQL получить блокировку на уровне таблицы без использования LOCK TABLES
?То есть таким образом, который будет работать, если мы переедем в Галеру?