我想从MS Access文件中读取整个表,我正在尝试尽快完成。在测试大样本时,我发现循环计数器在读取与表的最后记录相比的最高记录时增加得更快。以下是演示此内容的示例代码:
procedure TForm1.Button1Click(Sender: TObject);
const
MaxRecords = 40000;
Step = 5000;
var
I, J: Integer;
Table: TADOTable;
T: Cardinal;
Ts: TCardinalDynArray;
begin
Table := TADOTable.Create(nil);
Table.ConnectionString :=
'Provider=Microsoft.ACE.OLEDB.12.0;'+
'Data Source=BigMDB.accdb;'+
'Mode=Read|Share Deny Read|Share Deny Write;'+
'Persist Security Info=False';
Table.TableName := 'Table1';
Table.Open;
J := 0;
SetLength(Ts, MaxRecords div Step);
T := GetTickCount;
for I := 1 to MaxRecords do
begin
Table.Next;
if ((I mod Step) = 0) then
begin
T := GetTickCount - T;
Ts[J] := T;
Inc(J);
T := GetTickCount;
end;
end;
Table.Free;
// Chart1.SeriesList[0].Clear;
// for I := 0 to Length(Ts) - 1 do
// begin
// Chart1.SeriesList[0].Add(Ts[I]/1000, Format(
// 'Records: %s %d-%d %s Duration:%f s',
// [#13, I * Step, (I + 1)*Step, #13, Ts[I]/1000]));
// end;
end;
该表有两个字符串字段,一个double和一个整数。它没有主键也没有索引字段。为什么会发生这种情况?如何防止它?
答案 0 :(得分:18)
我可以使用AdoQuery重现您的结果,其中MS Sql Server数据集的大小与您的相似。
然而,在做了一些线条剖析之后,我想我已经找到了答案,这有点违反直觉。我确信每个人都这样做 Delphi中的数据库编程用于这样的想法,即如果通过调用Disable / EnableControls来环绕循环,则循环数据集的速度会快得多。但是,如果没有数据集附加数据集感知控件,那谁还会这么做呢?
嗯,事实证明,在您的情况下,即使没有DB-aware控件,如果您使用Disable / EnableControls,速度也会大大增加。
原因是AdoDB.Pas中的TCustomADODataSet.InternalGetRecord包含:
if ControlsDisabled then
RecordNumber := -2 else
RecordNumber := Recordset.AbsolutePosition;
根据我的行分析器,而不是AdoQuery1.Eof做AdoQuery1.Next循环花费98.8%的时间执行赋值
RecordNumber := Recordset.AbsolutePosition;
! Recordset.AbsolutePosition的计算当然隐藏在"错误的一面"对于Recordset界面,但事实上,调用它的时间显然会增加你进入记录集的距离,这使得通过从记录集的数据开始计算它来计算它是合理的。
当然,ControlsDisabled
如果已调用DisableControls
而未通过调用EnableControls
撤消,则返回true。所以,使用Disable / EnableControls包围的循环重新测试,希望你能得到类似的结果。看起来你认为减速与内存分配无关是正确的。
使用以下代码:
procedure TForm1.btnLoopClick(Sender: TObject);
var
I: Integer;
T: Integer;
Step : Integer;
begin
Memo1.Lines.BeginUpdate;
I := 0;
Step := 4000;
if cbDisableControls.Checked then
AdoQuery1.DisableControls;
T := GetTickCount;
{.$define UseRecordSet}
{$ifdef UseRecordSet}
while not AdoQuery1.Recordset.Eof do begin
AdoQuery1.Recordset.MoveNext;
Inc(I);
if I mod Step = 0 then begin
T := GetTickCount - T;
Memo1.Lines.Add(IntToStr(I) + ':' + IntToStr(T));
T := GetTickCount;
end;
end;
{$else}
while not AdoQuery1.Eof do begin
AdoQuery1.Next;
Inc(I);
if I mod Step = 0 then begin
T := GetTickCount - T;
Memo1.Lines.Add(IntToStr(I) + ':' + IntToStr(T));
T := GetTickCount;
end;
end;
{$endif}
if cbDisableControls.Checked then
AdoQuery1.EnableControls;
Memo1.Lines.EndUpdate;
end;
我得到以下结果(除非另有说明,否则调用DisableControls 不):
Using CursorLocation = clUseClient
AdoQuery.Next AdoQuery.RecordSet AdoQuery.Next
.MoveNext + DisableControls
4000:157 4000:16 4000:15
8000:453 8000:16 8000:15
12000:687 12000:0 12000:32
16000:969 16000:15 16000:31
20000:1250 20000:16 20000:31
24000:1500 24000:0 24000:16
28000:1703 28000:15 28000:31
32000:1891 32000:16 32000:31
36000:2187 36000:16 36000:16
40000:2438 40000:0 40000:15
44000:2703 44000:15 44000:31
48000:3203 48000:16 48000:32
=======================================
Using CursorLocation = clUseServer
AdoQuery.Next AdoQuery.RecordSet AdoQuery.Next
.MoveNext + DisableControls
4000:1031 4000:454 4000:563
8000:1016 8000:468 8000:562
12000:1047 12000:469 12000:500
16000:1234 16000:484 16000:532
20000:1047 20000:454 20000:546
24000:1063 24000:484 24000:547
28000:984 28000:531 28000:563
32000:906 32000:485 32000:500
36000:1016 36000:531 36000:578
40000:1000 40000:547 40000:500
44000:968 44000:406 44000:562
48000:1016 48000:375 48000:547
将AdoQuery1.Recordset.MoveNext
调用直接调用到MDac / ADO层
当然,而AdoQuery1.Next涉及标准TDataSet的所有开销
模型。正如Serge Kraikov所说,改变CursorLocation确实有所作为,并没有表现出我们注意到的减速,但显然它比使用clUseClient并调用DisableControls要慢得多。我想这取决于你是否可以利用在RecordSet.MoveNext中使用clUseClient的额外速度来确切地尝试做什么。
答案 1 :(得分:1)
当您打开表格时,ADO数据集会在内部创建特殊的数据结构,以向前/向后导航数据集 - "数据集CURSOR"。在导航期间,ADO存储已访问记录的列表以提供双向导航 似乎ADO游标代码使用二次时间O(n2)算法来存储此列表 但有解决方法 - 使用服务器端游标:
Table.CursorLocation := clUseServer;
我使用此修复程序测试了您的代码并获得线性提取时间 - 获取每个下一个记录块的时间与之前相同。
PS一些其他数据访问库提供特殊的"单向"数据集 - 此数据集只能向前遍历,甚至不存储已经遍历的记录 - 您将获得恒定的内存消耗和线性提取时间。
答案 2 :(得分:1)
DAO是Access的原生,而(IMHO)通常更快。
无论您是否切换,请使用GetRows方法。 DAO和ADO都支持它。
没有循环。您可以将整个记录集转储到包含几行代码的数组中。航空代码:
yourrecordset.MoveLast
yourrecordset.MoveFirst
yourarray = yourrecordset.GetRows(yourrecordset.RecordCount)