我正在使用 Delphi XE8 和 FireDAC 来加载大型 SQLite 数据库。为此,我使用 Array 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 数据库中的速度非常快,我对此速度没有任何问题。
让我慢下来的是在重复循环中将所有值分配给参数所花费的时间。
参数内置在 FireDAC 中并且是一个 TCollection。我无权访问源代码,所以我看不到 AsStrings 和 AsIntegers 方法实际上在做什么。
在我看来,将每个值分配给每个插入的每个参数并不是加载此 TCollection 的一种非常有效的方法。有没有更快的方法来加载这个?我在想一种方法可以一次加载一整套参数,例如 (IndiKey, HasData, ... FirstAncestralLoop) 全部作为一个。或者尽可能高效地加载我自己的TCollection,然后使用TCollection 的Assign 方法将我的TCollection 复制到FireDAC 的TCollection 中。
所以我的问题是,加载 FireDAC 所需的 TCollection 参数的最快方法是什么?
更新:我包括了 Arnaud 的一些时间安排。
如将 SQLite 与 FireDAC 一起使用(请参阅其数组 DML 部分)中所述:
从 v 3.7.11 开始,SQLite 支持具有多个 VALUES 的 INSERT 命令。当 Params.BindMode = pbByNumber 时,FireDAC 使用此功能来实现 Array DML。否则,FireDAC 会模拟 Array 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 中每次测试运行都需要整整 93 毫秒。无论是一次添加 1 个还是一次添加 10000 个,填充 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)
对于此测试的所有情况,加载 TCollection of Params 的开销大约需要 503 毫秒。
因此,TCollection 的加载似乎是每秒大约 400,000 条记录。这是插入时间的重要部分,一旦我开始使用数百万的大型数据库,这个增加的时间对于我的程序的用户来说将是非常明显的。
我想改进这一点,但我还没有找到一种方法来加快参数的加载。
更新 2:通过将我的所有代码放在 StartTransaction 和 Commit 之间,我能够获得大约 10% 的时间改进,以便立即处理所有块。
但我仍在寻找某种方法来更快地加载 TCollection of Params。
另一个想法:
如果可能的话,可能会运行良好并且速度可能会提高 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 只会分配给第一组 Params,即 NumIndiParms = 0。
有没有办法为循环中的每个索引执行此操作,即 NumIndiParms 的每个实例?
Bounty:我真的很想加快 Params 的加载速度。我现在为某人提供赏金,以帮助我找到一种方法来加快在 FireDAC 中实现的 Params 数组 TCollection 的加载。