Что нового в Firebird 5.0. Кеш подготовленных запросов, сжатие данных и другие улучшения

Симонов Денис version 1.0 от 12.02.2024


Этот материал был создан при поддержке и спонсорстве компании iBase.ru, которая разрабатывает инструменты Firebird SQL для предприятий и предоставляет сервис технической поддержки для Firebird SQL.

Предисловие

Недавно вышел релиз СУБД 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 ... 
  • Это односторонняя модификация, возврат назад невозможен. Поэтому перед обновлением сделайте копию базы данных (с помощью nbackup -b 0), чтобы иметь точку восстановления, если что-то пойдет не так во время процесса.
  • Обновление ODS с помощью gfix -upgrade не изменяет страницы данных пользовательских таблиц, таким образом записи не будут перепакованы с помощью нового алгоритма сжатия RLE. Но вновь вставляемые записи будут сжаты с помощью усовершенствованного RLE.

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.

Статистика в Firebird 4.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
Статистика в Firebird 5.0
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.

Статистика в Firebird 4.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
Статистика в Firebird 5.0
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.

Статистика в Firebird 4.0
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
Статистика в Firebird 5.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.

Table 1. Описание столбцов таблицы MON$COMPILED_STATEMENTS
Наименование столбца Тип данных Описание

MON$COMPILED_STATEMENT_ID

BIGINT

Идентификатор скомпилированного запроса.

MON$SQL_TEXT

BLOB TEXT

Текст оператора на языке SQL. Внутри PSQL объектов текст SQL операторов не отображается.

MON$EXPLAINED_PLAN

BLOB TEXT

План оператора в explain форме.

MON$OBJECT_NAME

CHAR(63)

Имя PSQL объекта, в котором был компилирован SQL оператор.

MON$OBJECT_TYPE

SMALLINT

Тип объекта. 2 — триггер;
5 — хранимая процедура;
15 — хранимая функция.

MON$PACKAGE_NAME

CHAR(63)

Имя PSQL пакета.

MON$STAT_ID

INTEGER

Идентификатор статистики.

В таблицах 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. О ней в следующий раз.

Подпишитесь на новости Firebird в России

Подписаться