Данная статья посвящена многопоточному режиму FireDAC. Она основана на официальной документации.
FireDAC потокобезопасен, если выполняются следующие условия
-FDConnection и все ассоциированные с ним объекты (такие как FDQuery, FDTransaction и так далее) используются в одном потоке в каждый момент.
-FDManager активирован до того как потоки начали свою работу. Свойство FDManager.Active поставлено в True
Это означает, что после того как поток открывает запрос (query) и до того как он не закончит его, приложение не может использовать этот запрос (query) и объекты соединений из других потоков.Аналогично, после того как поток начинает транзакцию и до того как транзакция не закончилась, приложение не может использовать эту транзакцию, а также объекты соединений из других потоков.
Такой подход может создать проблемы. Можно, например, получить ошибки “Connection is busy with results for another command” и др.
Решение проблемы – использовать для каждого потока свое соединение (FDConnection), которое работает с DBMS. Например, таким образом… (далее в документации приводится такой пример)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
type TDBThread = class(TThread) protected procedure Execute; override; end; procedure TDBThread.Execute; var oConn: TFDConnection; oPrc: TFDQuery; begin FreeOnTerminate := False; oConn := TFDConnection.Create(nil); oConn.ConnectionDefName := 'Oracle_Pooled'; // see next section oPrc := TFDStoredProc.Create(nil); oPrc.Connection := oConn; try oConn.Connected := True; oPrc.StoredProcName := 'MY_LONG_RUNNING_PROC'; oPrc.ExecProc; finally oPrc.Free; oConn.Free; end; end; // main application code var oThread1, oThread2: TDBThread; begin ADManager.Active := True; ... oThread1 := TDBThread.Create(False); oThread2 := TDBThread.Create(False); ... oThread1.WaitFor; oThread1.Free; oThread2.WaitFor; oThread2.Free; end; |
Замечание. Для выше приведенного случая, где приложение запускает одиночный SQL запрос (in background) рекомендуется использовать асинхронный режим (asynchronous query execution mode).
Замечание. Многопоточное приложение может закрывать соединения, открытые в background threads. Можно использовать событие TFDManager.BeforeShutdown, чтобы это предотвратить.
Пулы соединений (Connection Pooling)
Одно из самых затратных взаимодействий с БД, с точки зрения вычислительных ресурсов это установка соединения (the connection establishment). В многопоточном приложении, где каждый поток стартует, устанавливает соединение, выполняет некоторую работу и освобождает соединение, повторяющиеся установки соединения могут привести к деградации производительности всей системы. Чтобы избежать этого – можно использовать пулы соединений.
Пулы соединений могут быть использованы только для ersistent or private connection definition, при помощи установки в True параметра Pooled. (Pooled=True)
Пример для PersistentDefinition
1 2 3 4 5 6 |
[Oracle_Pooled] DriverID=Ora Database=ORA_920_APP User_Name=ADDemo Password=a Pooled=True |
Или пример для PrivateDefinition
1 2 3 4 5 6 7 8 9 10 11 12 |
var oParams: TStrings; begin oParams := TStringList.Create; oParams.Add('Database=ORA_920_APP'); oParams.Add('User_Name=ADDemo'); oParams.Add('Password=a'); oParams.Add('Pooled=True'); FDManager.AddConnectionDef('Oracle_Pooled', 'Ora', oParams); ..................... FDConnection1.ConnectionDefName := 'Oracle_Pooled'; FDConnection1.Connected := True; |
Никаких дополнительных параметров не может быть в свойстве TFDConnection.Params, потому что все соединения, находящиеся в пуле должны обладать одними и теми же параметрами.
Устанавливая свойство TFDConnection.Connected в True мы даем команду пулу соединений физически соединиться с БД.
Устанавливая свойство TFDConnection.Connected в False, мы отсоединяем FDConnection от пула, но оставляем соединение пула с БД открытым. Чтобы закрыть и уничтожить все соединения пула, приложение может вызвать метод TFDManager.CloseConnectionDef.
1 |
FDManager.CloseConnectionDef('Oracle_Pooled'); |
Либо можно закрыть FDManager
1 |
FDManager.Close; |
Для пула соединений могут быть настроены следующие дополнительные параметры
POOL_CleanupTimeout – время, в течение которого FireDAC удаляет ненужные соединения (те, которые превысили время POOL_ExpireTimeout)
POOL_ExpireTimeout – время, после которого неактивные соединения могут быть удалены из пула и уничтожены. Значение по умолчанию 90000 милисекунд (90 сек.)
POOL_Maximumitems – максимальное значение пула. Когда приложение требует больше соединений, выскакивает исключение. Значение по умолчанию 50.