Практическая ситуация: две транзакции одновременно вставляют одинаковое значение в столбец с уникальным огр...

Практическая ситуация: две транзакции одновременно вставляют одинаковое значение в столбец с уникальным ограничением. Как СУБД гарантирует, что после фиксации появится не более одной такой строки?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

СУБД синхронизирует проверку уникальности через уникальный индекс или другой внутренний механизм резервирования ключа. Если две транзакции вставляют одинаковое значение, одна из них обычно ждёт решения по конфликту; после фиксации первой вторая получает ошибку нарушения уникальности, а при откате первой может продолжить операцию.

Исторический контекст

Проверка уникальности на уровне приложения через отдельный запрос создаёт окно гонки: обе транзакции могут одновременно увидеть, что значения ещё нет. Поэтому контроль уникальности перенесли в СУБД, где проверка и вставка выполняются как единая конкурентно защищённая операция.

Это позволяет сохранять инвариант независимо от числа приложений, серверов и клиентских соединений, работающих с таблицей.

Постановка проблемы

Пусть столбец email должен быть уникальным. Если каждая транзакция сначала выполняет проверку отсутствия значения, а затем вставку, обе могут получить одинаковый результат проверки и попытаться вставить одну и ту же почту.

Без ограничения базы данных возможны дубликаты. Если ограничение есть, приложение должно корректно обработать ошибку одной из транзакций, не считая её неожиданным сбоем инфраструктуры.

Подробное решение

При вставке СУБД проверяет уникальный индекс. Для конфликтующего ключа она учитывает не только уже зафиксированные строки, но и незавершённые вставки других транзакций, поэтому результат проверки нельзя получить простым чтением обычного снимка данных.

Типичный сценарий выглядит так:

CREATE TABLE users ( id integer PRIMARY KEY, email text NOT NULL UNIQUE ); -- Две транзакции одновременно INSERT INTO users(id, email) VALUES (1, 'a@example.com'); INSERT INTO users(id, email) VALUES (2, 'a@example.com');

Одна вставка получает право занять ключ первой. Вторая может заблокироваться, пока первая транзакция не завершится. Если первая зафиксирована, вторая завершается ошибкой уникальности; если первая откатилась, вторая обычно получает возможность завершиться успешно.

Точный способ ожидания и момент обнаружения конфликта зависят от СУБД и режима ограничения. Для немедленного ограничения ошибка обычно возникает на INSERT или UPDATE; для отложенного ограничения проверка может переноситься к фиксации транзакции.

Уникальность нужно отличать от обычного индекса: индекс ускоряет поиск, но сам по себе не обязан запрещать дубликаты. Также важно учитывать семантику NULL: во многих СУБД несколько NULL не считаются нарушением обычного уникального ограничения, поскольку NULL не равен NULL.

Уникальное ограничение защищает именно указанный ключ. Если бизнес-правило зависит от диапазона, нескольких таблиц или агрегата, одного уникального индекса может быть недостаточно: потребуются подходящая модель данных, блокировки, сериализуемая изоляция или отдельный механизм координации.

Ситуация из практики

В сервисе регистрации приложение сначала искало пользователя по адресу электронной почты, а затем создавало запись. При высокой нагрузке два запроса иногда проходили проверку одновременно, что приводило к дубликатам.

Рассматривались три варианта. Одна лишь предварительная проверка была дешёвой, но неустойчивой к гонке. Повышение изоляции до SERIALIZABLE могло защитить более сложную последовательность, но увеличивало вероятность конфликтов и требовало повторения транзакций. Уникальное ограничение оказалось самым прямым решением для инварианта одного ключа.

В таблицу добавили уникальное ограничение, а приложение стало обрабатывать ошибку конфликта как штатный результат конкурентной регистрации. Это устранило дубликаты без сериализации всех операций регистрации.

Что кандидаты часто упускают

1. Достаточно ли выполнить проверку отсутствия значения перед вставкой?

Нет. Такая проверка и последующая вставка являются отдельными операциями, между которыми другая транзакция может вставить то же значение. Надёжным источником истины должно быть ограничение СУБД, а предварительная проверка может использоваться только для оптимизации пользовательского сценария.

2. Что произойдёт со второй вставкой, если первая транзакция ещё не завершена?

Вторая операция обычно не может немедленно принять решение по уникальности, потому что результат зависит от исхода первой транзакции. Она ждёт фиксации или отката первой: после фиксации получает конфликт, после отката может продолжить работу. Поэтому ожидание уникального ключа — это форма конкурентной синхронизации, а не признак зависания без причины.

3. Чем изменяется поведение отложенного уникального ограничения?

При отложенной проверке транзакция может временно создать состояние, нарушающее уникальность, если к моменту фиксации конфликт устранён. Финальная проверка выполняется при COMMIT; если допустимого состояния нет, фиксация завершается ошибкой. Это удобно для многошаговых изменений, но увеличивает время обнаружения ошибки и требует, чтобы приложение умело обрабатывать неуспешный COMMIT.