Модульні уроки (2/4)
Умови перегонів
Зрозуміти, як ізольовано працює база даних на одному терміналі, легко, але в реальному веб-сценарії будуть сотні одночасних процесів «Node/PHP/Go», які надсилатимуть запити. Саме тут народжуються знамениті умови перегонів.
Проблема «Втрачене оновлення».
Втрачене оновлення трапляється, коли два потоки читають один і той самий старий рядок, обчислюють його на сервері JavaScript, а потім надсилають ОНОВЛЕННЯ, щоб зберегти результат.
Сценарій:
- Продукт #5 має 10 одиниць.
- Користувач А купує один (Сервер А читає «10», обчислює «9»).
- За ту саму мілісекунду користувач B купує інший, перш ніж покупка A завершить фазу COMMIT.
- Сервер Б також читає "10", обчислює "9".
- Сервер A надсилає
UPDATE: set quantity = 9. - Сервер B надсилає
UPDATE: set quantity = 9. - Обидва вважають, що вони успішно зменшили число. Але кінцеве значення мало бути 8 одиниць! Фантомну покупку втрачено.
Рішення без важких транзакцій (атомні оновлення)
Замість того, щоб читати та обчислювати ззовні, дозвольте БД робити це під час самого оновлення, яке завжди синхронізовано.
-- Wrong
UPDATE products SET stock = 9 WHERE id = 5;
-- Correct and natively thread-safe
UPDATE products SET stock = stock - 1 WHERE id = 5;Більш екстремальні аномалії (брудні читання та фантоми)
- Брудне читання: ви читаєте чужі тимчасові дані, які ще не були підтверджені COMMIT (і можуть раптово перейти до ROLLBACK, роблячи вас недійсними).
- Фантомні зчитування: ви скануєте всі рядки, тим часом другий потік вставляє новий запис, потім ви виконуєте ще одне сканування під час тієї ж транзакції, і дивним чином виявляєте, що новий запис з’явився нізвідки, скомпрометувавши розраховані фінансові звіти.
Атомно змініть запис клієнтів для невеликої маніпуляції рядком.\nПрипустімо, ми хочемо перейменувати «місто» клієнта, щоб додати зірочку. \nЗробіть це, використавши власне значення рядка для рядка з id=10, об’єднавши '- Old' до поточного значення (використовуйте конвеєр postgres ||).
Показати підказку
ОНОВИТИ клієнтів SET місто = місто || ' - Старий' WHERE id = 10;
Рішення доступне після 3 спроб
Класичне втрачене оновлення відбувається в платежах і цінах.\nПрипустимо, що відбувається надзвичайне підвищення цін: збільште 'unit_price' таблиці 'order_items' (для продукту 1 замовлення 1) на 20% атомарно, тобто помножте його на * 1,2 замість того, щоб вставляти нову ціну, попередньо розраховану вручну в JS.
Показати підказку
UPDATE order_items SET unit_price = unit_price * 1.2 WHERE order_id = 1 AND product_id = 1;
Рішення доступне після 3 спроб