← Назад к вопросам

Какие знаешь случаи, в которых нельзя использовать индексы?

2.0 Middle🔥 211 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Случаи, когда индексы неэффективны или неприменимы

Индексы — это мощный инструмент оптимизации, но они не панацея. Есть множество ситуаций, где индексы бесполезны или даже вредны.

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" (плохо)

Выводы

  1. Low cardinality столбцы (boolean, enum) — индексы неэффективны
  2. LIKE '%pattern'% — не использует B-tree индекс
  3. Маленькие таблицы — Full Scan быстрее
  4. Функции в WHERE — скрывают индекс
  5. Частые обновления — затраты на индекс превышают выгоду
  6. Большая выборка (> 20%) — Full Scan быстрее индекса
  7. !=, NOT IN — индексы редко помогают
  8. IS NULL — нужны partial индексы
  9. RANDOM, CASE — индексы не помогают
  10. Всегда проверяй с EXPLAIN перед добавлением индекса
Какие знаешь случаи, в которых нельзя использовать индексы? | PrepBro