open atlas
↑ К треку
Базы данных DB · 02 · 02

Leading-column rule: почему порядок столбцов в composite-индексе важен

Composite B-tree индекс на (a, b, c) ускоряет запросы, фильтрующие по a, a+b или a+b+c — но не запросы только по b или c. Это самое нарушаемое правило в продакшн-индексировании.

DB Middle ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior

Дашборд фильтрует задачи по workspace_id и status. Команда добавляет индекс (status, workspace_id). В staging работает быстро. В продакшне с 50M строк запрос со временем начинает делать seq scan, потому что фильтр WHERE workspace_id = $1 без status не использует leading-столбец. Один неправильный порядок столбцов — seq scan на 50M строк.

Как устроен B-tree внутри

Прежде чем проектировать composite-индексы правильно, нужно понять, что физически означает «отсортировано по (a, b, c)» — потому что именно физический layout делает правило неизбежным, а не произвол Postgres.

B-tree — сбалансированное дерево, где каждый узел — disk page (8KB) с отсортированным списком ключей. Для composite-индекса (a, b, c) ключ — это кортеж (a, b, c), отсортированный лексикографически: сначала по a, при равных a — по b, при равных (a, b) — по c.

Для поиска по индексу планировщик должен начать с leading-столбца. Только зная значение a, он может перейти к правой ветке дерева. Без значения a вся структура бесполезна — как искать в телефонной книге, зная только второе имя, но не фамилию.

Фильтр запросаИндекс (a, b, c) работает?
WHERE a = $1Да — только leading-столбец
WHERE a = $1 AND b = $2Да — leading prefix a, b
WHERE a = $1 AND b = $2 AND c = $3Да — полное использование
WHERE a = $1 AND c = $3 (нет b)Частично — сужает по a, затем фильтрует c без индекса
WHERE b = $2Нет — b не leading-столбец
WHERE c = $3Нет — c не leading-столбец

Проектирование composite-индексов

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

Когда видишь, что индекс «должен работать», но EXPLAIN показывает Seq Scan — первым делом проверяй: запрос фильтрует по leading-столбцу или нет? Если нет — вот твой баг.

Пример: tenant-scoped дашборд

-- Запрос: список задач в проекте
SELECT id, title FROM tasks
WHERE workspace_id = $1 AND status = 'open'
ORDER BY created_at DESC;

-- Правильный индекс: workspace_id — tenant-граница (всегда присутствует)
CREATE INDEX ON tasks (workspace_id, status, created_at DESC);

-- Неправильный: status первым — только небольшая часть запросов фильтрует ТОЛЬКО по status
CREATE INDEX ON tasks (status, workspace_id, created_at DESC);

Один composite vs два single-column индекса

Один composite (a, b) обычно лучше двух single (a) и (b):

  • Меньше overhead на запись (одна структура вместо двух).
  • Меньше суммарный размер.
  • Composite обслуживает запросы по a (использует только leading-часть) И запросы по a, b.

Два single-column нужны, когда: запросы часто фильтруют только по a ИЛИ только по b (не вместе). Тогда ни один composite не обслуживает оба.

Кардинальность не определяет порядок

Распространённое заблуждение: «ставь столбец с большей кардинальностью первым». Это неверно. Порядок определяется паттерном запросов:

  • Если workspace_id (5 значений) всегда присутствует в фильтре, он идёт первым.
  • Если user_id (1M значений) встречается в фильтре редко, он идёт последним или в отдельный индекс.

Единственный источник правды — EXPLAIN ANALYZE на реальных запросах с реалистичными параметрами.

Проследи
1/3

Команда добавляет индекс (status, workspace_id) для дашборда. Проследи проблему.

1
Step 1 of 3
Шаг 1: дашборд фильтрует по workspace_id = $1. Какой plan?
2
Locked
Шаг 2: как исправить не переписывая запрос?
3
Locked
Шаг 3: как убедиться, что новый индекс используется?
Викторина

Индекс: CREATE INDEX ON events (tenant_id, event_type, created_at). Какой запрос его использует?

Викторина

Команда проводит аудит индексов. Находит (user_id) и (user_id, created_at DESC). Что делать?

Расставь шаги по порядку

Упорядочи шаги проектирования composite-индекса под hot query:

  1. 1 Определи hot queries и их фильтр-паттерны
  2. 2 Найди столбец, который всегда присутствует в фильтре — это leading-столбец
  3. 3 Добавь secondary столбцы по убыванию частоты фильтрации
  4. 4 Убедись, что ORDER BY столбцы идут после filter-столбцов
  5. 5 Добавь INCLUDE для столбцов только в SELECT projection
  6. 6 CREATE INDEX CONCURRENTLY и проверь EXPLAIN ANALYZE
  7. 7 Удали одиночные индексы, которые стали prefix нового composite
Вспомните перед уходом
  1. 01
    Объясни детально, почему leading-column rule существует и какие продакшн-паттерны помогают его обойти.
  2. 02
    Почему CREATE INDEX CONCURRENTLY — продакшн-дефолт, несмотря на медлительность, и каковы его failure mode?
  3. 03
    Один composite дешевле двух одиночных индексов — объясни почему, и когда два одиночных лучше.
Итог

Composite B-tree на (a, b, c) отсортирован лексикографически: сначала по a, затем по b, затем по c. Запросы, которые не фильтруют по leading-столбцу a, не могут использовать индекс — планировщик не имеет точки входа в дерево. Это самое нарушаемое правило в продакшн-индексировании: команды добавляют composite не задумываясь о порядке и получают seq scan.

Правило проектирования: leading-столбец — это столбец, присутствующий в фильтре большинства горячих запросов (обычно tenant-scope вроде workspace_id). Secondary столбцы — по убыванию частоты фильтрации. ORDER BY столбцы — после filter-столбцов в ключе. Столбцы только для projection — в INCLUDE.

Один хорошо спроектированный composite часто заменяет 3-5 одиночных индексов: меньше write-overhead, меньше суммарный размер. EXPLAIN ANALYZE — единственный способ убедиться, что планировщик действительно использует индекс.

Теперь, когда встретишь composite-индекс в code review или постмортеме — первым делом проверяй, какой столбец стоит первым и присутствует ли он в фильтре самого горячего запроса. Чаще всего именно здесь и зарыт баг.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки
встречается в177

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.