Работа с различными СУБД

d

Миф о «всеядности» dbExpress и ADO: когда скорость убивает данные

Первое, что бросается в глаза новичку — иллюзия, будто смена СУБД сводится к замене драйвера. На деле каждая база данных требует своей философии работы с транзакциями. Например, в Firebird вы привыкли полагаться на многоверсионность (MVCC) и редко блокируете таблицы целиком. Переходя на PostgreSQL через dbExpress, вы внезапно обнаружите, что забытый LOCK TABLE в хранимой процедуре превращает SELECT в ожидание. Профессионал всегда проверяет, какой уровень изоляции транзакций установлен по умолчанию для конкретного набора компонентов — ADO может эмулировать READ UNCOMMITTED, даже если база поддерживает SNAPSHOT.

Совет: для каждого бэкенда делайте отдельную прослойку с явным указанием IsolationLevel. Не полагайтесь на «магические константы» из TDatabase — в Migrate-проектах это главный источник race condition-ов, от которых потом невозможно отловить стек.

Неочевидные нюансы работы с BLOB и Unicode в разных СУБД

Показательная ловушка — передача изображений через TBlobField. В SQLite, если не задать DataType = ftBlob явно, библиотека может интерпретировать данные как строку и молча обрезать нулевой байт. В Firebird, наоборот, через FIBPlus вы обязаны указывать BlobType = btStream, иначе после первого Post часть информации превратится в бинарный мусор. Ещё тоньше: кодировка UTF-8 в Memo-полях. Никогда не верьте маппингу по умолчанию. Если ваша Delphi версия (особенно старые 7/XE) передаёт строку как AnsiString — при вставке в PostgreSQL с ENCODING 'UTF8' вы получите замену символов на «?». Хитрость: используйте TStringField с явным свойством Transliterate или перекодируйте данные на клиенте через UTF8Encode/UTF8Decode перед записью.

Почему «INSERT … RETURNING» в Firebird требует пересмотра архитектуры

Многие разработчики до сих пор используют GEN_ID и второй запрос для получения ID вставленной записи. Это не только медленно, но и опасно в многопоточных приложениях. В Firebird 3+ и PostgreSQL есть INSERT … RETURNING, но через dbExpress вы не получите результат напрямую — компонент TSQLQuery просто вернёт пустой датасет, если не сделать OPEN после ExecSQL. Профессиональный приём: для Firebird используйте хранимую процедуру-обёртку, которая принимает параметры и возвращает курсор с одним полем. Для PostgreSQL — применяйте RETURNING * вместе с SELECT … INTO в динамическом SQL. Эту разницу надо закладывать ещё на этапе шаблона генерации кода.

Пул соединений: дешевле — не значит быстрее

В ADO настройка ConnectionPoolSize кажется очевидной, но сразу несколько БД через один пул — почти всегда потеря производительности. Если вы держите соединение с Firebird и SQLite в одном Pool, драйвер ADO переключает контексты при каждом запросе, что съедает до 40% времени на аутентификацию. Профессиональный совет: создавайте отдельные пулы для каждой СУБД, даже если используется общий TADOConnection. В dbExpress ситуация ещё тоньше — некоторые сборки (например, от DevArt) не поддерживают масштабирование пула под SQLite, и при повторном подключении вы можете получить SQLITE_BUSY, хотя база свободна. Причина: драйвер кеширует SQLite-соединение, а лок-файл остаётся. Сбрасывайте пул принудительно через TSQLConnection.Close после каждой пачки операций записи.

Индексы и планы запросов: что проверяют профи, когда тормозит JOIN

Распространённая ошибка — переносить все индексы из одной СУБД в другую без анализа. В PostgreSQL планировщик любит секционированные таблицы и использование INCLUDE в индексах. В Firebird, наоборот, избыточные индексы на столбцах с низкой селективностью только замедляют вставку. Реальный чек-лист профи:

Скрытая цена хранимых процедур при миграции

Когда вы переносите бизнес-логику из Delphi-кода в процедуру базы данных, кажется, что это универсально. Но не все СУБД одинаково эффективны с процедурами. В Firebird вызов PL-процедуры из dbExpress может вызвать deadlock, если процедура открывает транзакцию внутри себя — из-за различий в обработке вложенных транзакций. Профи в таких случаях используют AUTOCOMMIT=False и выносят всякие COMMIT/ROLLBACK исключительно на клиент. Альтернатива — писать процедуры без транзакционных команд, а управлять всем из Delphi через StartTransaction/Commit. Для PostgreSQL есть полезный трюк: используйте LISTEN/NOTIFY, чтобы Delphi получала события после выполнения длительной процедуры — это снижает нагрузку на polling.

Резюме для профессионала

Главный вывод: не пытайтесь сделать «абстрактный движок СУБД». На практике вы столкнётесь с тем, что код, отлаженный для Firebird, может вызвать непротиворечивость данных в PostgreSQL из-за разной обработки NULL и строковых сравнений. Инвестируйте в модульное тестирование каждого специфичного для БД запроса — только тогда «кроссплатформенность» станет реальностью, а не источником багов. И всегда помните: драйвер — это не магия, а прослойка с ограничениями, которые нужно знать до первого деплоя.

Добавлено: 27.04.2026