Разработчик создал обычный индекс для ускорения поиска по столбцу с адресами электронной почты. Почему это ...

Разработчик создал обычный индекс для ускорения поиска по столбцу с адресами электронной почты. Почему это не гарантирует уникальность значений?

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

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

Обычный индекс только ускоряет поиск и не запрещает одинаковые значения. Для гарантии уникальности требуется ограничение UNIQUE или другой механизм, явно проверяющий отсутствие дубликатов.

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

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

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

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

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

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

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

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

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

Минимальный пример:

CREATE TABLE users ( user_id INTEGER PRIMARY KEY, email VARCHAR(255), CONSTRAINT uq_users_email UNIQUE (email) ); CREATE INDEX ix_users_email ON users (email);

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

Следует учитывать семантику NULL. В стандартной реляционной логике сравнение с отсутствующим значением не равно сравнению двух обычных значений, поэтому поведение UNIQUE для нескольких NULL зависит от правил SQL и конкретной СУБД. Если значение обязательно, обычно задают одновременно NOT NULL и UNIQUE.

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

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

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

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

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

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

  1. Дополнительный вопрос: может ли уникальный индекс гарантировать уникальность?

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

  2. Дополнительный вопрос: почему проверка уникальности только в приложении ненадёжна?

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

  3. Дополнительный вопрос: почему обычный индекс не всегда нужен рядом с UNIQUE-ограничением?

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