Короткий ответ
Lost update возникает, когда две параллельные транзакции читают одно и то же значение, затем каждая вычисляет новое значение на основе прочитанного и записывает его обратно — одно из обновлений «теряется»; решение — атомарное обновление на уровне SQL (UPDATE ... SET x = x + 1) или явная блокировка строки (SELECT ... FOR UPDATE).
Как это работает подробнее
Проблема возникает из-за паттерна «прочитать в приложении → изменить в памяти → записать обратно» между двумя отдельными SQL-командами, в промежутке между которыми другая транзакция успевает сделать то же самое с тем же исходным значением.
Пример кода
example.py
# Подвержено lost update: два параллельных запроса читают balance=100, оба пишут 100-30=70
account = Account.objects.get(id=1)
account.balance -= 30
account.save()
# Безопасно: атомарное выражение на уровне SQL, выполняется БД как единая операция
Account.objects.filter(id=1).update(balance=F("balance") - 30)Что использовать на практике
- Практика. Если логика обновления простая арифметика, надёжнее выразить её прямо в одном SQL-запросе (balance = balance - 100), который СУБД выполнит атомарно; если нужна сложная проверка перед записью, используют SELECT ... FOR UPDATE или optimistic locking.
Подводные камни
- Ошибка. Читают значение через ORM (obj = Model.objects.get(...)), изменяют атрибут в Python и сохраняют через save() — именно этот паттерн подвержен lost update, даже если каждая SQL-операция сама по себе использует Read Committed.