← Назад к вопросам
Как понять что запросы попадает в индекс?
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"
)
Итог
Ищите эти признаки использования индекса:
- PostgreSQL: Index Scan вместо Seq Scan
- MySQL: type=ref или type=const (избегайте type=ALL)
- SQLite: SEARCH TABLE ... USING INDEX вместо SCAN TABLE
Отличные метрики:
- cost (стоимость) — низкая = быстро
- rows — ожидаемое количество строк совпадает с реальностью
- actual time (реальное время) — как можно ниже
**Используй EXPLAIN перед production для всех критических запросов!