БД:Теория:Глава 6
Глава 6: Индексы и Выполнение Запросов
В предыдущих главах мы научились создавать таблицы, наполнять их данными и извлекать эти данные с помощью SQL-запросов. Теперь разберемся, как СУБД выполняет эти запросы “под капотом” и как можно ускорить их выполнение с помощью индексов. Эта глава основана на материалах Лекций 6: “Индексы. Выполнение запросов” и Лекции 7: “Выполнение запросов”.
Часть 1: Повышение Производительности Запросов
Когда данных в таблицах становится много (тысячи, миллионы строк), даже простые запросы могут выполняться долго. Почему? Потому что в самом простом случае СУБД приходится последовательно просматривать всю таблицу, чтобы найти нужные строки (это называется полное сканирование таблицы или Sequential Scan).
Способы повышения производительности:
- Грамотное проектирование модели данных: Это основа. Если модель данных изначально плохо спроектирована (например, ненормализована, что приводит к избыточности, или наоборот, излишне нормализована, что требует множества сложных соединений для простых задач), то оптимизировать запросы будет очень сложно.
- Использование индексов: Создание дополнительных структур данных, которые позволяют СУБД быстро находить нужные строки по значениям в определенных столбцах, не просматривая всю таблицу. (Основная тема этой главы).
- Оптимизация запросов: Переписывание SQL-запросов так, чтобы они были более понятны оптимизатору СУБД и могли быть выполнены более эффективно (например, избегая ненужных соединений или используя
EXISTS
вместоIN
в некоторых случаях). - Настройка физических параметров СУБД: Оптимизация конфигурации сервера PostgreSQL (выделение памяти, настройка параллелизма, параметры ввода-вывода и т.д.).
Мы сосредоточимся на индексах и понимании выполнения запросов.
Часть 2: Индексы
Что такое Индекс?
Представьте себе предметный указатель в конце большой книги. Вместо того чтобы листать всю книгу в поисках нужного термина, вы смотрите в указатель, находите термин и видите номера страниц, где он встречается.
Индекс SQL работает похожим образом:
- Это отдельная структура данных (обычно хранящаяся в отдельном файле), связанная с таблицей.
- Он содержит копии значений из одного или нескольких столбцов таблицы.
- Эти значения в индексе упорядочены определенным образом (например, по алфавиту для строк, по возрастанию для чисел, или организованы в специфическую структуру данных типа хеш-таблицы).
- Каждое значение в индексе имеет указатель (ссылку, адрес, TID - Tuple Identifier) на те строки в основной таблице, где это значение встречается.
Как индекс помогает?
Рассмотрим таблицу STUDENT
:
StudID | GroupID | Name |
---|---|---|
14 | 51 | Ivan Petrov |
23 | 53 | Vladimir Ivanov |
18 | 54 | Eugene Serov |
13 | 55 | Gennady Surikov |
25 | 56 | Nikolay Petrov |
25 | 58 | Svetlana Ivanova |
25 | 59 | Antony Fedorov |
18 | 63 | Petr Menchikov |
27 | 67 | Gennady Klokov |
23 | 69 | Serge Vidov |
18 | 72 | Roman Klever |
13 | 73 | Pavel Borisov |
И запрос:
SELECT * FROM STUDENT WHERE StudID = 18;
- Без индекса: СУБД читает каждую строку таблицы
STUDENT
и сравнивает значение в колонкеStudID
с числом 18. Если таблица большая (миллионы строк), это займет много времени, так как СУБД должна прочитать все данные с диска в память и проверить каждую строку. - С индексом по
StudID
:- СУБД обращается к структуре индекса по
StudID
. - Если индекс, например, B-дерево (значения отсортированы), СУБД быстро находит значение 18 (например, используя бинарный поиск или навигацию по дереву).
- Из индекса СУБД получает указатели (TID) на строки в основной таблице, где
StudID = 18
. - СУБД читает только эти конкретные строки из основной таблицы, используя полученные указатели. Это значительно быстрее, чем читать всю таблицу.
- СУБД обращается к структуре индекса по
Пример (слайды 10-17, упрощенная иллюстрация):
Допустим, у нас есть упорядоченный список значений StudID
(это и есть наш “игрушечный” индекс) и стрелки, указывающие на строки в основной таблице.
Индекс (упорядоченный StudID) | Таблица STUDENT (StudID, GroupID, Name) |
---|---|
13 →
|
14, 51, Ivan Petrov
|
13 →
|
23, 53, Vladimir Ivanov
|
14 →
|
18, 54, Eugene Serov <– Нужная строка
|
18 →
|
13, 55, Gennady Surikov
|
18 →
|
25, 56, Nikolay Petrov
|
18 →
|
25, 58, Svetlana Ivanova
|
23 →
|
25, 59, Antony Fedorov
|
23 →
|
18, 63, Petr Menchikov <– Нужная строка
|
25 →
|
27, 67, Gennady Klokov
|
25 →
|
23, 69, Serge Vidov
|
25 →
|
18, 72, Roman Klever <– Нужная строка
|
27 →
|
13, 73, Pavel Borisov
|
- Запрос:
SELECT * FROM STUDENT WHERE StudID = 18;
- СУБД смотрит в индекс.
- Поскольку индекс отсортирован (в нашем примере это простой список, но представьте более сложную структуру), СУБД быстро находит первое вхождение
18
. - Берет указатель и читает строку
18, 54, Eugene Serov
. - Переходит к следующему значению
18
в индексе. - Берет указатель и читает строку
18, 63, Petr Menchikov
. - Переходит к следующему значению
18
в индексе. - Берет указатель и читает строку
18, 72, Roman Klever
. - Следующее значение в индексе уже
23
(или больше 18). Поиск завершен.
Вместо сканирования всех 12 строк таблицы, мы быстро нашли 3 нужные строки через индекс.
Важные моменты:
- Индексы работают неявно: Вам не нужно указывать в
SELECT
-запросе, какой индекс использовать. Оптимизатор СУБД сам решает, использовать ли индекс и какой именно, на основе статистики данных и стоимости выполнения разных планов. - Иногда СУБД может не использовать индекс, даже если он есть. Например, если запрос выбирает очень большую часть таблицы (проще прочитать всю таблицу), или если условия в
WHERE
таковы, что индекс неприменим (например, функция над индексированным столбцомWHERE upper(StudName) = 'ИВАНОВ'
).
Когда Индексы Полезны?
Индексы ускоряют операции, включающие:
- Поиск строк по условиям (
WHERE
): Особенно для условий равенства (=
), диапазонных запросов (>
,<
,BETWEEN
) по индексированному столбцу. - Соединения таблиц (
JOIN
): Индексы по столбцам, участвующим в условииON
, значительно ускоряют поиск совпадающих строк в соединяемых таблицах. Первичные и уникальные ключи автоматически индексируются в PostgreSQL, что ускоряет соединения по ним. - Поиск минимального/максимального значения (
MIN()
,MAX()
): Если столбец индексирован (например, B-деревом), СУБД может просто взять первое/последнее значение из индекса, не сканируя таблицу. - Сортировку (
ORDER BY
): Если порядок сортировки совпадает с порядком индекса (или обратен ему, и индекс это поддерживает), СУБД может избежать фактической сортировки данных, просто прочитав строки в порядке индекса. - Группировку (
GROUP BY
): Индексы могут помочь СУБД быстрее найти строки, относящиеся к одной группе.
Недостатки Индексов:
- Занимают место на диске и в памяти: Индекс — это дополнительная структура данных. Чем больше индексов, тем больше места они занимают.
- Замедляют операции записи (
INSERT
,UPDATE
,DELETE
): При изменении данных в индексированном столбце или добавлении/удалении строки СУБД должна обновить не только саму таблицу, но и все связанные с ней индексы. Это добавляет накладные расходы. Чем больше индексов на таблице, тем медленнее будут операции записи.- Пример (слайд 23-24):
DELETE FROM STUDENT WHERE GroupID = 54;
Если поGroupID
есть индекс, то СУБД должна не только удалить строку из таблицыSTUDENT
, но и удалить соответствующие записи из индекса поGroupID
, а также, возможно, из индекса поStudID
(еслиStudID
был ключом этого индекса и строка удаляется).
- Пример (слайд 23-24):
- Неэффективны на маленьких таблицах: Если таблица настолько мала, что целиком помещается в несколько блоков памяти, СУБД прочитает ее полностью быстрее, чем будет обращаться к индексу.
- Неэффективны при выборке больших объемов данных: Если запрос выбирает значительную часть таблицы (например,
WHERE column > 0
, где почти все значения больше нуля), СУБД, скорее всего, решит, что проще прочитать всю таблицу последовательно (Seq Scan
), чем много раз обращаться к индексу и затем к разным частям таблицы за строками (Index Scan
+ много случайных чтений с диска, что медленнее последовательного).
Стратегии Применения Индексов:
- Анализируйте запросы: Какие столбцы часто используются в
WHERE
иJOIN
? Какие операции преобладают — чтение или запись? - Индексируйте столбцы в
WHERE
: Особенно те, по которым идет поиск на равенство или по диапазону и которые обладают хорошей селективностью (т.е. по значению в этом столбце можно отобрать небольшую долю строк таблицы). Индексировать столбец “пол” (М/Ж), где всего два значения, обычно бессмысленно, так как каждое значение выбирает примерно половину таблицы. - Индексируйте столбцы внешних ключей: Это критически важно для ускорения
JOIN
. PostgreSQL не создает их автоматически для внешних ключей (в отличие от первичных/уникальных), поэтому это нужно делать вручную. - Индексируйте столбцы в
ORDER BY
иGROUP BY
: Если часто нужна сортировка или группировка по определенному столбцу. - Используйте составные индексы: Если часто ищете по комбинации столбцов (
WHERE col1 = ? AND col2 = ?
), создайте индекс по(col1, col2)
. Порядок столбцов в составном индексе важен! Индекс по(col1, col2)
может использоваться для запросов поcol1
и для запросов поcol1
Иcol2
, но не для запросов только поcol2
. - Не создавайте лишних индексов: Каждый индекс замедляет запись. Регулярно проверяйте и удаляйте неиспользуемые индексы.
- Первичные и уникальные ключи индексируются автоматически в PostgreSQL (с помощью B-Tree индекса).
Создание Индексов (CREATE INDEX
)
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ IF NOT EXISTS ] имя_индекса
ON имя_таблицы [ USING метод ]
( { имя_колонки | ( выражение ) } [ COLLATE имя_сортировки ] [ класс_операторов ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
[ WITH ( параметр_хранения = значение [, ... ] ) ]
[ TABLESPACE имя_табличного_пространства ]
[ WHERE предикат ]; -- Частичный индекс
Полный синтаксис сложнее, здесь приведены основные части.
UNIQUE
: Создает уникальный индекс.CONCURRENTLY
: (PostgreSQL) Позволяет создать индекс без длительной блокировки операций записи в таблицу.IF NOT EXISTS
: Не выдавать ошибку, если индекс с таким именем уже существует.имя_индекса
: Имя для вашего индекса.имя_таблицы
: Таблица, для которой создается индекс.USING метод
: Указывает тип индекса (btree
,hash
,gist
,gin
и др.). По умолчанию в PostgreSQL этоbtree
.( имя_колонки | ( выражение ) ... )
: Список столбцов или выражений, по которым строится индекс.ASC | DESC
: Порядок сортировки (для B-Tree).NULLS FIRST | NULLS LAST
: Где размещатьNULL
-значения.WHERE предикат
: Создает частичный индекс. Индексируются только строки, удовлетворяющие предикату. Полезно, если часто ищутся строки с определенным значением в одном столбце, а по другому нужно индексировать только подмножество.
Примеры:
-- Простой B-Tree индекс по одной колонке
CREATE INDEX idx_student_groupid ON student (GroupID);
-- Составной B-Tree индекс по двум колонкам
CREATE INDEX idx_student_surname_name ON student (Surname, Name);
-- Индекс по выражению (по фамилии в нижнем регистре)
CREATE INDEX idx_student_surname_lower ON student (lower(Surname));
-- Хеш-индекс (только для оператора равенства '=')
CREATE INDEX idx_student_name_hash ON student USING hash (Name);
-- Пример из слайда 29 (B-Tree по двум колонкам)
CREATE INDEX idx_example ON table_name USING btree (column1, column2);
Часть 3: Типы Индексов
PostgreSQL поддерживает несколько типов индексов, оптимизированных для разных задач.
- B-дерево (B-Tree):
- Используется по умолчанию (
USING btree
). - Структура: Сбалансированное, многоуровневое дерево.
- Листовые узлы: Содержат индексируемые значения (ключи) и указатели (TID - Tuple Identifier, адрес строки на диске) на строки таблицы. Ключи в листовых узлах отсортированы. Листовые узлы связаны между собой для быстрого последовательного прохода (полезно для диапазонных запросов).
- Внутренние узлы (включая корневой): Содержат “разделители” (ключи) и ссылки на дочерние узлы. Помогают быстро сузить область поиска.
- Преимущества: Очень универсальный. Эффективен для:
- Поиска по равенству (
=
). - Диапазонных запросов (
>
,<
,BETWEEN
,LIKE 'abc%'
(поиск по префиксу)). - Сортировки (
ORDER BY
). - Поиска
NULL
(IS NULL
,IS NOT NULL
). - Работы с
MIN()
иMAX()
.
- Поиска по равенству (
- Пример поиска (StudID = 85, слайды 36-41):
- Запрос:
SELECT * FROM STUDENT WHERE StudID = 85;
- Начинаем с корневого узла (например,
[3, 30, 63]
). 85 > 63
, идем по правой ветке к узлу (например,[63, 76, 88]
).76 < 85 <= 88
, идем по ветке, соответствующей диапазону (88), к листовому узлу (например, содержащему ключи[76(tid), 85(tid)]
).- В листовом узле находим ключ
85
и соответствующийtid
. - По
tid
читаем строку из таблицы.
- Запрос:
- Пример диапазонного поиска (StudID >= 85, слайды 42-43):
- Аналогично находим первый ключ
85
. - Далее, благодаря связям между листовыми узлами, последовательно читаем следующие листовые узлы (
[88(tid), 95(tid)]
и т.д.), пока условие выполняется.
- Аналогично находим первый ключ
- Используется по умолчанию (
- Хеш-индекс (Hash):
- Структура: Основан на хеш-таблице. Для индексируемого значения вычисляется хеш-код, который используется как индекс в массиве “корзин” (buckets). Каждая корзина содержит указатели на строки таблицы, чьи индексируемые значения дали этот хеш-код.
- Пример (слайды 46-49):
- Значения:
(23, 2)
,(34, 2)
,(34543, 4)
,(234, 4)
,(2355, 5)
- Хеш-функция
Hash_Function(val1, val2)
дает, например:Hash_Function(23, 2)
->...2345
(хэш значение)Hash_Function(34, 2)
->...3423
Hash_Function(34543, 4)
->...2345
(коллизия с первым!)
- Хэш-индекс:
- Корзина
2345
:tid1
(для(23,2)
),tid3
(для(34543,4)
) - Корзина
3423
:tid2
(для(34,2)
)
- Корзина
- При поиске, например,
WHERE StudID=23 AND GroupID=2
:- Вычисляется
Hash_Function(23, 2)
->...2345
. - Смотрим в корзину
2345
. - Для каждой строки (
tid1
,tid3
) в этой корзине проверяем точное совпадение исходных значенийStudID
иGroupID
(т.к. в корзине могут быть коллизии).
- Вычисляется
- Значения:
- Преимущества: Очень быстрый для операций поиска по точному равенству (
=
). - Недостатки:
- Бесполезен для диапазонных запросов, сортировки, поиска по префиксу.
- Менее эффективен при большом количестве коллизий (когда много разных значений попадают в одну корзину).
- Когда использовать: Только если запросы к столбцу — это исключительно проверки на точное равенство (
WHERE attr = 1
), и другие операции не нужны. B-Tree обычно является более гибким выбором.
- GiST (Generalized Search Tree - Обобщенное дерево поиска):
- Гибкая инфраструктура для создания индексов для сложных типов данных (геометрические данные, диапазоны, некоторые виды текстового поиска).
- Позволяет индексировать операции типа “пересекается”, “содержит”.
- Используется расширениями, такими как PostGIS (для геоданных) и
pg_trgm
(для поиска по похожести строк - триграммам).
- GIN (Generalized Inverted Index - Обобщенный инвертированный индекс):
- Оптимизирован для индексации составных значений, где одно значение в столбце может содержать несколько “ключей” (например, массив слов в документе, элементы массива
jsonb
, элементыhstore
). - Хранит карту “ключ из составного значения -> список строк, где этот ключ встречается”.
- Преимущества: Очень эффективен для поиска строк, содержащих определенные значения внутри составного типа (например, найти все документы, содержащие слово ‘postgresql’).
- Используется для полнотекстового поиска, индексации
jsonb
(операторы?
,?|
,?&
),hstore
, массивов (операторы@>
,<@
,&&
).
- Оптимизирован для индексации составных значений, где одно значение в столбце может содержать несколько “ключей” (например, массив слов в документе, элементы массива
Часть 4: Выполнение Запросов и Оптимизация
(Этот раздел повторяет и немного расширяет информацию из предыдущей версии методички, основываясь на слайдах Лекции 7).
Декларативность SQL: Пользователь указывает, что он хочет получить, а не как это сделать. СУБД сама решает, как выполнить запрос.
Этапы выполнения запроса в PostgreSQL:
- Разбор запроса (Parser): SQL-текст преобразуется во внутреннее представление — дерево разбора (parse tree). Проверяется синтаксис.
- Преобразование запроса (Rewriter/Analyzer): Дерево разбора анализируется, преобразуется с учетом правил и представлений (например, запрос к представлению заменяется на запрос к базовым таблицам). Семантическая проверка. Результат — дерево запроса (query tree).
- Планировщик/Оптимизатор (Planner/Optimizer):
- Генерирует множество возможных планов выполнения для дерева запроса, используя:
- Законы реляционной алгебры для эквивалентных преобразований.
- Информацию об имеющихся индексах.
- Доступные алгоритмы выполнения операций (Seq Scan, Index Scan, Nested Loop Join, Hash Join, Merge Join, Sort, Aggregate и т.д.).
- Оценивает стоимость каждого плана на основе статистики таблиц (количество строк, распределение значений, размер таблицы, кардинальность и т.д.) и стоимости операций (I/O, CPU).
- Выбирает план с минимальной оцененной стоимостью.
- Результат — план выполнения (execution plan).
- Генерирует множество возможных планов выполнения для дерева запроса, используя:
- Выполнение плана (Executor): План выполнения передается исполнителю, который шаг за шагом выполняет операции плана и возвращает результат.
План выполнения запроса:
- Это, по сути, программа, которую СУБД строит для ответа на SQL-запрос.
- Для одного SQL-запроса может существовать несколько эквивалентных планов, но с разной стоимостью выполнения.
- Критерий выбора плана: Оценочная стоимость (число обменов с диском, время обмена, загрузка CPU).
Оптимизация на основе Реляционной Алгебры:
Оптимизатор использует законы реляционной алгебры (коммутативность, ассоциативность соединений, проталкивание выборок и проекций и т.д.) для генерации различных, но семантически эквивалентных планов.
- Пример (из слайдов 16-22 Лекции 7):
sql SELECT * FROM STUDENTS ST JOIN EXAMS EXAM ON ST.ID = EXAM.STUD_ID WHERE ST.GROUP = '3100' AND ST.ID >= 150000;
- План 1 (сначала JOIN, потом WHERE):
σ(ST.GROUP='3100' ∧ ST.ID>=150000) (STUDENTS ⋈(ST.ID=EXAM.STUD_ID) EXAMS)
Представление в виде дерева: сначала соединение, потом две операции выборки. - План 2 (сначала WHERE для STUDENTS, потом JOIN):
(σ(ST.GROUP='3100' ∧ ST.ID>=150000) (STUDENTS)) ⋈(ST.ID=EXAM.STUD_ID) EXAMS
Представление в виде дерева: сначала две операции выборки дляSTUDENTS
, потом результат соединяется сEXAMS
. Этот план обычно эффективнее, так как соединяется меньший объем данных.
- План 1 (сначала JOIN, потом WHERE):
Материализация vs Конвейерная Обработка (Pipelining):
- Материализация: Результат промежуточной операции полностью вычисляется и сохраняется во временную структуру, затем передается следующей операции.
- Конвейерная обработка: Как только первая операция производит первую строку результата, эта строка немедленно передается на вход следующей операции. Экономит ресурсы и время. Оптимизатор старается использовать конвейер. Левосторонние деревья планов хорошо подходят для конвейерной обработки (результат предыдущего соединения сразу идет на вход следующего).
Советы по построению (оптимизации) планов (что пытается делать оптимизатор):
- Использовать конвейерную обработку (левосторонние планы, избегать блокирующих операций типа полной сортировки без необходимости).
- Делать выборку как можно раньше.
- Делать проекцию как можно раньше.
- Грамотно планировать соединения (порядок, алгоритмы).
- Цель: Уменьшение размеров промежуточных данных => уменьшение числа операций чтения/записи во внешнюю память.
Влияние индексов на планы: Индексы могут кардинально изменить выбранный план, позволяя заменить полное сканирование таблицы (Seq Scan) на более эффективное индексное сканирование (Index Scan) или улучшить производительность соединений (Index Nested Loop Join).
Стоимость плана: Складывается из стоимости чтения входных/промежуточных таблиц, записи промежуточных данных (при материализации), сортировки и других операций.
Часть 5: Алгоритмы Выполнения Соединений (JOIN
)
(Детальное описание алгоритмов было в предыдущей версии, здесь краткое повторение с акцентом на примеры из Лекции 7)
- Nested Loop Join (Соединение вложенными циклами):
- R ⋈condition S (R - внешнее, S - внутреннее отношение)
- Простой: Для каждой строки
r
изR
, просматриваем все строкиs
изS
. Еслиcondition(r, s)
истинно, добавляемr
+s
в результат.- Пример (слайды 54-70 Лекции 7): Таблицы
Groups
иStudents
. Для первой группы изGroups
перебираются все студенты изStudents
. Потом для второй группы изGroups
снова перебираются все студенты изStudents
, и так далее.
- Пример (слайды 54-70 Лекции 7): Таблицы
- Block Nested Loop: Внешнее отношение
R
читается блоками (несколько страниц). Для каждого блокаR
, сканируется вся внутренняя таблицаS
. Уменьшает число полных сканированийS
.- Пример (слайды 88-91 Лекции 7): Если в буфере помещается блок из 4 страниц
Groups
, то для этого блока один раз сканируется вся таблицаStudents
. Затем загружается следующий блокGroups
и снова сканируется всяStudents
. Эффективнее, если внешняя таблица меньше внутренней.
- Пример (слайды 88-91 Лекции 7): Если в буфере помещается блок из 4 страниц
- Index Nested Loop: Если по столбцу соединения во внутренней таблице
S
есть индекс. Для каждой строкиr
изR
выполняется быстрый поиск по индексу вS
.
- Sort-Merge Join (Соединение слиянием):
- Обе таблицы (
R
иS
) сортируются по атрибутам соединения. - Затем отсортированные таблицы “сливаются” (как при merge sort), одновременно проходя по обеим и комбинируя строки с совпадающими значениями атрибутов соединения.
- Пример (слайды 93-99 Лекции 7): Таблицы
Groups
иStudents
(предполагается, что они уже отсортированы поGroupID
). Указатели одновременно движутся по обеим таблицам.
- Обе таблицы (
Часть 6: Просмотр Плана Выполнения в PostgreSQL
Команда EXPLAIN
показывает план, который СУБД выбрала для выполнения запроса.
EXPLAIN <SQL_ЗАПРОС>;
: Показывает план выполнения, но не выполняет сам запрос.- Вывод (слайд 103-104):
QUERY PLAN --------------------------------------------------------------------- Seq Scan on Students (cost=0.00..309.00 rows=900 width=170)
Seq Scan on Students
: Операция — последовательное сканирование таблицыStudents
.cost=0.00..309.00
: Оценочная стоимость (начальная..общая).rows=900
: Оценочное количество строк, которое вернет эта операция.width=170
: Оценочный средний размер строки.
- Вывод с индексом (слайд 105):
QUERY PLAN ---------------------------------------------------------------------------------------- Index Scan using students_studid_pkey on students (cost=0.32..8.34 rows=1 width=150) Index Cond: (StudId = 942)
Index Scan using students_studid_pkey on students
: Используется индексstudents_studid_pkey
.Index Cond: (StudId = 942)
: Условие, применяемое к индексу.
- Вывод (слайд 103-104):
EXPLAIN ANALYZE <SQL_ЗАПРОС>;
: Выполняет запрос и показывает реальный план выполнения с фактическим временем выполнения каждой операции и количеством строк.Вывод (примерный, на основе слайда 106):
EXPLAIN ANALYZE SELECT * FROM students WHERE StudID = 942; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------ Index Scan using students_studid_pkey on students (cost=0.29..8.30 rows=1 width=153) (actual time=0.020..0.021 rows=1 loops=1) Index Cond: (studid = 942) Planning Time: 0.075 ms Execution Time: 0.035 ms (4 rows)
(actual time=0.020..0.021 rows=1 loops=1)
: Фактическое время (начало..конец), фактическое число строк, сколько раз узел выполнялся.Planning Time
: Время, затраченное на планирование/оптимизацию.Execution Time
: Общее время выполнения запроса (включая планирование, если не используетсяSET anl_separate_planning_info = on;
или аналогичная опция).
Анализ вывода EXPLAIN
(особенно EXPLAIN ANALYZE
) помогает понять, почему запрос выполняется медленно, используются ли индексы, корректны ли оценки оптимизатора, и какие части запроса являются “бутылочным горлышком”.