Какие варианты решения проблемы параллельного обновления одной записи в базе данных?
Что проверяют
Понимаете ли вы, откуда берётся потерянное обновление, и умеете ли решить его средствами базы, а не памяти процесса. Следом обычно просят показать код на database/sql или pgx и спрашивают, что будет при нескольких репликах сервиса.
Как отвечать
Проблема возникает в цикле «прочитал, изменил в Go, записал»: две горутины или два пода читают одно значение, и последний записавший затирает первого. Решения по возрастанию сложности:
- атомарный
UPDATE:UPDATE items SET stock = stock - $1 WHERE id = $2 AND stock >= $1, затем проверкаRowsAffected(). Если 0, товара не хватило. Чтения нет, гонки тоже; - оптимистическая блокировка: колонка
versionи запросUPDATE ... SET ..., version = version + 1 WHERE id = $1 AND version = $2. Ноль затронутых строк означает конфликт: перечитать и повторить с ограничением числа попыток; - пессимистическая блокировка: в транзакции
SELECT ... FOR UPDATE, вторая транзакция ждёт на этой строке.NOWAITсразу возвращает ошибку,SKIP LOCKEDпропускает занятые строки, на этом строят очередь задач в таблице. Ожидание ограничивают черезSET LOCAL lock_timeout; - строгая изоляция:
sql.TxOptions{Isolation: sql.LevelSerializable}илиLevelRepeatableReadв PostgreSQL. База сама отменит одну из конфликтующих транзакций с кодом40001, вpgxего видно черезerrors.As(err, &pgErr)иpgErr.Code. Транзакцию повторяют целиком; - advisory lock:
pg_advisory_xact_lock(key), когда блокировать нужно не строку, а операцию.
Где спотыкаются
Ставят sync.Mutex. Он защищает только один процесс, а сервис обычно работает в нескольких репликах.
Внутри транзакции делают запрос через db, а не через tx. Он уходит в другое соединение пула вне транзакции: SELECT ... FOR UPDATE через db снимает блокировку сразу, а UPDATE через db после блокировки в tx повиснет в ожидании этой же транзакции.
Не проверяют RowsAffected() после оптимистического UPDATE или повторяют без лимита и паузы. В первом случае конфликт проходит молча, во втором сервис под нагрузкой крутится в бесконечных повторах.