FirebirdSQL logo

Новые типы данных

Актуально: при миграции с Firebird версий 2.5, 3.0.

Как уже говорилось ранее, некоторые выражения могут возвращать новые типы данных, которые не могут быть интерпретированы вашим приложением без его доработки.Такая доработка может занять существенное время или оказаться вам не по силам.Для упрощения миграции на новые версии вы можете установить параметр DataTypeCompatibility в режим совместимости с необходимой версией в firebird.conf или databases.conf.

DataTypeCompatibility = 3.0

или

DataTypeCompatibility = 2.5

Это самый быстрый путь добиться совместимости с новыми типами данных.Однако со временем вы можете начать внедрять поддержку новых типов в своё приложение.Естественно, это будет происходить постепенно - сначала один тип, потом другой и так далее.В этом случае вам надо настроить отображение тех типов, поддержку которых вы ещё не доделали, на другие типы данных.Для этого используется оператор SET BIND OF.

Синтаксис
SET BIND OF { <type-from> | TIME ZONE } TO { <type-to> | LEGACY | NATIVE | EXTENDED }

Ключевое слово LEGACY в части TO используется, когда тип данных, отсутствующий в предыдущей версии Firebird, должен быть представлен способом понятным для старого клиентского программного обеспечения (возможна некоторая потеря данных). Существуют следующие преобразования в LEGACY типы:

Table 1. Преобразования в legacy типы
DataTypeCompatibility Native тип Legacy тип

2.5

BOOLEAN

CHAR(5)

2.5 или 3.0

DECFLOAT

DOUBLE PRECISION

2.5 или 3.0

INT128

BIGINT

2.5 или 3.0

TIME WITH TIME ZONE

TIME WITHOUT TIME ZONE

2.5 или 3.0

TIMESTAMP WITH TIME ZONE

TIMESTAMP WITHOUT TIME ZONE

При установке параметра DataTypeCompatibility выполняется преобразование новых типов данных в legacy типы согласно таблице описанной выше.

Подробное описание этого оператора есть в "Firebird 4.0 Release Notes" и "Руководство по языку SQL СУБД Firebird 5.0".С помощью него вы можете управлять отображением новых типов в вашем приложении выполнив соответствующий запрос сразу после подключения, и даже написать AFTER CONNECT триггер в котором использовать несколько таких операторов.

Например, предположим, что вы добавили в ваше приложение поддержку даты и времени с часовыми поясами, но у вас до сих пор не поддерживаются типы INT128 и DECFLOAT. В этом случае вы можете написать следующий триггер.

create or alter trigger tr_ac_set_bind
on connect
as
begin
  set bind of int128 to legacy;
  set bind of decfloat to legacy;
end

Согласованное чтение в транзакциях READ COMMITTED

Актуально: при миграции с Firebird версий 2.5, 3.0.

Firebird 4 не только вводит согласованность чтения (READ CONSISTENCY) для запросов в транзакциях READ COMMITTED, но также делает его режимом по умолчанию для всех транзакций READ COMMITTED, независимо от их свойств RECORD VERSION или NO RECORD VERSION.

Это сделано для того, чтобы обеспечить пользователям лучшее поведение — как соответствующее спецификации SQL, так и менее подверженное конфликтам. Однако это новое поведение может также иметь неожиданные побочные эффекты.

Пожалуй самый важный из них это так называемые рестарты при обработке конфликтов обновления. Это может привести к тому, что некоторый код, не подверженный транзакционному контролю, может выполняться многократно в рамках PSQL. Примерами такого кода может быть:

  • использование внешних таблиц, последовательностей или контекстных переменных;

  • отправка электронных писем с использованием UDF;

  • использование автономных транзакций или внешних запросов.

Note

В режиме изолированности READ COMMITTED READ CONSISTENCY конфликт обновлений обрабатывается иначе. Если при выполнении UPDATE или DELETE обнаруживается запись,которая уже изменена или удалена другой транзакцией (транзакция подтверждена), то все изменения выполенные в текущем запросе откатываются и он выполняется заново. Это называется рестартом запроса.

Подробнее о согласованном чтении в транзакциях READ COMMITTED вы можете прочитать "Firebird 4.0 Release Notes".

