Ich verwende Delphi XE8 mit FireDAC, um eine große SQLite-Datenbank zu laden. Dazu verwende ich die Array DML Ausführungstechnik, um effizient eine große Anzahl von Datensätzen auf einmal einsetzen, wie folgt aus:Der schnellste Weg zum Laden einer Array-DML in Delphi FireDAC
FDQueryAddINDI.SQL.Text := 'insert into indi values ('
+ ':indikey, :hasdata, :gedcomnames, :sex, :birthdate, :died, '
+ ':deathdate, :changed, :eventlinesneedprocessing, :eventlines, '
+ ':famc, :fams, :linkinfo, :todo, :nextreportindi, :firstancestralloop'
+ ')';
FDQueryAddINDI.Params.Bindmode := pbByNumber; {more efficient than by name }
FDQueryAddINDI.Params.ArraySize := MaxParams; { large enough to load all of them }
NumParams := 0;
repeat
{ the code to determin IndiKey,... is not shown, but goes here }
FDQueryAddINDI.Params[0].AsStrings[NumParams] := IndiKey;
FDQueryAddINDI.Params[1].AsIntegers[NumParams] := HasData;
FDQueryAddINDI.Params[2].AsStrings[NumParams] := GedcomNames;
FDQueryAddINDI.Params[3].AsStrings[NumParams] := Sex;
FDQueryAddINDI.Params[4].AsStrings[NumParams] := Birthdate;
FDQueryAddINDI.Params[5].AsIntegers[NumParams] := Died;
FDQueryAddINDI.Params[6].AsStrings[NumParams] := Deathdate;
FDQueryAddINDI.Params[7].AsStrings[NumParams] := Changed;
FDQueryAddINDI.Params[8].AsIntegers[NumParams] := EventLinesNeedProcessing;
FDQueryAddINDI.Params[9].AsStrings[NumParams] := EventLines;
FDQueryAddINDI.Params[10].AsIntegers[NumParams] := FamC;
FDQueryAddINDI.Params[11].AsIntegers[NumParams] := FamS;
FDQueryAddINDI.Params[12].AsIntegers[NumParams] := Linkinfo;
FDQueryAddINDI.Params[13].AsIntegers[NumParams] := ToDo;
FDQueryAddINDI.Params[14].AsIntegers[NumParams] := NextReportIndi;
FDQueryAddINDI.Params[15].AsIntegers[NumParams] := FirstAncestralLoop;
inc(NumParams);
until done;
FDQueryAddINDI.Params.ArraySize := NumParams; { Reset to actual number }
FDQueryAddINDI.Execute(LogoAppForm.FDQueryAddINDI.Params.ArraySize);
Die eigentliche Laden der Daten in die SQLite-Datenbank ist sehr schnell, und ich habe kein Problem mit der Geschwindigkeit davon.
Was mich verlangsamt, ist die Zeit, die in der Wiederholungsschleife benötigt wird, um alle Werte den Parametern zuzuweisen.
Die Parameter sind in FireDAC integriert und sind eine TCollection. Ich habe keinen Zugriff auf den Quellcode, daher kann ich nicht sehen, was die AsStrings- und AsIntegers-Methoden tatsächlich tun.
Das Zuweisen jedes Werts zu jedem Parameter für jede Einfügung scheint mir keine sehr effiziente Möglichkeit zu sein, diese TCollection zu laden. Gibt es eine schnellere Möglichkeit, dies zu laden? Ich denke, vielleicht eine Möglichkeit, einen ganzen Satz von Parametern gleichzeitig zu laden, z.B. (IndiKey, HasData, ... FirstAncestralLoop) alle als eins. Oder vielleicht, um meine eigene TCollection so effizient wie möglich zu laden, und dann die Assign-Methode der TCollection zu verwenden, um meine TCollection in die TCollection des FireDAC zu kopieren.
Also meine Frage ist, was wäre der schnellste Weg zum Laden dieser TCollection von Parametern, die FireDAC benötigt?
Update: Ich bin ein paar Timings für Arnaud enthalten.
Wie in Using SQLite with FireDAC angegeben (seine Array DML Abschnitt):
mit v Starten 3.7.11 unterstützt SQLite den INSERT-Befehl mit mehr Werten. FireDAC verwendet diese Funktion zum Implementieren von Array DML, , wenn Params.BindMode = pbByNumber. Andernfalls emuliert FireDAC Array DML.
Ich habe 33.790 Datensätze getestet Einsetzen des Arraysize Ändern (Anzahl der Datensätze pro ausführen zu laden) und zeitlich gesteuert, die Ladezeit mit beiden pbByName (für die Emulation) und pbByNumber (mehrere Werte einfügen verwenden).
Dies war das Timing:
Arraysize: 1, Executes: 33,790, Timing: 1530 ms (pbByName), 1449 ms (pbByNumber)
Arraysize: 10, Executes: 3,379, Timing: 1034 ms (pbByName), 782 ms (pbByNumber)
Arraysize: 100, Executes: 338, Timing: 946 ms (pbByName), 499 ms (pbByNumber)
Arraysize: 1000, Executes: 34, Timing: 890 ms (pbByName), 259 ms (pbByNumber)
Arraysize: 10000, Executes: 4, Timing: 849 ms (pbByName), 227 ms (pbByNumber)
Arraysize: 20000, Executes: 2, Timing: 594 ms (pbByName), 172 ms (pbByNumber)
Arraysize: 50000, Executes: 1, Timing: 94 ms (pbByName), 94 ms (pbByNumber)
Nun das Interessante an diesen Zeitpunkt ist, dass das Laden dieser 33.790 Datensätze in die TCollection ist ein vollen 93 ms jeden einzelnen Testlauf nehmen. Es spielt keine Rolle, ob sie 1 gleichzeitig oder 10000 gleichzeitig hinzugefügt werden, dieser Overhead zum Füllen der TCollection of Params ist immer da.
Zum Vergleich habe ich einen größeren Test mit 198.522 Einsätzen nur für pbByNumber:
Arraysize: 100, Executes: 1986, Timing: 2774 ms (pbByNumber)
Arraysize: 1000, Executes: 199, Timing: 1371 ms (pbByNumber)
Arraysize: 10000, Executes: 20, Timing: 1292 ms (pbByNumber)
Arraysize: 100000, Executes: 2, Timing: 894 ms (pbByNumber)
Arraysize: 1000000, Executes: 1, Timing: 506 ms (pbByNumber)
Für alle Fälle dieses Tests, der Aufwand für die TCollection von Param Laden dauert etwa 503 ms.
So scheint das Laden der TCollection bei etwa 400.000 Datensätze pro Sekunde zu sein. Dies ist ein bedeutender Teil der Einfügezeit, und sobald ich anfange, mit großen Datenbanken in Millionenhöhe zu arbeiten, wird diese zusätzliche Zeit für den Benutzer meines Programms ziemlich auffällig sein.
Ich möchte dies verbessern, aber ich habe noch keinen Weg gefunden, um das Laden der Params zu beschleunigen.
Update 2: Ich konnte über eine 10% ige Zeitverbesserung erhalten, indem all meine Codes zwischen einer Starttransaction setzen und einem Commit, so dass alle Blöcke auf einmal verarbeitet werden.
Aber ich bin immer noch auf der Suche nach einer Möglichkeit, die TCollection of Params viel schneller zu laden.
Eine andere Idee:
Was könnte gut funktionieren und schneller bis zu 16-mal sein könnte, wenn es möglich wäre, so etwas wie the ParamValues method wäre. Dies weist mehrere Parameter gleichzeitig zu und hat den zusätzlichen Vorteil, dass direkt ein variantes Array bereitgestellt wird und die Notwendigkeit, Werte zu konvertieren, vermieden wird.
Es würde so funktionieren:
FDQueryAddINDI.Params.ParamValues['indikey;hasdata;gedcomnames;sex;birthdate;died;deathdate;changed;eventlinesneedprocessing;eventlines;famc;fams;linkinfo;todo;nextreportindi;firstancestralloop']
:= VarArrayOf([Indikey, 0, ' ', ' ', ' ', 0, ' ', ' ', 1, ' ', -1, -1, -1, -1, -1, -1]);
jedoch ParamValues nur auf den ersten Satz von Param vergeben wird, dh wo NumIndiParms = 0.
Gibt es eine Möglichkeit, dies für jeden Index zu tun in der Schleife, dh jede Instanz von NumIndiParms?
Bounty: Ich möchte wirklich das Laden der Params beschleunigen. Ich biete jetzt ein Kopfgeld für jemanden an, der mir hilft, das Laden des Params-Arrays TCollection, wie es in FireDAC implementiert ist, zu beschleunigen.
Danke, Arnaud, für Ihre Ideen. Dies ist keine vorzeitige Optimierung. Ich mache jetzt gerade die Optimierung. :-) Ich hatte vorher verschiedene Arraygrößen pro Transaktion getestet und habe nun einige davon in meinem Update zu meiner Frage hinzugefügt. Ich stimme Ihnen zu, dass eine Integer- oder String-Zuweisung sofort erfolgen sollte, daher ist offensichtlich, dass beim Hinzufügen zu einer TCollection viel mehr passiert als nur die Zuweisungen. Hoffentlich kann mir jemand einen Einblick geben, wie ich diesen Teil schneller machen kann. – lkessler
@lkessler Verwenden Sie also nicht die FireDAC-Abstraktion, sondern direkt die SQLite3-Ebene. Siehe mein Update. –
@lkessler Ich habe gerade die Methode 'TSQLRequest.BindS' optimiert, um jegliche Speicherzuordnung während der' string' Parameterbindung zu vermeiden. Es kann helfen, die Zeichenfolgenwerte direkt von Ihrer 'TCollection' zu binden. Siehe [dieses Commit] (http://synopse.info/fossil/info/88ef687fa2). –