Индекс в MySQL — структура данных, которая хранит отсортированные копии значений столбцов и указатели на физическое расположение строк. Работает как предметный указатель в книге: вместо того чтобы листать все страницы — сразу открываете нужную. Без индекса СУБД перебирает каждую строку таблицы, что при миллионах записей критически сказывается на скорости. Индексы в MySQL ускоряют SELECT-запросы, но за это платят замедлением операций записи и дополнительным местом на диске. В статье разберём виды индексов, механизм B-дерева, синтаксис CREATE/DROP INDEX, покрывающий индекс и диагностику через EXPLAIN.
Индексирование баз данных решает одну задачу: как находить нужные строки быстро, не перебирая всё подряд. Индекс в базе данных — специальная структура данных, которую СУБД создаёт и поддерживает рядом с таблицей. Она хранит отсортированные копии значений одного или нескольких столбцов и указатели на физическое расположение соответствующих строк.
Учитесь бесплатно за счёт государства
Экономия до 100 000 ₽ на любой программе
Когда запрос выполняется без индекса, система проводит полное сканирование таблицы (Full Table Scan): проверяет каждую строку от первой до последней и отбирает подходящие. На таблице с тысячей строк — незаметно. На таблице с десятью миллионами — уже ощутимо, а на сотнях миллионов — блокировка или timeout.
Индекс в SQL устраняет эту проблему: оптимизатор обращается к отсортированной структуре, находит нужные строки за O(log n) и уже не перебирает таблицу строка за строкой.
Важно понять, чего индекс не делает: он не меняет данные в таблице, не копирует строки целиком. Это отдельная структура, которая ссылается на оригинальные строки. Поэтому индекс занимает дополнительное дисковое пространство и требует обновления при каждой операции записи.
Хорошая аналогия — алфавитный указатель в адресной книге. Вместо того чтобы листать все страницы в поисках «Смирнова», вы открываете раздел «С» и сразу попадаете на нужную строку. Индекс в таблице базы данных работает так же: хранит упорядоченные ключи и ссылки на строки.
Большинство индексов в MySQL построены на основе B-дерева (сбалансированного дерева поиска; англ. B-Tree). Это значение по умолчанию — если при создании не указан другой тип, СУБД создаёт именно его. Разберём внутреннее устройство и принцип работы оптимизатора.
B-дерево состоит из трёх уровней узлов:
MySQL использует B+-дерево: листовые узлы связаны в двусвязный список, что позволяет эффективно выполнять диапазонные запросы (BETWEEN, >, <) и сортировку ORDER BY — движение по дереву всегда идёт слева направо.
Поиск занимает O(log n) операций — логарифмическое время от числа записей. Если в таблице миллион строк, для поиска нужно порядка 20 сравнений, а не миллион. Именно это даёт индексу колоссальный выигрыш на больших таблицах.

