为什么滚动ADOTable变得越来越慢?

saa*_*stn 10 delphi ms-access ado delphi-xe5

我想从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;
Run Code Online (Sandbox Code Playgroud)

在我的电脑上的结果: 在此输入图像描述

该表有两个字符串字段,一个double和一个整数.它没有主键也没有索引字段.为什么会发生这种情况,我该如何预防呢?

Mar*_*ynA 19

我可以使用AdoQuery重现您的结果,其中MS Sql Server数据集的大小与您的相似.

然而,在做了一些线性分析之后,我想我已经找到了答案,这有点违反直觉.我敢肯定,在Delphi中进行数据库编程的每个人都习惯了如果通过调用Disable/EnableControls来环绕循环,那么循环数据集往往要快得多.但是,如果没有数据集附加数据集感知控件,谁会费心去做?

好吧,事实证明,在你的情况下,即使没有DB-aware控件,如果你使用Disable/EnableControls,速度也会大大增加.

原因是AdoDB.Pas中的TCustomADODataSet.InternalGetRecord包含:

      if ControlsDisabled then
        RecordNumber := -2 else
        RecordNumber := Recordset.AbsolutePosition;
Run Code Online (Sandbox Code Playgroud)

根据我的行分析器,而不是AdoQuery1.Eof做AdoQuery1.Next循环花费98.8%的时间执行赋值

        RecordNumber := Recordset.AbsolutePosition;
Run Code Online (Sandbox Code Playgroud)

!Recordset.AbsolutePosition的计算当然是隐藏在Recordset接口的"错误的一面",但事实上,调用它的时间显然会增加你进入记录集的进一步,这使得推测它的计算是合理的从记录集的数据开始计数.

当然,ControlsDisabled如果DisableControls已经调用而不是通过调用撤消,则返回true EnableControls.因此,使用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;
Run Code Online (Sandbox Code Playgroud)

我得到以下结果(除非另有说明,否则调用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
Run Code Online (Sandbox Code Playgroud)

AdoQuery1.Recordset.MoveNext当然,将调用直接调用到MDac/ADO层,而AdoQuery1.Next涉及标准TDataSet模型的所有开销.正如Serge Kraikov所说,改变CursorLocation肯定会有所不同,并没有表现出我们注意到的减速,但显然它比使用clUseClient并调用DisableControls要慢得多.我想这取决于你是否可以利用与RecordSet.MoveNext一起使用clUseClient的额外速度.