Перейти к основному содержимому
Модуль 11 · Урок 2 из 442/57 в курсе~10 min
Уроки модуля (2/4)

Условия гонки

Понять, как база данных работает изолированно на одном терминале, просто, но в реальном веб-сценарии будут сотни одновременных процессов «Node/PHP/Go», отправляющих запросы. Именно здесь рождаются знаменитые условия гонки.

Проблема «потерянного обновления»

Потерянное обновление происходит, когда два потока читают одну и ту же старую строку, выполняют некоторые математические вычисления на сервере JavaScript, а затем отправляют обратно UPDATE, чтобы сохранить результат.

Сценарий:

  • Товар #5 имеет 10 единиц.
  • Пользователь А покупает один (Сервер А читает «10», вычисляет «9»).
  • За ту же миллисекунду пользователь B покупает еще один, прежде чем покупка A завершит фазу COMMIT.
  • Сервер B также читает «10», вычисляет «9».
  • Сервер А отправляет UPDATE: set quantity = 9.
  • Сервер Б отправляет UPDATE: set quantity = 9.
  • Оба думают, что успешно уменьшили это число. Но итоговое значение должно было составить 8 единиц! Фантомная покупка потеряна.

Решение без тяжелых транзакций (Atomic Updates)

Вместо того, чтобы читать и выполнять математические вычисления снаружи, позвольте БД делать это во время самого обновления, которое всегда синхронизируется.

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 city = city || '- Старый' ГДЕ id = 10;

Решение доступно после 3 попыток

Упражнение#sql.m11.l2.e2
Попыток: 0Загрузка…

Классическое потерянное обновление происходит в платежах и ценах.\nПредположим, произошло экстренное повышение цен: увеличьте 'unit_price' таблицы 'order_items' (для продукта 1 заказа 1) на 20% атомарно, т. е. умножив ее * 1,2 вместо вставки новой цены, предварительно рассчитанной вручную в JS.

Загрузка редактора…
Показать подсказку

ОБНОВЛЕНИЕ order_items SET unit_price = unit_price * 1.2 ГДЕ order_id = 1 И product_id = 1;

Решение доступно после 3 попыток