2017-05-27 1 views
3

Ich arbeite gerade an einer Multithread-Server-App und plane, Firedac für den Datenzugriff zu verwenden. Aus den hier bereitgestellten Dokumenten: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Multithreading_(FireDAC) scheint es, dass auf dieselben TFDConnection und/oder TFDQuery nicht gleichzeitig von mehreren Threads aus zugegriffen werden sollte (stattdessen sollten diese Objekte pro Thread-Basis erstellt werden).Firedac in einer Multithread-App verwenden

Daher zentralisiert das Beispiel in der vorherigen Verknüpfung die TFDConnection und TFDQuery in einem TThread Objekt. In meinem Fall habe ich jedoch keine Kontrolle über die Thread-Erstellung (die von der Serverumgebung verwaltet wird). Ich beschränke daher den Lebenszyklus von meinen TFDConnection und TFDQuery Objekten auf die Lebensdauer eines Verfahrens, das möglicherweise von mehreren Threads aufgerufen werden kann:

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; 

Ist dieser Ansatz gültig? Wird die Performance beeinträchtigt, indem das Objekt TFDQuery systematisch erstellt wird?

Hinweis: Um die Leistung zu verbessern, plane ich die Verwendung einer privaten gepoolten Verbindungsdefinition (die von der TFDConnection verwendet wird). Also von meinem Verständnis, auch wenn ich die TFDConnection befreien, wird die physikalische Verbindung nicht zerstört, sondern wieder in den Pool:

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

Es gibt eine Leistungseinbuße für den ersten Ansatz. Lesen Sie weiter zu [Connection Pooling] (http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Multithreading_ (FireDAC) #Connection_Pooling). Sie können dann [das Beispiel] (http://docwiki.embarcadero.com/CodeExamples/Tokyo/en/FireDAC.Pooling_Sample) überprüfen. – Victoria

+0

Ich dachte daran, einen Verbindungspool im Voraus über den 'FDManager' zu erstellen und dann diese gepoolte Verbindung in meiner Prozedur zu verwenden (siehe Anmerkung). Also auch, wenn ich die 'TFDConnection', die physikalischen Verbindungen oder die aus dem Pool kommen, freistelle. Dies sollte die Leistung verbessern. Ich bin mir jedoch nicht sicher, was ich tun kann, um 'TFDQuery' zu optimieren. – BigONotation

+1

Das Vorbereiten einer Abfrage kann eine andere teure Operation sein. Wenn Ihr Server Keep-Alive-Art von Verbindung unterstützt, erstellen Sie das Abfrageobjekt und bereiten Sie die Abfrage vor, wenn der Client eine Verbindung herstellt, und zerstören Sie die Abfrage, wenn die Verbindung getrennt wird. Jede Anfrage kann dann mit dem vorbereiteten Anfrageobjekt (referenziert von der Kontextklasse) bearbeitet werden. – Victoria

Antwort

2

Es ist ein brauchbares Konzept für Thread-Kontext Ausführung, aber es hat eine Leistungseinbuße in Datenbankverbindung Einrichtung und Abfragevorbereitung mit jeder Client-Anfrage (vorausgesetzt, Sie verwenden einen Indy-Server).

Um das erste Problem zu beheben, verwenden Sie die connection pooling (Sie können the example folgen).

Um das letztere Problem zu beheben, kann es auch eine Lösung geben. Wenn Ihr Server die Verbindung vom Typ "Keep Alive" unterstützt, erstellen Sie das Abfrageobjekt und prepare the query, wenn der Client eine Verbindung herstellt, und zerstören Sie ihn, wenn die Verbindung getrennt wird. Dieses vorbereitete Objekt können Sie über die erweiterte Kontextklasse an die Verarbeitungsmethode für Serveranforderungen übergeben.

Zum Beispiel mit TIdTCPServer könnte es sein:

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; 
Verwandte Themen