Рассуждения о блокировке пропусков в MySQL

У меня тупик, и я пытаюсь выяснить причину этого.

Вопрос можно свести к такому:

Таблица:

create table testdl (id int auto_increment, c int, primary key (id), key idx_c (c));

Уровень изоляции - повторяемое считывание

(Tx1): begin; delete from testdl where c = 1000; -- nothing is deleted coz the table is empty

(Tx2): begin; insert into testdl (c) values (?);

Каким бы ни было значение в Tx2, оно зависает. Таким образом, это в основном означает, что Tx1 удерживает разрыв во всем диапазоне (-∞, + ∞), когда delete from testdl where c = 1000 не может найти совпадение, верно ?.

Итак, мой вопрос: это намеренно? Какой в ​​этом смысл?

Обновлять:

Допустим, у нас уже есть запись в testdl:

+----+------+
| id | c    |
+----+------+
|  1 | 1000 |
+----+------+

Дело 1:

(Tx1): select * from testdl where c = 500 for update; -- c = 500 not exists

(TX2): insert into testdl (c) values (?);

В этом случае можно вставить любое значение> = 1000, поэтому Tx1 блокирует пробел (-∞, 1000)

Опять же, необходима ли блокировка (-∞, 1000)? В чем причина этого?


person Dean Winchester    schedule 02.03.2017    source источник
comment
вы можете проверить это на dev.mysql.com/doc /refman/5.7/en/innodb-locking.html   -  person Gonzalo.-    schedule 02.03.2017
comment
Да, там написано, что есть что, но я до сих пор не могу найти причину, по которой весь диапазон заблокирован в этом случае, а delete from testdl where c = 1000 не находит совпадения   -  person Dean Winchester    schedule 02.03.2017
comment
См. Этот вопрос. stackoverflow.com/ вопросы / 23193761 /   -  person Gonzalo.-    schedule 02.03.2017
comment
Что бы вы предложили в качестве альтернативы? В пустой таблице нет диапазона (-∞, + ∞). Насколько мне известно, здесь есть единственный разрыв нулевой ширины между псевдозаписями точной и верхней граней ... и этот единственный разрыв заблокирован.   -  person Michael - sqlbot    schedule 03.03.2017
comment
@ Michael-sqlbot Меня смущает то, что когда select * from testdl where c = 500 for update не соответствует записи, я не могу вставить запись с c = 100. В конце концов, меня выбрали c = 500 for update, а не c < 500. Что будет не так, если разрешена вставка записи с c = 100? Для этого есть какая-то причина? Или, может быть, это просто реализация, ничего плохого не случится?   -  person Dean Winchester    schedule 03.03.2017
comment
Блокировка блокирует реальные вещи, и нет 500 вещей, которые можно было бы заблокировать. Есть только промежуток между инфимумом и 1000. Если вы хотите вставить 500, а я хочу вставить 100, мы оба соперничаем за то, что в настоящее время является одним и тем же промежутком, поэтому одному из нас нужно дождаться другого.   -  person Michael - sqlbot    schedule 03.03.2017
comment
@ Michael-sqlbot, извините, я не понимаю, о чем вы говорите. Я имею в виду, что если я select * from testdl where c > 500 for update, для меня имеет смысл запретить все одновременные записи с c > 500, пока я не закончу. Но если я select * from testdl where c = 500 for update, какой вред может быть, если другая транзакция вставит запись с c = 100?   -  person Dean Winchester    schedule 03.03.2017
comment
Если другая транзакция вставляет c = 100, она должна быть вставлена ​​точно в то же место, где будет c = 500 - обе потенциальные новые строки должны находиться в одном и том же месте: промежуток, непосредственно предшествующий c = 1000. Есть ровно один пробел там, и тот факт, что вы заблокировали несуществующие 500, не создает двух дыр, одну ‹500 и одну› = 500. Это все еще один пробел. Для двух транзакций требуется одно и то же, поэтому нужно подождать.   -  person Michael - sqlbot    schedule 03.03.2017


Ответы (2)


Это похоже на то, что мне самому недавно было любопытно, поэтому позвольте мне попытаться объяснить ...

Каким бы ни было значение в Tx2, оно зависает. Таким образом, это в основном означает, что Tx1 удерживает разрыв во всем диапазоне (-∞, + ∞), когда удаление из testdl, где c = 1000, не может найти совпадение, верно ?.

Итак, мой вопрос: это намеренно? Какой в ​​этом смысл?

Это сделано намеренно, основная цель блокировок промежутков - предотвратить вставку любых записей в эти промежутки, чтобы избежать _ 1_.

Итак, представьте, что у вас есть пустая таблица и внутри транзакции вы выполняете delete from testdl where c = 1000;. Теперь, независимо от того, сколько таких строк существовало до того, как вы ожидали, что после этого запроса у вас не будет таких строк в вашей таблице, верно? Итак, если после этого вы выполните select * from testdl where c = 1000 for update; в той же транзакции, вы ожидаете, что это будет пустой результат.

