Выполнение запросов

Миф о «правильном» компоненте: ADO против FireDAC
Распространённое заблуждение среди начинающих разработчиков — считать, что выбор компонента для работы с базой данных (TADOQuery или TFDQuery) не влияет на логику выполнения запросов. На деле разница в реализации состояний Prepared и поведения с курсорами приводит к разным результатам при выполнении одинакового SQL-кода. Профессионалы знают: в ADO состояние Prepared:=True не гарантирует компиляции плана выполнения до первого открытия, в то время как FireDAC действительно кеширует подготовленный запрос. Второй нюанс — работа с ParamCheck: в ADO он включён по умолчанию, что порождает ошибки при именовании полей с символом двоеточия. FireDAC требует явного манипулирования свойством ResourceOptions.ParamExpandChars.
Неочевидная опасность AutoCalcFields при выполнении запросов
Событие OnCalcFields срабатывает не только при прокрутке набора данных, но и при выполнении методов Open и Refresh. Если в обработчике этого события содержится тяжелый код (например, вызов другого запроса или пересчёт агрегатов), производительность может упасть в разы. Экспертный приём — временно отключать AutoCalcFields перед выполнением массовых операций: Form1.ADOTable1.AutoCalcFields := False;, а затем восстанавливать его. Это исключит лишние вызовы во время цикла while not DataSet.Eof do ....
Транзакции: границы и скрытые блокировки
Простая ошибка — помещать в одну транзакцию сотни SELECT-запросов «на всякий случай». На практике длинная транзакция с чтениями блокирует страницы данных для других сессий (даже при READ COMMITTED в SQL Server). Вместо этого используйте короткие транзакции только для групп связанных записей. Профессионалы применяют явное управление транзакциями через TADOConnection.BeginTrans и TFDConnection.StartTransaction, а не автоматический режим. Также распространён «синдром забытой откатки»: когда при исключении в середине транзакции разработчик не вызывает Rollback. Всегда используйте блок try...finally для фиксации или отмены. Важный параметр — IsolationLevel. Большинство проектов по умолчанию работают с xiCursorStability (READ COMMITTED), но для отчётов подходит ReadUncommitted (грязное чтение разрешено), что снимает блокировки.
Параметризация запросов: частые подводные камни
- Типизация параметра для NULL. Параметр
ParamByName('id').Value := Nullчасто приводит к ошибке несоответствия типа. Специалисты советуют устанавливатьDataTypeиClear:ParamByName('id').DataType := ftInteger; ParamByName('id').Clear;— только так FireDAC воспринимает NULL корректно. - Порядок параметров при повторном выполнении. ADO запоминает старые значения параметров, если не вызван
Parameters.Refresh. МетодCloseне очищает буфер параметров. Лучшая практика — пересоздавать объект запроса для каждого уникального набора условий. - Имена параметров в хранимых процедурах. Для SQL Server используйте имена вида
@Param— именно так их видит ADO. FireDAC позволяет писать:Param, что порождает путаницу при переносе кода. - Параметры для IN-списков. Стандартный подход
WHERE id IN (:list)не работает — параметр не раскрывается в список. Решение — построение динамического SQL с количеством параметров, равным числу элементов в списке. FireDAC предлагает макросы{IN (ids)}, но требуется дополнительная серверная поддержка. - Автоматическое приведение строки в дату. Если вы передаёте строку через параметр в поле даты, Delphi может конвертировать её под локальную культуру. В серверной культуре (US) формат отличается — используйте явное
Date := StrToDate(s, FormatSettings).
Производительность Batch-операций: 10x ускорение
Многие разработчики выполняют вставку 1000 записей в цикле: for i := 0 to 999 do begin Query.SQL.Text := 'INSERT ...'; Query.ExecSQL; end;. Это катастрофически медленно: каждый ExecSQL — отдельный сетевой вызов. Решение FireDAC — класс TFDQuery с ExecSQL в режиме ArrayDML. Формируется один запрос с параметрами-массивами: FDQuery1.Params[0].AsIntegers := [1,2,3]; и выполняется однократно. Второй подход — использование прямых Bulk Insert через TADOCommand (Execute с набором записей). Третий вариант — отключение индексов и триггеров до начала массовой загрузки и их включение после. Всегда измеряйте время выполнения до и после оптимизации — визуально разница незаметна, а профайлер показывает выигрыш в 7–15 раз.
Обработка ошибок выполнения: что скрыто в исключениях
- EADOError без контекста. Стандартное сообщение теряет номер строки в SQL-скрипте. Используйте
TADOQuery.SQLв блоке catch для распечатки текст запроса и значения параметров — это сокращает время дебага на 40%. - Транзакционные исключения при ошибке констрейнта. Если вставка нарушает foreign key, транзакция всё равно остаётся открытой — необходимо вызывать
Rollbackнемедленно. Автоматический откат не происходит ни в ADO, ни в FireDAC. - Игнорирование предупреждений об изменении набора данных. Метод
Openможет вернуть пустой набор без ошибки, если SQL написан с нарушением синтаксиса, но сервер его выполнил (например,SELECT 1 WHERE 1=2). ПроверяйтеIsEmptyиRecordCountпосле открытия. - Deadlock в цикле. При переборе записей с обновлением внутри цикла возрастает вероятность взаимоблокировок. Специалисты выносят SELECT в отдельный курсор (статический или динамический —
CursorLocation := clUseServerдля ADO) и фиксируют изменения отдельным пакетом.
Профессиональные инструменты мониторинга запросов
Отлаживать запросы через ShowMessage — моветон. Используйте FDConnection.Tracing или ADOConnection.OpenSchema для сбора статистики. Сторонние утилиты: DBMonitor для FireDAC (показывает SQL-текст, время выполнения, переданные параметры) и SQL Server Profiler для ADO. В среде Delphi добавьте замеры через TStopWatch из модуля System.Diagnostics — это точнее, чем GetTickCount. Включайте или выключайте логирование через условную компиляцию: {$IFDEF DEBUG}, чтобы не замедлять релизные сборки. Ещё один ценный совет — ведение журнала времени выполнения каждого запроса и построение графика распределения — узкие места выявляются автоматически после 1000 выполнений.
Часто забываемые настройки соединения
- ConnectionTimeout. Значение по умолчанию (15 секунд) заставляет пользователя ждать при недоступном сервере. Увеличивайте до 30–60 секунд в стабильной сети, уменьшайте до 5 секунд в внешнем API.
- KeepConnection. Установка
Falseзакрывает физическое соединение когда нет активных запросов. Для пула соединений FireDAC это полезно, а для ADO — наоборот, увеличивает накладные расходы на повторную аутентификацию. - MarshalOptions. В локальной сети рекомендован
moMarshalByValue, а неmoMarshalByValue— разница в том, как передаются изменения обратно на сервер. Путаница в этих двух значениях — источник «потерянных» обновлений. - FetchOptions.Mode (FireDAC). Режим
fmAllзагружает весь набор сразу — это убивает память для больших таблиц. НастройтеfmOnDemandилиfmExactRecsMaxс лимитом записей. - CursorType в ADO.
ctStaticне отражает изменения, которые сделали другие пользователи.ctDynamicпотребляет больше ресурсов, но данные всегда актуальны. Выбор зависит от сценария: отчёты —ctStatic, редактор —ctKeyset.
Добавлено: 27.04.2026
