#mysql #locking #innodb
#mysql #блокировка #innodb
Вопрос:
Описание проблемы:
В mysql 8.0 я запустил транзакцию и выполнил SELECT * FROM child WHERE id > 1000 FOR UPDATE;
(транзакция один), затем я запустил другую транзакцию и выполнил update child set id = id 10 where id = 101;
(транзакция два), и в таблице есть строка id = 101
child
, но эта транзакция два была заблокирована.
Если я выполню update child set id = id 10 where id = 102;
(транзакция три) и id = 102
в таблице не будет строки из, транзакция три не будет заблокирована и может быть выполнена успешно.
Насколько я понимаю, mysql 8.0 gap lock блокирует только строки, идентификаторы которых больше 1000, но во второй транзакции идентификатор строки равен 101 и не превышает 1000, поэтому две транзакции не будут конфликтовать друг с другом. Итак, почему вторая транзакция была заблокирована первой транзакцией?
Дополнительные сведения, как показано ниже :
child
Структура таблицы:
CREATE TABLE `child` (n `id` int NOT NULL,n PRIMARY KEY (`id`)n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
- Все данные в таблице
child
:
mysql root@localhost:test> select * from child;
------
| id |
------
| 90 |
| 101 |
| 105 |
| 106 |
| 109 |
| 111 |
| 1007 |
------
7 rows in set
- Все конфигурации mysql 8.0 по умолчанию.
- Две транзакции выполняются параллельно.
Комментарии:
1. затем я начал другую транзакцию , эта транзакция параллельна или вложена?
2. Поведение блокировки будет зависеть от ваших данных, поэтому, пожалуйста, добавьте подробности об этом. В частности: есть ли у вас идентификатор, который находится в промежутке (например, значение от 112 до 1001) в вашей таблице? Если нет, то такое поведение ожидается. (Если вы все еще хотите знать, почему, мы можем уточнить, но потребуется подтверждение ваших данных).
3. Я добавил необходимые детали выше, очень ценю ваш ответ.
4. Наличие строки 111 в вашей таблице приведет к нарушению первичного ключа при обновлении с 101 на 111, тем не менее, ваши примерные данные подтвердили мои общие предположения о блокировке пробелов, охватывающей область, в которую обновление поместит строку.
Ответ №1:
Немного упрощая, блокировка всегда будет привязана к существующим объектам, например, к строке.
Предположим, у вас есть идентификаторы 90, 101, 105, 106, 109, 110 и 1007 в вашей таблице. Обратите внимание, что я изменил вашу примерную строку 111 на 110, так как в противном случае обновление со 101 на 111 завершилось бы неудачно из-за нарушения первичного ключа, прежде чем возникли проблемы с блокировками.
Если MySQL необходимо выдать блокировку WHERE id > 1000
, он не будет (поскольку не может) отслеживать точное значение > 1000
. Вместо этого он возьмет существующий объект, который ближе всего к этому значению, которым в вашем случае является строка с идентификатором 110, и добавит блокировку пробелов, которая покрывает пространство от 110 до 1006. И еще одна блокировка блокировка пробелов в строке с идентификатором 1007, которая покрывает пространство от 1007 и выше.
Да, это занимает больше места, чем необходимо. Но эта блокировка включает в себя необходимое пространство, что является важным аспектом.
Ваш update child set id = id 10 where id = 101
now переместит строку в заблокированное пространство и, таким образом, должен дождаться снятия существующей блокировки. Так что это на самом деле ожидаемое поведение из-за того, как работает MySQL.
Если у вас будет еще одна строка между 112 и 1000, блокировка пробелов может начаться там, и вашей транзакции обновления не придется ждать.
Практически, это проблема, особенно с небольшими таблицами (или с конкретными шаблонами обновления / вставки). Если вы добавляете все больше и больше строк, вероятность того, что случайное обновление попадет в один «перекрытый» пробел, будет ниже.