Но чтобы убедиться, что в таблице нет новых строк с c = 1000, нам нужно заблокировать пробелы, в которые можно вставить такие записи. А в пустой таблице есть только один пробел: пробел между псевдозаписями инфимума и супремума (как указал Майкл).

В этом случае можно вставить любое значение> = 1000, поэтому Tx1 блокирует пробел (-∞, 1000)

Опять же, необходима ли блокировка (-∞, 1000)? В чем причина этого?

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

В своей первой транзакции вы выполняете select * from testdl where c = 500 for update;, и теперь нам нужно убедиться, что нет новых записей с c = 500, если мы решим снова сделать такой запрос внутри этой транзакции. Поэтому нам нужно заблокировать для него все необходимые зазоры. Какие у нас есть пробелы? (-∞, 1000) и (1000, +∞), очевидно, новые записи, где c = 500 не будет вставлен во второй промежуток, но они будут вставлены в первый промежуток, поэтому мы должны заблокировать его.

Надеюсь, это ответит на него.

person GProst    schedule 30.06.2019
comment
Прошло два года :) Я знаю о фантомном чтении. Позвольте мне сказать так, теоретически все, что нам нужно, это запретить вставку c=1000 правильно (в первом примере)? И если мы забудем о том, что `` вы не можете заблокировать то, чего не существует, блокировка - это просто некоторый сериализованный доступ к некоторому адресу памяти '', это больше похоже на реализацию (или выбор, или это лучшее, что мы можем сделать прямо сейчас) правильно? - person Dean Winchester; 01.07.2019
comment
Это верно только в том случае, если ваш столбец в предложении where проиндексирован, в противном случае предложение игнорируется - innodb блокирует индексы, а не записи. Если вы индексируете c, он будет выполнять блокировку записи и разрешать вставки / удаления / обновления в промежуток, в противном случае он будет выполнять блокировку следующей клавиши, которая представляет собой блокировку записи и блокировку промежутка вместе. - person Nikola Jankovic; 26.11.2020

После выполнения

create table testdl (id int auto_increment, c int, primary key (id), key idx_c (c));

-- transaction 1
begin;
delete from testdl where c = 1000;

-- transaction 2
begin;
insert into testdl (c) values (?); -- ? can be any int

Результат select * from performance_schema.data_locks:

+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+--------------------+-------------+------------------------+
| ENGINE | ENGINE_LOCK_ID                         | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE          | LOCK_STATUS | LOCK_DATA              |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+--------------------+-------------+------------------------+
| INNODB | 140043377180024:1073:140043381454544   |                  2322 |        48 |      218 | test          | testdl      | NULL           | NULL              | NULL       |       140043381454544 | TABLE     | IX                 | GRANTED     | NULL                   |
| INNODB | 140043377180024:12:5:1:140043381451552 |                  2322 |        48 |      218 | test          | testdl      | NULL           | NULL              | idx_c      |       140043381451552 | RECORD    | X,INSERT_INTENTION | WAITING     | supremum pseudo-record |
| INNODB | 140043377180872:1073:140043381460688   |                  2321 |        49 |      154 | test          | testdl      | NULL           | NULL              | NULL       |       140043381460688 | TABLE     | IX                 | GRANTED     | NULL                   |
| INNODB | 140043377180872:12:5:1:140043381457776 |                  2321 |        49 |      154 | test          | testdl      | NULL           | NULL              | idx_c      |       140043381457776 | RECORD    | X                  | GRANTED     | supremum pseudo-record |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+--------------------+-------------+------------------------+

транзакция 2 ожидает блокировки намерения вставки (отрицательная бесконечность, положительная бесконечность), потому что транзакция 1 удерживает блокировку следующего ключа (отрицательная бесконечность, положительная бесконечность), поэтому она не может продолжаться.

Но после выполнения

create table testdl (id int auto_increment, c int, primary key (id), key idx_c (c));
insert into testdl values(1, 1000);

-- transaction 1
begin;
select * from testdl where c = 500 for update;

Результат select * from performance_schema.data_locks:

+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+
| ENGINE | ENGINE_LOCK_ID                         | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+
| INNODB | 140043377180872:1074:140043381460688   |                  2341 |        49 |      167 | test          | testdl      | NULL           | NULL              | NULL       |       140043381460688 | TABLE     | IX        | GRANTED     | NULL      |
| INNODB | 140043377180872:13:5:2:140043381457776 |                  2341 |        49 |      167 | test          | testdl      | NULL           | NULL              | idx_c      |       140043381457776 | RECORD    | X,GAP     | GRANTED     | 1000, 1   |
+--------+----------------------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+-----------+-------------+-----------+

транзакция 1 удерживает блокировку промежутка (отрицательная бесконечность, (1000, 1)), поэтому другие транзакции не могут вставлять данные в этот промежуток.

person Jason Law    schedule 05.07.2020