Когда используется блокировка FOR UPDATE MySQL, что именно блокируется? - PullRequest
55 голосов
/ 20 мая 2011

Это не полный / правильный псевдокод запроса MySQL:

Select *
 from Notifications as n
 where n.date > (CurrentDate-10 days)
 limit by 1
 FOR UPDATE

http://dev.mysql.com/doc/refman/5.0/en/select.html гласит: Если вы используете FOR UPDATE с механизмом хранения, который использует блокировки страниц или строк, строки, проверенные запросом, блокируются на запись до конца текущей транзакции

Здесь только одна запись, возвращенная заблокированной MySQL, или все записи, которые он должен сканировать, чтобы найти единственную запись?

Ответы [ 6 ]

87 голосов
/ 20 мая 2011

Почему бы нам просто не попробовать?

Настроить базу данных

CREATE DATABASE so1;
USE so1;
CREATE TABLE notification (`id` BIGINT(20), `date` DATE, `text` TEXT) ENGINE=InnoDB;
INSERT INTO notification(id, `date`, `text`) values (1, '2011-05-01', 'Notification 1');
INSERT INTO notification(id, `date`, `text`) values (2, '2011-05-02', 'Notification 2');
INSERT INTO notification(id, `date`, `text`) values (3, '2011-05-03', 'Notification 3');
INSERT INTO notification(id, `date`, `text`) values (4, '2011-05-04', 'Notification 4');
INSERT INTO notification(id, `date`, `text`) values (5, '2011-05-05', 'Notification 5');

Теперь запустите два соединения с базой данных

Соединение 1

BEGIN;
SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;

Соединение 2

BEGIN;

Если MySQL блокирует все строки, следующий оператор будет заблокирован.Если он блокирует только возвращаемые строки, он не должен блокировать.

SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;

И действительно, он блокирует.

Интересно, что мы также не можем добавлять записи, которые будут прочитаны, т.е.1025 *

INSERT INTO notification(id, `date`, `text`) values (6, '2011-05-06', 'Notification 6');

блоков также!

На данный момент я не могу быть уверен, что MySQL просто работает и блокирует всю таблицу, когда определенный процент строк заблокирован, или где он на самом деледействительно умный, чтобы убедиться, что результат запроса SELECT ... FOR UPDATE никогда не может быть изменен другой транзакцией (с INSERT, UPDATE или DELETE), пока удерживается блокировка.

20 голосов
/ 14 января 2015

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

Структура таблицы:

CREATE TABLE `t1` (                       
  `id` int(11) NOT NULL AUTO_INCREMENT,                 
  `notid` int(11) DEFAULT NULL,                         
  PRIMARY KEY (`id`)                                    
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

12 строк, вставленных с INSERT INTO t1 (notid) VALUES (1), (2),..., (12).При соединении 1 :

BEGIN;    
SELECT * FROM t1 WHERE id=5 FOR UPDATE;

При соединении 2 блокируются следующие операторы:

SELECT * FROM t1 WHERE id!=5 FOR UPDATE;
SELECT * FROM t1 WHERE id<5 FOR UPDATE;
SELECT * FROM t1 WHERE notid!=5 FOR UPDATE;
SELECT * FROM t1 WHERE notid<5 FOR UPDATE;
SELECT * FROM t1 WHERE id<=4 FOR UPDATE;

Самое странное, что SELECT * FROM t1 WHERE id>5 FOR UPDATE; не заблокирован , а также

...
SELECT * FROM t1 WHERE id=3 FOR UPDATE;
SELECT * FROM t1 WHERE id=4 FOR UPDATE;
SELECT * FROM t1 WHERE id=6 FOR UPDATE;
SELECT * FROM t1 WHERE id=7 FOR UPDATE;
...

Я также хотел бы отметить, что кажется, что вся таблица заблокирована, когда WHERE условие в запросе от connection 1 соответствует неиндексированной строке.Например, когда соединение 1 выполняет SELECT * FROM t1 WHERE notid=5 FOR UPDATE, все запросы выбора с FOR UPDATE и UPDATE запросами из соединение 2 блокируются.

- EDIT -

Это довольно специфическая ситуация, но это было единственное, что я смог найти, демонстрирующее такое поведение:

Соединение 1:

BEGIN;
SELECT *, @x:=@x+id AS counter FROM t1 CROSS JOIN (SELECT @x:=0) b HAVING counter>5 LIMIT 1 FOR UPDATE;
+----+-------+-------+---------+
| id | notid | @x:=0 | counter |
+----+-------+-------+---------+
|  3 |     3 |     0 |       9 |
+----+-------+-------+---------+
1 row in set (0.00 sec)

С соединение 2 :

SELECT * FROM t1 WHERE id=2 FOR UPDATE; заблокировано;

SELECT * FROM t1 WHERE id=4 FOR UPDATE; является не заблокировано.

9 голосов
/ 20 мая 2011

Следующие ссылки со страницы документации, которую вы разместили, дают больше информации о блокировке . На этой странице

A SELECT ... FOR UPDATE считывает последние доступные данные, устанавливая эксклюзивные блокировки для каждой строки, которую читает. Таким образом, он устанавливает те же блокировки, что и поиск SQL UPDATE для строк.

Кажется, довольно ясно, что сканировать нужно все строки.

1 голос
/ 23 мая 2019

Из официального документа MySQL:

Чтение блокировки, UPDATE или DELETE обычно устанавливают блокировки записи для каждой индексной записи, которая сканируется при обработке оператора SQL. Неважно, есть ли в операторе условия WHERE, исключающие строку.

В случае, рассмотренном в ответе Франса, все строки заблокированы, поскольку во время обработки sql происходит сканирование таблицы:

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

Проверьте последнюю версию документа здесь: https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html

1 голос
/ 09 апреля 2019

Поток довольно старый, просто чтобы поделиться моими двумя центами относительно тестов, выполненных выше @Frans

Connection 1

BEGIN;
SELECT * FROM notification WHERE `date` >= '2011-05-03' FOR UPDATE;

Connection2

BEGIN;

SELECT * FROM notification WHERE `date` = '2011-05-02' FOR UPDATE;

Параллельная транзакция 2 будет точно заблокирована, но причина в том, что НЕ , что транзакция 1 удерживает блокировку на всей таблице.Ниже объясняется, что произошло за кулисами:

Прежде всего, уровень изоляции по умолчанию для механизма хранения InnoDB - RR.В этом случае

1- Когда столбец используется в том месте, где условие не проиндексировано (как в случае выше):

Движок обязан выполнить полное сканирование таблицы, чтобы отфильтровать записине соответствует критериям. КАЖДАЯ СТРОКА сканированные блокируются в первую очередь.MySQL может снять блокировки на этих записях, не соответствующих предложению where, позже.Это оптимизация для производительности, однако такое поведение нарушает ограничение 2PL.

Когда транзакция 2 начинается, как объяснено, она должна получить блокировку X для каждой извлеченной строки, хотя существует только одна запись (id = 2) соответствует предложению where.В конечном счете транзакция 2 будет ожидать блокировки X первой строки (id = 1), пока транзакция 1 не завершит фиксацию или откат.

2 - Когда столбец, в котором условие является первичным индексом

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

3 - когда столбец, в котором условие является индексом, но не уникальным

Этот случай более сложный.1) Индексная запись заблокирована.2) Одна X-блокировка прикреплена к соответствующему первичному индексу.3) Два несоответствия прикрепляются к несуществующим записям непосредственно перед и после записи, соответствующей критериям поиска.

0 голосов
/ 10 мая 2018

Блокирует все строки, выбранные по запросу.

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