Управление пулом соединений
{
"title": "Управление пулом соединений в Delphi: технические аспекты и реализация",
"keywords": "пул соединений Delphi, TThreadPool, TSQLConnection, управление подключениями, многопоточность Delphi, ADO пул, dbExpress пул, FireDAC пул соединений",
"description": "Техническая реализация управления пулом соединений в Delphi: отличия ADO, dbExpress и FireDAC, спецификация TThreadPool, параметры настройки, стандарты качества соединений.",
"html_content": "Материалы и внутренняя архитектура пула соединений в Delphi
\n\nПул соединений в Delphi представляет собой контейнер предварительно инициализированных подключений к базе данных. В отличие от классического подхода (создание/уничтожение TSQLConnection или TADOConnection на каждый запрос), пул хранит живые сокеты или сессии, что устраняет накладные расходы на трехстороннее рукопожатие TCP (SYN, SYN-ACK, ACK) и аутентификацию (для Firebird — инициализация isc_database_connect занимает 40–120 мс в зависимости от версии ОС). Материальная база — объекты TPooledConnection (для dbExpress) или TFDConnection.SpecifyPool (для FireDAC), которые хранят ссылку на физический канал. Каждый объект занимает от 4 КБ (пустой ADO-пул) до 16 КБ (FireDAC с кэшем метаданных) в памяти процесса.
\n\nСпецификации и параметры настройки
\n\nТехнические характеристики пула регулируются через набор директив:
\n- \n
- MinPoolSize / MaxPoolSize — задают количество предварительно открытых и максимально допустимых соединений. Для зоопарка с 50 потоками в TThreadPool типичные значения — 5/25. Превышение лимита вызывает queue overflow (исключение EDatabaseError с кодом -2147467259 в ADO). \n
- ConnectionLifeTime — время жизни бездействующего соединения (по умолчанию 600 секунд). FireDAC использует таймер на основе GetTickCount64, и при простое более заданного порога физическое подключение закрывается, освобождая файловый дескриптор (в MS SQL — handle count в sys.dm_os_handles). \n
- CleanupTimeout — интервал сканирования мусорных соединений (оптимально 30–120 секунд). Для TSQLConnectionPool (dbExpress) чистильщик запускается в фоновом потоке через WaitForSingleObject с нулевым сигналом. \n
Отличия реализации ADO, dbExpress и FireDAC
\n\nADO (dbGo) использует встроенный механизм Microsoft OLEDB Session Pooling. При создании TADOConnection флаг KeepConnection := True включает пул на стороне провайдера (SQLNCLI11.1). Недостаток — невозможность управления размером из Delphi (MaxPoolSize задается строкой подключения «Connection Pool Size=10»). Каждый TDataSet.Open вызывает QueryInterface у пула, что в 64-битных сборках приводит к утечкам при частом закрытии/открытии (из-за неправильного подсчета ссылок в интерфейсе IDBCreateSession).
\n\ndbExpress (TSQLConnection) — собственная реализация на уровне TSQLConnectionPool. Хранит пул как TList
FireDAC — наиболее продвинутый вариант. Пуловые соединения хранятся в TFDCustomConnection.Driver.Pool, который использует ConcurrentHashMap для потокобезопасности. Отличительная особенность — TFDPoolManager.OnValidate, вызываемый при каждом checkout (выборка соединения из пула). Внутри выполняется ping-запрос к СУБД (SELECT 1 для MySQL, EXECUTE BLOCK для Firebird). Если ping проваливается, соединение помечается как stale и уничтожается физически (freeConnection). Стандартная реализация использует критическую секцию с спин-локом (FAFCriticalSection), что на многоядерных системах (Core i7-12700K) дает задержку 0.2–1.5 мкс на захват.
\n\nМатериалы сборки и качество стандартов
\n\nПри разработке собственного пула (например, на основе TThreadedQueue и TDatabaseLoginDialog) необходимо соблюдать стандарты:
\n- \n
- Thread-safety — доступ к пулу через TMonitor.Enter (3.5 мкс в среднем) или тонкий семафор (CreateSemaphore — 5.8 мкс для одного слота). В демо-примерах на Embarcadero Circle используется TObjectMonitor для защиты TStack
. \n - Connection leak detection — трассировка с помощью GetReportMemoryLeaks (по умолчанию отключено). В production следует реализовать WMI-счетчик (пример: Performance Counter «Connection Pool\Active Sessions») через интерфейс IWbemServices. \n
- Качество соединений — контроль параметров через TFDConnection.GetFDMetaInfoCommand, который валидирует версию протокола (например, для Oracle — проверка на OCI_VERSION). Несоответствие вызывает EFDNotSupportedException. \n
Различия в управлении временем жизни
\n\nВ ADO каждый объект TADOConnection создает внутренний дескриптор, который живет до вызова Close. В FireDAC физическое соединение может быть переиспользовано даже после вызова Disconnect, если пул активен (свойство Pooled := True). Это критично для мобильных сборок (Android API 30+), где эмуляция SQLite через ICS-драйвер имеет лимит в 2 одновременных подключения. dbExpress в этом случае выбрасывает исключение EDBXConnectionLost, а FireDAC корректно ждет освобождения слота (вызов Sleep(50) в цикле Repeat-Until). Разница в поведении связана с тем, что FireDAC использует WaitForMultipleObjects для ожидания свободного слота, тогда как dbExpress использует busy-wait с вызовом ProcessMessages, что приводит к 100% загрузке одного ядра при высоком конкурентном спросе (более 10 потоков на одном TSQLConnectionPool).
\n\nПример технической реализации на FireDAC
\n\nБазовая настройка в коде (без визуальных компонентов):
\n- \n
- Pooled := True; ResourceOptions.AutoReconnect := True; — включает автоматическую валидацию. Свойство LoginPrompt := False обязательно для пула, иначе каждый checkout вызовет форму входа (нарушает потокобезопасность). \n
- FormatOptions.SetOwnsValue := True — для типов TFieldDataLink, чтобы избежать false sharing в кэше L1 (размер строки кэша 64 байта). При использовании TFDMemTable разница в производительности на Intel Xeon составляет 8–12%. \n
- FetchOptions.Mode := fmAll — для OLTP-сценаря с пулом оптимизирует количество возвратов курсоров (ROUND-TRIP до сервера сокращается с 3 до 1). \n
Добавлено: 27.04.2026
