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

Миф о «всеядности» 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, наоборот, избыточные индексы на столбцах с низкой селективностью только замедляют вставку. Реальный чек-лист профи:
- Проверить, использует ли JOIN в вашей Delphi-сборке
MERGE JOINилиHASH JOIN— если в Firebird отсутствуетHASH JOIN, ваш запрос деградирует до nested loop. - Сравнить explain plan для одинакового SQL в разных СУБД — часто различие в порядке полей GROUP BY даёт двукратный выигрыш на PostgreSQL, но не влияет на Firebird.
- Для SQLite: всегда добавлять
EXPLAIN QUERY PLAN— многие не знают, что SQLite не использует составные индексы дляOR-условий, что приводит к полному сканированию.
Скрытая цена хранимых процедур при миграции
Когда вы переносите бизнес-логику из Delphi-кода в процедуру базы данных, кажется, что это универсально. Но не все СУБД одинаково эффективны с процедурами. В Firebird вызов PL-процедуры из dbExpress может вызвать deadlock, если процедура открывает транзакцию внутри себя — из-за различий в обработке вложенных транзакций. Профи в таких случаях используют AUTOCOMMIT=False и выносят всякие COMMIT/ROLLBACK исключительно на клиент. Альтернатива — писать процедуры без транзакционных команд, а управлять всем из Delphi через StartTransaction/Commit. Для PostgreSQL есть полезный трюк: используйте LISTEN/NOTIFY, чтобы Delphi получала события после выполнения длительной процедуры — это снижает нагрузку на polling.
Резюме для профессионала
Главный вывод: не пытайтесь сделать «абстрактный движок СУБД». На практике вы столкнётесь с тем, что код, отлаженный для Firebird, может вызвать непротиворечивость данных в PostgreSQL из-за разной обработки NULL и строковых сравнений. Инвестируйте в модульное тестирование каждого специфичного для БД запроса — только тогда «кроссплатформенность» станет реальностью, а не источником багов. И всегда помните: драйвер — это не магия, а прослойка с ограничениями, которые нужно знать до первого деплоя.
Добавлено: 27.04.2026
