Представьте составной B деревянный индекс по полям «клиент» и «дата». Почему запрос, фильтрующий только по ...

Представьте составной B-деревянный индекс по полям «клиент» и «дата». Почему запрос, фильтрующий только по дате, может почти не получить от него пользы?

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

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

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

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

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

Индексы появились как способ не читать всю таблицу при поиске небольшого набора строк. Сбалансированные деревья, включая B-деревья, позволяют находить диапазон отсортированных ключей за небольшое число обращений к структуре индекса.

Когда индекс строится по нескольким полям, их порядок становится частью его физической логики. Сначала оптимизируется доступ по ведущим полям, а последующие поля используются внутри найденного диапазона.

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

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

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

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

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

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

Иллюстрация механизма:

CREATE INDEX orders_client_date ON orders (client_id, created_at); -- Хорошо использует левый префикс индекса SELECT * FROM orders WHERE client_id = 42 AND created_at >= DATE '2025-01-01'; -- Может потребовать отдельный индекс по дате SELECT * FROM orders WHERE created_at >= DATE '2025-01-01';

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

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

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

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

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

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

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

  1. Всегда ли индекс по «клиенту, дате» бесполезен для поиска только по дате?

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

  1. Почему индекс по «дате, клиенту» не всегда лучше индекса по «клиенту, дате»?

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

  1. Может ли добавление индекса ухудшить производительность системы?

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