Этот материал был создан при поддержке и спонсорстве компании iBase.ru, которая разрабатывает инструменты Firebird SQL для предприятий и предоставляет сервис технической поддержки для Firebird SQL.
Материал выпущен под лицензией Public Documentation License https://www.firebirdsql.org/file/documentation/html/en/licenses/pdl/public-documentation-license.html
Все части
- Что Нового в Firebird 5.0. Часть 1: Оптимизатор - в формате pdf
- Что Нового в Firebird 5.0, Часть 2: SQLs - в формате pdf
- Что нового в Firebird 5.0, Часть 3: SKIP LOCKED - в формате pdf
- Что нового в Firebird 5.0. Часть 4. Параллельные возможности бэкап/рестор/sweep/сздание индекса - в формате PDF
- Что нового в Firebird 5.0. Часть 5.Кеш подготовленных запросов, сжатие данных и другие улучшения
- Что нового в Firebird 5.0. Часть 6. Профилирование SQL запросов - скоро!
- Параллельное чтение данных в Firebird - статья Д.Симонов, В.Хорсун
Предисловие
Недавно вышел релиз СУБД Firebird 5.0, а это обозначает, что пришло время ознакомиться с новыми возможностями. Это восьмой основной выпуск СУБД Firebird, разработка которого началась в мае 2021 года.
В Firebird 5.0 команда разработчиков сосредоточила свои усилия на повышение производительности СУБД в различных аспектах, таких как:
-
параллельное выполнение для распространённых задач: backup, restore, sweep, создание и перестроение индекса;
-
улучшение масштабирования в многопользовательской среде;
-
ускорение повторной подготовки запросов (кеш компилированных запросов);
-
улучшение оптимизатора;
-
улучшение алгоритма сжатия записей;
-
поиск узких мест с помощью плагина профилирования.
Поскольку объём материала довольно большой, то я разделю описание новых функций на несколько частей:
-
улучшение в оптимизаторе запросов;
-
новые возможности в языке SQL Firebird 5.0;
-
параллелизм и другие функции появившиеся в Firebird 5.0;
-
поиск узких места с помощью плагина PSQL профилирования.
В прошлых частях я рассказал о улучшении оптимизатора, новинках SQL и параллельных возможностях. Теперь я расскажу о других не менее важных функциях и улучшениях в Firebird 5.0.
1. Обновление ODS
Традиционным способом обновления ODS (On-Disk Structure) является выполнение backup на старой версии Firebird и restore на новой. Это довольно длительный процесс, особенно на больших базах данных.
Однако в случае обновления минорной версии ODS (номер после точки) backup/restore является избыточным (необходимо лишь добавить недостающие системные таблицы и поля, а также некоторые пакеты). Примером такого обновления является обновление ODS 13.0 (Firebird 4.0) до ODS 13.1 (Firebird 5.0), поскольку мажорная версия ODS 13 осталось той же.
Начиная с Firebird 5.0 появилась возможность обновления минорной версии ODS без длительный операция backup и restore. Для этого используется утилита gfix
с переключателем -upgrade
.
Ключевые моменты:
-
Обновление необходимо производить вручную с помощью команды
gfix -upgrade
-
Требуется монопольный доступ к базе данных, в противном случае выдается ошибка.
-
Требуется системная привилегия
USE_GFIX_UTILITY
. -
Обновление является транзакционным, все изменения отменяются в случае возникновения ошибки.
-
После обновления Firebird 4.0 больше не может открывать базу данных.
Использование:
gfix -upgrade -user ... -pass ...
|
2. Улучшение алгоритма сжатия данных
Как известно, в Firebird записи таблиц располагаются на страницах данных (DP - Data Pages) в сжатом виде. Это сделано для того, чтобы на одной странице поместилось как можно больше записей, а это в свою очередь экономит дисковый ввод-вывод. До Firebird 5.0 для сжатия записей использовался классический алгоритм Run Length Encoding (RLE).
Классический алгоритм RLE работает следующим образом. Последовательность повторяющихся символов сокращается до управляющего байта, который определяет количество повторений, за которым следует фактический повторяемый байт. Если данные не могут быть сжаты, управляющий байт указывает, что "следующие n байт должны выводиться без изменений".
Управляющий байт используется следующим образом:
-
n > 0
[1 .. 127] - следующие n байт сохраняются как есть; -
n < 0
[-3 .. -128] - следующий байт повторяется n раз, но сохраняется только один раз; -
n = 0
- конец данных. Обычно заполняющий байт.
В основном RLE эффективен для сжатия хвостовых нулей в полях типа VARCHAR(N)
, которые заполнены не целиком или равны NULL. Он достаточно быстрый и не сильно нагружает процессор в отличие от алгоритмов на основе словарей, таких как LHZ, ZIP, GZ.
Но у классического алгоритма RLE есть недостатки:
-
максимальная степень сжатия составляет 64 раза: управляющий байт может закодировать 128 повторяющихся байтов превращая их в 2 байта. Таким образом 32000 одинаковых байт будут занимать 500 байт. Эта проблема особенно усугубилась в последнее время с приходом кодировки UTF8, где на каждый символ отводится 4 байта.
-
в некоторых случаях сжатая последовательность байт может стать длиннее, чем не сжатая, если данные несжимаемые.
-
частое чередование коротких сжимаемых и несжимаемых последовательностей дополнительно нагружает процессор, нивелируя тем самым выигрыш от экономии дискового ввода-вывода.
Поэтому в Firebird 5.0 был разработан усовершенствованный алгоритм сжатия RLE (со счётчиком переменной длины). Этот алгоритм доступен только в базах данных с ODS 13.1 и выше.
Обновление ODS с помощью
gfix -upgrade не изменяет страницы данных пользовательских таблиц, таким образом записи не будут перепакованы с помощью нового алгоритма сжатия RLE. Но вновь вставляемые записи будут сжаты с помощью усовершенствованного RLE. |
Усовершенствованный алгоритм RLE работает следующим образом. Две ранее не используемые длины -1 и -2 используется как специальные маркеры для более длинных сжимаемых последовательностей:
-
{-1, двух-байтный счётчик, значение байта}
- повторяющиеся последовательности длинной от 128 байт до 64 КБайт; -
{-2, четырёх-байтный счётчик, значение байта}
- повторяющиеся последовательности длинной более 64 КБайт.
Сжимаемые последовательности длинной 3 байта не имеют смысла если расположены между двумя несжимаемыми прогонами. Сжимаемые последовательности длинной от 4 до 8 байт являются пограничным случаем, поскольку они не сильно сжимаются, но увеличивают общее количество прогонов, что негативно влияет на скорость распаковки. Начиная с Firebird 5.0 фрагменты короче 8 байт не сжимаются.
Кроме того в Firebird 5.0 (ODS 13.1) есть ещё одно улучшение: если в результате применения алгоритма сжатия RLE к записи последовательность байт оказалась длиннее (несжимаемые данные), то запись будет записана на страницу как есть и помечена специальным флагом как несжатая.
Теперь покажу на примерах как новый алгоритм RLE увеличивает производительность запросов.
Для начала отмечу, что сжатие записей совсем не бесплатная операция. В этом легко убедится выполнив два запроса:
SELECT COUNT(*) FROM BIG_TABLE;
SELECT COUNT(SOME_FIELD) FROM BIG_TABLE;
Первый запрос не использует распаковку записей, поскольку нас не интересует их содержимое (достаточно просто посчитать количество). Второй запрос вынужден делать распаковку каждой записи, чтобы убедится что в поле SOME_FIELD
не NULL. Сначала посмотрим как это выполняется в Firebird 4.0.
SELECT COUNT(*)
FROM WORD_DICTIONARY;
COUNT ===================== 4079052 Current memory = 2610594912 Delta memory = 0 Max memory = 2610680272 Elapsed time = 0.966 sec Buffers = 153600 Reads = 0 Writes = 0 Fetches = 4318077
SELECT COUNT(CODE_DICTIONARY)
FROM WORD_DICTIONARY;
COUNT ===================== 4079052 Current memory = 2610596096 Delta memory = 1184 Max memory = 2610685616 Elapsed time = 1.770 sec Buffers = 153600 Reads = 0 Writes = 0 Fetches = 4318083
1.770 - 0.966 = 0.804
- большая часть этого времени это как раз затраты на распаковку записей.
Теперь посмотрим то же самое на Firebird 5.0.
SELECT COUNT(*)
FROM WORD_DICTIONARY;
COUNT ===================== 4079052 Current memory = 2577478608 Delta memory = 176 Max memory = 2577562528 Elapsed time = 0.877 sec Buffers = 153600 Reads = 0 Writes = 0 Fetches = 4342385
SELECT COUNT(CODE_DICTIONARY)
FROM WORD_DICTIONARY;
COUNT ===================== 4079052 Current memory = 2577491280 Delta memory = 12672 Max memory = 2577577520 Elapsed time = 1.267 sec Buffers = 153600 Reads = 0 Writes = 0 Fetches = 4342393
1.267 - 0.877 = 0.390
- это вдвое меньше чем в Firebird 4.0. Давайте глянем на статистику этой таблицы в Firebird 4.0 и Firebird 5.0.
WORD_DICTIONARY (265) Primary pointer page: 855, Index root page: 856 Total formats: 1, used formats: 1 Average record length: 191.83, total records: 4079052 Average version length: 0.00, total versions: 0, max versions: 0 Average fragment length: 0.00, total fragments: 0, max fragments: 0 Average unpacked length: 670.00, compression ratio: 3.49 Pointer pages: 19, data page slots: 59752 Data pages: 59752, average fill: 87% Primary pages: 59752, secondary pages: 0, swept pages: 0 Empty pages: 1, full pages: 59750 Fill distribution: 0 - 19% = 1 20 - 39% = 0 40 - 59% = 0 60 - 79% = 1 80 - 99% = 59750
WORD_DICTIONARY (265) Primary pointer page: 849, Index root page: 850 Total formats: 1, used formats: 1 Average record length: 215.83, total records: 4079052 Average version length: 0.00, total versions: 0, max versions: 0 Average fragment length: 0.00, total fragments: 0, max fragments: 0 Average unpacked length: 670.00, compression ratio: 3.10 Pointer pages: 21, data page slots: 65832 Data pages: 65832, average fill: 88% Primary pages: 65832, secondary pages: 0, swept pages: 0 Empty pages: 4, full pages: 65824 Fill distribution: 0 - 19% = 5 20 - 39% = 2 40 - 59% = 0 60 - 79% = 1 80 - 99% = 65824
Из приведённой статистики видно, что коэффициент сжатия даже меньше чем в Firebird 4.0. Так за счёт чего же такой колоссальный выигрыш? Для этого надо посмотреть на структуру этой таблицы:
CREATE TABLE WORD_DICTIONARY (
CODE_DICTIONARY BIGINT NOT NULL,
CODE_PART_OF_SPEECH INTEGER NOT NULL,
CODE_WORD_GENDER INTEGER NOT NULL,
CODE_WORD_AFFIXE INTEGER NOT NULL,
CODE_WORD_TENSE INTEGER DEFAULT -1 NOT NULL,
NAME VARCHAR(50) NOT NULL COLLATE UNICODE_CI,
PARAMS VARCHAR(80),
ANIMATE D_BOOL DEFAULT 'Нет' NOT NULL /* D_BOOL = VARCHAR(3) CHECK (VALUE IN('Да', 'Нет')) */,
PLURAL D_BOOL DEFAULT 'Нет' NOT NULL /* D_BOOL = VARCHAR(3) CHECK (VALUE IN('Да', 'Нет')) */,
INVARIABLE D_BOOL DEFAULT 'Нет' NOT NULL /* D_BOOL = VARCHAR(3) CHECK (VALUE IN('Да', 'Нет')) */,
TRANSITIVE D_BOOL DEFAULT 'Нет' NOT NULL /* D_BOOL = VARCHAR(3) CHECK (VALUE IN('Да', 'Нет')) */,
IMPERATIVE D_BOOL DEFAULT 'Нет' NOT NULL /* D_BOOL = VARCHAR(3) CHECK (VALUE IN('Да', 'Нет')) */,
PERFECT D_BOOL DEFAULT 'Нет' NOT NULL /* D_BOOL = VARCHAR(3) CHECK (VALUE IN('Да', 'Нет')) */,
CONJUGATION SMALLINT,
REFLEXIVE D_BOOL DEFAULT 'Нет' NOT NULL /* D_BOOL = VARCHAR(3) CHECK (VALUE IN('Да', 'Нет')) */,
PROHIBITION D_BOOL DEFAULT 'Нет' NOT NULL /* D_BOOL = VARCHAR(3) CHECK (VALUE IN('Да', 'Нет')) */
);
В этой таблице хорошо могут быть сжаты только поля NAME
и PARAMS
. Поскольку у полей типа INTEGER есть модификатор NOT NULL, и поле занимает 4 байта, то в Firebird 5.0 такие поля не сжимаются. Поля с доменом D_BOOL в кодировке UTF8 могут быть сжаты для значения 'Да' (12 - 4 = 8 байт) и не будут для значения 'Нет' (12 - 6 = 6 байт).
Поскольку в таблице много коротких последовательностей, которые могли быть сжаты Firebird 4.0 и не сжимаются в Firebird 5.0, то в Firebird 5.0 количество обрабатываемых прогонов для распаковки меньше, за счёт чего и получаем выигрыш в производительности.
Теперь я покажу пример, где новый алгоритм RLE сильно выигрывает в сжатии. Для этого выполним следующий скрипт:
CREATE TABLE GOOD_ZIP
(
ID BIGINT NOT NULL,
NAME VARCHAR(100),
DESCRIPTION VARCHAR(1000),
CONSTRAINT PK_GOOD_ZIP PRIMARY KEY(ID)
);
SET TERM ^;
EXECUTE BLOCK
AS
DECLARE I BIGINT = 0;
BEGIN
WHILE (I < 100000) DO
BEGIN
I = I + 1;
INSERT INTO GOOD_ZIP (
ID,
NAME,
DESCRIPTION
)
VALUES (
:I,
'OBJECT_' || :I,
'OBJECT_' || :I
);
END
END^
SET TERM ;^
COMMIT;
А теперь посмотрим статистику таблицы GOOD_ZIP в Firebird 4.0 и Firebird 5.0.
GOOD_ZIP (128) Primary pointer page: 222, Index root page: 223 Total formats: 1, used formats: 1 Average record length: 111.09, total records: 100000 Average version length: 0.00, total versions: 0, max versions: 0 Average fragment length: 0.00, total fragments: 0, max fragments: 0 Average unpacked length: 4420.00, compression ratio: 39.79 Pointer pages: 2, data page slots: 1936 Data pages: 1936, average fill: 81% Primary pages: 1936, secondary pages: 0, swept pages: 0 Empty pages: 0, full pages: 1935 Fill distribution: 0 - 19% = 0 20 - 39% = 0 40 - 59% = 1 60 - 79% = 5 80 - 99% = 1930
GOOD_ZIP (128) Primary pointer page: 225, Index root page: 226 Total formats: 1, used formats: 1 Average record length: 53.76, total records: 100000 Average version length: 0.00, total versions: 0, max versions: 0 Average fragment length: 0.00, total fragments: 0, max fragments: 0 Average unpacked length: 4420.00, compression ratio: 82.22 Pointer pages: 1, data page slots: 1232 Data pages: 1232, average fill: 70% Primary pages: 1232, secondary pages: 0, swept pages: 0 Empty pages: 2, full pages: 1229 Fill distribution: 0 - 19% = 3 20 - 39% = 0 40 - 59% = 0 60 - 79% = 1229 80 - 99% = 0
Как видите в данном случае коэффициент сжатия в Firebird 5.0 в два раза выше.
И наконец рассмотрим пример с несжимаемыми данными. Для этого выполним скрипт:
CREATE TABLE NON_ZIP
(
UID BINARY(16) NOT NULL,
REF_UID_1 BINARY(16) NOT NULL,
REF_UID_2 BINARY(16) NOT NULL
);
SET TERM ^;
EXECUTE BLOCK
AS
DECLARE I BIGINT = 0;
BEGIN
WHILE (I < 100000) DO
BEGIN
I = I + 1;
INSERT INTO NON_ZIP (
UID,
REF_UID_1,
REF_UID_2
)
VALUES (
GEN_UUID(),
GEN_UUID(),
GEN_UUID()
);
END
END^
SET TERM ;^
COMMIT;
А теперь посмотрим статистику таблицы NON_ZIP.
NON_ZIP (129) Primary pointer page: 2231, Index root page: 2312 Total formats: 1, used formats: 1 Average record length: 53.00, total records: 100000 Average version length: 0.00, total versions: 0, max versions: 0 Average fragment length: 0.00, total fragments: 0, max fragments: 0 Average unpacked length: 52.00, compression ratio: 0.98 Pointer pages: 1, data page slots: 1240 Data pages: 1240, average fill: 69% Primary pages: 1240, secondary pages: 0, swept pages: 0 Empty pages: 5, full pages: 1234 Fill distribution: 0 - 19% = 5 20 - 39% = 1 40 - 59% = 0 60 - 79% = 1234 80 - 99% = 0
NON_ZIP (129) Primary pointer page: 1587, Index root page: 1588 Total formats: 1, used formats: 1 Average record length: 52.00, total records: 100000 Average version length: 0.00, total versions: 0, max versions: 0 Average fragment length: 0.00, total fragments: 0, max fragments: 0 Average unpacked length: 52.00, compression ratio: 1.00 Pointer pages: 1, data page slots: 1240 Data pages: 1240, average fill: 68% Primary pages: 1240, secondary pages: 0, swept pages: 0 Empty pages: 5, full pages: 1234 Fill distribution: 0 - 19% = 5 20 - 39% = 1 40 - 59% = 0 60 - 79% = 1234 80 - 99% = 0
В Firebird 4.0 в результате компрессии длина записи увеличилась, Firebird 5.0 увидел, что в результате сжатия записи получаются длиннее и сохранил запись как есть.
3. Кэш подготовленных запросов
Любой SQL запрос проходит две обязательные стадии: подготовку (компиляцию) и собственно выполнение.
Во время подготовки запроса происходит его синтаксический разбор, выделение буферов под входные и выходные сообщения, построение плана запроса и дерева его выполнения.
Если в приложении требуется многократное выполнение одного и того же запроса с разным набором входных параметров, то обычно отдельно вызывается prepare, хендл подготовленного запроса сохраняется в приложении, а затем для этого хендла вызывается execute. Это позволяется сократить затраты на переподготовку одного и того же запроса при каждом выполнении.
Начиная с Firebird 5.0 поддерживается кэш компилированных (подготовленных) запросов для каждого соединения. Это позволяет сократить затраты для повторной подготовки одних и тех же запросов, если в вашем приложении не используется явное кэширование хендлов подготовленных запросов (на глобальном уровне это не всегда просто). По умолчанию кэширование включено, порог кэширования определяется параметром MaxStatementCacheSize
в firebird.conf
. Его можно отключить, установив для MaxStatementCacheSize
значение ноль. Кэш поддерживается автоматически: кэшированные операторы становятся недействительными, когда это необходимо (обычно при выполнении какого-либо оператора DDL).
Запрос считается одинаковым, если он совпадает с точностью до символа, то есть если у вас семантические одинаковые запросы, но они отличаются комментарием, то для кэша подготовленных запросов это разные запросы.
|
Помимо запросов верхнего уровня в кэш подготовленных запросов попадают также хранимые процедуры, функции и триггеры. Содержимое кэша компилированных запросов можно посмотреть с помощью новой таблицы мониторинга MON$COMPILED_STATEMENTS
.
Наименование столбца | Тип данных | Описание |
---|---|---|
|
|
Идентификатор скомпилированного запроса. |
|
|
Текст оператора на языке SQL. Внутри PSQL объектов текст SQL операторов не отображается. |
|
|
План оператора в explain форме. |
|
|
Имя PSQL объекта, в котором был компилирован SQL оператор. |
|
|
Тип объекта. |
|
|
Имя PSQL пакета. |
|
|
Идентификатор статистики. |
В таблицах MON$STATEMENTS
и MON$CALL_STACK
появился новый столбец MON$COMPILED_STATEMENT_ID
, который ссылается на соответствующий подготовленный оператор в MON$COMPILED_STATEMENTS
.
Таблица мониторинга 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
Обратите внимание, что одна и та же хранимая процедура может встречаться в
MON$COMPILED_STATEMENTS многократно. Это связанно с тем, что в настоящее время кэш подготовленных запросов сделан для каждого соединения. В следующих версиях планируется сделать кэш скомпилированных запросов и кэш метаданных общим для всех соединений в архитектуре Super Server. |
4. Поддержка двунаправленных курсоров в сетевом протоколе
Курсор в SQL - это объект, который позволяет перемещаться по записям любого результирующего набора. С его помощью можно обработать отдельную запись базы данных, возвращаемую запросом. Различают однонаправленные и двунаправленные (прокручиваемые) курсоры.
Однонаправленный курсор не поддерживает прокрутку, то есть получение записей из такого курсора возможно только последовательно, от начала до конца курсора. Этот вид курсоров доступен в Firebird с самых ранних версий, как в PSQL (явно объявленные и неявные курсоры), так и через API.
Прокручиваемый или двунаправленные курсор, позволяет перемещаться по курсору в любом направлении, двигаться скачками и даже перемещаться на заданную позицию. Поддержка двунаправленных (прокручиваемых) курсоров впервые появилась в 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 поддерживает двунаправленные курсоры в сетевом протоколе.
5. Трассировка события 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
6. Потабличная статистика в 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| | | | | | | --------------+---------+---------+---------+---------+---------+---------+---------+---------+
7. Заключение
Я рассказал о почти всех новинках Firebird 5.0.
У нас осталась ещё одна крупная функция - а именно профилировщик SQL и PSQL. О ней в следующий раз.