Другим важным эффектом является то, что недофетченные курсоры в транзакциях READ COMMITTED READ CONSISTENCY в Read Only режиме теперь удерживают сборку мусора.Рекомендуем вам отказаться от использования в приложении единой длинной READ COMMITTED READ ONLY транзакции, и заменить её на несколько таких транзакций, каждая из которых активна ровно столько времени сколько это необходимо.

Если особенности режима READ CONSISTENCY по каким-либо причинам нежелательны, то чтобы вернуть устаревшее поведение, необходимо установить параметр конфигурации ReadConsistency равным 0.

Изменения в оптимизаторе

Оптимизизатор меняется в каждой версии Firebird. В основном эти изменения положительные, то есть ваши запросы должны работать быстрее, но часть запросов может замедлиться, поэтому необходимо тестировать производительность вашего приложения, и если где-то произошло замедление, то необходимо вмешательство со стороны программиста.

Для большинства изменений оптимизатора вы не можете повлиять на план запроса, изменяя конфигурацию сервера. В этом случае, вы можете сделать следующее:

  • переписать SQL запрос так, чтобы он работал быстрее на новой версии сервера;

  • создать или удалить индексы;

  • если ничего из выше перечисленного не помогло, то создайте тикет о регрессии по адресу https://github.com/FirebirdSQL/firebird/issues.

Есть пару моментов в работе оптимизатора, на которые можно повлиять с помощью изменения конфигурации:

Использование Refetch при сортировке широких наборов данных

Актуально: при миграции с Firebird версий 2.5, 3.0.

Начиная с Firebird 4.0 появился новый метод доступа Refetch, который позволяет оптимизировать сортировку широких наборов данных. Под широким набором данных понимается набор данных в котором суммарная длина полей записи велика.

Исторически сложилось так, что при выполнении внешней сортировки Firebird записывает как ключевые поля (то есть, которые указаны в предложении ORDER BY или GROUP BY), так и неключевые поля (все остальные поля, на которые имеются ссылки внутри запроса) в блоки сортировки, которые либо сохраняются в памяти, либо во временные файлы. После завершения сортировки эти поля считываются обратно из блоков сортировки. Обычно этот подход считается более быстрым, поскольку записи считываются из временных файлов в порядке соответстветствующему отсортированным записям, а не выбираются случайным образом со страницы данных. Однако если неключевые поля большие (например, используются длинные VARCHAR), то это увеличивает размер блоков сортировки и, таким образом, приводит к большему количеству операций ввода-вывода для временных файлов. Firebird 4 предлагает альтернативный подход (метод доступа Refetch), когда внутри блоков сортировки хранятся только ключевые поля и записи DBKEY, а неключевые поля извлекаются из страниц данных после сортировки. Это повышает производительность сортировки в случае длинных неключевых полей.

Таким образом планы ваших запросов, использующих сортировку могут поменятся. Для управления данным методом доступа введён новый параметр конфигурации InlineSortThreshold. Значение, указанное для InlineSortThreshold, определяет максимальный размер записи сортировки (в байтах), которая может храниться встроенно, то есть внутри блока сортировки. Ноль означает, что записи всегда перезагружаются. Оптимальное значение данного параметра необходимо подбирать экспериментальным путём. Значение по умолчанию равно 1000 байт.

Рассмотрим следующий пример:

SELECT
  field_1, field_2, field_3, field_4
FROM SomeTable
ORDER BY field_1

До Firebird 4.0 в блоки сортировки всегда были включены все 4 поля. Начиная с Firebird 4.0, если суммарная длина полей field_1 .. field_4 превышает значение InlineSortThreshold, то в блоки сортировки попадёт только field_1, а затем будет выполнен Refetch.

Преобразование OUTER JOINs в INNER JOINs

Существует ряд проблем с оптимизацией OUTER JOINs в Firebird.

Во-первых, в настоящее время OUTER JOIN может быть выполнен только одним алгоритмом соединения NESTED LOOP JOIN, что может быть изменено в следующих версиях.

Во-вторых, при соединении потоков внешними соединениями порядок соединения строго фиксирован, то есть, оптимизатор не может изменить его, чтобы результат оставался правильным.

Однако, если в условии WHERE существует предикат для поля "правой" (присоединяемой) таблицы, который явно не обрабатывает значение NULL, то во внешнем соединении нет смысла. В этом случае начиная с Firebird 5.0 такое соединение будет преобразовано во внутреннее, что позволяет оптимизатору применять весь спектр доступных алгоритмов соединения.

