Что такое…

Что такое оптимистичная блокировка — и куда пропала новая цена

?

Иллюстрация: два человека тянутся к одной карточке с номером версии в углу

В твоём магазине два менеджера. Аня открывает карточку кроссовок и меняет цену с 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 внутри транзакции.

Пессимистичная надёжнее, но дороже. Запертая запись держит других в очереди. А если менеджер открыл карточку и ушёл обедать — замок висит, пока не истечёт.

Для форм, которые человек редактирует минутами, почти всегда выбирают оптимистичную. Для коротких операций в одну транзакцию, вроде списания денег с баланса, — пессимистичную.

Как сделать это у себя

Нужны три шага.

  1. Добавь в таблицу колонку version целого типа со значением по умолчанию 1.
  2. Когда отдаёшь запись в форму, отдавай и версию. Форма хранит её в скрытом поле.
  3. При сохранении добавляй к условию 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.

Оптимистичная блокировка запирает запись?

Нет, в этом её смысл. Ничего не запирается: все редактируют свободно. Конфликт просто обнаруживается при сохранении, а не предотвращается заранее.

Что показать пользователю при конфликте?

Самое простое — «Карточку изменили, обнови страницу». Получше — показать обе версии и дать выбрать. Главное — не сохранять молча и не делать вид, что всё прошло.

А если в приложении один пользователь?

Конфликт всё равно возможен: один человек, две открытые вкладки. Или телефон и ноутбук. Версия стоит одну колонку — дешевле, чем объяснять, куда делась правка.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →