Индексы и базовая производительность: помогаем PostgreSQL быстрее находить данные

Возвращаемся к PostgreSQL.
В предыдущем уроке ты изучил агрегатные функции.
Ты научился считать строки.
Суммировать значения.
Вычислять средние значения.
Находить минимальные и максимальные значения.
Группировать данные.
Фильтровать группы с помощью HAVING.
Очень хорошо.
Теперь PostgreSQL может отвечать на вопросы вроде:
Сколько продуктов у нас есть?
Какая общая стоимость склада?
Сколько заказов у каждого клиента?
Какая категория имеет больше всего продуктов?
Отлично.
Но теперь появляется другой вопрос.
Очень серьёзный вопрос.
Что случится, когда таблица станет огромной?
Когда таблица имеет десять строк, всё быстро.
Когда таблица имеет десять миллионов строк, PostgreSQL начинает смотреть на тебя по-другому.
Примерно так:
Ты уверен в этом запросе?
Вот здесь помогают индексы.
Индексы помогают PostgreSQL быстрее находить данные.
Это не магия.
Но это мощно.
Как хорошая карта.
Без индекса PostgreSQL может быть вынужден читать всю таблицу.
С индексом PostgreSQL может быстрее перейти к нужным данным.
Очень полезно.
Очень база данных.
Очень “пожалуйста, не заставляй мой сервер плакать”.
Что ты изучишь
В этом уроке ты изучишь:
- почему запросы могут становиться медленными;
- что такое индекс;
- как индексы помогают PostgreSQL быстрее находить строки;
- что такое последовательное сканирование;
- что такое сканирование через индекс;
- как использовать
EXPLAIN; - как использовать
EXPLAIN ANALYZE; - как создать индекс;
- когда индексы полезны;
- когда индексы не полезны;
- почему индексы не бесплатные;
- как индексы влияют на вставку и обновление данных;
- как работают уникальные индексы;
- как работают индексы на несколько колонок;
- типичные ошибки с индексами.
К концу этого урока ты поймёшь базовую идею производительности в базах данных.
Не ракетостроение.
Ещё нет.
Просто достаточно, чтобы перестать писать запросы, от которых PostgreSQL выглядит уставшим.
Благородная цель.
Подготовь базу данных
Открой PostgreSQL:
sudo -iu postgres psql
Подключись к своей базе данных:
\c learning_postgresql
Если базы ещё нет, создай её:
CREATE DATABASE learning_postgresql;
Потом подключись:
\c learning_postgresql
Теперь удали старую таблицу, если она существует:
DROP TABLE IF EXISTS products;
Создай новую таблицу products:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(100) NOT NULL,
price NUMERIC(10, 2) CHECK (price >= 0),
quantity INTEGER CHECK (quantity >= 0),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Эта таблица простая.
Но её достаточно, чтобы изучать индексы.
У нас есть:
- название продукта;
- категория;
- цена;
- количество;
- дата создания.
Обычная маленькая таблица.
Пока что.
Скоро она станет маленькой армией строк.
PostgreSQL готов.
Возможно.
Добавляем несколько строк
Добавь несколько продуктов:
INSERT INTO products (name, category, price, quantity)
VALUES
('Laptop', 'Electronics', 900.00, 5),
('Mouse', 'Electronics', 25.00, 30),
('Keyboard', 'Electronics', 70.00, 15),
('Desk Chair', 'Furniture', 150.00, 8),
('Bookshelf', 'Furniture', 120.00, 4),
('Notebook', 'Office', 5.00, 100),
('Pen', 'Office', 2.00, 200),
('Whiteboard', 'Office', 80.00, 3);
Проверь данные:
SELECT * FROM products;
Эта таблица крошечная.
Крошечная таблица быстрая даже без индексов.
PostgreSQL может моргнуть и прочитать её.
Но реальные приложения не остаются маленькими навсегда.
Данные растут.
Логи растут.
Заказы растут.
Пользователи растут.
База данных растёт.
А потом однажды какой-то запрос становится медленным, и все смотрят на разработчика.
Очень несправедливо.
Но иногда правильно.
Добавляем больше строк для теста
Чтобы увидеть идеи производительности, нам нужно больше строк.
PostgreSQL имеет полезную функцию generate_series.
Она может генерировать много строк.
Выполни это:
INSERT INTO products (name, category, price, quantity)
SELECT
'Product ' || number,
CASE
WHEN number % 4 = 0 THEN 'Electronics'
WHEN number % 4 = 1 THEN 'Furniture'
WHEN number % 4 = 2 THEN 'Office'
ELSE 'Kitchen'
END,
(number % 100) + 1,
(number % 50) + 1
FROM generate_series(1, 100000) AS number;
Это вставит сто тысяч продуктов.
Не настоящих продуктов.
Фейковых продуктов.
Но полезных фейковых продуктов.
Как тренировочные гантели для PostgreSQL.
Теперь проверь количество строк:
SELECT COUNT(*) AS total_products
FROM products;
Ты должен увидеть больше ста тысяч строк.
Теперь таблица достаточно большая, чтобы примеры производительности стали интереснее.
Ещё не огромная.
Но уже не маленькая игрушка.
Почему запросы становятся медленными
Запрос может быть медленным по многим причинам.
Для начинающего самая частая причина простая:
PostgreSQL должен проверить слишком много строк.
Пример:
SELECT *
FROM products
WHERE category = 'Electronics';
Если нет индекса на category, PostgreSQL может быть вынужден проверить каждую строку.
Одну за другой.
Как искать один листок бумаги в захламлённом гараже.
Возможно.
Но не элегантно.
Это называется последовательное сканирование.
Последовательное сканирование
Последовательное сканирование означает, что PostgreSQL читает таблицу строка за строкой.
Он проверяет каждую строку и спрашивает:
Эта строка соответствует условию?
Для маленьких таблиц это нормально.
Для больших таблиц это может быть медленно.
Пример:
SELECT *
FROM products
WHERE category = 'Electronics';
Без индекса на category, PostgreSQL может прочитать всю таблицу.
Это не всегда плохо.
Иногда последовательное сканирование — лучший выбор.
Но если ты часто ищешь по определённой колонке, индекс может помочь.
Ключевое слово — часто.
Не создавай индексы случайно.
Индексы — это инструменты.
Не украшения.
Что такое индекс?
Индекс — это специальная структура базы данных, которая помогает PostgreSQL быстрее находить строки.
Представь книгу.
Если у книги нет указателя, тебе придётся листать страницу за страницей.
Если у книги есть указатель, ты можешь найти тему и перейти к нужной странице.
Индекс в базе данных работает по похожей идее.
Без индекса:
Читать много строк и проверять их.
С индексом:
Использовать индекс, чтобы быстрее найти подходящие строки.
Индексы в PostgreSQL обычно хранятся отдельно от таблицы.
Таблица содержит реальные данные.
Индекс содержит организованные ссылки на эти данные.
Очень упрощённо.
Но для начала достаточно.
Как GPS для строк.
Без раздражающего голоса.
Проверяем запрос через EXPLAIN
PostgreSQL может показать, как он планирует выполнить запрос.
Используй EXPLAIN.
EXPLAIN
SELECT *
FROM products
WHERE category = 'Electronics';
Ты можешь увидеть что-то такое:
Seq Scan on products
Filter: ((category)::text = 'Electronics'::text)
Точный вывод может быть другим.
Но важная часть:
Seq Scan
Это означает, что PostgreSQL планирует сканировать таблицу.
Он будет читать строки и фильтровать их.
Это очень полезная информация.
PostgreSQL показывает тебе свой план.
Как GPS базы данных.
Но с меньшим количеством цветов.
EXPLAIN ANALYZE
EXPLAIN показывает план.
EXPLAIN ANALYZE реально выполняет запрос и показывает, что случилось.
Выполни:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Electronics';
Ты можешь увидеть:
Seq Scan on products
Filter: ((category)::text = 'Electronics'::text)
Rows Removed by Filter: ...
Planning Time: ...
Execution Time: ...
Точные числа зависят от твоего компьютера.
Важные части:
Seq Scan
Execution Time
Rows Removed by Filter
Rows Removed by Filter означает, что PostgreSQL проверил строки и отбросил многие из них.
Это работа.
Иногда необходимая работа.
Иногда работа, которой можно избежать.
EXPLAIN ANALYZE очень полезен.
Но помни:
Он реально выполняет запрос.
Поэтому будь осторожен с запросами, которые изменяют данные.
Не запускай его легкомысленно на опасном DELETE.
PostgreSQL не скажет:
Ты эмоционально готов?
Он просто выполнит.
Создаём индекс
Теперь создай индекс на category:
CREATE INDEX idx_products_category
ON products(category);
Название idx_products_category — это просто название.
Распространённый стиль именования:
idx_table_column
Теперь выполни запрос ещё раз:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Electronics';
Ты можешь увидеть, что используется индекс.
Например:
Bitmap Index Scan
Bitmap Heap Scan
или:
Index Scan
Точный план зависит от PostgreSQL и твоих данных.
Главная идея:
PostgreSQL теперь имеет ещё один вариант.
Он может использовать индекс.
Без индекса он должен был читать таблицу.
С индексом он может быстрее найти нужные строки.
Может.
Не всегда.
PostgreSQL выбирает то, что считает лучшим.
Он умный.
Обычно.
Почему PostgreSQL всё равно может использовать последовательное сканирование
Иногда ты создаёшь индекс, а PostgreSQL всё равно использует последовательное сканирование.
Не паникуй.
Это может быть нормально.
Почему?
Потому что если много строк соответствует условию, читать всю таблицу может быть быстрее.
Пример:
SELECT *
FROM products
WHERE category = 'Electronics';
Если двадцать пять процентов таблицы — это Electronics, PostgreSQL может решить:
Много строк подходит. Прочитаю таблицу.
Индекс наиболее полезен тогда, когда запрос возвращает маленькую часть таблицы.
Например:
SELECT *
FROM products
WHERE id = 50000;
Это очень избирательно.
Одна строка.
Идеально для индекса.
Поиск продукта по id быстрый, потому что id уже индексирован первичным ключом.
Первичные ключи автоматически создают индексы.
PostgreSQL сделал это за тебя.
Очень мило.
Очень профессионально.
Индекс первичного ключа
Когда ты создаёшь это:
id SERIAL PRIMARY KEY
PostgreSQL автоматически создаёт уникальный индекс для id.
Именно поэтому этот запрос быстрый:
SELECT *
FROM products
WHERE id = 50000;
Проверь:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE id = 50000;
Ты должен увидеть что-то вроде:
Index Scan using products_pkey
products_pkey — это индекс первичного ключа.
Ты не создавал его вручную.
PostgreSQL создал его, потому что первичные ключи должны быть уникальными и пригодными для поиска.
Это одна из причин, почему первичные ключи важны.
Они не украшения.
Они удостоверения личности базы данных.
Индекс на price
Создадим индекс на price.
CREATE INDEX idx_products_price
ON products(price);
Теперь выполни:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE price = 50.00;
PostgreSQL может использовать индекс.
Это зависит от данных.
Теперь попробуй:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE price > 90.00;
Индекс может помочь и с поиском по диапазону.
Примеры:
WHERE price = 50.00
WHERE price > 90.00
WHERE price BETWEEN 10.00 AND 20.00
Индексы особенно полезны, когда ты ищешь, фильтруешь, сортируешь или делаешь JOIN по определённой колонке.
Но снова:
Не создавай индексы на каждую колонку.
Это не оптимизация.
Это конфетти для базы данных.
А конфетти потом трудно убирать.
Индекс и ORDER BY
Индексы также могут помогать с сортировкой.
Пример:
EXPLAIN ANALYZE
SELECT *
FROM products
ORDER BY price ASC
LIMIT 10;
Так как мы создали индекс на price, PostgreSQL может использовать его, чтобы быстрее найти самые дешёвые продукты.
Это полезно для запросов вроде:
Показать самые дешёвые продукты.
Показать самые дорогие продукты.
Показать самые новые посты.
Показать последние заказы.
Распространённый пример — индекс на created_at.
CREATE INDEX idx_products_created_at
ON products(created_at);
Тогда этот запрос может стать быстрее:
SELECT *
FROM products
ORDER BY created_at DESC
LIMIT 10;
Это очень часто встречается в реальных приложениях.
Блоги.
Заказы.
Сообщения.
Логи.
Все хотят последние десять вещей.
Потому что, кажется, люди очень любят слово “последнее”.
Индекс и JOIN
Индексы могут помогать и с JOIN.
Представь две таблицы:
orders
customers
Обычно:
orders.customer_id references customers.id
Когда ты делаешь JOIN:
SELECT
o.id,
c.name,
o.total
FROM orders AS o
JOIN customers AS c
ON o.customer_id = c.id;
PostgreSQL должен сопоставить строки.
Индексы на колонках, которые используются в JOIN, могут помочь.
Первичные ключи индексируются автоматически.
Поэтому customers.id уже индексирован, если это первичный ключ.
Но внешние ключи не всегда индексируются автоматически.
Это означает, что orders.customer_id может нуждаться в индексе, если ты часто делаешь JOIN или фильтруешь по этой колонке.
Пример:
CREATE INDEX idx_orders_customer_id
ON orders(customer_id);
Это очень распространённый индекс в реальных проектах.
Колонки внешних ключей часто хорошие кандидаты на индекс.
Не всегда.
Но часто.
Ответ номер один в базах данных:
Зависит.
Раздражает.
Но правда.
Уникальный индекс
Уникальный индекс запрещает дубликаты значений.
Пример:
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL
);
Создай уникальный индекс:
CREATE UNIQUE INDEX idx_users_email_unique
ON users(email);
Добавь пользователя:
INSERT INTO users (email, name)
VALUES ('anna@example.com', 'Anna');
Попробуй добавить другого пользователя с таким же email:
INSERT INTO users (email, name)
VALUES ('anna@example.com', 'Another Anna');
PostgreSQL отклонит это.
Хорошо.
Email обычно должен быть уникальным.
Уникальный индекс делает две вещи:
- помогает быстрее искать;
- защищает данные от дубликатов.
Очень полезно.
Очень ответственно.
Как охранник для твоей таблицы.
Но без тёмных очков.
UNIQUE constraint или уникальный индекс?
Также можно написать:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL
);
Это создаёт уникальное ограничение.
PostgreSQL также создаёт уникальный индекс за кулисами.
Для начинающего простое правило:
Используй UNIQUE в определении таблицы, когда это правило данных.
Например:
Email должен быть уникальным.
Username должен быть уникальным.
Код продукта должен быть уникальным.
Используй явные индексы, когда хочешь улучшить скорость поиска.
Простое правило:
Правило данных: constraint.
Помощь производительности: index.
Не идеально.
Но очень практично.
Индексы на несколько колонок
Индекс может содержать больше одной колонки.
Пример:
CREATE INDEX idx_products_category_price
ON products(category, price);
Это может помочь запросам вроде:
SELECT *
FROM products
WHERE category = 'Electronics'
ORDER BY price ASC;
Индекс упорядочен сначала по category, потом по price.
Порядок колонок имеет значение.
Этот индекс полезен для:
WHERE category = 'Electronics'
и:
WHERE category = 'Electronics'
ORDER BY price ASC
Но он может быть менее полезен для:
WHERE price = 50.00
Почему?
Потому что price — вторая колонка в индексе.
Первая колонка — category.
Индексы на несколько колонок мощные.
Но их нужно создавать на основе реальных запросов.
Не на основе вайба.
Базы данных не оптимизируют вайб.
К сожалению.
Индексы не бесплатные
Индексы делают некоторые чтения быстрее.
Но у них есть цена.
Индекс:
- использует место на диске;
- должен обновляться, когда меняются данные;
- может замедлять
INSERT; - может замедлять
UPDATE; - может замедлять
DELETE; - добавляет сложность.
Когда ты вставляешь строку, PostgreSQL должен вставить данные в таблицу.
Но он также должен обновить индексы.
Если таблица имеет много индексов, запись становится тяжелее.
Поэтому не создавай индексы везде.
Таблица со слишком большим количеством индексов — как человек с десятью рюкзаками.
Возможно, подготовленный.
Но точно более медленный.
Когда нужно создавать индекс?
Создавай индекс, когда:
- ты часто ищешь по колонке;
- ты часто фильтруешь по колонке через
WHERE; - ты часто сортируешь по колонке через
ORDER BY; - ты часто делаешь
JOINчерез колонку; - таблица достаточно большая, чтобы производительность имела значение;
- запрос возвращает маленькую часть таблицы;
EXPLAIN ANALYZEпоказывает медленное сканирование.
Примеры:
WHERE email = 'anna@example.com'
WHERE customer_id = 10
WHERE created_at >= '2026-01-01'
ORDER BY created_at DESC LIMIT 10
JOIN orders ON orders.customer_id = customers.id
Это типичные места, где индексы могут помочь.
Но не угадывай вслепую.
Измерь.
Потом создай индекс.
Потом измерь снова.
Это взрослый способ.
Немного скучный.
Очень эффективный.
Когда индекс может не помочь
Индекс может не помочь, когда:
- таблица очень маленькая;
- запрос возвращает почти всю таблицу;
- колонка имеет очень мало разных значений;
- ты используешь колонку так, что индекс не может помочь;
- индекс не соответствует запросу;
- данные очень часто меняются, а чтение редкое.
Пример:
WHERE category = 'Electronics'
Если существует только четыре категории и каждая категория имеет много строк, индекс может не сильно помочь.
Другой пример:
WHERE quantity > 0
Если почти каждый продукт имеет quantity > 0, PostgreSQL может выбрать последовательное сканирование.
Потому что почти всё подходит.
Индекс полезен, когда может избежать работы.
Если запросу нужно почти всё, избегать почти нечего.
Грустно, но логично.
Функции могут прятать индексы
Будь осторожен с функциями в WHERE.
Допустим, у тебя есть индекс на email.
CREATE INDEX idx_users_email
ON users(email);
Этот запрос может использовать индекс:
SELECT *
FROM users
WHERE email = 'anna@example.com';
А этот запрос может не использовать обычный индекс:
SELECT *
FROM users
WHERE LOWER(email) = 'anna@example.com';
Почему?
Потому что PostgreSQL ищет не сырое значение email.
Он ищет результат LOWER(email).
Это другое.
Для такого запроса может понадобиться индекс на выражение:
CREATE INDEX idx_users_lower_email
ON users(LOWER(email));
Теперь PostgreSQL может использовать индекс для:
SELECT *
FROM users
WHERE LOWER(email) = 'anna@example.com';
Это немного сложнее.
Но важно.
Если запрос трансформирует колонку, обычный индекс может не помочь.
База данных умная.
Но мысли не читает.
К счастью.
LIKE и индексы
Индексы иногда могут помочь с LIKE.
Пример:
SELECT *
FROM products
WHERE name LIKE 'Product 12%';
Это ищет названия, которые начинаются с Product 12.
В некоторых случаях это может использовать индекс.
Но этот запрос сложнее:
SELECT *
FROM products
WHERE name LIKE '%12';
Почему?
Потому что шаблон начинается с wildcard.
PostgreSQL не может легко перейти к началу значения.
Ему приходится искать больше.
Простое правило:
LIKE 'abc%' может дружить с индексами.
LIKE '%abc' обычно не дружит с индексами.
LIKE '%abc%' обычно не дружит с индексами.
Существуют специальные типы индексов для продвинутого текстового поиска.
Но пока запомни:
Искать с начала легче.
Искать с середины сложнее.
Как найти название книги, когда помнишь только одно слово из середины.
Удачи, храбрый библиотекарь.
Удаление индекса
Иногда ты создаёшь индекс, а потом понимаешь, что он не полезен.
Его можно удалить.
Пример:
DROP INDEX IF EXISTS idx_products_price;
Это удаляет индекс.
Не таблицу.
Не данные.
Только индекс.
Но в реальных проектах будь осторожен.
Индексы могут использоваться важными запросами.
Удаление неправильного индекса может сделать всё медленным.
И тогда сервер начинает свою драматическую оперу.
Типичные ошибки
Создавать индексы на каждую колонку
Плохая идея:
Каждой колонке индекс!
Нет.
Остановись.
Это не новогодняя ёлка.
Индексы имеют цену.
Создавай индексы на основе реальных запросов.
Не использовать EXPLAIN ANALYZE
Угадывание — это не работа с производительностью.
Используй:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Electronics';
Измеряй до и после.
Без измерения ты занимаешься астрологией баз данных.
Ожидать, что индексы исправят плохие запросы
Индексы помогают.
Но они не исправляют всё.
Ужасный запрос может остаться ужасным даже с индексом.
Если ты выбираешь слишком много данных, плохо делаешь много JOIN, или странно фильтруешь, индексы могут не спасти.
Индексы — это инструменты.
Не чудеса.
Забывать, что индексы замедляют записи
Если таблица получает много вставок и обновлений, слишком много индексов могут ухудшить производительность.
Чтения могут стать быстрее.
Записи могут стать медленнее.
Всегда есть компромисс.
База данных даёт.
База данных забирает.
Очень философски.
Игнорировать индексы на внешних ключах
Внешние ключи важны для связей.
Но если ты часто делаешь JOIN или фильтруешь по колонке внешнего ключа, подумай об индексе.
Пример:
CREATE INDEX idx_orders_customer_id
ON orders(customer_id);
Это распространено в реальных приложениях.
Не всегда нужно.
Но очень часто полезно.
Практика
Создай индекс на category:
CREATE INDEX idx_products_category
ON products(category);
Проверь запрос:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Electronics';
Создай индекс на price:
CREATE INDEX idx_products_price
ON products(price);
Проверь запрос по цене:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE price BETWEEN 10.00 AND 20.00;
Проверь сортировку:
EXPLAIN ANALYZE
SELECT *
FROM products
ORDER BY price ASC
LIMIT 10;
Создай индекс на created_at:
CREATE INDEX idx_products_created_at
ON products(created_at);
Проверь последние продукты:
EXPLAIN ANALYZE
SELECT *
FROM products
ORDER BY created_at DESC
LIMIT 10;
Создай индекс на несколько колонок:
CREATE INDEX idx_products_category_price
ON products(category, price);
Проверь этот запрос:
EXPLAIN ANALYZE
SELECT *
FROM products
WHERE category = 'Office'
ORDER BY price ASC
LIMIT 10;
Запускай эти запросы.
Смотри на результат.
Не волнуйся, если план отличается от примеров.
PostgreSQL выбирает планы на основе твоих данных, индексов, настроек и компьютера.
Цель не в том, чтобы запомнить каждый план.
Цель в том, чтобы понять, что PostgreSQL пытается сделать.
Пока этого достаточно.
Мини-задание
Создай две таблицы:
customers
orders
Правила:
- один клиент может иметь много заказов;
- каждый заказ принадлежит одному клиенту;
- email клиента должен быть уникальным;
- заказы имеют общую сумму;
- заказы имеют дату создания.
Создай таблицы:
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS customers;
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id),
total NUMERIC(10, 2) CHECK (total >= 0),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Добавь клиентов:
INSERT INTO customers (email, name)
VALUES
('anna@example.com', 'Anna'),
('marco@example.com', 'Marco'),
('sofia@example.com', 'Sofia');
Добавь заказы:
INSERT INTO orders (customer_id, total)
SELECT
(number % 3) + 1,
(number % 200) + 10
FROM generate_series(1, 50000) AS number;
Теперь создай полезные индексы:
CREATE INDEX idx_orders_customer_id
ON orders(customer_id);
CREATE INDEX idx_orders_created_at
ON orders(created_at);
CREATE INDEX idx_orders_total
ON orders(total);
Протестируй эти запросы через EXPLAIN ANALYZE:
EXPLAIN ANALYZE
SELECT *
FROM customers
WHERE email = 'anna@example.com';
EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE customer_id = 1;
EXPLAIN ANALYZE
SELECT *
FROM orders
ORDER BY created_at DESC
LIMIT 10;
EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE total > 150.00;
Потом спроси себя:
Какие индексы были использованы?
Какие запросы всё ещё использовали последовательное сканирование?
Почему?
Запрос вернул много строк или мало строк?
Так начинается настоящая работа с производительностью.
Не угадыванием.
А наблюдением.
PostgreSQL оставляет подсказки.
Нужно только их прочитать.
Как детектив.
Но с большим количеством точек с запятой.
Итог
Сегодня ты изучил:
- запросы могут становиться медленными, когда таблицы растут;
- последовательное сканирование читает строки одну за другой;
- индекс помогает PostgreSQL быстрее находить строки;
- первичные ключи автоматически создают индексы;
EXPLAINпоказывает план запроса;EXPLAIN ANALYZEвыполняет запрос и показывает реальные детали выполнения;- индексы могут помогать с
WHERE; - индексы могут помогать с
ORDER BY; - индексы могут помогать с
JOIN; - уникальные индексы запрещают дубликаты;
- индексы на несколько колонок зависят от порядка колонок;
- индексы используют место на диске;
- индексы могут замедлять вставку, обновление и удаление;
- не каждой колонке нужен индекс;
- производительность нужно измерять, а не угадывать.
Это очень важный шаг.
Ты больше не просто пишешь SQL.
Ты начинаешь думать о том, как PostgreSQL выполняет SQL.
Это другой уровень.
Лучший уровень.
Уровень, где база данных всё ещё немного пугает.
Но теперь у тебя есть инструменты.
Следующий урок
В следующем уроке мы создадим небольшой практический проект на PostgreSQL.
Мы спроектируем таблицы.
Создадим связи.
Добавим данные.
Выполним запросы.
Используем JOIN.
Используем агрегатные функции.
И подумаем об индексах.
Другими словами, мы соберём части курса вместе.
Как собирать мебель.
Но с меньшим количеством потерянных винтов.
Надеюсь.