Delphi XE8 と FireDAC を使用して、大規模な SQLite データベースをロードしています。そのために、配列 DML 実行手法を使用して、次のように多数のレコードを一度に効率的に挿入しています。
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);
SQLite データベースへのデータの実際のロードは非常に高速で、その速度に問題はありません。
私を遅くしているのは、繰り返しループですべての値をパラメーターに割り当てるのにかかる時間です。
Params は FireDAC に組み込まれており、TCollection です。ソース コードにアクセスできないため、AsStrings および AsIntegers メソッドが実際に何を行っているかを確認できません。
各挿入の各パラメータに各値を割り当てることは、この TCollection をロードするための非常に効率的な方法ではないように思えます。これをロードするより速い方法はありますか?パラメータのセット全体を一度にロードする方法を考えています。たとえば、(IndiKey、HasData、... FirstAncestralLoop) をすべて 1 つとしてロードします。または、自分の TCollection をできる限り効率的にロードし、TCollection の Assign メソッドを使用して、自分の TCollection を FireDAC の TCollection にコピーすることもできます。
私の質問は、FireDAC が必要とするパラメータのこの TCollection をロードする最速の方法は何でしょうか?
更新: Arnaud のタイミングをいくつか含めています。
Using SQLite with FireDAC で説明されているように(配列 DML セクションを参照):
v 3.7.11 以降、SQLite は複数の値を持つ INSERT コマンドをサポートしています。Params.BindMode = pbByNumber の場合、FireDAC はこの機能を使用して配列 DML を実装します。それ以外の場合、FireDAC は配列 DML をエミュレートします。
配列サイズ (実行ごとにロードするレコード数) を変更して 33,790 レコードの挿入をテストし、pbByName (エミュレーション用) と pbByNumber (複数の値の挿入を使用) の両方でロード時間を計りました。
これはタイミングでした:
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)
これらのタイミングの興味深い点は、これらの 33,790 レコードを TCollection にロードするのに、1 回のテスト実行ごとに 93 ミリ秒かかっていることです。一度に 1 つずつ追加されるか、一度に 10000 ずつ追加されるかは関係ありません。Param の TCollection を埋めるこのオーバーヘッドは常に存在します。
比較のために、pbByNumber のためだけに 198,522 個の挿入でより大きなテストを行いました。
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)
このテストのすべてのケースで、Params の TCollection をロードするオーバーヘッドは約 503 ミリ秒かかります。
したがって、TCollection の読み込みは 1 秒あたり約 400,000 レコードのようです。これは挿入時間のかなりの部分であり、何百万もの大規模なデータベースで作業を開始すると、この追加時間はプログラムのユーザーにとって非常に顕著になります。
これを改善したいのですが、Params の読み込みを高速化する方法をまだ見つけていません。
更新 2: すべてのコードを StartTransaction と Commit の間に配置して、すべてのブロックが一度に処理されるようにすることで、約 10% の時間を短縮できました。
しかし、Params の TCollection をより高速にロードする方法をまだ探しています。
もう1つのアイデア:
うまく機能し、可能であれば最大 16 倍高速になる可能性があるのは、ParamValues メソッドのようなものです。これにより、一度に複数のパラメーターが割り当てられ、バリアント配列を直接提供できるという利点が追加され、値をキャストする必要がなくなります。
次のように機能します。
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]);
ただし、ParamValues は、Param の最初のセット、つまり NumIndiParms = 0 にのみ割り当てられます。
ループ内の各インデックス、つまり NumIndiParms のすべてのインスタンスに対してこれを行う方法はありますか?
Bounty: Params の読み込みを高速化したいです。現在、FireDAC に実装されている Params 配列 TCollection の読み込みを高速化する方法を見つけてくれる人に報奨金を提供しています。