Що використовує блокування MySQL FOR UPDATE, що саме блокується?


83

Це не лише повний / правильний запит 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, або всі записи, які він повинен відсканувати, щоб знайти один запис?


1
Блокування all Records it has to SCAN TO FIND the SINGLE RECORDбуло б настільки жахливим дурним, що я дійсно сумніваюся, що MySQL працює так. Подумайте про алгоритм у пошуковій системі MySQL - коли він бачить якийсь рядок і знає, що це не той рядок, який вам потрібен, чому б на Землі витрачати додатковий час, щоб встановити блокування ?! Я пропоную вам не приймати відповідь, щоб інші люди MySQL могли коментувати це
Олександр Малахов

Крім того, будучи розробником Oracle DB, я запевняю вас, що Oracle блокує лише ті рядки, які відповідають WHEREумові. Отже , це технічно можливо , і я не думаю , що MySQL , що набагато гірше
Олександр Малахов

1
Хоча, здається, моя відповідь виявилася правильною, я пропоную замість цього вибрати іншу відповідь, оскільки вона є правильною і насправді її перевірила , тоді як моя просто посилається на документацію, яку, як зазначає Олександр, можна було прочитати більше, ніж односторонній.
El Yobo

Відповіді:


115

Чому б нам просто не спробувати?

Налаштування бази даних

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;

І справді це блокує.

Цікаво, що ми також не можемо додавати записи, які будуть прочитані, тобто

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

блоки також!

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


6
+1 для доказу на прикладі. Така поведінка виглядає жахливо. Ви намагалися зафіксувати лише 1 рядок? Чому б вам не вибрати за idстовпчиком, я не довіряю літералам дати :). Яка версія MySQL / InnoDB?
Олександр Малахов

44
+1. вся таблиця не блокується, якщо заблоковано унікальний стовпець. я спробував CREATE TABLE notification (id` BIGINT (20) NOT NULL AUTO_INCREMENT, dateDATE, textTEXT, PRIMARY KEY ( id)) ENGINE = InnoDB; `який працює. Але це не працює, якщо не вказано унікальний / первинний ключ, тобто. SELECT * FROM notification WHERE id` = '1' ДЛЯ ОНОВЛЕННЯ; `на оригінальній схемі
deepak

6
@fabspro Вам не потрібно використовувати унікальний ключ. Будь-який ключ буде працювати, незалежно від того, унікальний він чи ні.
thekingoftruth

3
Мої власні тести показують, що використання for updateз фільтрами де в неіндексованих стовпцях призводить до блокування цілої таблиці, тоді як там, де фільтри на індексованих стовпцях приводить до бажаної поведінки відфільтрованого блокування рядків. Отже, первинний ключ не потрібен, достатньо будь-якого ключа.
CMCDragonkai

2
Однак при подальшому тестуванні з індексом, якщо ви працюєте for updateна фільтрі де, що призводить до порожнього набору, це не блокує інший запит для того самого порожнього набору. Тоді як без індексу запуск for updateбуде блокувати запит для того самого порожнього набору.
CMCDragonkai

26

Я знаю, що це запитання досить давнє, але я хотів поділитися результатами деяких відповідних тестів, які я провів, з індексованими стовпцями, що дало досить дивні результати.

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

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умова у запиті з підключення 1 відповідає неіндексованому рядку. Наприклад, коли з'єднання 1 виконати ють SELECT * FROM t1 WHERE notid=5 FOR UPDATEвсі запити з виберіть FOR UPDATEі UPDATEзапити з сполуки 2 блокуються.

- РЕДАГУВАТИ -

Це досить специфічна ситуація, але це єдина ситуація, яку я зміг виявити:

Підключення 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;це НЕ заблокований.


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

це через блокування проміжків, яке базується на індексах.
М Ростамі

21

Потік досить старий, щоб поділитися своїми двома копійками щодо тестів вище, проведених @Frans

З'єднання 1

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

З'єднання 2

BEGIN;

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

Одночасна транзакція 2 буде заблокована напевно, але причина НЕ в тому, що транзакція 1 утримує блокування на всій таблиці. Далі пояснюється, що сталося за лаштунками:

Перш за все, за замовчуванням рівень ізоляції механізму зберігання InnoDB - Repeatable Read. В цьому випадку,

1- Коли стовпець, що використовується там, де умова не індексується (як у випадку вище):

Механізм зобов'язаний виконати повне сканування таблиці, щоб відфільтрувати записи, що не відповідають критеріям. КОЖНИЙ РЯД, який було відскановано, заблоковано в першу чергу. MySQL може звільнити блокування для тих записів, які згодом не відповідають пункту where. Це оптимізація продуктивності, однак така поведінка порушує обмеження 2PL.

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

2- Коли стовпець, що використовується в де умова, є первинним індексом

Заблоковано лише запис індексу, що відповідає критеріям. Тому в коментарях хтось говорить, що деякі тести не блокуються.

3 - Коли стовпець, що використовується в де умова, є індексом, але не унікальним

Цей випадок складніший. 1) Запис індексу заблоковано. 2) Один замок X прикріплений до відповідного первинного індексу. 3) До неіснуючих записів безпосередньо перед записом та після нього, що відповідає критеріям пошуку, прикріплені два зазори.


