2017-05-27 33 views
3

我目前正在研究多線程服務器應用程序,並計劃使用Firedac進行數據訪問。從這裏提供的文檔:http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Multithreading_(FireDAC),似乎不應該同時從多個線程訪問相同的TFDConnection和/或TFDQuery(而應根據每個線程創建這些對象)。在多線程應用程序中使用Firedac

因此,上一個鏈接中顯示的示例將TFDConnectionTFDQuery集中在一個TThread對象中。但是,在我的情況下,我無法控制線程創建(由服務器環境管理)。因此,我限制我的TFDConnectionTFDQuery對象的生命週期過程的生命週期,它可以從多個線程可能叫做:

procedure TAPMFiredacTemplate.executeSQL(sql:string); 
    var 
    oConn: TFDConnection; 
    oQuery: TFDQuery; 
    begin 
    oConn := TFDConnection.Create(nil); 
    oQuery := TFDQuery.Create(nil); 
    try 
    oConn.ConnectionDefName := self.ConnectionDefinitionName; 
    oConn.Connected := True; 
    oQuery.Connection := oConn; 
    oQuery.Open(sql); 
    while not oQuery.Eof do 
    begin 
     // process query 
     oQuery.Next; 
    end; 

    finally 
    if assigned(oQuery) then 
    begin 
     oQuery.Close; 
     oQuery.Free; 
    end; 
    if assigned (oConn) then 
    begin 
     oConn.Connected := False; 
     oConn.Free; 
    end; 

    end; 

是這種方法有效嗎?有系統地創建TFDQuery對象是否有性能問題?

注意:爲了提高性能,我打算使用專用池連接定義(由TFDConnection使用)。所以,即使當我釋放TFDConnection我的理解,物理連接不被破壞,但返回到池中,而不是:

oParams := TStringList.Create; 
oParams.Add('Database=localhost:c:\apm\databases\mydb.FDB'); 
oParams.Add('User_Name=xxxxx'); 
oParams.Add('Password=xxxxxx'); 
oParams.Add('Pooled=True'); 
FDManager.AddConnectionDef('Firebird_Pooled','FB',oParams); 
FDManager.Active := True; 
+1

沒有爲第一種方法的性能損失。繼續閱讀[連接池](http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Multithreading_(FireDAC)#Connection_Pooling)。然後你可以檢查[示例](http://docwiki.embarcadero.com/CodeExamples/Tokyo/en/FireDAC.Pooling_Sample)。 – Victoria

+0

我正在考慮通過'FDManager'事先創建一個連接池,然後在我的過程中使用這些池連接(請參閱附加註釋)。所以即使我創建/釋放'TFDConnection',物理連接或來自池。這應該會提高性能。但是我不確定我可以做什麼來優化'TFDQuery' – BigONotation

+1

準備查詢可能是另一個昂貴的操作。如果您的服務器支持保持連接類型,請創建查詢對象,並在客戶端連接時準備查詢,並在斷開連接時將其銷燬。然後,您可以使用準備好的查詢對象(由上下文類引用)處理每個請求。 – Victoria

回答

2

它是線程上下文執行一個有效的方法,但它在數據庫連接的性能損失建立和查詢每個客戶端請求準備(假設您正在使用一些Indy服務器)。

要修復第一個問題,請使用connection pooling(您可以按照the example)。

要解決後一個問題,也可以有解決方案。如果您的服務器支持保持活動連接類型,請在客戶端連接並斷開連接時創建查詢對象並將其銷燬。這個準備好的對象可以通過擴展上下文類傳遞給服務器請求處理方法。

例如與TIdTCPServer則可能是:

type 
    { server context in the meaning of a "task" class } 
    TMyContext = class(TIdServerContext) 
    private 
    FQuery: TFDQuery; 
    public 
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); override; 
    destructor Destroy; override; 
    property Query: TFDQuery read FQuery; 
    end; 

    TForm1 = class(TForm) 
    IdTCPServer1: TIdTCPServer; 
    FDConnection1: TFDConnection; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure IdTCPServer1Connect(AContext: TIdContext); 
    procedure IdTCPServer1Disconnect(AContext: TIdContext); 
    procedure IdTCPServer1Execute(AContext: TIdContext); 
    procedure IdTCPServer1Exception(AContext: TIdContext; AException: Exception); 
    end; 

implementation 

constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TThreadList = nil); 
begin 
    inherited; 
    FQuery := TFDQuery.Create(nil); 
end; 

destructor TMyContext.Destroy; 
begin 
    FQuery.Free; 
    inherited; 
end; 

procedure TForm1.FormCreate(Sender: TObject); 
var 
    Params: TStrings; 
begin 
    Params := TStringList.Create; 
    try 
    Params.Add('Database=localhost:C:\MyDatabase.fdb'); 
    Params.Add('User_Name=xxxxx'); 
    Params.Add('Password=xxxxx'); 
    Params.Add('Pooled=True'); 
    { add the definition to the global connection manager singleton } 
    FDManager.AddConnectionDef('FirebirdPooled', 'FB', Params); 
    finally 
    Params.Free; 
    end; 

    { use the added definition and establish the connection to the DB } 
    FDConnection1.ConnectionDefName := 'FirebirdPooled'; 
    FDConnection1.Connected := True; 

    { setup the context class, add a port binding and start the TCP server } 
    IdTCPServer1.ContextClass := TMyContext; 
    IdTCPServer1.Bindings.Add.Port := 6000; 
    IdTCPServer1.Active := True; 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    { stop the TCP server and destroy all pooled physical connections } 
    IdTCPServer1.Active := False; 
    FDManager.CloseConnectionDef('FirebirdPooled'); 
end; 

procedure TForm1.IdTCPServer1Connect(AContext: TIdContext); 
begin 
    { client just connected, assign to the context query object the pooled 
    connection and a command text } 
    TMyContext(AContext).Query.Connection := FDConnection1; 
    TMyContext(AContext).Query.SQL.Text := 'SELECT * FROM MyTable WHERE ID=:ID'; 
    { preparing the query will acquire one physical connection from the pool 
    as this method internally opens the connection } 
    TMyContext(AContext).Query.Prepare; 
end; 

procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext); 
begin 
    { client just disconnected, return the physical connection to the pool } 
    TMyContext(AContext).Query.Connection.Close; 
end; 

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); 
var 
    ID: Integer; 
    Query: TFDQuery; 
begin 
    { read an integer from socket } 
    ID := AContext.Connection.IOHandler.ReadInteger; 
    { just a reference helper } 
    Query := TMyContext(AContext).Query; 

    { fill the parameter and refresh the prepared query object's dataset } 
    Query.Params[0].AsInteger := ID; 
    Query.Refresh; 

    while not Query.Eof do 
    begin 
    { process the dataset somehow } 
    Query.Next; 
    end; 

    { do not close the dataset, keep it prepared for the next possible request } 
end; 

procedure TForm1.IdTCPServer1Exception(AContext: TIdContext; AException: Exception); 
begin 
    if AException is EFDException then 
    begin 
    { something bad happened with the DB, this is a base FireDAC exception 
     class but you can be more specific of course } 
    end; 
end;