11 вопросов
Индексы ускоряют поиск, сортировку и JOIN за счет структуры данных (обычно B-tree), позволяя не сканировать всю таблицу. Ускоряют WHERE, ORDER BY, GROUP BY. Минусы: место на диске, замедление INSERT/UPDATE/DELETE (обновление индекса). В Go приложение лишь выполняет запросы; индексы создают миграциями под реальные паттерны доступа.
B-tree - сбалансированное дерево: данные в листьях, ключи в узлах для навигации. Поиск, вставка, удаление - O(log N). Листья отсортированы - удобно для диапазонов (BETWEEN, >, ORDER BY). В Postgres и MySQL индексы по умолчанию - B-tree. Подходит для сравнений и сортировки по одному или нескольким столбцам.
Индекс по нескольким столбцам (a, b, c) может использоваться для запросов по (a), (a, b), (a, b, c), но не эффективно для (b) или (c) отдельно. Порядок важен: сначала столбцы с равенством (=), затем диапазон/сортировка. Пример: WHERE status = 'active' ORDER BY created_at - индекс (status, created_at). В Go запросы формулируют так, чтобы префикс индекса совпадал.
CREATE INDEX idx ON orders (status, created_at);Partial index строится по подмножеству строк: CREATE INDEX ... WHERE status = 'active'. Меньше размер, быстрее обновление. Covering index содержит все столбцы запроса (Index-Only Scan): в Postgres через INCLUDE. Запрос выполняется без обращения к таблице. В Go выгодно выбирать только нужные столбцы, чтобы использовать covering index.
CREATE INDEX idx ON orders (user_id) INCLUDE (total, status);Индекс строится по выражению (функция от столбца), а не по столбцу напрямую. Пример: WHERE lower(email) = 'a@b.com' - индекс CREATE INDEX ON users (lower(email)). В Postgres поддерживается; в MySQL - через виртуальный столбец и индекс по нему. Запрос должен использовать то же выражение, что и индекс.
CREATE INDEX idx_lower_email ON users (lower(email));Hash-индекс подходит только для точного совпадения (=), не для диапазонов и ORDER BY. В Postgres hash редко выигрывает у B-tree; в MySQL MEMORY-таблицы используют hash по умолчанию. Для точечных запросов по ключу иногда выгоден. В приложении на Go выбор типа индекса делают в миграциях под паттерны запросов.
CREATE INDEX idx_hash ON t USING HASH (key_col);GIN (Generalized Inverted Index) - для полнотекста, массивов, JSONB (операторы @>, ?, содержания). Построен по элементам; эффективен когда один документ дает много ключей. GiST - для геоданных, диапазонов, полнотекста; может давать false positives, нужна проверка по таблице. В Go запросы к JSONB и полнотексту автоматически используют подходящий индекс при правильном определении.
CREATE INDEX ON events USING GIN (data jsonb_path_ops);Много индексов на часто обновляемой таблице - каждый INSERT/UPDATE/DELETE обновляет все затронутые индексы. Лишние индексы занимают место и замедляют запись. Индекс не используется, если условие не совпадает (другое выражение, тип), или оптимизатор выбирает full scan (малая таблица, большая доля строк). Удаляют неиспользуемые индексы по pg_stat_user_indexes.
Seq Scan - последовательное чтение всей таблицы. Index Scan - обход индекса, по ссылкам чтение строк таблицы. Index-Only Scan - данные берутся только из индекса (covering). Bitmap Index Scan - по индексу строят битовую карту строк, затем читают таблицу по порядку. В EXPLAIN видно тип; в Go оптимизируют запросы и индексы под план.
EXPLAIN (ANALYZE) показывает план: тип узла (Seq Scan, Index Scan, Index Only Scan), оценка и фактические rows, cost, время. Смотреть: нет ли Seq Scan по большой таблице без необходимости; используется ли ожидаемый индекс; высокие rows или cost. В Go выполняют EXPLAIN вручную или логируют планы при отладке; индексы добавляют миграциями по результатам анализа.
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email = $1;Создавать индексы под реальные запросы после анализа (EXPLAIN, pg_stat_statements). На проде - CREATE INDEX CONCURRENTLY, чтобы не блокировать запись. Не создавать индексы "на всякий случай"; мониторить использование (pg_stat_user_indexes.idx_scan). В Go миграции с CONCURRENTLY выполняют отдельно от транзакции; при сбое проверять pg_index на invalid и пересоздавать.
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_name ON table (col);