11 января 2024 года, выпущен Firebird 5.0 — восьмой основной выпуск СУБД Firebird, разработка которого началась в мае 2021 года. В Firebird 5.0 команда разработчиков сосредоточила свои усилия на повышении производительности СУБД: параллельное выполнение backup, restore, sweep, создания и перестроение индексов, улучшение масштабирования в многопользовательской среде, ускорение повторной подготовки запросов (кеш компилированных запросов), улучшение оптимизатора, улучшение алгоритма сжатия записей. Кроме того, появились и новые возможности в языке SQL и PSQL.
В версии Firebird 5 также появился встроенный инструмент для профилирования SQL и PSQL, что существенно облегчит поиск узких мест и отладку сложных SQL.
Базы данных, созданные в Firebird 5.0 имеют версию ODS (On-Disk Structure) 13.1. Firebird 5.0 позволяет работать и с базами данных с ODS 13.0 (созданные в Firebird 4.0), но при этом некоторые возможности будут недоступны.
Для того чтобы переход на Firebird 5.0 был проще, в утилиту командной строки gfix
был добавлен новый переключатель -upgrade
, который позволяет обновлять минорную версию ODS без длительных операций backup и restore.
Также хочется отметить тот факт, что новый релиз Firebird доступен сразу на 11 платформах, включая ARM для Linux и Android. Скачать готовые сборки и дистрибутивы можно по адресу https://firebirdsql.org/en/firebird-5-0/.
Далее я перечислю ключевые улучшения, сделанные в Firebird 5.0, и их краткое описание. Подробное описание всех изменений можно прочитать в Firebird 5.0 Release Notes. Кроме того подробный разбор новых функций Firebird 5.0 вы можете найти в серии статей на ресурсе ibase.ru.
Улучшение алгоритма сжатия данных
Как известно, в Firebird записи таблиц располагаются на страницах данных (DP) в сжатом виде. Это сделано для того, чтобы на одной странице поместилось как можно больше записей, а это в свою очередь экономит дисковый ввод-вывод. До Firebird 5.0 для сжатия записей использовался классический алгоритм Run Length Encoding (RLE).
В Firebird 5.0 был разработан усовершенствованный алгоритм сжатия RLE (со счётчиком переменной длины). Этот алгоритм доступен
только в базах данных с ODS 13.1 и выше.
Улучшенный алгоритм сжатия RLE имеет следующие преимущества:
- записи таблиц, содержащие поля с типом
VARCHAR(N)
хранятся компактнее, если N достаточно велико. Особенно это актуально для полей с кодировкой UTF8, в которой для хранения одного символа резервируется 4 байта; - короткие последовательности (менее 8 одинаковых байт) больше не сжимаются. С одной стороны, степень сжатия записи может стать немного ниже, чем было ранее, но с другой — это экономит процессорное время;
- в некоторых случаях запись невозможно сжать, и при применении алгоритма RLE её размер может увеличиться. Начиная с ODS 13.1, если сжатая запись длиннее оригинала, то она сохраняется "как есть" и помечается специальным флагом.
Благодаря новому алгоритму сжатия записей извлечение записей и их распаковка происходит быстрее на 5-10%.
Параллельное выполнение задач
Начиная с версии 5.0, Firebird может выполнять некоторые задачи, используя несколько потоков параллельно. Часть этих задач
использует параллелизм на уровне ядра Firebird, другие реализованы непосредственно в утилитах.
Для обработки задачи с несколькими потоками движок Firebird запускает дополнительные рабочие потоки и создает внутренние рабочие соединения.
По умолчанию параллельное выполнение отключено. Существует два способа включить параллелизм в пользовательском соединении:
- Установить количество параллельных рабочих процессов в DPB, используя тег
isc_dpb_parallel_workers
; - Установить количество параллельных рабочих процессов по умолчанию с помощью параметра
ParallelWorkers
вfirebird.conf
.
Некоторые утилиты (gfix
, gbak
), поставляемые с Firebird, имеют ключ командной строки -parallel
для установки количества параллельных рабочих процессов.
В настоящее время параллелизм может быть использован в следующих задачах:
- Создание резервной копии с помощью утилиты
gbak
(до 4 раз быстрее); - Восстановление из резервной копии с помощью утилиты
gbak
(до 3 раз быстрее); - Ручной sweep с помощью утилиты
gfix
и автоматический sweep (до 6 раз быстрее); - Обновление icu с помощью утилиты
gfix
; - Построение и перестроение индексов (
CREATE INDEX ...
,ALTER INDEX ... ACTIVE
).
Кэш подготовленных запросов
Любой SQL запрос проходит две обязательные стадии: подготовку (компиляцию) и собственно выполнение.
Во время подготовки запроса происходит его синтаксический разбор, выделение буферов под входные и выходные сообщения, построение плана запроса и дерева его выполнения.
Если в приложении требуется многократное выполнение одного и того же запроса с разным набором входных параметров, то обычно отдельно вызывается prepare, хендл
подготовленного запроса сохраняется в приложении, а затем для этого хендла вызывается execute. Это позволяется сократить затраты на переподготовку одного и того же запроса
при каждом выполнении.
Начиная с Firebird 5.0 поддерживается кэш компилированных (подготовленных) запросов для каждого соединения.
Это позволяет сократить затраты для повторной подготовки одних и тех же запросов,
если в вашем приложении не используется явное кэширование хендлов подготовленных запросов (на глобальном уровне это не всегда просто).
Помимо запросов верхнего уровня в кэш подготовленных запросов попадают также хранимые процедуры, функции и триггеры. Содержимое кэша компилированных запросов можно
посмотреть с помощью новой таблицы мониторинга MON$COMPILED_STATEMENTS
.
Поддержка двунаправленных курсоров в сетевом протоколе
Поддержка двунаправленных (прокручиваемых) курсоров впервые появилась в Firebird 3.0. Они так же доступны в PSQL и через API интерфейс.
Однако до Firebird 5.0 прокручиваемые курсоры не поддерживались на уровне сетевого протокола. Это обозначает, что вы могли использовать использовать API двунаправленных курсоров в своём приложении, только если ваше подключение происходит в embedded режиме. Начиная с Firebird 5.0, вы можете использовать API прокручиваемых курсоров даже если соединяетесь с базой данных по сетевому протоколу, при этом клиентская библиотека fbclient должна быть не ниже версии 5.0.
Если ваше приложение не использует fbclient (например, оно написано на Java или .NET), то соответствующий драйвер должен поддерживать сетевой протокол Firebird 5.0. Например, Jaybird 5 поддерживает двунаправленные курсоры в сетевом протоколе.
Улучшение оптимизатора
В Firebird 5.0 оптимизатор запросов подвергся самым значительным изменениям со времён Firebird 2.0. Далее я перечислю что именно изменилось в оптимизаторе Firebird 5.0.
Стоимостная оценка HASH vs NESTED LOOP JOIN
Соединение потоков с помощью алгоритма HASH JOIN появилось в Firebird 3.0. До Firebird 5.0 метод соединения HASH JOIN применялся только при отсутствии индексов по условию связи или их неприменимости, в противном случае оптимизатор выбирал алгоритм NESTED LOOP JOIN с использованием индексов. Это не всегда оптимально. Если большой поток соединяется с маленькой таблицей по первичному ключу, то каждая запись такой таблицы будет читаться многократно, кроме того, многократно будут прочтены и страницы индексов, если они используются. При использовании соединения HASH JOIN меньшая таблица будет прочитана ровно один раз.
Естественно, стоимость хеширования и пробирования не бесплатны, поэтому выбор, какой алгоритм применять, происходит на основе стоимости.
Стоимостная оценка HASH vs MERGE JOIN
Алгоритм соединения слиянием MERGE JOIN был временно отключен в Firebird 3.0 в пользу соединения алгоритмом HASH JOIN. Обычно он применялся в тех случаях, когда использование алгоритма NESTED LOOP JOIN было неоптимальным, то есть, в первую очередь при отсутствии индексов по условию связи или их неприменимости, а также при отсутствии зависимости между входными потоками.
В большинстве случаев соединение методом HASH JOIN более эффективно, поскольку не требуется выполнять предварительную сортировку потоков по ключам соединения, но есть случаи, когда MERGE JOIN более эффективен. Теперь в ряде случаев оптимизатор может предпочесть алгоритм соединения MERGE JOIN вместо HASH JOIN.
Трансформация OUTER JOIN в INNER JOIN
Существует ряд проблем с оптимизацией OUTER JOINs в Firebird.
Во-первых, в настоящее время OUTER JOIN может быть выполнен только одним алгоритмом соединения NESTED LOOP JOIN, что может быть изменено в следующих версиях. Если возможно, то будет использован индекс по ключу присоединяемой таблицы, но как мы уже видели выше — это не гарантирует наиболее быстрое выполнение.
Во-вторых, при соединении потоков внешними соединениями порядок соединения строго фиксирован, то есть, оптимизатор не может изменить его, чтобы результат оставался правильным.
Однако, если в условии WHERE существует предикат для поля "правой" (присоединяемой) таблицы, который явно не обрабатывает значение NULL
, то во внешнем соединении нет смысла. В этом случае начиная с Firebird 5.0 такое соединение будет преобразовано во внутреннее, что позволяет оптимизатору применять весь спектр доступных алгоритмов соединения.
Если LEFT JOIN
использовался в качестве подсказки для указания порядка соединения очень активно, то переписать множество запросов на новый лад может быть проблематично. Для таких разработчиков существует параметр конфигурации OuterJoinConversion
в firebird.conf
или database.conf
. Установка параметра OuterJoinConversion
в false
отключает трансформацию Outer Join во внутренние соединение. Обратите внимание на то, что этот параметр является временным решением для облегчения миграции, в будущих версиях Firebird он может быть удалён.
Раннее вычисление инвариантных предикатов
Начиная с Firebird 5.0, если фильтрующий предикат инвариантен, то он не вычисляется заново для каждой новой записи. Если значение инвариантного предиката равно FALSE, то извлечение записей из входного потока немедленно прекращается. Предикат является инвариантным, если его значение не зависит от полей фильтруемых потоков.
Простейшим случаем инвариантного предиката является "фейковое" ложное условие фильтрации 1=0
, однако существуют и более сложные случаи.
Эффективное выполнение IN со списком констант
До Firebird 5.0 предикат IN со списком констант был ограничен 1500 элементами, поскольку обрабатывался рекурсивно, преобразуя исходное выражение в эквивалентную форму.
То есть,
F IN (V1, V2, ... VN)
преобразуется в
F = V1 OR F = V2 OR .... F = VN
Начиная с Firebird 5.0, обработка предикатов IN <list>
является линейной. Лимит в 1500 элементов увеличен до 65535 элементов.
Списки констант в IN, предварительно оцениваются как инварианты и кэшируются как двоичное дерево поиска, что ускоряет сравнение, если условие необходимо проверить для многих записей или если список значений длинный.
Если список очень длинный или если предикат IN не является избирательным, то сканирование индекса поддерживает поиск групп с использованием указателя одного уровня (т. е. по горизонтали), а не поиск каждой группы от корня (т. е. по вертикали), таким образом, используя одно сканирование индекса для всего списка IN.
Стратегия оптимизатора ALL ROWS vs FIRST ROWS
Существует две стратегии оптимизации запросов:
- FIRST ROWS — оптимизатор строит план запроса так, чтобы наиболее быстро извлечь
только первые строки запроса; - ALL ROWS — оптимизатор строит план запроса так, чтобы наиболее быстро извлечь все
строки запроса.
Начиная с Firebird 5.0, по умолчанию используется стратегия оптимизации, указанная в параметре OptimizeForFirstRows
конфигурационного файла firebird.conf
или database.conf
.
OptimizeForFirstRows = false
соответствует стратегии ALL ROWS
, OptimizeForFirstRows = true
соответствует стратегии FIRST ROWS
.
Вы можете изменить стратегию оптимизатора на уровне текущей сессии с помощью оператора:
SET OPTIMIZE FOR {FIRST | ALL} ROWS
Кроме того, стратегия оптимизации может быть переопределена на уровне SQL оператора с помощью предложения OPTIMIZE FOR
. SELECT запрос с предложением OPTIMIZE FOR
имеет следующий синтаксис:
SELECT ...
FROM [...]
[WHERE ...]
[...]
[OPTIMIZE FOR {FIRST | ALL} ROWS]
Предложение OPTIMIZE FOR
всегда указывает самым последним в SELECT запросе. В PSQL его необходимо указывать перед предложением INTO
.
Новые возможности в языке SQL
Поддержка предложения WHEN NOT MATCHED BY SOURCE
в операторе MERGE
Оператор MERGE
производит слияние записей источника и целевой таблицы (или
обновляемым представлением). В процессе выполнения оператора MERGE
читаются записи источника и выполняются INSERT
, UPDATE
или DELETE
для целевой таблицы в зависимости от условий.
Синтаксис оператора MERGE
выглядит следующим образом:
MERGE
INTO _target_ [[AS] _target_alias_]
USING <source> [[AS] _source_alias_]
ON <join condition>
<merge when> [<merge when> ...]
[<plan clause>]
[<order by clause>]
[<returning clause>]
``` ::= _tablename_ | (<select_stmt>)
<merge when> ::=
<merge when matched>
| <merge when not matched by target>
| <merge when not matched by source>
<merge when matched> ::=
WHEN MATCHED [ AND <condition> ]
THEN { UPDATE SET <assignment_list> | DELETE }
<merge when not matched by target> ::=
WHEN NOT MATCHED [ BY TARGET ] [ AND <condition> ]
THEN INSERT [ <left paren> <column_list> <right paren> ]
VALUES <left paren> <value_list> <right paren>
<merge when not matched by source> ::=
WHEN NOT MATCHED BY SOURCE [ AND <condition> ] THEN
{ UPDATE SET <assignment list> | DELETE }
В Firebird 5.0 появились условные ветки <merge when not matched by source>
, которые позволяют обновить или удалить записи из целевой таблицы, если они отсутствуют в источнике данных.
Теперь оператор MERGE
является по-настоящему универсальным комбайном для любых модификаций целевой таблицы по некоторому набору данных.
Пример использования:
MERGE INTO price
USING tmp_price
ON price.good_id = tmp_price.good_id
WHEN NOT MATCHED
-- добавляем если не было
THEN INSERT(good_id, name, cost)
VALUES(tmp_price.good_id, tmp_price.name, tmp_price.cost)
WHEN MATCHED AND price.cost <> tmp_price.cost THEN
-- обновляем цену, если товар есть в новом прайсе и цена отличается
UPDATE SET cost = tmp_price.cost
WHEN NOT MATCHED BY SOURCE
-- если в новом прайсе товара нет, то удаляем его из текущего прайса
DELETE;
Предложение SKIP LOCKED
В Firebird 5.0 появилось предложение SKIP LOCKED
, которое может использоваться в операторах SELECT .. WITH LOCK
, UPDATE
и DELETE
. Использование этого предложения заставляет движок пропускать записи, заблокированные другими транзакциями, вместо того, чтобы ждать их, или вызывать ошибки конфликта обновления.
Использование SKIP LOCKED
полезно для реализации рабочих очередей, в которых один или несколько процессов отправляют работу в таблицу и выдают событие, в то время как рабочие потоки прослушивают события и читают/удаляют элементы из таблицы. Используя SKIP LOCKED
, несколько работников могут получать эксклюзивные задания из таблицы без конфликтов.
SELECT
[FIRST ...]
[SKIP ...]
FROM <sometable>
[WHERE ...]
[PLAN ...]
[ORDER BY ...]
[{ ROWS ... } | {OFFSET ...} | {FETCH ...}]
[FOR UPDATE [OF ...]]
[WITH LOCK [SKIP LOCKED]]
UPDATE <sometable>
SET ...
[WHERE ...]
[PLAN ...]
[ORDER BY ...]
[ROWS ...]
[SKIP LOCKED]
[RETURNING ...]
DELETE FROM <sometable>
[WHERE ...]
[PLAN ...]
[ORDER BY ...]
[ROWS ...]
[SKIP LOCKED]
[RETURNING ...]
Пример использования SKIP LOCKED
Подготовка метаданных
create table emails_queue (
subject varchar(60) not null,
text blob sub_type text not null
);
set term !;
create trigger emails_queue_ins after insert on emails_queue
as
begin
post_event('EMAILS_QUEUE');
end!
set term ;!
Отправка приложением или подпрограммой
insert into emails_queue (subject, text)
values ('E-mail subject', 'E-mail text...');
commit;
Клиентское приложение
-- Клиентское приложение может прослушивать событие EMAILS_QUEUE,
-- чтобы отправлять электронные письма, используя этот запрос:
delete from emails_queue
rows 10
skip locked
returning subject, text;
Может быть запущено более одного экземпляра приложения, например, для балансировки нагрузки.
Частичные индексы
В Firebird 5.0 при создании индекса появилась возможность указать необязательное предложение WHERE
, которое определяет условие поиска, ограничивающее подмножество записей таблицы для индексирования. Такие индексы называются частичными индексами. Условие поиска должно содержать один или несколько столбцов таблицы.
Определение частичного индекса может включать спецификацию UNIQUE
. В этом случае каждый ключ в индексе должен быть уникальным. Это позволяет обеспечить уникальность для некоторого подмножества строк таблицы.
Определение частичного индекса также может включать предложение COMPUTED BY
, таким образом, частичный индекс может быть вычисляемым.
Полный синтаксис создания индекса выглядит следующим образом:
CREATE [UNIQUE] [ASC[ENDING] | DESC[ENDING]]
INDEX _indexname_ ON _tablename_
{(<column_list>) | COMPUTED [BY] (<value_expression>)}
[WHERE <search_condition>]
<column_list> ::= _col_ [, _col_ ...]
Пример создания частичных индексов
--- частичный индекс с исключением значения NULL
CREATE INDEX IDX_HORSE_DEATHDATE
ON HORSE(DEATHDATE) WHERE DEATHDATE IS NOT NULL;
-- частичный индекс с условием фильтрации не входящим в ключ индекса
CREATE UNIQUE INDEX IDX_UNIQUE_DEFAULT_MAN_EMAIL
ON MAN_EMAILS(CODE_MAN) WHERE DEFAULT_FLAG IS TRUE;
Пакет RDB$BLOB_UTIL
Традиционно работа c BLOB внутри PSQL кода обходилась дорого, поскольку при любой модификации BLOB всегда создаётся новый временный BLOB. Это приводит к дополнительному потреблению памяти, а в ряде случае и к разрастанию файла базы данных для хранения временных BLOB.
В Firebird 4.0.2 для решения проблем конкатенации BLOB была добавлена встроенная функция BLOB_APPEND
. В Firebird 5.0 был добавлен встроенный пакет RDB$BLOB_UTIL
с процедурами и функциями, который позволяет достичь ещё большей эффективности при других манипуляциях с BLOB.
Средства для поиска узких мест
В Firebird 5.0 появился новый инструмент для поиска узких мест — профилировщик запросов.
Профилирование SQL и PSQL
Одной из задач разработчика или администратора базы данных является выяснение причин "тормозов" информационной системы.
После того, как медленные запросы "отловлены" трассировкой, можно приступать к их оптимизации. Однако такие запросы могут быть довольно сложны, а иногда и вовсе вызывать хранимые процедуры, поэтому необходим инструмент профилирования, который поможет выявить узкие места в самом запросе или вызываемом PSQL модуле. Начиная с Firebird 5.0, такой инструмент появился.
Профилировщик позволяет пользователям измерять затраты на производительность кода SQL и PSQL. Это реализовано с помощью системного пакета в движке, передающего данные в плагин профилировщика.
Пакет RDB$PROFILER
может профилировать выполнение кода PSQL, собирая статистику о том, сколько раз была выполнена каждая строка, а также ее минимальное, максимальное и суммарное время выполнения (с точностью до наносекунд), а также статистику об открытии и извлечении записей из неявных и явных SQL курсоров. Кроме того, можно получать статистику SQL курсоров в разрезе источников данных (методов доступа) расширенного плана запроса.
Чтобы собрать данные профилирования, пользователь должен сначала запустить сеанс профилирования с помощью функции RDB$PROFILER.START_SESSION
. Эта функция возвращает идентификатор сеанса профилирования, который позже сохраняется в таблицах снимков профилировщика. Позже вы можете выполнить запросы к этим таблицам для анализа пользователем. Сеанс профилировщика может быть локальным (то же соединение) или удаленным (другое соединение).
После запуска сеанса статистика PSQL и SQL операторов собирается в памяти. Сеанс профилирования собирает данные только об операторах, выполненных в соединении, связанном с сеансом. Данные агрегируются и сохраняются для каждого запроса (т. е. выполняемого оператора). При запросе к таблицам моментальных снимков пользователь может выполнять дополнительную агрегацию для каждого оператора или использовать вспомогательные представления, которые делают это автоматически.
Сеанс можно приостановить, чтобы временно отключить сбор статистики. Позже его можно возобновить, чтобы вернуть сбор статистики в том же сеансе.
Чтобы проанализировать собранные данные, пользователь должен сбросить данные в таблицы моментальных снимков, что можно сделать, завершив или приостановив сеанс (с параметром FLUSH
, установленным в TRUE), или вызвав RDB$PROFILER.FLUSH
. Данные сбрасываются с помощью автономной транзакции (транзакция начинается и завершается с конкретной целью обновления данных профилировщика).
Все процедуры и функции пакета RDB$PROFILER
содержат параметр ATTACHMENT_ID
, который следует указывать если вы хотите управлять удалённым сеансом профилирования; если этот параметр равен NULL
или не указан, то процедуры и функции управляют сеансом локального профилирования.
Пример использования профилировщика
Подготовка метаданных:
create table tab (
id integer not null,
val integer not null
);
set term !;
create or alter function mult(p1 integer, p2 integer) returns integer
as
begin
return p1 * p2;
end!
create or alter procedure ins
as
declare n integer = 1;
begin
while (n <= 1000)
do
begin
if (mod(n, 2) = 1) then
insert into tab values (:n, mult(:n, 2));
n = n + 1;
end
end!
set term ;!
Запуск профилировщика:
select rdb$profiler.start_session('Profile Session 1', null, null, null, 'DETAILED_REQUESTS') from rdb$database;
set term !;
execute block
as
begin
execute procedure ins;
delete from tab;
end!
set term ;!
execute procedure rdb$profiler.finish_session(true);
commit;
execute procedure ins;
commit;
select rdb$profiler.start_session('Profile Session 2') from rdb$database;
select mod(id, 5),
sum(val)
from tab
where id <= 50
group by mod(id, 5)
order by sum(val);
execute procedure rdb$profiler.finish_session(true);
commit;
Анализ данных профилирования:
set transaction read committed;
select * from plg$prof_sessions;
select * from plg$prof_psql_stats_view;
select * from plg$prof_record_source_stats_view;
select preq.*
from plg$prof_requests preq
join plg$prof_sessions pses
on pses.profile_id = preq.profile_id and
pses.description = 'Profile Session 1';
select pstat.*
from plg$prof_psql_stats pstat
join plg$prof_sessions pses
on pses.profile_id = pstat.profile_id and
pses.description = 'Profile Session 1'
order by pstat.profile_id,
pstat.request_id,
pstat.line_num,
pstat.column_num;
select pstat.*
from plg$prof_record_source_stats pstat
join plg$prof_sessions pses
on pses.profile_id = pstat.profile_id and
pses.description = 'Profile Session 2'
order by pstat.profile_id,
pstat.request_id,
pstat.cursor_id,
pstat.record_source_id;
Помимо новых инструментов, были расширены возможности и ранее существующих инструментов.
По табличная статистика в isql
По табличная статистика показывает сколько записей для каждой таблицы при выполнении запроса было прочитано полным сканированием, сколько с использованием индекса, сколько вставлено, обновлено или удалено и другие счётчики. Значения этих счётчиков с давних пор доступно через API функцию isc_database_info
, что использовалось многими графическими инструментами, но не консольным инструментом isql
. Значения этих же счётчиков можно получить через совместное использование таблиц мониторинга MON$RECORD_STATS
и MON$TABLE_STATS
или в трассировке. Начиная с Firebird 5.0, эта полезная функция появилась и в isql
.
По умолчанию вывод по табличной статистики выключен.
Для её включения необходимо набрать команду:
SET PER_TAB ON;
А для отключения:
SET PER_TAB OFF;
Команда SET PER_TAB
без слов ON
или OFF
переключает состояние вывода статистики.
Полный синтаксис этой команды можно получить, используя команду HELP SET
.
Пример вывода по табличной статистики:
SQL> SET PER_TAB ON;
SQL> SELECT COUNT(*)
CON> FROM HORSE
CON> JOIN COLOR ON COLOR.CODE_COLOR = HORSE.CODE_COLOR
CON> JOIN BREED ON BREED.CODE_BREED = HORSE.CODE_BREED;
COUNT
=====================
519623
Per table statistics:
--------------+---------+---------+---------+---------+---------+---------+---------+---------+
Table name | Natural | Index | Insert | Update | Delete | Backout | Purge | Expunge |
--------------+---------+---------+---------+---------+---------+---------+---------+---------+
BREED | 282| | | | | | | |
COLOR | 239| | | | | | | |
HORSE | | 519623| | | | | | |
--------------+---------+---------+---------+---------+---------+---------+---------+---------+
Улучшенный вывод планов
В выводе подробного плана теперь различаются определяемые пользователем операторы SELECT (сообщаемые как select expression), объявленные PSQL курсоры и подзапросы (sub-query). Как legacy, так и explain планы теперь также включают информацию о положении курсора (строка/столбец) внутри модуля PSQL.
Планы хранимых процедур
Можно ли получать планы хранимых процедур по аналогии с тем, как мы получаем планы для EXECUTE BLOCK
?
Ответ: и да и нет.
Если мы пойдёт простым путём, то есть попытаемся посмотреть план процедуры для следующего запроса, то ответ будет "Нет".
SELECT *
FROM SP_PEDIGREE(?, 5, 1)
Select Expression
-> Procedure "SP_PEDIGREE" Scan
Как и ожидалось, отображён план запроса верхнего уровня без деталей планов курсоров внутри хранимой процедуры. До Firebird 3.0 такие детали отображались в плане, но они были перемешаны в кучу и разобрать там что-либо было очень затруднительно.
Но не расстраивайтесь. В Firebird 5.0 появился кеш подготовленных запросов, и таблица мониторинга MON$COMPILED_STATEMENTS
отображает его содержимое. Как только мы подготовили запрос, содержащий нашу хранимую процедуру, то эта процедура также попадает в кеш компилированных запросов и для неё можно посмотреть план с помощью следующего запроса:
SELECT CS.MON$EXPLAINED_PLAN
FROM MON$COMPILED_STATEMENTS CS
WHERE CS.MON$OBJECT_NAME = 'SP_PEDIGREE'
AND CS.MON$OBJECT_TYPE = 5
ORDER BY CS.MON$COMPILED_STATEMENT_ID DESC
FETCH FIRST ROW ONLY
Cursor "V" (scrollable) (line 19, column 3)
-> Record Buffer (record length: 132)
-> Nested Loop Join (inner)
-> Window
-> Window Partition
-> Record Buffer (record length: 82)
-> Sort (record length: 84, key length: 12)
-> Window Partition
-> Window Buffer
-> Record Buffer (record length: 41)
-> Procedure "SP_HORSE_INBRIDS" as "V H_INB SP_HORSE_INBRIDS" Scan
-> Filter
-> Table "HUE" as "V HUE" Access By ID
-> Bitmap
-> Index "HUE_IDX_ORDER" Range Scan (full match)
Select Expression (line 44, column 3)
-> Recursion
-> Filter
-> Table "HORSE" as "PEDIGREE HORSE" Access By ID
-> Bitmap
-> Index "PK_HORSE" Unique Scan
-> Union
-> Filter (preliminary)
-> Filter
-> Table "HORSE" as "PEDIGREE HORSE" Access By ID
-> Bitmap
-> Index "PK_HORSE" Unique Scan
-> Filter (preliminary)
-> Filter
-> Table "HORSE" as "PEDIGREE HORSE" Access By ID
-> Bitmap
-> Index "PK_HORSE" Unique Scan
Трассировка события COMPILE
В Firebird 5.0 появилась возможность отслеживать новое событие трассировки: парсинг хранимых модулей. Оно позволяет отслеживать моменты парсинга хранимых модулей, соответствующее затраченное время и самое главное — планы запросов внутри этих модулей PSQL. Отслеживание плана также возможно, если модуль PSQL уже был загружен до начала сеанса трассировки; в этом случае о плане будет сообщено во время первого выполнения, замеченного сеансом трассировки.
Для отслеживания события парсинга модуля в конфигурации трассировки появились следующие параметры:
log_procedure_compile
— включает трассировку событий парсинга процедур;log_function_compile
— включает трассировку событий парсинга функций;log_trigger_compile
— включает трассировку событий парсинга триггеров.
Допустим, у нас есть следующий запрос:
SELECT * FROM SP_PEDIGREE(7435, 8, 1);
Для того чтобы в сеансе трассировки отслеживать план хранимой процедуры, необходимо установить параметр log_procedure_compile = true
. В этом случае при подготовке этого запроса или его выполнении в логе трассировки появится событие парсинга процедуры, которое выглядит так:
2023-10-18T20:40:51.7620 (3920:00000000073A17C0) COMPILE_PROCEDURE
horses (ATT_30, SYSDBA:NONE, UTF8, TCPv6:::1/54464)
C:\Firebird\5.0\isql.exe:10960
Procedure SP_PEDIGREE:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Cursor "V" (scrollable) (line 19, column 3)
-> Record Buffer (record length: 132)
-> Nested Loop Join (inner)
-> Window
-> Window Partition
-> Record Buffer (record length: 82)
-> Sort (record length: 84, key length: 12)
-> Window Partition
-> Window Buffer
-> Record Buffer (record length: 41)
-> Procedure "SP_HORSE_INBRIDS" as "V H_INB SP_HORSE_INBRIDS" Scan
-> Filter
-> Table "HUE" as "V HUE" Access By ID
-> Bitmap
-> Index "HUE_IDX_ORDER" Range Scan (full match)
Select Expression (line 44, column 3)
-> Recursion
-> Filter
-> Table "HORSE" as "PEDIGREE HORSE" Access By ID
-> Bitmap
-> Index "PK_HORSE" Unique Scan
-> Union
-> Filter (preliminary)
-> Filter
-> Table "HORSE" as "PEDIGREE HORSE" Access By ID
-> Bitmap
-> Index "PK_HORSE" Unique Scan
-> Filter (preliminary)
-> Filter
-> Table "HORSE" as "PEDIGREE HORSE" Access By ID
-> Bitmap
-> Index "PK_HORSE" Unique Scan
28 ms
Заключение
На этом обзор новинок Firebird 5.0 окончен. Разработчики Firebird проделали огромную работу, за что им огромная благодарность.
Подробное описание всех изменений можно прочитать в Firebird 5.0 Release Notes. Кроме того подробный разбор новых функций Firebird 5.0 вы можете найти в серии статей на ресурсе ibase.ru.