Какие знаешь случаи, в которых нельзя использовать индексы?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Случаи, когда индексы неэффективны или неприменимы
Индексы — это мощный инструмент оптимизации, но они не панацея. Есть множество ситуаций, где индексы бесполезны или даже вредны.
1. Индекс на столбце с малым количеством уникальных значений (Low Cardinality)
-- ❌ Плохо: только 2 значения (true/false)
CREATE INDEX idx_is_active ON users(is_active);
-- Оптимизатор игнорирует индекс и сканирует всю таблицу
SELECT * FROM users WHERE is_active = true; -- Full Table Scan
Почему? Если столбец имеет только несколько уникальных значений, индекс даст немного преимущества, потому что множество строк всё равно нужно будет прочитать.
// Пример: статус заказа (только 5 значений: NEW, PROCESSING, SHIPPED, DELIVERED, CANCELLED)
// Индекс даст доступ к 20% всей таблицы, что почти равно полному сканированию
2. Индекс на текстовом поле с использованием LIKE '%pattern'
CREATE INDEX idx_name ON users(name);
-- ❌ Индекс не используется
SELECT * FROM users WHERE name LIKE '%john%';
-- ✅ Индекс используется (pattern с начала)
SELECT * FROM users WHERE name LIKE 'john%';
Почему? B-tree индекс отсортирован по начальным символам. Если ищем по '%pattern', оптимизатор не может использовать упорядоченность.
-- Для LIKE '%pattern' нужен FULL TEXT INDEX или trigram индекс
CREATE INDEX idx_name_fulltext ON users USING gin(to_tsvector('english', name));
3. Очень маленькие таблицы
-- ❌ Индекс неэффективен
CREATE TABLE countries(id INT, name VARCHAR(100));
CREATE INDEX idx_name ON countries(name);
-- Таблица с 195 строками (все страны мира)
-- Full Table Scan быстрее, чем обращение к индексу
Правило: Если в таблице < 1000 строк, индекс редко помогает.
4. Столбцы, часто используемые в предложениях WHERE с функциями
-- ❌ Индекс не используется
CREATE INDEX idx_created_at ON orders(created_at);
SELECT * FROM orders WHERE YEAR(created_at) = 2024; -- Функция скрывает индекс
-- ✅ Использует индекс
SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
Почему? Индекс содержит полное значение created_at, но функция YEAR() преобразует его. Оптимизатор не может использовать упорядоченность.
// Java пример неправильного использования
Query query = session.createQuery(
"FROM Order WHERE FUNCTION('YEAR', createdAt) = :year"
); // ❌ Индекс не используется
// Правильно
Query query = session.createQuery(
"FROM Order WHERE createdAt >= :start AND createdAt < :end"
); // ✅ Индекс используется
5. Столбцы с высокой частотой обновлений
-- ❌ Плохая идея
CREATE INDEX idx_balance ON accounts(balance);
-- В конкурентной системе этот индекс постоянно обновляется
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
Почему? Каждое обновление требует переиндексирования. Для быстро изменяющихся данных затраты на поддержание индекса превышают выгоду.
6. Выборка большого процента данных из таблицы
CREATE INDEX idx_status ON orders(status);
-- ❌ Индекс неэффективен (выбираем 50% таблицы)
SELECT * FROM orders WHERE status IN ('PROCESSING', 'SHIPPED'); -- 50% строк
-- ✅ Индекс эффективен (выбираем 1% таблицы)
SELECT * FROM orders WHERE status = 'PROCESSING' AND amount > 10000; -- 1% строк
Правило: Если запрос возвращает > 10-20% таблицы, Full Table Scan быстрее.
7. Индексы на столбцах в условии с операторами != или NOT IN
CREATE INDEX idx_status ON orders(status);
-- ❌ Индекс не используется
SELECT * FROM orders WHERE status != 'CANCELLED'; -- Нужно взять всё кроме CANCELLED
-- ❌ Тоже не используется
SELECT * FROM orders WHERE status NOT IN ('CANCELLED', 'FAILED');
Почему? Для таких операций нужно исключить значения из индекса, что требует знать все остальные значения.
8. Непортативные индексы (специфичные для конкретной СУБД)
-- ❌ Oracle specific
CREATE INDEX idx_expr ON employees(UPPER(last_name));
-- ✅ Портативный эквивалент в PostgreSQL
CREATE INDEX idx_expr ON employees(LOWER(last_name));
9. Индексы на вычисляемых выражениях без функциональных индексов
-- ❌ Индекс не поможет
CREATE INDEX idx_age ON users(age);
SELECT * FROM users WHERE (EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM birth_date)) > 18;
-- ✅ Функциональный индекс
CREATE INDEX idx_age_calc ON users(
EXTRACT(YEAR FROM CURRENT_DATE) - EXTRACT(YEAR FROM birth_date)
);
10. Частые запросы без фильтрации (SELECT count(*))
-- Индекс не поможет
SELECT COUNT(*) FROM users; -- Нужно просканировать всю таблицу
SELECT COUNT(*) FROM orders; -- Нужно просканировать всю таблицу
11. NULL значения и индексы
CREATE INDEX idx_phone ON users(phone);
-- ❌ В большинстве СУБД индекс не используется
SELECT * FROM users WHERE phone IS NULL;
-- ❌ Тоже не используется (зависит от СУБД)
SELECT * FROM users WHERE phone IS NOT NULL;
Примечание: В PostgreSQL можно использовать PARTIAL INDEX:
CREATE INDEX idx_phone_not_null ON users(phone) WHERE phone IS NOT NULL;
12. Случайные (RANDOM) запросы
-- ❌ Индексы бесполезны
SELECT * FROM users ORDER BY RANDOM() LIMIT 10;
-- Нужно просканировать всю таблицу и отсортировать случайно
Практический пример оптимизации
// ❌ Плохой запрос
@Query("SELECT o FROM Order o WHERE YEAR(o.createdAt) = ?1 AND o.status != 'CANCELLED'")
List<Order> getOrdersByYearNotCancelled(int year);
// ✅ Хороший запрос
@Query("SELECT o FROM Order o WHERE o.createdAt >= ?1 AND o.createdAt < ?2 AND o.status IN ('PROCESSING', 'SHIPPED', 'DELIVERED')")
List<Order> getOrdersByYearNotCancelled(LocalDateTime start, LocalDateTime end);
Инструменты анализа
-- PostgreSQL
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'PROCESSING';
-- MySQL
EXPLAIN SELECT * FROM orders WHERE status = 'PROCESSING';
-- Ищи в плане: "Index", "Seq Scan" (плохо)
Выводы
- Low cardinality столбцы (boolean, enum) — индексы неэффективны
- LIKE '%pattern'% — не использует B-tree индекс
- Маленькие таблицы — Full Scan быстрее
- Функции в WHERE — скрывают индекс
- Частые обновления — затраты на индекс превышают выгоду
- Большая выборка (> 20%) — Full Scan быстрее индекса
- !=, NOT IN — индексы редко помогают
- IS NULL — нужны partial индексы
- RANDOM, CASE — индексы не помогают
- Всегда проверяй с EXPLAIN перед добавлением индекса