Перейти к основному содержимому

Проверки уникальности индексов

примечание

Эта страница переведена при помощи нейросети GigaChat.

PostgreSQL применяет ограничения уникальности SQL с помощью уникальных индексов — индексов, которые не допускают наличия нескольких записей с одинаковыми ключами. Метод доступа, поддерживающий эту функцию, устанавливает значение amcanunique в true. (В настоящее время только b-tree поддерживает эту функцию.) Столбцы, перечисленные в предложении INCLUDE, не учитываются при обеспечении уникальности.

Из-за MVCC необходимо допускать физическое существование дубликатов записей в индексе: записи могут относиться к последовательным версиям одного логического ряда. Основная цель — гарантировать, что ни один снимок MVCC не включает две строки с одинаковыми индексными ключами. Это приводит к следующим случаям, которые проверяются при вставке новой строки в уникальный индекс:

  • Если конфликтующий действительный ряд был удален текущей транзакцией, вставка допустима. (В частности, UPDATE всегда удаляет старую версию ряда перед вставкой новой версии, поэтому обновление ряда без изменения ключа возможно).
  • Если конфликтующая строка была вставлена транзакцией, которая еще не зафиксирована, будущий «вставщик» должен дождаться завершения этой транзакции. При откате конфликта не будет, при фиксации без удаления конфликтующего ряда возникает нарушение уникальности. На практике выполняется ожидание завершения другой транзакции с последующей повторной проверкой видимости.
  • Аналогично, если конфликтующий ряд был удален еще не зафиксированной транзакцией, потенциальный «вставщик» ожидает завершения этой транзакции и затем повторяет проверку.

Кроме того, непосредственно перед сообщением о нарушении уникальности в соответствии с указанными правилами метод доступа должен перепроверить актуальность вставляемой строки. Если она зафиксирована как мертвая, то ошибка не генерируется. Такая ситуация невозможна при обычной вставке строки в текущей транзакции, но может возникнуть при CREATE UNIQUE INDEX CONCURRENTLY.

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

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

Чтобы исключить повторные ненужные поиски, метод доступа выполняет предварительную проверку уникальности при первой вставке. Если она показывает отсутствие конфликтов, дальнейшие действия не требуются. При обнаружении потенциального конфликта планируется повторная проверка при применении ограничения. Если в момент перепроверки и вставленный кортеж, и другой кортеж с тем же ключом окажутся живыми, возникает ошибка. Под «живым» в данном контексте понимается наличие хотя бы одного живого кортежа в цепочке HOT для данной индексной записи.

Для реализации этого механизма функции aminsert передается параметр checkUnique со значением:

  • UNIQUE_CHECK_NO — проверка уникальности не выполняется (не уникальный индекс).
  • UNIQUE_CHECK_YES — неоткладываемый уникальный индекс, проверка уникальности выполняется немедленно.
  • UNIQUE_CHECK_PARTIAL — ограничение уникальности отложено. Индекс допускает дублирование записей, метод доступа сообщает о потенциальных конфликтах, возвращая false. Для таких строк назначается отложенная перепроверка. При этом допускается ложное срабатывание: возможные конфликты фиксируются для последующей перепроверки после завершения транзакций.
  • UNIQUE_CHECK_EXISTING — отложенная перепроверка ранее отмеченной строки. В этом режиме метод доступа не вставляет новую запись, а проверяет наличие другой действующей индексной записи. Если она существует и целевая строка также жива, фиксируется ошибка.

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