Рассмотрите соединение с таблицей, где индекс по ключу объявлен уникальным: как это свойство может изменить план выполнения?
Уникальность ключа сообщает оптимизатору, что поиск по нему вернёт не более одной строки. Это уменьшает оценку кардинальности и стоимости соответствующей операции, поэтому оптимизатор может выбрать другой порядок соединения или более подходящий алгоритм, например nested loop с точечным доступом к уникальному индексу. Само наличие уникального индекса не гарантирует его использование: решение зависит от полного плана, фильтров, объёма данных и стоимости альтернатив.
Оптимизатор SQL изначально должен был выбирать план без выполнения всех возможных вариантов запроса. Одной статистики недостаточно: она оценивает распределение данных, но не всегда точно описывает логические ограничения схемы.
Уникальные индексы и ограничения уникальности появились не только для контроля целостности. Они также дают оптимизатору точное знание о максимальном количестве строк, которое может соответствовать ключу, и тем самым уменьшают неопределённость при оценке плана.
Предположим, запрос соединяет заказы с клиентами по идентификатору клиента. Если оптимизатор знает, что идентификатор клиента уникален в таблице клиентов, каждая найденная строка заказа может соответствовать максимум одной строке клиента.
Если уникальность не объявлена, оптимизатору приходится допускать несколько совпадений. Он может завысить число строк после соединения, выбрать дорогостоящую сортировку или отказаться от точечных обращений к индексу. Ошибка особенно заметна в цепочке из нескольких соединений, где оценки кардинальности влияют друг на друга.
Уникальный индекс поддерживает два эффекта. На уровне данных он запрещает появление двух одинаковых индексных ключей, а на уровне метаданных предоставляет оптимизатору функциональную зависимость: значение ключа определяет не более одной строки.
Например, в следующем запросе поиск клиента по client_id имеет известную верхнюю границу — одну строку:
Если фильтр по заказу также достаточно селективен, оптимизатор может выбрать nested loop: найти один заказ, затем выполнить не более одного индексного поиска клиента. Без информации об уникальности тот же поиск мог бы оцениваться как возвращающий несколько строк, что увеличивает расчётную стоимость и иногда делает другой алгоритм соединения предпочтительным.
Уникальность может влиять не только на выбор индекса, но и на оценку результата соединения. При соединении с уникальной стороной дублирование строк через эту сторону невозможно. Это помогает оценивать последующие операции — сортировку, агрегацию, выделение памяти и передачу строк между операторами.
Однако уникальность не означает автоматическое ускорение. Если запрос читает большую часть таблицы, последовательное сканирование может быть дешевле. Кроме того, оптимизатор использует только те свойства, которые достоверно видит в схеме; фактическое отсутствие дублей без соответствующего ограничения обычно не даёт ему права делать такой вывод.
Важно учитывать NULL. Поведение уникальности для nullable-столбцов различается между СУБД: некоторые допускают несколько NULL, поскольку NULL не считается равным NULL. Поэтому вывод «по ключу будет максимум одна строка» безопасен только с учётом определения столбца, предиката и правил конкретной СУБД.
В отчёте соединялись миллионы заказов с таблицей клиентов. Таблица клиентов фактически содержала уникальные идентификаторы, но столбец был лишь индексирован, без ограничения PRIMARY KEY или UNIQUE. План оценивал несколько совпадений клиента для одного заказа, завышал размер промежуточного результата и выбирал hash join с заметными затратами памяти.
Рассматривались три варианта. Увеличение памяти снижало вероятность сброса hash-таблицы на диск, но не исправляло неверную оценку. Принудительный hint закреплял текущий план и делал решение хрупким при изменении объёмов данных. Добавление подтверждённого ограничения уникальности исправляло метаданные схемы и сохраняло свободу выбора оптимизатора.
После проверки данных добавили UNIQUE для идентификатора клиента и обновили статистику. Оптимизатор стал оценивать сторону клиента как точечную, выбрал более дешёвый план с индексным доступом, а объём промежуточных данных и время выполнения уменьшились. Результат зависел не от самого слова UNIQUE, а от того, что ограничение отражало реальное свойство данных.
Нет. Обычный индекс ускоряет поиск, но не доказывает оптимизатору отсутствие повторяющихся ключей. Для использования свойства уникальности оно обычно должно быть выражено ограничением UNIQUE, PRIMARY KEY или эквивалентным уникальным индексом, который СУБД учитывает при оптимизации.
Нет. Она ограничивает кардинальность поиска, но алгоритм выбирается по совокупной стоимости. При большом внешнем наборе строк, дорогом случайном доступе или подходящем порядке данных hash join или merge join могут оказаться дешевле даже при уникальной внутренней стороне.
Нет. Уникальность задаёт точное ограничение на максимум совпадений, но не описывает распределение других значений, частоту фильтров и объём таблицы. Для оценки стоимости всё ещё важны статистика, селективность предикатов, актуальность данных и физическая стоимость чтения.