Delphi性能:读取数据集中一个字段下的所有值



我们正试图找出一些性能修复从TADOQuery读取。目前,我们使用"while not Q.eof do begin…"循环遍历记录。Q.next方法。对于每条记录,我们读取每条记录的ID和Value,并将它们添加到组合框列表中。

是否有一种方法可以将指定字段的所有值转换为一个列表?而不是遍历数据集?如果我能做这样的事情,那就太方便了。

TStrings(MyList).Assign(Q.ValuesOfField['Val']);

我知道这不是一个真正的命令,但这是我正在寻找的概念。寻找快速响应和解决方案(一如既往,但这是为了解决一个非常紧急的性能问题)。

看看你的评论,这里有一些建议:

在这种情况下,有一些事情可能成为瓶颈。第一种是反复查看田野。如果你在循环中调用FieldByNameFindField,你是在浪费CPU时间来重新计算一个不会改变的值。

为你正在读取的每个字段调用一次FieldByName,并将它们赋值给本地变量。

当从字段中检索值时,调用AsStringAsInteger,或其他返回您正在寻找的数据类型的方法。如果你从TField.Value属性中读取,你就是在variant转换上浪费时间。

如果你要在Delphi组合框中添加一堆项目,你可能要处理的是Items属性形式的字符串列表。设置列表的Capacity属性,并确保在开始更新之前调用BeginUpdate,并在最后调用EndUpdate。这可以实现一些内部优化,使加载大量数据更快。

根据您使用的组合框,它在处理内部列表中的大量项目时可能会遇到一些麻烦。看看它是否有一个"虚拟"模式,而不是你预先加载所有内容,你只需告诉它需要多少项,当它下拉时,它为每个应该显示在屏幕上的项目调用事件处理程序,你给它正确的文本显示。这确实可以加快某些UI控件。

当然,您应该确保您的数据库查询本身是快速的,但是SQL优化超出了这个问题的范围。

最后,Mikael Eriksson的评论绝对值得关注!

可以使用Getrows。指定感兴趣的列,它将返回一个带有值的数组。在我的测试中,将22,000行添加到组合框所需的时间从while not ADOQuery1.Eof ...循环的7秒减少到1.3秒。

示例代码:

var
  V: Variant;
  I: Integer;
begin
  V := ADOQuery1.Recordset.GetRows(adGetRowsRest, EmptyParam, 'ColumnName');
  for I:= VarArrayLowBound(V, 2) to VarArrayHighBound(V, 2) do
    ComboBox1.Items.Add(V[0, I]));
end;

如果你想在数组中有多个列,你应该使用一个变量数组作为第三个参数。

