Back

Понимание индексирования баз данных для повышения производительности

Понимание индексирования баз данных для повышения производительности

Ваши запросы к базе данных выполняются секундами, когда должны занимать миллисекунды. Виновник? Отсутствующие или плохо спроектированные индексы. Индексирование базы данных — это разница между сканированием миллионов строк и прямым переходом к нужным данным. Но неправильное использование индексов может замедлить операции записи и раздуть хранилище.

В этой статье рассматриваются основы индексирования баз данных, от B-tree и составных индексов до продвинутых техник, таких как покрывающие индексы и планы запросов. Вы узнаете, как индексировать современные типы данных, включая JSON и текстовые поля, и, что важнее всего, как избежать ловушки производительности избыточного индексирования в современных базах данных.

Ключевые выводы

  • Индексы баз данных обменивают более быстрое чтение на более медленную запись и увеличенное хранилище
  • B-tree индексы эффективно обрабатывают большинство запросов, в то время как специализированные индексы обслуживают JSON и текстовые данные
  • Порядок столбцов в составном индексе определяет поддержку запросов — столбцы с условиями равенства должны идти первыми
  • Покрывающие индексы обеспечивают сканирование только по индексу, включая все столбцы запроса
  • Избыточное индексирование снижает производительность записи — регулярно отслеживайте и удаляйте неиспользуемые индексы

Что на самом деле делают индексы баз данных

Представьте запрос к таблице users с 10 миллионами строк:

SELECT * FROM users WHERE email = 'user@example.com';

Без индекса PostgreSQL или MySQL должны проверить каждую строку — полное сканирование таблицы. С индексом на email база данных переходит напрямую к соответствующей строке, используя структуру B-tree, обычно сокращая время поиска с O(n) до O(log n).

Индекс — это по сути отсортированная копия определённых столбцов с указателями обратно на полные строки. Компромисс: более быстрое чтение за счёт более медленной записи (индекс должен обновляться) и дополнительного хранилища (обычно 10-20% от размера таблицы на индекс).

B-tree индексы: рабочая лошадка баз данных

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

CREATE INDEX idx_users_created_at ON users(created_at);

B-tree отлично справляются с:

  • Проверками на равенство (WHERE status = 'active')
  • Диапазонными запросами (WHERE created_at > '2024-01-01')
  • Сортировкой (ORDER BY created_at)
  • Поиском по префиксу (WHERE email LIKE 'john%')

PostgreSQL и MySQL InnoDB используют варианты B+tree, где все данные находятся в листовых узлах, что делает сканирование диапазонов более эффективным. MongoDB использует похожие структуры B-tree для своих индексов, хотя документные базы данных добавляют сложность с индексированием вложенных полей.

Составные индексы: порядок столбцов имеет значение

Составные индексы охватывают несколько столбцов, но порядок столбцов критически важен:

CREATE INDEX idx_events_user_date ON events(user_id, created_at);

Этот индекс поддерживает:

  • WHERE user_id = 123
  • WHERE user_id = 123 AND created_at > '2024-01-01'

Но НЕ:

  • WHERE created_at > '2024-01-01' (без user_id)

Индекс работает слева направо. Размещайте столбцы, используемые для равенства, первыми, затем диапазоны. Для API-эндпоинта, который фильтрует события по пользователю и диапазону дат, этот составной индекс устраняет необходимость в отдельных индексах для каждого столбца.

Покрывающие индексы и планы запросов

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

-- Пример для PostgreSQL
CREATE INDEX idx_orders_covering 
ON orders(user_id, status, created_at) 
INCLUDE (total_amount);

Для этого запроса:

SELECT total_amount FROM orders 
WHERE user_id = 123 AND status = 'completed';

База данных никогда не обращается к таблице — всё берётся из индекса. Используйте EXPLAIN для проверки:

EXPLAIN (ANALYZE, BUFFERS) SELECT ...;

Ищите “Index Only Scan” в PostgreSQL или “Using index” в MySQL. Планы запросов показывают, действительно ли используются ваши индексы. Последовательные сканирования с высокой стоимостью указывают на отсутствующие индексы, в то время как неиспользуемые индексы тратят ресурсы впустую.

Индексирование JSON и текстовых полей

