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

Как понять что запросы попадает в индекс?

2.2 Middle🔥 91 комментариев
#Базы данных (SQL)

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

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

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

Как понять что запросы попадает в индекс?

Это критический навык для оптимизации производительности SQL запросов. Есть несколько инструментов для анализа планов выполнения.

1. EXPLAIN PLAN — основной инструмент

EXPLAIN (или EXPLAIN ANALYZE в PostgreSQL) показывает план выполнения запроса.

-- PostgreSQL
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';

-- MySQL
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';

-- SQLite
EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = 'alice@example.com';

Результат с использованием индекса:

Index Scan using idx_users_email on users
  Index Cond: (email = 'alice@example.com')

Результат БЕЗ индекса (Seq Scan = полный скан таблицы):

Seq Scan on users (cost=0.00..35.50 rows=1 width=32)
  Filter: (email = 'alice@example.com')

2. Ищем Index Scan вместо Seq Scan

Ключевое отличие:

-- Запрос использует индекс (быстро)
EXPLAIN ANALYZE SELECT * FROM users WHERE id = 1;

-- Результат (ищите Index Scan):
Index Scan using users_pkey on users (cost=0.29..8.31 rows=1 width=32)
  Index Cond: (id = 1)

-- Запрос БЕЗ индекса (медленно)
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';

-- Результат (ищите Seq Scan):
Seq Scan on users (cost=0.00..35.50 rows=1 width=32)
  Filter: (email = 'alice@example.com')

3. Практические примеры на PostgreSQL

Создаём таблицу и индекс:

-- Таблица без индекса на email
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255),
    name VARCHAR(255),
    created_at TIMESTAMP
);

-- Добавляем данные (100,000 записей)
INSERT INTO users (email, name) VALUES ('user1@example.com', 'User 1');
-- ... ещё 99,999 записей

-- Проверяем план без индекса (медленно)
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';

-- Результат: Seq Scan on users — полный скан таблицы!
-- Execution time: 25.432 ms

-- Добавляем индекс
CREATE INDEX idx_users_email ON users(email);

-- Проверяем план с индексом (быстро)
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';

-- Результат: Index Scan using idx_users_email on users
-- Execution time: 0.145 ms (в 100+ раз быстрее!)

4. PostgreSQL: подробный анализ EXPLAIN ANALYZE

EXPLAIN (ANALYZE, BUFFERS, VERBOSE) 
SELECT u.id, u.email, o.order_id 
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.email = 'alice@example.com';

-- Результат:
Hash Join (cost=1.17..2.45 rows=1 width=40)
  Hash Cond: (o.user_id = u.id)
  Buffers: shared hit=5 read=0
  -> Seq Scan on orders o (cost=0.00..1.15 rows=10 width=8)
        Filter: (user_id IS NOT NULL)
        Buffers: shared hit=3
  -> Hash (cost=1.16..1.16 rows=1 width=32)
        Buffers: shared hit=2
        -> Index Scan using idx_users_email on users u
              Index Cond: (email = 'alice@example.com')
              Buffers: shared hit=2

Что здесь означает:

  • Index Scan using idx_users_email — используется индекс
  • Seq Scan on orders — полный скан таблицы (может быть нормально, если таблица маленькая)
  • cost=0.00..35.50 — стоимость выполнения (первое число — начальная стоимость, второе — конечная)
  • rows=1 — ожидаемое количество строк
  • actual time=0.145..0.156 ms — реальное время выполнения

5. MySQL: EXPLAIN и EXPLAIN FORMAT=JSON

-- MySQL базовый вывод
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';

-- Результат:
| id | select_type | table | type  | possible_keys | key            | key_len | ref   | rows | Extra       |
|----|-------------|-------|-------|---------------|----------------|---------|-------|------|-------------|
| 1  | SIMPLE      | users | ref   | idx_users_email | idx_users_email | 768    | const | 1    | Using index |

-- Более детальный анализ
EXPLAIN FORMAT=JSON 
SELECT * FROM users WHERE email = 'alice@example.com';

-- Результат (JSON):
{
  "query_block": {
    "select_id": 1,
    "table": {
      "table_name": "users",
      "access_type": "ref",
      "possible_keys": ["idx_users_email"],
      "key": "idx_users_email",
      "key_length": "768",
      "used_key_parts": ["email"],
      "rows": 1,
      "filtered": 100.0
    }
  }
}

Ключевые значения в MySQL:

  • ALL = Seq Scan (медленно)
  • index = Index Scan (может быть медленно)
  • range = диапазон в индексе (нормально)
  • ref = равенство в индексе (хорошо)
  • const = одна строка (отлично)
  • eq_ref = JOIN с индексом (отлично)

