Можно ли использовать индекс если в предикатах указано не первое поле индекса
Что проверяют
Понимаете ли вы, как устроен составной B-tree индекс и почему порядок колонок в нём важен. Простое «нет» здесь слабый ответ: следом обычно спрашивают про skip scan и про то, как проверить, использует ли база индекс.
Как отвечать
Индекс (a, b, c) отсортирован сначала по a, внутри по b, дальше по c. Поэтому поиск по дереву возможен по левому префиксу: a, a и b, все три. Условие только по b не даёт точки входа в дерево, но индекс от этого не становится бесполезным:
- полное сканирование индекса: база читает весь индекс вместо таблицы. Выгодно, когда индекс сильно уже таблицы и покрывает запрос целиком (index-only scan в PostgreSQL, покрывающий индекс в MySQL);
- skip scan: база перебирает различные значения
aи для каждого ищет нужныйb. Давно есть в Oracle, в MySQL с 8.0.13, в PostgreSQL с 18-й версии. Хорошо работает, только когда у первой колонки мало различных значений; - комбинация через bitmap: PostgreSQL может сложить несколько индексов через
BitmapAnd, если поbесть отдельный индекс; - полный проход таблицы: всё остальное, и планировщик часто выбирает его сам.
Проверять не на словах, а через EXPLAIN ANALYZE (в PostgreSQL удобно с BUFFERS). Для Java-разработчика есть практическая часть: SQL, который генерирует Hibernate, берётся из лога org.hibernate.SQL, а порядок колонок составного индекса задаётся в @Table(indexes = @Index(columnList = "a, b")) и в миграции Flyway или Liquibase. Если частые запросы фильтруют по b, решение обычно простое: отдельный индекс по b или другой порядок колонок. Правило построения: колонки с равенством вперёд, колонку с диапазоном последней.
Где спотыкаются
Думают, что важен порядок условий в WHERE. Оптимизатор переставит их сам, значение имеет только порядок колонок в индексе.
Отвечают категорично «никогда не используется», не зная про skip scan и сканирование покрывающего индекса.
Не учитывают диапазон на первой колонке. В индексе (a, b) условие a > 100 AND b = 5 сужает поиск только по a, а b проверяется внутри прочитанного диапазона, так что индекс (b, a) здесь был бы эффективнее.