Перед выполнением каждого запроса оптимизатор запросов строит план: запрашивает у хранилища статистику — число строк, кардинальность столбцов — перебирает доступные индексы и оценивает cost (условную стоимость каждого варианта).
Важно понимать: план запроса — предположение, а не гарантия. Cost-метрика условна и не учитывает ни кэш буферного пула, ни особенности конкретного железа. Реальное время выполнения может отличаться от прогноза.
При низкой кардинальности — например, столбец «статус» с тремя значениями — оптимизатор нередко предпочитает полное сканирование таблицы. Причина: если индекс всё равно приводит к обходу половины строк, Full Table Scan окажется быстрее. Кардинальность и селективность — ключевые параметры при решении, нужен ли индекс конкретному столбцу.
Индексирование в MySQL реализовано на уровне движков хранилищ, а не стандарта SQL. Доступные типы зависят от конкретного движка — InnoDB, MyISAM и другие поддерживают разные наборы. PostgreSQL предлагает ещё более широкий набор специализированных структур.
Если хотите освоить SQL и работу с базами данных на практике — обучение в рамках нацпроекта «Кадры» даёт такую возможность бесплатно. Смотрите каталог программ обучения.
В MySQL доступны четыре основных типа:
| Тип |
Назначение |
Когда использовать |
|---|---|---|
| B-Tree | Сравнения (<, >, BETWEEN), диапазоны, сортировка | Большинство случаев; тип по умолчанию в InnoDB и MyISAM |
| Hash | Только точное равенство (=) | Таблицы MEMORY; в InnoDB создаётся адаптивно, явно не задать |
| Full-text | Поиск слов в текстовых столбцах | Запросы MATCH … AGAINST по VARCHAR/TEXT |
| Spatial | Геоданные: точки, полигоны, линии | ГИС-задачи в MyISAM и InnoDB |
Таблица 1 (часть 1). Типы индексов MySQL. Источник: документация MySQL 8.0.
Разница между InnoDB и MyISAM по структуре вторичного индекса существенна. В InnoDB вторичный индекс хранит значение индексируемого столбца плюс указатель на первичный ключ. Чтобы получить строку, СУБД делает два прохода: сначала находит значение во вторичном индексе, затем идёт по первичному индексу к данным. В MyISAM вторичный индекс хранит прямой указатель на физическое расположение строки — один проход. Это влияет на производительность и на стратегию покрывающих индексов, которую разберём ниже.
PostgreSQL предлагает пять типов, включая специализированные:
| Тип |
Назначение |
Когда использовать |
|---|---|---|
| B-Tree | Стандартные сравнения и сортировка | Дефолт, подходит для большинства запросов |
| GIN | Составные значения: JSONB, массивы, полнотекстовый поиск | Поиск по элементам массива, JSONB-документы |
| GiST | Геоданные, полнотекстовый поиск, диапазонные типы | PostGIS, сложные пользовательские типы |
| BRIN | Большие таблицы с физически упорядоченными данными | Таблицы событий и логов с монотонным ID |
| Hash | Только точное равенство | Специфические сценарии без диапазонов |
Таблица 1 (часть 2). Типы индексов PostgreSQL. Источник: документация PostgreSQL 16.
Отдельное преимущество PostgreSQL — команда EXPLAIN ANALYZE, которая запускает запрос и показывает реальную статистику выполнения, а не только план. Для просмотра всех индексов используется системное представление pg_indexes.
Работа с индексами сводится к трём операциям: создать, просмотреть существующие и удалить ненужные. Синтаксис незначительно различается между MySQL и PostgreSQL — рассмотрим оба варианта с примерами.
Базовый синтаксис для MySQL:
— Обычный индекс
CREATE INDEX idx_email ON users (email);
— Уникальный индекс
CREATE UNIQUE INDEX idx_email_unique ON users (email);
Синтаксис PostgreSQL идентичен. При создании таблицы PRIMARY KEY генерирует индекс типа PRIMARY автоматически:
CREATE TABLE orders (
id INT PRIMARY KEY, — автоматически создаёт PRIMARY-индекс
user_id INT,
created_at DATETIME
);
Уникальный индекс выполняет двойную функцию: ускоряет поиск и запрещает дублирование значений. При нарушении уникальности СУБД вернёт ошибку Duplicate entry. Создавать отдельный уникальный индекс на первичный ключ — избыточно: PRIMARY KEY уже уникален по определению.
В InnoDB первичный ключ физически кластеризует строки — порядок данных в таблице совпадает с порядком в кластеризованном индексе. Поэтому выбор первичного ключа в InnoDB влияет не только на поиск, но и на физическую организацию хранилища данных.
Составной индекс охватывает два и более столбца:
CREATE INDEX idx_user_date ON orders (user_id, created_at, status);
MySQL строит B-дерево над конкатенацией значений столбцов слева направо. Здесь работает правило левого префикса: оптимизатор движется строго слева и обрывается на первом условии с неравенством.
Как это выглядит для индекса (A, B, C):
| Условие WHERE |
Индекс используется |
Задействованные столбцы |
|---|---|---|
| A = 1 | Да | A |
| A = 1 AND B = 2 | Да | A, B |
| A = 1 AND B = 2 AND C = 3 | Да | A, B, C |
| A = 1 AND B > 5 | Да | A, B (диапазон на B останавливает) |
| A = 1 AND B > 5 AND C = 3 | Частично | A, B (C через индекс не фильтруется) |
| B = 2 | Нет | — |
| B = 2 AND C = 3 | Нет | — |
| A IN (1, 2) AND B = 3 | Да | A, B (IN = множественное равенство) |
IN(список) MySQL расценивает как множественное равенство — индекс продолжает работать за таким условием. BETWEEN — диапазон, и следующий столбец через индекс уже не фильтруется.
Правило порядка столбцов: сначала условия равенства, затем диапазонные. Важен и вопрос дублированных индексов: если уже есть index(A, B), отдельный index(A) избыточен и только нагружает операции записи.
Просмотр всех индексов таблицы в MySQL:
SHOW INDEX FROM orders;
Вывод включает имя индекса, тип, столбцы и кардинальность. В PostgreSQL аналог — системное представление:
SELECT * FROM pg_indexes WHERE tablename = ‘orders’;
Удаление:
— MySQL
DROP INDEX idx_email ON users;
— PostgreSQL (без ON table)
DROP INDEX idx_email;
— Первичный ключ в MySQL
ALTER TABLE users DROP PRIMARY KEY;
Удалять стоит дублирующие, временно созданные и неиспользуемые индексы — они нагружают каждую операцию записи без пользы для чтения.
Покрывающий индекс — сценарий, при котором все столбцы из SELECT и WHERE уже содержатся в самом индексе. СУБД получает полный результат без обращения к таблице с данными.
Хотите сменить профессию или повысить квалификацию?
Федеральный проект «Активные меры содействия занятости» даёт возможность пройти обучение бесплатно за счёт государства
Почему это быстрее? Индекс компактнее таблицы, чаще помещается в буферный кэш и содержит только нужные значения. Два прохода — вторичный индекс → первичный ключ → таблица — заменяются одним.
Пример:
— Таблица: users(id, email, name, bio, created_at, …)
— Запрос: выбрать email и name по user_id
SELECT email, name FROM users WHERE user_id = 42;
— Если есть индекс (user_id, email, name) — запрос покрывающий:
— СУБД получает ответ целиком из индекса, не обращаясь к строкам таблицы
Диагностика: в выводе EXPLAIN поле Extra содержит Using index без пометки о доступе к таблице — значит, весь запрос прошёл через индекс.
InnoDB-нюанс: вторичные индексы неявно включают значение первичного ключа. Если PK входит в нужный набор столбцов, покрывающий индекс в InnoDB получается компактнее — это стоит учитывать при проектировании.

