Программирование SQLDML и запросыРазработчик серверной части

В практическом запросе фильтр применяет функцию к индексированному столбцу: почему это часто мешает эффекти...

В практическом запросе фильтр применяет функцию к индексированному столбцу: почему это часто мешает эффективно использовать обычный индекс?

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

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

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

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

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

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

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

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

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

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

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

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

SELECT order_id FROM orders WHERE created_at >= '2025-03-01 00:00:00' AND created_at < '2025-03-02 00:00:00';

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

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

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

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

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

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

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

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

  1. Всегда ли функция над столбцом полностью запрещает использование индекса?

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

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

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

  1. Почему добавление индекса не гарантирует ускорение такого фильтра?

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