← Назад к вопросам
В чём разница между index scan и index only scan?
2.7 Senior🔥 111 комментариев
#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Index Scan vs Index Only Scan в PostgreSQL
Это два разных способа использования индексов при поиске данных в PostgreSQL. Понимание разницы критично для оптимизации performance запросов.
Index Scan
Index Scan использует индекс для поиска строк, но затем обращается к основной таблице для получения полных данных.
-- Таблица users с индексом на name
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100),
age INT
);
CREATE INDEX idx_users_name ON users(name);
-- Query с Index Scan
EXPLAIN ANALYZE
SELECT id, email, age FROM users WHERE name = 'Alice';
-->
-- Index Scan using idx_users_name on users (cost=0.29..4.31)
-- Index Cond: (name = 'Alice')
-- -> Heap Fetch (обращение к основной таблице)
Процесс Index Scan:
- PostgreSQL ищет в индексе строки где name = 'Alice'
- Находит CTID (физический адрес строки)
- По CTID идёт в основную таблицу (Heap)
- Получает полную строку со всеми столбцами
Когда используется:
- Нужны столбцы которые НЕ в индексе
- Нужны часто обновляемые данные
Index Only Scan
Index Only Scan находит данные полностью в индексе, не обращаясь к основной таблице.
-- Query с Index Only Scan
EXPLAIN ANALYZE
SELECT id, name FROM users WHERE name = 'Alice';
-->
-- Index Only Scan using idx_users_name on users (cost=0.29..2.15)
-- Index Cond: (name = 'Alice')
-- -> NO Heap Fetch!
Процесс Index Only Scan:
- PostgreSQL ищет в индексе строки где name = 'Alice'
- Находит id и name в индексе
- Проверяет visibility (хотя ли эта версия видима в этой транзакции)
- Возвращает результат - БЕЗ обращения в основную таблицу!
Когда используется:
- Все нужные столбцы есть в индексе
- Индекс достаточно свежий (Visibility OK)
Практический пример
-- Создаём таблицу и индекс
CREATE TABLE products (
id SERIAL PRIMARY KEY,
sku VARCHAR(50),
name VARCHAR(200),
price DECIMAL(10,2),
description TEXT
);
CREATE INDEX idx_products_sku ON products(sku);
INSERT INTO products VALUES (1, 'SKU-001', 'Laptop', 999.99, '...');
-- QUERY 1: Index Only Scan (оптимально!)
EXPLAIN ANALYZE
SELECT id, sku FROM products WHERE sku = 'SKU-001';
-->
-- Index Only Scan using idx_products_sku
-- Cost: 0.29..1.80
-- QUERY 2: Index Scan (нужны дополнительные данные)
EXPLAIN ANALYZE
SELECT id, sku, name, price FROM products WHERE sku = 'SKU-001';
-->
-- Index Scan using idx_products_sku
-- Heap Fetches: 1
-- Cost: 0.29..8.31 <- Дороже!
Visibility Map (VM)
ПостгресQL использует Visibility Map для оптимизации Index Only Scan:
Визуальность строк:
Independent Scan Index Only Scan
┌─────────────────────┐ ┌─────────────────────┐
│ Index │ │ Index │
│ id -> name, CTID │ │ id -> name, CTID │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
v v
┌──────────┐ ┌─────────────┐
│ Heap Tab │ │ Visibility │
│ Fetch all│ │ Map Check │
│ columns │ │ (no heap) │
└──────────┘ └─────────────┘
INCLUDE индексы (PostgreSQL 11+)
Індексы с INCLUDE позволяют добавить столбцы в индекс для Index Only Scan:
-- Обычный индекс
CREATE INDEX idx_users_email ON users(email);
-- С INCLUDE - добавляем столбцы без участия в сортировке
CREATE INDEX idx_users_email_inc ON users(email) INCLUDE (name, age);
-- Теперь это будет Index Only Scan!
SELECT email, name, age FROM users WHERE email = 'test@example.com';
-->
-- Index Only Scan using idx_users_email_inc
Сравнение производительности
-- Таблица с 1,000,000 строк
CREATE TABLE large_table AS
SELECT
generate_series(1, 1000000) as id,
'name_' || (random() * 100000)::int as name,
'email_' || generate_series(1, 1000000) || '@example.com' as email,
random() * 100 as score,
md5(random()::text) as big_data -- Large column
FROM generate_series(1, 1000000);
CREATE INDEX idx_large_name ON large_table(name);
-- Index Only Scan: 2-3ms
EXPLAIN ANALYZE
SELECT id, name FROM large_table WHERE name = 'name_50000';
-- Index Scan: 5-8ms (обращение к основной таблице)
EXPLAIN ANALYZE
SELECT id, name, email, score FROM large_table WHERE name = 'name_50000';
Проблемы с Index Only Scan
-- VACUUM нужен для обновления Visibility Map
VACUUM large_table;
-- БЕЗ VACUUM индекс может не использоваться как Index Only
EXPLAIN ANALYZE
SELECT id, name FROM large_table WHERE name = 'test'; -- Может быть Index Scan!
-- После VACUUM
VACUUM large_table;
EXPLAIN ANALYZE
SELECT id, name FROM large_table WHERE name = 'test'; -- Index Only Scan
Когда Index Only Scan невозможен
-- 1. Нужны столбцы вне индекса
CREATE INDEX idx_users_name ON users(name);
SELECT email FROM users WHERE name = 'Alice'; -- Index Scan
-- 2. Функции на индексированном столбце
SELECT id, UPPER(name) FROM users WHERE UPPER(name) = 'ALICE'; -- Index Scan
-- 3. Недавно обновлённые данные (старый VM)
UPDATE users SET age = 25 WHERE id = 1;
SELECT id, name FROM users WHERE name = 'Alice'; -- Может быть Index Scan!
VACUUM; -- Обновляет Visibility Map
SELECT id, name FROM users WHERE name = 'Alice'; -- Index Only Scan
Оптимизация: Стратегия индексов
-- ❌ Плохо: индекс не помогает Index Only Scan
CREATE INDEX idx_users_email ON users(email);
SELECT email, phone, address FROM users WHERE email = 'test@example.com';
-- ✅ Хорошо: INCLUDE индекс для Index Only Scan
CREATE INDEX idx_users_email_opt ON users(email) INCLUDE (phone, address);
SELECT email, phone, address FROM users WHERE email = 'test@example.com';
-- ✅ Или составной индекс
CREATE INDEX idx_users_multi ON users(email, phone, address);
Таблица сравнения
| Параметр | Index Scan | Index Only Scan |
|---|---|---|
| Обращение к Heap | Да | Нет |
| Скорость | Медленнее | Быстрее |
| Когда | Нужны доп столбцы | Все в индексе |
| IO операций | Больше | Меньше |
| Зависит от VM | Нет | Да |
| Требует VACUUM | Нет | Да |
Резюме
- Index Scan — ищет в индексе, затем идёт в таблицу за полными данными
- Index Only Scan — всё находится в индексе, таблица не нужна
- Index Only Scan быстрее — меньше IO операций
- Используй INCLUDE индексы для Index Only Scan (PostgreSQL 11+)
- VACUUM регулярно для актуализации Visibility Map
- Профилируй EXPLAIN ANALYZE для оптимизации запросов