Как гарантировать уникальность записи при параллельной обработке в базе данных
Что проверяют
Понимаете ли вы, что гарантию уникальности даёт только база, а не код приложения. Классическая ловушка: «сначала проверю SELECT-ом, потом вставлю». Следом спрашивают, что делать, если сервис запущен в нескольких репликах, и как обработать конфликт в Go.
Как отвечать
Стройте ответ от главного механизма к вспомогательным:
- уникальное ограничение:
UNIQUEна колонку или составной ключ, например(user_id, order_ref). Два параллельныхINSERTне пройдут оба, второй получит ошибку. Для мягкого удаления нужен частичный индексWHERE deleted_at IS NULL; INSERT ... ON CONFLICT: в PostgreSQLDO NOTHINGилиDO UPDATEпревращают гонку в штатный сценарий, аRETURNINGотдаёт id без второго запроса. В MySQL аналогINSERT ... ON DUPLICATE KEY UPDATE;- обработка ошибки: с
pgxэтоerrors.As(err, &pgErr)и проверкаpgErr.Code == "23505", кодunique_violation. Такую ошибку переводят в доменную, например «уже существует», и не отдают клиенту как 500; - идемпотентность: ключ идемпотентности от клиента в уникальной колонке защищает от повторной отправки запроса;
- advisory lock:
pg_advisory_xact_lock(hash)сериализует обработку по ключу, когда уникальность сложнее одного индекса; SERIALIZABLE: работает, но транзакцию приходится повторять при ошибке40001.
Про Go обязательно добавьте, что sync.Mutex защищает только в пределах одного процесса. Как только инстансов два, он бесполезен. И транзакцию закрывают через defer tx.Rollback() сразу после Begin: после успешного Commit откат ничего не делает, зато любой ранний return не оставит висящую транзакцию.
Где спотыкаются
Делают SELECT, потом INSERT в одной транзакции и считают, что это атомарно. На уровне READ COMMITTED две транзакции обе увидят пустоту и обе вставят.
Предлагают SELECT ... FOR UPDATE. В PostgreSQL он блокирует только существующие строки, а строку, которой ещё нет, заблокировать нельзя, так что от дубля при вставке он не спасает.
Ловят любую ошибку вставки как «дубль», не проверяя код. Тогда обрыв соединения или нарушение внешнего ключа молча превращается в «запись уже есть».