Современные приложения хранят неструктурированные данные, требующие специализированных индексов:

Индексирование JSON (PostgreSQL)

CREATE INDEX idx_metadata_gin ON products 
USING GIN (metadata);

-- Поддерживает запросы типа:
SELECT * FROM products 
WHERE metadata @> '{"category": "electronics"}';

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

Полнотекстовый поиск

CREATE INDEX idx_posts_text ON posts 
USING GIN (to_tsvector('english', content));

-- Обеспечивает:
SELECT * FROM posts 
WHERE to_tsvector('english', content) @@ 'database & indexing';

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

Избегание избыточного индексирования в современных базах данных

Больше индексов не всегда лучше. Каждый индекс:

  • Замедляет операции INSERT/UPDATE/DELETE (необходимо обновлять индекс)
  • Увеличивает хранилище (10-20% на индекс)
  • Добавляет накладные расходы на обслуживание (фрагментация, перестроение)

Признаки избыточного индексирования:

  • Таблицы с высокой нагрузкой на запись с 5+ индексами
  • Дублирующиеся индексы (например, (a, b) и (a))
  • Неиспользуемые индексы (проверьте pg_stat_user_indexes или sys.dm_db_index_usage_stats)
  • Размер индекса превышает размер таблицы

Лучшие практики:

  1. Начните с индексов на первичных и внешних ключах
  2. Добавляйте индексы на основе журналов медленных запросов, а не предположений
  3. Консолидируйте с помощью составных индексов, где это возможно
  4. Удаляйте неиспользуемые индексы после мониторинга в течение 30+ дней
  5. Рассмотрите частичные индексы для больших таблиц с фильтрованными запросами:
CREATE INDEX idx_orders_recent 
ON orders(created_at) 
WHERE created_at > '2024-01-01';

Селективность индекса и реальная производительность

Селективность индекса — соотношение уникальных значений к общему количеству строк — определяет эффективность. Индекс на логическом столбце (2 значения) имеет низкую селективность и редко помогает. Индекс на email (все уникальные) имеет идеальную селективность.

Отслеживайте реальные производственные метрики:

  • Время выполнения запроса до/после индексирования
  • Коэффициенты попадания в индекс
  • Влияние на производительность записи
  • Рост хранилища

Инструменты, такие как pgAdmin, MySQL Workbench или MongoDB Compass, предоставляют визуальный анализ запросов. Для производственного мониторинга рассмотрите DataDog или New Relic.

Заключение

Эффективное индексирование базы данных требует понимания ваших паттернов запросов, а не следования жёстким правилам. Начните с B-tree индексов на столбцах в предложениях WHERE, ORDER BY и JOIN. Используйте составные индексы стратегически, учитывая порядок столбцов. Применяйте покрывающие индексы для запросов с высокой нагрузкой на чтение и специализированные индексы для JSON и текстовых данных.

Самое главное — измеряйте всё. Используйте EXPLAIN для понимания планов запросов, отслеживайте статистику использования индексов и следите за реальным влиянием как на производительность чтения, так и записи. Цель — не индексировать всё, а индексировать именно то, что нужно вашему приложению.

Часто задаваемые вопросы

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

Используйте EXPLAIN ANALYZE в PostgreSQL или EXPLAIN в MySQL для просмотра планов выполнения запросов. Проверяйте системные таблицы, такие как pg_stat_user_indexes или sys.dm_db_index_usage_stats, чтобы отслеживать использование индексов с течением времени. Неиспользуемые индексы после 30 дней являются кандидатами на удаление.

Каждый индекс замедляет операции записи, поскольку база данных должна обновлять все соответствующие индексы. Хранилище увеличивается на 10-20% на индекс. Накладные расходы на обслуживание растут с фрагментацией и требованиями к перестроению. Пять или более индексов на таблицах с высокой нагрузкой на запись часто указывают на избыточное индексирование.

Используйте составные индексы, когда запросы фильтруют по нескольким столбцам вместе. База данных может использовать один составной индекс более эффективно, чем комбинировать несколько одиночных индексов. Порядок имеет значение: размещайте условия равенства первыми, затем диапазоны. Составной индекс на user_id и created_at обслуживает оба столбца вместе.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay