Неожиданная блокировка для таблицы с первичным ключом и уникальным ключом - PullRequest
9 голосов
/ 31 января 2011

Я столкнулся с проблемой блокировки innodb для транзакций в таблице с первичным ключом и отдельным уникальным индексом.Похоже, если TX удалит запись, используя уникальный ключ, а затем повторно вставит эту же запись, это приведет к блокировке следующего ключа вместо ожидаемой блокировки записи (поскольку ключ уникален).Ниже приведен тестовый пример, а также разбивка записей, которые, как я ожидаю, будут иметь какие блокировки:

DROP TABLE IF EXISTS foo; 
CREATE TABLE `foo` ( 
  `i` INT(11) NOT NULL, 
  `j` INT(11) DEFAULT NULL, 
  PRIMARY KEY (`i`), 
  UNIQUE KEY `jk` (`j`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ; 
INSERT INTO foo VALUES (5,5), (8,8), (11,11); 

(Примечание. Просто запустите TX2 sql после TX1 sql в отдельном соединении)

TX1

START TRANSACTION; 
DELETE FROM foo WHERE i=8; 

приводит к исключительной блокировке для i = 8 (без блокировки пробела, поскольку i является первичным ключом и уникальным)

INSERT INTO foo VALUES(8,8); 

приводит к исключительной блокировке для i = 8& j = 8, и совместно используемая блокировка намерения для i = 6 & i = 7, а также j = 6 & j = 7

TX2

START TRANSACTION; 
INSERT INTO foo VALUES(7,7); 

приводит к исключительной блокировке для i= 7 & j = 7, а также блокировка общего намерения на i = 6 & j = 6

Я бы ожидал, что TX1 не будет заблокирован TX1, однако это так.Как ни странно, блокировка, кажется, связана со вставкой TX1.Я говорю это потому, что если оператор вставки TX1 не выполняется после удаления, вставка TX2 не блокируется.Это почти так, как будто повторная вставка TX1 (8,8) вызывает блокировку следующего ключа для индекса j для (6,8).

Любое понимание будет высоко ценится.

Ответы [ 2 ]

6 голосов
/ 13 февраля 2011

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

DROP TABLE IF EXISTS foo;
CREATE TABLE `foo` (
  `i` INT(11) NOT NULL,
  `j` INT(11) DEFAULT NULL,
  PRIMARY KEY (`i`),
  UNIQUE KEY `jk` (`j`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ;
INSERT INTO foo VALUES (5,5), (8,8), (11,11);

Предположим, вы начинаете с транзакции TX1:

START TRANSACTION;
REPLACE INTO foo VALUES(8,8);

Тогда, если вы начнете транзакцию TX2, все, что INSERT или REPLACE с использованием id между 5 и 11 будет заблокировано:

START TRANSACTION;
REPLACE INTO foo VALUES(11,11);

Похоже, что MySQL использует этот тип блокировки, чтобы избежать описанной здесь «фантомной проблемы»: http://dev.mysql.com/doc/refman/5.0/en/innodb-next-key-locking.html, MySQL использует «блокировку следующего ключа», которая объединяет блокировку строки индекса с блокировкой пробела, это означает, что нам, что он заблокирует много возможных идентификаторов между предыдущим и следующим идентификаторами, а также заблокирует предыдущий и следующий идентификаторы.

Чтобы избежать этого, попытайтесь создать серверный алгоритм, который вставляет ваши записи так, чтобы записи, вставленные в разные транзакции, не перекрывались или, по крайней мере, не выполняли все ваши транзакции одновременно, так что TX не делает приходится ждать друг друга.

4 голосов
/ 15 февраля 2011

Кажется, что проблема может заключаться в том, что индексы InnoDB странные.

Первичный ключ (кластеризованный) - i, и с ним будет связано rowid.

Уникальный ключ на j (некластеризованный) имеет rowid из i, связанный со значением j в индексе.

Выполнение DELETE с последующим INSERT для одного и того же значения ключа для i должно привести к появлению предстоящего другого rowid для первичного ключа (кластеризованного) и, аналогично, предстоящего другого rowid для сопоставления со значением j (некластеризованного).

Это потребует некоторой причудливой внутренней блокировки в механизме MVCC.

Вам может потребоваться изменить уровень изоляции транзакций, чтобы разрешить грязные чтения (т. Е. Не иметь повторяющихся операций чтения)

Воспроизвестинекоторые игры с переменной tx_isolation в течение сеанса
Попробуйте READ_COMMITTED и READ_UNCOMMITTED

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

В противном случае просто установите следующее в /etc/my.cnf (Пример)

[mysqld]
transaction_isolation=read-committed

Попробуй !!!

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