Работа с транзакциями в BDE

Транзакции в BDE: что на самом деле гарантировано
BDE (Borland Database Engine) предоставляет механизм транзакций, но его гарантии отличаются от современных СУБД. Основной контракт: при вызове StartTransaction начинается логическая единица работы, а Commit фиксирует изменения. Однако BDE не гарантирует изоляцию на уровне, привычном для PostgreSQL или MSSQL. Если два пользователя одновременно изменяют одну запись, BDE не блокирует второй поток — он выдаст исключение EDBEngineError с кодом 13059 (Update failed). Это не гарантия блокировки, а гарантия того, что конфликт будет обнаружен на этапе применения изменений.
Что действительно под защитой? BDE гарантирует атомарность в рамках одного соединения. Если происходит разрыв связи до вызова Commit, все изменения откатываются автоматически. Но если физический сбой случается после Commit, но до записи на диск СУБД — возникает зона неопределённости. Это риск, который нужно учитывать при проектировании.
Риски, о которых молчат примеры
Главный риск при работе с BDE — отсутствие явного управления блокировками на уровне таблиц. В отличие от InterBase или Firebird, BDE полагается на блокировки на уровне записей, которые может снять любое прерывание. Не надейтесь, что BDE удержит блокировку после паузы в 10 секунд — если другой процесс обратится к той же записи, вы получите конфликт, а не ожидание.
Второй риск — глубина вложенности транзакций. BDE поддерживает только один уровень вложенности. Попытка вложить вторую транзакцию (BDE.StartTransaction внутри другой) приведёт к исключению EDBEngineError. Это не документировано явно, но выясняется в production при нагрузке.
Третий риск — поведение при откате. Если в рамках транзакции вы изменили 100 записей и вызвали Rollback, BDE корректно откатывает данные. Но если хотя бы одна запись была изменена через ExecuteDirect (сырой SQL), откат может не сработать для неё — проверяйте, что все изменения проходят через UpdateObject или TQuery с BDE-прокси.
Как избежать сожалений: точки контроля
- Проверьте режим кэширования. Убедитесь, что
CachedUpdatesвыключен (False) при использовании транзакций. Иначе BDE кэширует изменения локально, и Commit/ Rollback применяются не к базе, а к локальному кэшу. Это частая ловушка: вы откатываете, а данные всё равно фиксируются. - Принудительно управляйте исключениями. Всегда оборачивайте транзакцию в try/except. В блоке except вызывайте Rollback, но после него проверяйте свойство
InTransaction. Если оно True — вызывайтеDisconnectи повторное соединение, иначе остаётесь в подвешенном состоянии. - Тестируйте параллельный доступ. Напишите тест, в котором два потока из разных приложений стартуют транзакции на одну таблицу. Засеките время обнаружения конфликта. Если BDE отдаёт исключение после 2 секунд — нормально. Если молчит и блокирует — проверьте TTable.LockType (должен быть ltWrite).
- Контролируйте время транзакции. BDE не имеет тайм-аута на транзакцию по умолчанию. Установите собственный таймер (например, отмену через 30 секунд). Иначе зависшая транзакция заблокирует все изменения на таблице, пока вы не перезапустите приложение.
- Проверяйте работу с BLOB. BLOB-поля в транзакции BDE ведут себя нестабильно: при большом объёме (более 64 КБ) Commit может упасть с ошибкой «Invalid BLOB handle». Используйте временные файлы для BLOB или минимизируйте их включение в транзакцию.
Гарантии при выборе: что должно быть в вашем приложении
- Isolation Level — BDE не поддерживает.setting уровня изоляции; всегда по умолчанию Read Committed. Учитывайте, что фантомные чтения возможны и не защищены.
- Система повторных попыток. При получении исключения EDBEngineError о конфликте записи (код 13059) перезапускайте транзакцию автоматически. Без этого пользователь увидит «Record changed by another user» и потеряет данные.
- Логирование Commit/ Rollback. Каждый вызов Commit должен записываться в журнал с временем и количеством изменённых записей. Если BDE «проглотит» ошибку (редко, но бывает), вы сможете восстановить последовательность.
- Отказ от наследования транзакций. Не используйте классы потомки TDatabase, которые пытаются «подхватить» транзакцию предка — это ведёт к неопределённому состоянию BDE. Создавайте отдельное TDatabase для каждой транзакции.
Резюме: BDE — не «боевой» движок для конкурентного доступа. Его транзакции дают базовую атомарность, но требуют ручного управления временем жизни, блокировками и обработкой ошибок. Если вы строите систему, где целостность критична — переходите на Firebird или SQLite с явными транзакциями. Если остаётесь на BDE — каждая точка контроля выше должна быть внедрена до первого релиза.
Добавлено: 27.04.2026
