Ответ
Потерянное обновление (lost update) — это аномалия параллельного доступа, при которой успешное изменение данных одной транзакцией перезаписывается (теряется) в результате фиксации другой транзакции, работавшей с той же копией данных.
Классический сценарий:
- Транзакция A читает значение
X(например, баланс = 100). - Транзакция B читает то же значение
X(баланс = 100). - Транзакция A изменяет
Xна 150 (100 + 50) и фиксирует изменения. - Транзакция B, основываясь на старом значении (100), изменяет
Xна 80 (100 - 20) и фиксирует изменения. - Итог: Значение
Xстановится 80. Обновление транзакции A (увеличение на 50) потеряно.
Способы предотвращения:
- Пессимистичные блокировки: Использование
SELECT ... FOR UPDATE(илиFOR SHAREв некоторых СУБД) для монопольной блокировки строки на время транзакции. - Оптимистичные блокировки: Добавление поля-версии (или временной метки). Каждое обновление проверяет, не изменилась ли версия с момента чтения.
UPDATE accounts SET balance = 150, version = version + 1 WHERE id = 1 AND version = 1; -- Если версия изменилась, обновление не затронет строку - Уровни изоляции: Установка уровня изоляции транзакции
REPEATABLE READ(стандарт в PostgreSQL, MySQL/InnoDB) илиSERIALIZABLEгарантированно предотвращает эту аномалию.
Ответ 18+ 🔞
Да ты посмотри, какая дичь может твориться в базе, когда все лезут туда одновременно! Это ж пиздец, а не работа. Представь себе, классическая история про двух дураков и один счёт.
Сидит транзакция A, читает баланс: «О, сто рублей, сука, красота!». И тут же, блядь, рядом транзакция B подползает и тоже читает: «Ага, тоже сто, епта!». Ну и понеслась.
A думает: «Щас я ему 50 добавлю, будет 150, я молодец». Добавила, зафиксировала — всё, красава. А B-то, хитрая жопа, она же про это обновление не знает, у неё в голове всё ещё старая копия, сто рублей! И она такая: «А я 20 сниму, будет 80». Сняла, зафиксировала — и пошёл этот баланс по пизде.
И что мы имеем в сухом остатке? Было 100, транзакция A добавила 50, а в итоге на счету 80. Эти 50 просто испарились, в рот меня чих-пых! Обновление потерялось, как хуй в стогу сена. Вот это и есть lost update, ёперный театр.
Ну и как с этой хуйней бороться? Да есть же способы, не маленькие!
Первый способ — по-мужски, пессимистично. Берёшь и сразу хватаешь строку за горло, как SELECT ... FOR UPDATE. Типа «это моё, сука, пока я не отпущу, никто сюда не суйся». Просто, грубо, но надёжно. Правда, если все так начнут делать, можно в дедлоке все повиснуть, но это уже другая песня.
Второй способ — по-хитрому, оптимистично. Ты как будто говоришь: «Ладно, ребята, работайте все, но я потом проверю, не наебали ли меня». Для этого заводишь в таблице поле version. Читаешь строку, запоминаешь версию, например, 1. А когда пришло время обновлять, делаешь такую штуку:
UPDATE accounts
SET balance = 150, version = version + 1
WHERE id = 1 AND version = 1;
Суть в чём? Если за это время кто-то другой уже успел тронуть запись, версия изменится, и твой UPDATE просто не сработает — ноль затронутых строк. И ты уже на уровне приложения решаешь, что делать: перечитать данные и повторить операцию или в жопу эту логику. Хитро, да?
Ну и третий, самый мощный способ — административный. Берёшь и на всю базу или на конкретную транзакцию ставишь уровень изоляции REPEATABLE READ (а в некоторых случаях и SERIALIZABLE). База сама, своими внутренними механизмами, не даст прочитать старые данные, если их уже кто-то поменял. Красота! Правда, производительность может просесть, но зато спать спокойно будешь.
Вот так вот, а то думаешь, что просто UPDATE написал и всё. Ан нет, блядь, тут целая наука, чтобы друг другу в тарелку не насрать.