11

Наступні посилання зі сторінки документації, яку ви розмістили, дають більше інформації про блокування . На цій сторінці

SELECT ... FOR UPDATE читає останні доступні дані, встановлюючи ексклюзивні блокування для кожного зчитуваних рядків. Таким чином, він встановлює ті самі замки, які шуканий SQL UPDATE встановив би в рядках.

Це здається цілком зрозумілим, що всі рядки він повинен сканувати.


Не впевнені, чи правильно я вас зрозумів, але чи ви хочете сказати, що якщо я виберу останній рядок і стовпці в WHEREне проіндексовані, це заблокує всю таблицю? Це очевидно неправильно. Принаймні Oracle замикає лише selectрядки
Олександр Малахов

Oracle набагато краще , ніж MySQL :) Я не знаю , з допомогою експериментів, тільки через читання документації, але це те , що він , здається, говорять. Хоча це звучить досить по-дурному.
El Yobo,

1
Це звучить надзвичайно дурно. Враховуючи популярність MySQL, я справді сумніваюся, що це так, як це працює. А висловлювання setting exclusive locks on each row it readsможна трактувати по-іншому. Хоча я згоден, що це не на 100% ясно
Олександр Малахов

Не знаю; MySQL робить кілька неймовірно дурних речей: - / Я загалом просто ПОЧИНАЮ, роблю все, що мені потрібно, і КОМІТУЮ, але не маю уявлення, що він робить внутрішньо, коли я це роблю.
El Yobo,

3
@ Олександр Малахов - Це, здається, так і працює, я просто перевірив його і виявив, що він блокує всю таблицю, якщо ви не проіндексуєте стовпці у своєму реченні where. Тут слід справді вдосконалити документацію, оскільки це надзвичайно заплутано. "Кожен рядок, який він читає", змусив би мене думати, що рядки, які він читає, базуватимуться на умовах у моєму реченні WHERE. Отже, якщо моє речення WHERE обмежило результат 1 повернутим рядком, це рядок, який я очікував би бути заблокованим. Але, здається, "кожен рядок, який він читає" означає кожен рядок, "відсканований базою даних".
dcp

10

З офіційного документа MySQL:

Блокування читання, ОНОВЛЕННЯ або ВИДАЛЕННЯ зазвичай встановлюють блокування записів для кожного запису індексу, який перевіряється під час обробки оператора SQL. Не має значення, чи є в операторі умови WHERE, які виключають рядок.

Для випадку, обговореного у відповіді Франса, всі рядки заблоковані, оскільки під час обробки sql відбувається сканування таблиці:

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

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


1

Як вже згадували інші, SELECT ... FOR UPDATE блокує всі рядки, що зустрічаються на рівні ізоляції за замовчуванням. Спробуйте встановити ізоляцію для сеансу, який запускає цей запит, на READ COMMITTED, наприклад, перед запитом:set session transaction isolation level read committed;


-4

Він блокує всі рядки, вибрані за запитом.


3
Блокує всі відскановані рядки ; надмножина SELECTed рядків.
ijoseph
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.