6. SQLite: EXPLAIN QUERY PLAN

-- SQLite
EXPLAIN QUERY PLAN 
SELECT * FROM users WHERE email = 'alice@example.com';

-- Результат БЕЗ индекса:
SCAN TABLE users

-- Результат С индексом:
SEARCH TABLE users USING INDEX idx_users_email (email=?)

-- Что означает:
-- SCAN = полный скан таблицы (медленно)
-- SEARCH ... USING INDEX = используется индекс (быстро)

7. Python: использование EXPLAIN в коде

from sqlalchemy import text, create_engine

# Подключаемся к БД
engine = create_engine("postgresql://user:password@localhost/mydb")

# PostgreSQL
with engine.connect() as conn:
    result = conn.execute(text("""
        EXPLAIN ANALYZE 
        SELECT * FROM users WHERE email = :email
    """), {"email": "alice@example.com"})
    for row in result:
        print(row)

# MySQL
with engine.connect() as conn:
    result = conn.execute(text("""
        EXPLAIN SELECT * FROM users WHERE email = :email
    """), {"email": "alice@example.com"})
    for row in result:
        print(row)

8. Как проверить, используется ли индекс на JOIN'е?

-- PostgreSQL
EXPLAIN ANALYZE
SELECT u.id, u.email, o.order_id
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.email = 'alice@example.com';

-- Хороший результат (используются индексы):
Hash Join (cost=1.17..2.45 rows=1)
  -> Index Scan using idx_users_email on users u
       Index Cond: (email = 'alice@example.com')
  -> Seq Scan on orders o

-- Плохой результат (полный скан):
Nested Loop (cost=0.42..1.67 rows=1)
  -> Seq Scan on users u
       Filter: (email = 'alice@example.com')
  -> Seq Scan on orders o
       Filter: (user_id = u.id)

9. Проблемные запросы и как их найти

Проблема 1: Функция в WHERE clause

-- ПЛОХО — индекс не используется
EXPLAIN ANALYZE
SELECT * FROM users WHERE LOWER(email) = 'alice@example.com';

-- Результат: Seq Scan (индекс не работает из-за функции)

-- ХОРОШО — используется индекс
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = 'alice@example.com';

-- Результат: Index Scan

Проблема 2: LIKE с % в начале

-- ПЛОХО — индекс не используется
EXPLAIN ANALYZE
SELECT * FROM users WHERE email LIKE '%alice%';

-- Результат: Seq Scan

-- ХОРОШО — может использовать индекс
EXPLAIN ANALYZE
SELECT * FROM users WHERE email LIKE 'alice%';

-- Результат: Index Scan (если есть индекс на prefix)

Проблема 3: OR вместо IN

-- Может быть медленнее
EXPLAIN ANALYZE
SELECT * FROM users WHERE id = 1 OR id = 2 OR id = 3;

-- Быстрее
EXPLAIN ANALYZE
SELECT * FROM users WHERE id IN (1, 2, 3);

10. Практический чеклист для проверки индексов

# Django пример проверки индексов
from django.db import connection
from django.test.utils import override_settings

@override_settings(DEBUG=True)
def check_query_uses_index(query, expected_index=None):
    with connection.cursor() as cursor:
        # PostgreSQL
        cursor.execute(f"EXPLAIN ANALYZE {query}")
        explain_output = cursor.fetchall()
        
        uses_index = any("Index" in str(row) for row in explain_output)
        uses_seq_scan = any("Seq Scan" in str(row) for row in explain_output)
        
        print(f"Query: {query}")
        print(f"Uses index: {uses_index}")
        print(f"Uses Seq Scan: {uses_seq_scan}")
        print(f"Execution plan:")
        for row in explain_output:
            print(f"  {row}")
        
        return uses_index and not uses_seq_scan

# Проверяем наш запрос
check_query_uses_index(
    "SELECT * FROM users WHERE email = test@example.com"
)

Итог

Ищите эти признаки использования индекса:

  1. PostgreSQL: Index Scan вместо Seq Scan
  2. MySQL: type=ref или type=const (избегайте type=ALL)
  3. SQLite: SEARCH TABLE ... USING INDEX вместо SCAN TABLE

Отличные метрики:

  • cost (стоимость) — низкая = быстро
  • rows — ожидаемое количество строк совпадает с реальностью
  • actual time (реальное время) — как можно ниже

**Используй EXPLAIN перед production для всех критических запросов!