4 Оптимизация запросов
Лабораторная работа №1: Оптимизация запросов с использованием EXPLAIN и ANALYZЕ
Теоретические сведения
1. Оптимизация запросов в PostgreSQL
Оптимизация запросов направлена на уменьшение времени выполнения и ресурсозатрат. PostgreSQL использует планировщик запросов, который генерирует оптимальный план выполнения на основе статистики таблиц и индексов. Ключевые инструменты для анализа:
- EXPLAIN — показывает предполагаемый план выполнения без запуска запроса.
- EXPLAIN ANALYZE — выполняет запрос и выводит реальный план с метриками (время, строки).
2. Основные метрики в выводе EXPLAIN
- Cost (стоимость):
startup cost— стоимость начала выполнения узла плана.total cost— общая стоимость выполнения узла.- Rows (rows) — ожидаемое количество строк.
- Width (width) — средний размер строки в байтах.
- Actual Time — реальное время выполнения (в миллисекундах).
3. Индексы и их влияние
- Индексы ускоряют поиск данных, но замедляют вставку/обновление. Основные типы:
- B-tree — для диапазонных запросов и сортировки.
- Hash — для точного поиска по равенству.
- GIN/GiST — для сложных типов данных (JSONB, массивы, геоданные).
Примеры выполнения
Структура базы данных
Создайте базу данных для интернет-магазина:
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price DECIMAL(10, 2),
category_id INT REFERENCES categories(id)
);
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE,
registration_date DATE
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id),
order_date DATE,
total_amount DECIMAL(10, 2)
);
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INT REFERENCES orders(id),
product_id INT REFERENCES products(id),
quantity INT
);
Заполнение тестовыми данными (можно использовать generate_series):
-- Добавление категорий
INSERT INTO categories (name) VALUES ('Электроника'), ('Книги'), ('Одежда');
-- Добавление 1000 товаров
INSERT INTO products (name, price, category_id)
SELECT
'Товар ' || i,
(RANDOM() * 1000)::DECIMAL(10, 2),
(RANDOM() * 2 + 1)::INT
FROM generate_series(1, 1000) AS i;
-- Добавление 500 пользователей
INSERT INTO users (name, email, registration_date)
SELECT
'Пользователь ' || i,
'user' || i || '@example.com',
CURRENT_DATE - (RANDOM() * 365)::INT
FROM generate_series(1, 500) AS i;
-- Добавление 1000 заказов
INSERT INTO orders (user_id, order_date, total_amount)
SELECT
(RANDOM() * 499 + 1)::INT,
CURRENT_DATE - (RANDOM() * 365)::INT,
(RANDOM() * 1000)::DECIMAL(10, 2)
FROM generate_series(1, 1000) AS i;
-- Добавление 5000 позиций в заказах
INSERT INTO order_items (order_id, product_id, quantity)
SELECT
(RANDOM() * 999 + 1)::INT,
(RANDOM() * 999 + 1)::INT,
(RANDOM() * 10 + 1)::INT
FROM generate_series(1, 5000) AS i;
Пример анализа запроса
- Запрос с JOIN и подзапросом:
SELECT
u.name,
SUM(o.total_amount) AS total_spent
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.registration_date > '2023-01-01'
GROUP BY u.name
HAVING SUM(o.total_amount) > 500;
- Анализ плана выполнения:
EXPLAIN ANALYZE
SELECT ...; -- Вставить запрос выше
Вывод до оптимизации:
HashAggregate (cost=... rows=... width=...)
Group Key: u.name
Filter: (sum(o.total_amount) > 500.00)
-> Hash Join (cost=... rows=... width=...)
Hash Cond: (o.user_id = u.id)
-> Seq Scan on orders o (cost=... rows=... width=...)
-> Hash (cost=... rows=... width=...)
-> Seq Scan on users u (cost=... rows=... width=...)
Filter: (registration_date > '2023-01-01')
- Оптимизация через индексы:
CREATE INDEX idx_users_registration_date ON users(registration_date);
CREATE INDEX idx_orders_user_id ON orders(user_id);
- Повторный анализ:
HashAggregate (cost=... rows=... width=...) (actual time=...)
-> Nested Loop (cost=... rows=...) (actual time=...)
-> Index Scan using idx_users_registration_date on users u ...
-> Index Scan using idx_orders_user_id on orders o ...
Практические задания
Задание 1: Анализ запроса с JOIN
- Напишите запрос, который выводит:
- Название категории.
- Среднюю цену товаров в категории.
- Общее количество заказанных товаров из этой категории.
- Используйте
EXPLAIN ANALYZEдля анализа плана выполнения. - Определите «узкие места» (например, Seq Scan, высокий cost).
Задание 2: Создание индексов
- Создайте индексы для полей, участвующих в JOIN и WHERE.
- Например:
category_idвproducts,product_idвorder_items. - Повторно выполните
EXPLAIN ANALYZEи сравните метрики.
Задание 3: Оптимизация подзапроса
- Напишите запрос, который находит пользователей, сделавших заказы на сумму больше 1000 руб. за последний месяц.
- Замените подзапрос на JOIN или CTE (Common Table Expression).
- Сравните производительность до и после оптимизации.
Контрольные вопросы
- Что показывает параметр
costв выводеEXPLAIN? - Почему Seq Scan может быть медленнее Index Scan?
- Когда использование индекса неэффективно?
- Как влияет количество строк (rows) на выбор плана выполнения?
Работа позволяет освоить методы анализа и оптимизации запросов на реальных данных. Для углубленного изучения можно добавить работу с оконными функциями или более сложными индексами (частичными, составными).