Что такое оптимистичная блокировка — и куда пропала новая цена
В твоём магазине два менеджера. Аня открывает карточку кроссовок и меняет цену с 5000 на 4500. Сохраняет. Через минуту Борис, который открыл ту же карточку чуть раньше, исправляет опечатку в описании. Тоже сохраняет.
Опечатка исправлена. А цена снова 5000. Никто не видел ошибки, никто не получил предупреждения. Правка Ани просто исчезла.
Это называется потерянное обновление. И у него есть простое лекарство — оптимистичная блокировка.
Что случилось с ценой
Разберём по шагам. Форма Бориса загрузила карточку целиком, когда цена ещё была 5000. Борис поменял описание. При сохранении форма отправила на сервер всю карточку — с новым описанием и со старой ценой, которую она помнила.
Сервер честно записал то, что пришло. Последний сохранивший победил. Это классическая гонка: результат зависит от того, кто нажал «Сохранить» позже.
Подвох в том, что ни одна строчка кода тут не сломана. Каждый запрос по отдельности правильный. Ломается только их сочетание во времени — поэтому на тестах в одиночку ты эту ошибку никогда не увидишь.
Что такое оптимистичная блокировка
Идея — добавить к записи номер версии. Каждое сохранение увеличивает его на единицу.
Когда форма загружает карточку, она запоминает не только данные, но и версию: «я видела версию 3». При сохранении она говорит базе: «запиши мои изменения, но только если версия всё ещё 3».
На языке SQL это одна строка:
UPDATE products
SET description = 'Новое описание', price = 5000, version = version + 1
WHERE id = 7 AND version = 3;
Вернёмся к Ане и Борису. Аня сохранила первой — версия стала 4. Борис отправляет свой запрос с version = 3. Условие WHERE не находит ни одной строки. База отвечает: обновлено 0 строк.
Вот и сигнал. Код видит ноль и говорит Борису: «Пока ты редактировал, карточку изменили. Обнови страницу». Цена Ани на месте.
Почему «оптимистичная»
Потому что она ничего не запирает заранее. Она оптимистично верит, что конфликты редкие. Все редактируют свободно, а проверка случается только в момент сохранения.
Есть противоположный подход — пессимистичная блокировка. Там первый, кто открыл запись, запирает её: остальные ждут. В Postgres это делается через SELECT … FOR UPDATE внутри транзакции.
Пессимистичная надёжнее, но дороже. Запертая запись держит других в очереди. А если менеджер открыл карточку и ушёл обедать — замок висит, пока не истечёт.
Для форм, которые человек редактирует минутами, почти всегда выбирают оптимистичную. Для коротких операций в одну транзакцию, вроде списания денег с баланса, — пессимистичную.
Как сделать это у себя
Нужны три шага.
- Добавь в таблицу колонку
versionцелого типа со значением по умолчанию 1. - Когда отдаёшь запись в форму, отдавай и версию. Форма хранит её в скрытом поле.
- При сохранении добавляй к условию
version = <то, что пришло>и увеличивай версию на единицу. Если обновлено 0 строк — это конфликт, а не успех.
В Supabase это выглядит так:
const { data } = await supabase
.from("products")
.update({ description, price, version: version + 1 })
.eq("id", id)
.eq("version", version)
.select();
if (data.length === 0) showConflict();
Вместо номера версии можно сравнивать время последнего изменения updated_at. Работает так же, но номер надёжнее: две правки в одну миллисекунду время не различит.
В HTTP у этой идеи есть встроенная форма. Сервер отдаёт заголовок ETag — отпечаток версии. Клиент присылает его обратно в If-Match. Не совпало — сервер отвечает 412 Precondition Failed. Про такие коды есть отдельный разбор кодов ответа HTTP.
Как попросить ИИ
Нейросеть по умолчанию пишет «последний победил». Попроси явно:
сделай сохранение карточки товараДобавь оптимистичную блокировку к сохранению товара. В таблице products колонка version integer default 1. Форма получает version вместе с данными. UPDATE ставит version = version + 1 и фильтрует по id и по пришедшей version. Если обновлено 0 строк — не считай это успехом: верни 409 и покажи пользователю «Карточку изменили, обнови страницу». Без SELECT FOR UPDATE.Оптимистичная блокировка запирает запись?
Нет, в этом её смысл. Ничего не запирается: все редактируют свободно. Конфликт просто обнаруживается при сохранении, а не предотвращается заранее.
Что показать пользователю при конфликте?
Самое простое — «Карточку изменили, обнови страницу». Получше — показать обе версии и дать выбрать. Главное — не сохранять молча и не делать вид, что всё прошло.
А если в приложении один пользователь?
Конфликт всё равно возможен: один человек, две открытые вкладки. Или телефон и ноутбук. Версия стоит одну колонку — дешевле, чем объяснять, куда делась правка.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.