Index (индекс) — это специальная структура данных, которая ускоряет поиск записей в таблице, работая подобно алфавитному указателю в книге: вместо перебора всех страниц вы сразу находите нужный раздел.
Ключевые аспекты
Ускорение SELECT — поиск по индексированной колонке в разы быстрее
Дополнительное хранилище — индекс занимает место на диске
Замедление записи — при INSERT/UPDATE/DELETE индексы нужно обновлять
B-tree структура — большинство индексов используют сбалансированное дерево
Плюсы индексов
Драматическое ускорение запросов SELECT с WHERE
Ускорение ORDER BY и GROUP BY
Ускорение JOIN операций
Обеспечение уникальности (UNIQUE INDEX)
Минусы индексов
Занимают дисковое пространство
Замедляют операции вставки и обновления
Требуют обслуживания (фрагментация)
Избыточные индексы вредят производительности
Частые ошибки на собеседованиях
Считают, что индексы только ускоряют — они также замедляют запись
Создают индексы на все колонки — это плохая практика
Забывают про составные индексы — порядок колонок в них важен
Не понимают, что PRIMARY KEY автоматически создаёт индекс
Введение и проблематика
Представьте таблицу с миллионом записей. Без индекса каждый запрос SELECT * FROM users WHERE email = 'john@example.com' потребует проверки каждой строки — это называется Full Table Scan. С индексом база данных находит нужную запись практически мгновенно.
Индекс — это как алфавитный указатель в книге. Вместо чтения всей книги вы находите термин в указателе и переходите сразу на нужную страницу.
Почему это критически важно?
Операция
Без индекса
С индексом
Поиск 1 записи в 1 млн
~500 мс
~1 мс
Сложность
O(n)
O(log n)
Чтение с диска
Всё
Только нужное
Базовая теория
Как работает индекс
Большинство индексов используют структуру B-tree (сбалансированное дерево):
graphTD A[Корень: 50]--> B[Узел: 25] A --> C[Узел: 75] B --> D[Лист: 10, 15, 20] B --> E[Лист: 30, 35, 40] C --> F[Лист: 60, 65, 70] C --> G[Лист: 80, 85, 90]
Запрос: найти запись с id = 65
База данных начинает с корня (50). 65 > 50, идём вправо.
Переход к узлу 75
65 < 75, идём влево к листу (60, 65, 70).
Находим значение
Лист содержит 65 и указатель на строку таблицы. Готово за 3 шага вместо проверки всех записей!
Типы индексов
Тип
Описание
Когда использовать
B-tree
Стандартный, для сравнений и диапазонов
По умолчанию для большинства случаев
Hash
Только для точного совпадения (=)
Когда нужен только поиск по равенству
Full-text
Полнотекстовый поиск
Поиск по тексту
Spatial
Геопространственные данные
Координаты, геометрия
Практические примеры
Создание индексов
sql
-- Создаём индекс на колонку emailCREATEINDEXidx_users_emailON users(email);-- Теперь этот запрос работает быстроSELECT*FROM users WHERE email ='john@example.com';-- Удаление индексаDROPINDEX idx_users_email ON users;
Проверка использования индекса
sql
-- EXPLAIN показывает план выполнения запросаEXPLAIN SELECT*FROM users WHERE email ='john@example.com';-- Результат (с индексом):-- | type | key | rows |-- | ref | idx_users_email | 1 |-- Результат (без индекса):-- | type | key | rows |-- | ALL | NULL | 1000000 | -- Full Table Scan!
Пограничные кейсы
⚠️
Индекс не всегда используется! Оптимизатор может решить, что Full Scan быстрее.
Когда индекс не помогает
sql
-- 1. Функции на индексированной колонкеSELECT*FROM users WHERELOWER(email) ='john@example.com';-- Индекс на email НЕ используется!-- Решение: функциональный индекс (MySQL 8.0+, PostgreSQL)CREATEINDEXidx_users_email_lowerON users((LOWER(email)));-- 2. Оператор LIKE с % в началеSELECT*FROM users WHERE email LIKE'%@gmail.com';-- Индекс НЕ используется!-- Работает с индексом:SELECT*FROM users WHERE email LIKE'john%';-- 3. Неравенство на первой колонке составного индекса-- Индекс: (status, created_at)SELECT*FROM orders WHEREstatus!='completed'AND created_at >'2024-01-01';-- Индекс используется неэффективно
Когда слишком много индексов — плохо
sql
-- Каждый INSERT должен обновить ВСЕ индексыCREATETABLEproducts ( id INTPRIMARY KEY,nameVARCHAR(255), category_id INT, price DECIMAL(10,2), created_at TIMESTAMP, updated_at TIMESTAMP,statusINT);-- Плохо: индекс на каждую колонкуCREATEINDEXidx_1ON products(name);CREATEINDEXidx_2ON products(category_id);CREATEINDEXidx_3ON products(price);CREATEINDEXidx_4ON products(created_at);CREATEINDEXidx_5ON products(updated_at);CREATEINDEXidx_6ON products(status);-- INSERT теперь обновляет 7 индексов (включая PK)!
🚫
Избыточные индексы замедляют INSERT, UPDATE, DELETE и занимают место на диске. Создавайте индексы только для часто используемых запросов.
Влияние на производительность
Чтение vs Запись
graphLR A[Без индексов]--> B[SELECT: медленно] A --> C[INSERT: быстро] D[С индексами]--> E[SELECT: быстро] D --> F[INSERT: медленнее]
Операция
Без индекса
С 1 индексом
С 5 индексами
SELECT
500 мс
1 мс
1 мс
INSERT
1 мс
2 мс
6 мс
UPDATE
1 мс
3 мс
8 мс
Рекомендации по созданию индексов
Создавайте индекс
Не создавайте индекс
Колонки в WHERE
Редко используемые колонки
Колонки в JOIN
Маленькие таблицы (< 1000 строк)
Колонки в ORDER BY
Колонки с низкой кардинальностью
Foreign keys
Часто обновляемые колонки
Плюсы и минусы
Аспект
Плюсы
Минусы
Чтение
Драматическое ускорение
—
Запись
—
Замедление
Память
—
Дополнительное место
Уникальность
Гарантия через UNIQUE
—
Обслуживание
—
Нужна дефрагментация
Вопросы интервьюера
Q: Всегда ли индекс ускоряет запросы?
Нет. Для маленьких таблиц Full Scan может быть быстрее. Оптимизатор сам решает, использовать ли индекс.
Q: Создаётся ли индекс автоматически для PRIMARY KEY?
Да, PRIMARY KEY автоматически создаёт уникальный индекс. То же для UNIQUE constraint.
Q: Почему порядок колонок в составном индексе важен?
Индекс (A, B) эффективен для запросов с WHERE A=... или WHERE A=... AND B=..., но не для WHERE B=... (B не первая колонка).
Q: Когда использовать UNIQUE INDEX вместо обычного?
Когда нужно гарантировать уникальность значений: email, username, номер заказа.