EXPLAIN показывает план выполнения запроса без его запуска — предположение оптимизатора на основе статистики. В MySQL 8.0+ и PostgreSQL доступна команда EXPLAIN ANALYZE, которая запускает запрос и возвращает реальную статистику: сколько строк прочитано, сколько времени заняли отдельные шаги. В MariaDB аналог появился начиная с версии 10.1.
Добавить EXPLAIN перед любым SELECT — первый шаг при анализе медленных запросов и проверке работы индексов.
Самое важное поле — type. Оно показывает метод доступа к данным от лучшего к худшему:
| Значение type |
Что означает |
|---|---|
| const | Одна строка по уникальному или первичному ключу — оптимально |
| eq_ref | JOIN по уникальному индексу: ровно одна строка с правой таблицы |
| ref | Поиск по неуникальному индексу: несколько строк |
| range | Диапазонный поиск по индексу (BETWEEN, >, <) |
| index | Сканирование всего индекса (покрывающий, но не точечный) |
| ALL | Полное сканирование таблицы — сигнал проблемы |
Остальные значимые поля:
Есть несколько типичных ситуаций, когда индекс остаётся в стороне.
Выражение в условии. WHERE id + 1 = 3 — оптимизатор вычисляет выражение для каждой строки, индекс не применяется. Правильно: WHERE id = 2.
Пропуск первого столбца составного индекса. WHERE B = 3 при индексе (A, B) — не работает. Первый столбец обязателен по правилу левого префикса.
Несоответствие типов или кодировок. Столбец INT, условие WHERE id = ’42’ — неявное преобразование типа отключает индекс.
Поиск по суффиксу. LIKE ‘%слово’ — полное сканирование таблицы. LIKE ‘слово%’ — индекс работает.
Низкая кардинальность. Столбец «пол» с двумя значениями — оптимизатор сочтёт полный скан выгоднее.
Антипаттерн ORDER BY rand(). MySQL копирует все данные и выполняет дополнительную сортировку — сканирование индекса не спасает, медленные запросы гарантированы при любом объёме таблицы.

При каждом INSERT, UPDATE или DELETE СУБД обновляет все индексы таблицы. Пять индексов — минимум шесть операций записи вместо одной. Замедление операций записи растёт пропорционально числу индексов, и на write-heavy таблицах это становится узким местом.
Дисковый overhead: индекс — отдельная структура данных, она занимает место. Дублированные индексы MySQL не выявляет автоматически — нужно проверять через SHOW INDEX и искать пересекающиеся наборы столбцов.

