Перейти до основного вмісту
Модуль 11 · Урок 2 із 442/57 у курсі~10 min
Модульні уроки (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 одиниць! Фантомну покупку втрачено.

Рішення без важких транзакцій (атомні оновлення)

Замість того, щоб читати та обчислювати ззовні, дозвольте БД робити це під час самого оновлення, яке завжди синхронізовано.

SQL
-- 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, роблячи вас недійсними).
  • Фантомні зчитування: ви скануєте всі рядки, тим часом другий потік вставляє новий запис, потім ви виконуєте ще одне сканування під час тієї ж транзакції, і дивним чином виявляєте, що новий запис з’явився нізвідки, скомпрометувавши розраховані фінансові звіти.
вправи#sql.m11.l2.e1
Спроби: 0Завантаження…

Атомно змініть запис клієнтів для невеликої маніпуляції рядком.\nПрипустімо, ми хочемо перейменувати «місто» клієнта, щоб додати зірочку. \nЗробіть це, використавши власне значення рядка для рядка з id=10, об’єднавши '- Old' до поточного значення (використовуйте конвеєр postgres ||).

Завантаження редактора…
Показати підказку

ОНОВИТИ клієнтів SET місто = місто || ' - Старий' WHERE id = 10;

Рішення доступне після 3 спроб

вправи#sql.m11.l2.e2
Спроби: 0Завантаження…

Класичне втрачене оновлення відбувається в платежах і цінах.\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 спроб