Модульні уроки (3/4)
Блокування та ВИБРАТИ ДЛЯ ОНОВЛЕННЯ
Зрозуміти, як ізольовано працює база даних на одному терміналі, дуже просто... чекайте, я вже це сказав!
Іноді «Атомарних оновлень» (наприклад, stock = stock - 1) недостатньо.
Уявімо, що вам потрібно перевірити, чи достатньо на балансі користувача, щоб купити речі на суму 50 євро:
SELECT balance FROM users WHERE id = 1;- JS: Якщо баланс < 50, заблокуйте їх!
- В іншому випадку продовжте оформлення замовлення...
- Запустіть дедукцію! (І тут ви знаходите користувача, який, перевіряючи чек на кроці 2, запустив інший паралельний, який спалив його обліковий запис, і тепер ви падаєте нижче нуля!).
ДЛЯ ОНОВЛЕННЯ Явне блокування
Додавання FOR UPDATE в кінці SELECT чарівним чином перетворює ваше невинне читання на запечатане блокування для інших клієнтів до наступного COMMIT. Ніхто інший не зможе виконати ОНОВЛЕННЯ (або одночасне читання для оновлення) для цих конкретних вибраних рядків, доки ви не вирішите, що з ними робити!
BEGIN;
-- If id=3 had already been locked by T1, T2 will sit in perpetual loading on this "read"
-- frozen in place, before receiving the raw data, until T1 runs COMMIT freeing everyone!
SELECT balance
FROM users
WHERE id = 3
FOR UPDATE;
-- at this point our Node JS instance lives with the guarantee, knowing for certain that NO ONE ELSE in the world has
-- manipulated (nor been able to read with intent to alter) that balance while we complete the application logic
UPDATE users SET balance = balance - 50 WHERE id = 3;
COMMIT;Існує навіть FOR SHARE, який дозволяє іншим читачам продовжувати читання (якщо на базовому блокуванні), не блокуючи зчитування стану, але блокуючи будь-які можливі наступні оновлення, доки ви не зафіксуєте. Однак для змін завжди використовуйте грубу силу пункту ексклюзивного блокування:
Спробуйте самі
Відкрийте звичайну тимчасову транзакцію та виключно заблокуйте читання продукту ID=5.\nВиконайте двоетапний запит:\n1. Відкрийте транзакцію (ПОЧАТИ)\n2. Запит «продукти» отримує повний рядок для ідентифікатора 5 із гарантією, що ніхто не змінить його, додавши опцію «ДЛЯ ОНОВЛЕННЯ».
Показати підказку
BEGIN;, новий рядок, SELECT * FROM products WHERE id = 5 FOR UPDATE;
Рішення доступне після 3 спроб
Іноді транзакція зависає протягом катастрофічних періодів (взаємоблокування або нескінченне очікування).\nЩоб запобігти цьому, ви можете наказати базі даних відмовитися від моменту, коли рядок стає недоступним одразу: використовуючи NOWAIT наприкінці, що призведе до того, що виняток SQL-сервера повертатиметься назад до JS для спроби перехоплення без зависання назавжди.\nЗапустіть SELECT для категорії ID=1 і використовуйте FOR UPDATE, але застосувати NOWAIT.
Показати підказку
ВИБРАТИ * З категорій WHERE id = 1 ДЛЯ ОНОВЛЕННЯ NOWAIT;
Рішення доступне після 3 спроб