Частые операции INSERT, UPDATE и DELETE приводят к фрагментации B-дерева: в структуре появляются незаполненные блоки, дерево перестаёт быть оптимально сбалансированным, скорость поиска постепенно снижается.
Решение — перестроение индекса:
— InnoDB: перестройка через OPTIMIZE TABLE
OPTIMIZE TABLE orders;
— Альтернатива (эквивалент REBUILD)
ALTER TABLE orders ENGINE=InnoDB;
После значительных изменений данных — массовых загрузок или удалений — стоит обновить статистику для оптимизатора:
ANALYZE TABLE orders;
Устаревшая статистика приводит к неоптимальным планам запросов: оптимизатор может выбрать индекс там, где выгоднее полное сканирование, и наоборот. Регулярный просмотр лога медленных запросов (slow query log) помогает заметить деградацию производительности до того, как она станет критической.
Хороший индекс — баланс между скоростью чтения и стоимостью записи. Несколько проверенных ориентиров для индексирования баз данных:
Оптимизация индексов в MySQL — не разовая задача, а непрерывный процесс. Паттерны нагрузки меняются, данные растут, планы запросов устаревают. Регулярный просмотр медленных запросов и анализ через EXPLAIN — часть нормального цикла обслуживания базы данных.

Индекс в MySQL — структура данных (обычно B-дерево), которая хранит отсортированные копии значений столбцов и указатели на строки таблицы. Без индекса СУБД перебирает каждую строку (полное сканирование таблицы). С индексом оптимизатор находит нужные данные за O(log n) — это критично на таблицах с миллионами записей.
Базовая команда: CREATE INDEX имя ON таблица (столбец). Для уникального: CREATE UNIQUE INDEX. Для составного: CREATE INDEX idx ON orders (user_id, created_at). При создании таблицы PRIMARY KEY автоматически создаёт индекс типа PRIMARY — дублировать его вручную не нужно.
MySQL использует составной индекс строго слева направо. Индекс на (A, B, C) работает для WHERE A=… и WHERE A=… AND B=…, но не для WHERE B=… без A. Индекс прекращает работу на первом условии с неравенством — следующие столбцы через индекс не фильтруются.
Добавьте EXPLAIN перед SELECT. Поле key — выбранный индекс (NULL = не используется). Поле type: ALL — полный скан (плохо), const/eq_ref — оптимально. Extra: Using index означает покрывающий индекс — лучший сценарий: данные получены без обращения к таблице.
B-дерево поддерживает сравнения (<, >, BETWEEN), диапазоны и ORDER BY — универсальный тип по умолчанию. Hash-индекс работает только для точного равенства и не поддерживает диапазоны и сортировку. В InnoDB Hash-индекс адаптивный — создаётся автоматически, задать явно нельзя.
При изменении строки СУБД обновляет все индексы таблицы. Пять индексов — минимум шесть операций записи вместо одной. На таблицах с интенсивной записью избыточное индексирование создаёт серьёзные накладные расходы и заметно снижает общую производительность.
Индекс не используется в случаях: условие — выражение (WHERE id+1=3); пропущен первый столбец составного индекса; несоответствие типов или кодировок; поиск по суффиксу (LIKE ‘%слово’); низкая кардинальность столбца — оптимизатор предпочтёт полное сканирование. Проверяйте через EXPLAIN.
Using filesort в поле Extra — MySQL не смог упорядочить результат через индекс и выполняет дополнительный проход сортировки в памяти или на диске. Сигнал проблемы при ORDER BY на больших таблицах. Решение: добавить индекс на столбцы ORDER BY, совместимый с условиями WHERE.
MySQL: DROP INDEX имя ON таблица;. PostgreSQL: DROP INDEX имя; (без указания таблицы). Первичный ключ в MySQL удаляется через ALTER TABLE … DROP PRIMARY KEY. Удаляйте дублирующие и неиспользуемые индексы — они нагружают операции записи без пользы для чтения.
Частые INSERT/UPDATE/DELETE создают разрывы в структуре B-дерева — индекс фрагментируется и теряет эффективность. Решение: OPTIMIZE TABLE (InnoDB) или ALTER TABLE … ENGINE=InnoDB для перестроения индекса. После значимых изменений данных запускайте ANALYZE TABLE — чтобы обновить статистику для оптимизатора запросов.
Подайте заявку —
забронируйте место в группе
45 000 мест на 2026 год. Бесплатное обучение по федеральному проекту «Активные меры содействия занятости»