V := ADOQuery1.Recordset.GetRows(adGetRowsRest, EmptyParam, 
       VarArrayOf(['ColumnName1', 'ColumnName2']);

其他人提出了一些很好的性能建议,您应该在Delphi中实现。你应该考虑一下。我将重点讨论ADO。

你没有指定后端数据库服务器是什么,所以我不能太具体,但有一些事情你应该知道关于ADO。

ADO记录集

在ADO中,有一个RecordSet对象。这个RecordSet对象基本上是你的ResultSet。关于遍历RecordSet的有趣的事情是,它仍然与提供者耦合。

游标类型

如果游标类型是Dynamic或Delphi的默认Keyset,那么每次RecordSet向提供程序请求新行时,提供程序将在返回记录之前检查是否有任何更改。

因此,对于TADOQuery,您所做的一切都是读取结果集来填充组合框,并且它不太可能已经更改,您应该使用Static游标类型来避免检查更新的记录。

如果你不知道光标是什么,当你调用像Next这样的函数时,你是在移动光标,它代表当前记录。

不是每个提供程序都支持所有的游标类型。

CacheSize

Delphi和ADO的默认缓存大小为RecordSet为1。这是1条记录。这与游标类型结合使用。缓存大小告诉RecordSet一次要获取和存储多少条记录。

当你发出一个命令像下一步(真的在ADO的MoveNext)的缓存大小为1,RecordSet只有当前的记录在内存中,所以当它获取下一个记录,它必须再次从提供者请求它。如果游标不是Static,那么提供程序就有机会在返回下一条记录之前获取最新的数据。因此,对于Keyset或Dynamic来说,大小为1是有意义的,因为您希望提供程序能够为您提供更新的数据。

显然,如果值为1,则每次移动光标时提供程序和RecordSet之间都有通信。如果游标类型是静态的,这是我们不想要的开销。因此,增加缓存大小将减少RecordSet和提供程序之间的往返次数。这也会增加内存需求,但应该会更快。

还请注意,对于Keyset游标,如果缓存大小大于1,如果您想要的记录在缓存中,它将不会再次从提供程序请求它,这意味着您将看不到更新。

你无法避免循环。"Very long time"是相对的,但如果检索20000条记录花费的时间太长,那就有问题了。

  • 检查查询;也许SQL可以改进(缺少索引?)
  • 显示向组合框中添加项目的循环代码。也许它可以被优化。(在循环中反复调用FieldByName ?使用变量来检索字段值?)
  • 确保在循环之前调用ComboBox.Items.BeginUpdate;,之后调用ComboBox.Items.EndUpdate
  • 使用分析器查找瓶颈

您可以尝试将所有数据推入ClientDataSet并迭代此,但我认为将数据复制到CDS确实是您目前正在做的-循环和分配。

我曾经做过的是在服务器上连接值,将其批量传输到客户端,然后再次拆分。这实际上使系统更快,因为它减少了客户端和服务器之间必要的通信。

您必须仔细查看性能瓶颈在哪里。如果你在添加值时不阻止GUI更新,它也可以是组合框(特别是当我们谈论20K值时-这是很多滚动)。

编辑:当你不能改变通信,那么你也许可以使它异步。在线程中请求新数据,保持GUI响应,当数据存在时填充组合框。这意味着用户会在5秒内看到一个空的组合框,但至少他可以在此期间做其他事情。但这并不能改变所需的时间。

您的查询是否也绑定到一些数据感知控件或TDataSource?如果是这样,在disableccontrols和enablecontrol块中进行循环,这样你的可视控件就不会在每次移动到新记录时更新。

项目列表是静态的吗?如果是这样,请考虑在应用程序启动时创建一个组合框的非可视化实例,可能在单独的线程中,然后在创建表单时将非可视化组合框分配给可视化组合框。

尝试使用disableccontrols和enableccontrols来提高数据集中线性过程的性能。

   var
  SL: TStringList;
  Fld: TField;
begin
  SL := TStringList.Create;
  AdoQuery1.DisableControls;
  Fld := AdoQuery1.FieldByName('ListFieldName'); 
  try
    SL.Sorted := False; // Sort in the query itself first
    SL.Capacity := 25000; // Some amount estimate + fudge factor
    SL.BeginUpdate;  
    try
      while not AdoQuery1.Eof do
      begin
        SL.Append(Fld.AsString);
        AdoQuery1.Next;
      end;
    finally
      SL.EndUpdate;
    end;
    YourComboBox.Items.AddStrings(SL);
  finally
    SL.Free;
    AdoQuery1.EnableControls;
  end;
end;

不确定这是否有帮助,但我的建议是不要直接添加到ComboBox。加载到本地TStringList,使其尽可能快,然后使用TComboBox.Items.AddStrings一次添加它们:

var
  SL: TStringList;
  Fld: TField;
begin
  SL := TStringList.Create;
  Fld := AdoQuery1.FieldByName('ListFieldName'); 
  try
    SL.Sorted := False; // Sort in the query itself first
    SL.Capacity := 25000; // Some amount estimate + fudge factor
    SL.BeginUpdate;  
    try
      while not AdoQuery1.Eof do
      begin
        SL.Append(Fld.AsString);
        AdoQuery1.Next;
      end;
    finally
      SL.EndUpdate;
    end;
    YourComboBox.Items.BeginUpdate;
    try
      YourComboBox.Items.AddStrings(SL);
    finally
      YourComboBox.Items.EndUpdate;
    end;
  finally
    SL.Free;
  end;
end;

最新更新