Това е страхотен въпрос. InnoDB е механизъм за заключване на ниво ред, но той трябва да зададе допълнителни ключалки, за да гарантира безопасността с двоичния дневник (използван за репликация; възстановяване на точка във времето). За да започнете да го обяснявате, разгледайте следния (наивен) пример:
session1> START TRANSACTION;
session1> DELETE FROM users WHERE is_deleted = 1; # 1 row matches (user_id 10), deleted.
session2> START TRANSACTION;
session2> UPDATE users SET is_deleted = 1 WHERE user_id = 5; # 1 row matches.
session2> COMMIT;
session1> COMMIT;
Тъй като операторите се записват в двоичния регистрационен файл само след като бъдат записани, за подчинената сесия №2 ще се приложи първа и ще доведе до различен резултат, водещ до повреда на данните .
Така че това, което InnoDB прави, е да задава допълнителни ключалки. Ако is_deleted
е индексиран, то преди сесия1 да извърши ангажимент, никой друг няма да може да промени или да вмъкне в диапазона на записи, където is_deleted=1
. Ако няма индекси в is_deleted
, тогава InnoDB трябва да заключи всеки ред в цялата таблица, за да се увери, че повторението е в същия ред. Можете да мислите за това като заключване на празнината , което е различна концепция за разбиране от директното заключване на ниво ред .
Във вашия случай с този ORDER BY position ASC
, InnoDB трябва да се увери, че не могат да се променят нови редове между най-ниската ключова стойност и "специална" най-ниска възможна стойност. Ако сте направили нещо като ORDER BY position DESC
.. добре, тогава никой не може да вмъкне в този диапазон.
И така, тук идва решението:
-
Двоично регистриране на базата на изказвания е гадно. Наистина очаквам с нетърпение бъдеще, в което всички ще преминем към ред базирано на двоичен журнал (достъпно от MySQL 5.1, но не е включено по подразбиране).
-
При репликация, базирана на ред, ако промените нивото на изолация на четене-задължено, тогава само единият ред, който съвпада, трябва да бъде заключен.
-
Ако искате да сте мазохист, можете също да включите innodb_locks_unsafe_for_binlog с репликация, базирана на изрази.
Актуализация на 22 април :За да копирате + поставите моята подобрена версия на вашия тестов пример (не търсеше „в празнината“):
session1> CREATE TABLE test (id int not null primary key auto_increment, data1 int, data2 int, INDEX(data1)) engine=innodb;
Query OK, 0 rows affected (0.00 sec)
session1> INSERT INTO test VALUES (NULL, 1, 2), (NULL, 2, 1), (5, 2, 2), (6, 3, 3), (3, 3, 4), (4, 4, 3);
Query OK, 6 rows affected (0.00 sec)
Records: 6 Duplicates: 0 Warnings: 0
session1> start transaction;
Query OK, 0 rows affected (0.00 sec)
session1> SELECT id FROM test ORDER BY data1 LIMIT 1 FOR UPDATE;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
session2> INSERT INTO test values (NULL, 0, 99); # blocks - 0 is in the gap between the lowest value found (1) and the "special" lowest value.
# At the same time, from information_schema:
localhost information_schema> select * from innodb_locks\G
*************************** 1. row ***************************
lock_id: 151A1C:1735:4:2
lock_trx_id: 151A1C
lock_mode: X,GAP
lock_type: RECORD
lock_table: `so5694658`.`test`
lock_index: `data1`
lock_space: 1735
lock_page: 4
lock_rec: 2
lock_data: 1, 1
*************************** 2. row ***************************
lock_id: 151A1A:1735:4:2
lock_trx_id: 151A1A
lock_mode: X
lock_type: RECORD
lock_table: `so5694658`.`test`
lock_index: `data1`
lock_space: 1735
lock_page: 4
lock_rec: 2
lock_data: 1, 1
2 rows in set (0.00 sec)
# Another example:
select * from test where id < 1 for update; # blocks