Допустим у вас есть следующий запрос:

SELECT
  COUNT(*)
FROM
  HORSE
  LEFT JOIN FARM ON FARM.CODE_FARM = HORSE.CODE_FARM
WHERE FARM.CODE_COUNTRY = 1

В Firebird 5.0 такой запрос неявно будет преобразован в эквивалентную форму:

SELECT
  COUNT(*)
FROM
  HORSE
  JOIN FARM ON FARM.CODE_FARM = HORSE.CODE_FARM
WHERE FARM.CODE_COUNTRY = 1

Если LEFT JOIN использовался в качестве подсказки для указания порядка соединения очень активно, то переписать множество запросов на новый лад может быть проблематично. Для таких разработчиков существует параметр конфигурации OuterJoinConversion в firebird.conf или database.conf. Установка параметра OuterJoinConversion в false отключает трансформацию Outer Join во внутренние соединение. Отметим, что этот параметр является временным решением для облегчения миграции и, в будущих версиях Firebird он может быть удалён.

RETURNING, возращающий множество записей

Начиная с Firebird 5.0 клиентские модифицирующие операторы INSERT .. SELECT, UPDATE, DELETE, UPDATE OR INSERT и MERGE, содержащие предложение RETURNING возвращают курсор, то есть они способны вернуть множество записей вместо выдачи ошибки "multiple rows in singleton select", как это происходило ранее.

Теперь эти запросы во время подготовки описываются как isc_info_sql_stmt_select, тогда как в предыдущих версии они были описаны как isc_info_sql_stmt_exec_procedure.

Singleton-операторы INSERT .. VALUES, а также позиционированные операторы UPDATE и DELETE (то есть, которые содержат предложение WHERE CURRENT OF) сохраняют существующее поведение и описываются как isc_info_sql_stmt_exec_procedure.

Однако все эти запросы, если они используются в PSQL и применяется предложение RETURNING, по-прежнему рассматриваются как сингелтоны.

Если ваше приложение использует модифицирующие операторы INSERT .. SELECT, UPDATE, DELETE, UPDATE OR INSERT и MERGE, содержащие предложение RETURNING, тоэто может быть причиной возникновения ошибок. Убедитесь, что ваш драйвер или компонент доступа правильно обрабатывает подобные запросы, и если это не так, то либо модифицируйте код (приложения или компонента), либо дождитесь пока выйдет обновление соотвествующего драйвера/компонента правильно обрабатывающего данные запросы.

Примеры модифицирующих операторов содержащих RETURNING, и возвращающих набор данных:

INSERT INTO dest(name, val)
SELECT desc, num + 1 FROM src WHERE id_parent = 5
RETURNING id, name, val;

UPDATE dest
SET a = a + 1
WHERE id = ?
RETURNING id, a;

DELETE FROM dest
WHERE price < 0.52
RETURNING id;

MERGE INTO PRODUCT_INVENTORY AS TARGET
USING (
  SELECT
    SL.ID_PRODUCT,
    SUM(SL.QUANTITY)
  FROM
    SALES_ORDER_LINE SL
    JOIN SALES_ORDER S ON S.ID = SL.ID_SALES_ORDER
  WHERE S.BYDATE = CURRENT_DATE
    AND SL.ID_PRODUCT = :ID_PRODUCT
  GROUP BY 1
) AS SRC(ID_PRODUCT, QUANTITY)
ON TARGET.ID_PRODUCT = SRC.ID_PRODUCT
WHEN MATCHED AND TARGET.QUANTITY - SRC.QUANTITY <= 0 THEN
  DELETE
WHEN MATCHED THEN
  UPDATE SET
    TARGET.QUANTITY = TARGET.QUANTITY - SRC.QUANTITY,
    TARGET.BYDATE = CURRENT_DATE
RETURNING OLD.QUANTITY, NEW.QUANTITY, SRC.QUANTITY;

Заключение

В этой статье я постарался описать наиболее часто встречающиеся проблемы и их решения при миграции на Firebird 5.0 с Firebird 2.5, 3.0 и 4.0.Надеюсь, что данная статья поможет вам перевести ваши базы данных и приложения на Firebird 5.0 и воспользоваться всеми преимуществами новой версии.