Сколько строк будет заблокировано командой SELECT ... ORDER BY xxx LIMIT 1 FOR UPDATE? - PullRequest
31 голосов
/ 17 апреля 2011

У меня есть запрос со следующей структурой:

SELECT ..... WHERE status = 'QUEUED' ORDER BY position ASC LIMIT 1 FOR UPDATE;

Это оператор SELECT для одной таблицы в таблице InnoDB.Поле position (INT NOT NULL) имеет индекс.статус ENUM и также индексируется.

SELECT ... FOR UPDATE Страница справочника говорит, что она блокирует все строки, которые читает.Правильно ли я понимаю, что в этом случае будет заблокирована только одна строка?Или, скорее, он заблокирует всю таблицу?

Можно ли определить, какие строки будут заблокированы с помощью запроса EXPLAIN?Если да - как?Объяснение для запроса по пустой таблице показывает следующее:

1;'SIMPLE';'job';'index';<null>;'index_position';[34,...];<null>;1;'Using where'

Ответы [ 4 ]

26 голосов
/ 18 апреля 2011

Это отличный вопрос.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 проиндексирован, то перед фиксацией session1 никто не сможет изменить или вставить в диапазон записей, где is_deleted=1.Если для is_deleted нет индексов, то InnoDB необходимо заблокировать каждую строку во всей таблице, чтобы убедиться, что воспроизведение выполняется в том же порядке.Вы можете думать об этом как о блокировке пробела , , который отличается от концепции блокировки на уровне строки напрямую .

В вашем случае с этим ORDER BY position ASC,InnoDB должен убедиться, что никакие новые строки не могут быть изменены между самым низким значением ключа и "специальным" самым низким возможным значением.Если вы сделали что-то вроде ORDER BY position DESC .. ну, тогда никто не мог бы вставить в этот диапазон.

Итак, вот решение:

  • Двоичное ведение журнала на основе отстой.Я действительно с нетерпением жду будущего, когда мы все перейдем на двоичное ведение журнала на основе строк (доступно из MySQL 5.1, но не включено по умолчанию).

  • С Row-на основе репликации, если вы измените уровень изоляции на фиксацию для чтения, тогда необходимо заблокировать только одну соответствующую строку.

  • Если вы хотите стать мазохистом, вы также можете включить 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
2 голосов
/ 05 апреля 2014

В некоторых версиях MySQL есть ошибка: # 67745 Слишком много блокировок строк при использовании SELECT для UPDATE, LIMIT и ORDER BY .

Версия: 5.5.28, 5.5.30, 5.7.1

Та же ошибка на моем локальном mysql 5.5.25 win64.

2 голосов
/ 22 апреля 2011

Я сделал тесты.Создана следующая таблица:

id  data1   data2
1   1   2
2   2   1
5   2   2
6   3   3
3   3   4
4   4   3

Затем я создал первое соединение с транзакцией:

SELECT id FROM test ORDER BY data1 LIMIT 1 FOR UPDATE;

Результатом стала строка с id = 1;

Затем я создал второйтранзакция из другого соединения без коммитов:

SELECT id FROM test WHERE data1=2 FOR UPDATE;

Не заблокировано.И он блокировался только тогда, когда я пытался выбрать саму строку, выбранную первой транзакцией.Я попробовал следующее с изменением ORDER BY на DESC, оно также работает.

Вывод: MySQL блокирует только те строки, которые он фактически выбрал при использовании предложений ORDER BY и LIMIT.См. @Morgan ответ для объяснения блокировки пробела.

Моя версия MySQL 5.0.45

1 голос
/ 17 апреля 2011

В отличие от других баз данных, в MySQL запрос блокирует позиции индекса.Это фактически означает, что все строки, которые в настоящее время имеют status, равный 'QUEUED' или хотели бы изменить его на 'QUEUED' из другой транзакции, заблокированы.Единственное решение, которое я нашел, - это выбрать строки без FOR UPDATE, затем выбрать их с помощью фильтра на основе идентификаторов и повторно проверить условие, как только они будут заблокированы.Не хорошо, но это делает работу.

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