7

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

有没有办法一次性将指定字段的所有值转换为列表?而不是遍历数据集?如果我可以做类似的事情,那将非常方便......

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

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

4

8 回答 8

13

看了你的评论,有几点建议:

在这种情况下,有几件事可能会成为瓶颈。首先是反复查找字段。如果你正在调用FieldByNameFindField在你的循环中,你正在浪费 CPU 时间来重新计算一个不会改变的值。为您正在读取的每个字段调用一次 FieldByName 并将它们分配给局部变量。

从字段中检索值时,调用AsStringorAsInteger或其他返回您要查找的数据类型的方法。如果您是从TField.Value属性中读取数据,那么您就是在浪费时间进行variant转换。

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

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

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

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

于 2011-11-04T23:19:10.200 回答
9

您可以使用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']);
于 2011-11-05T10:28:47.757 回答
3

您可以尝试将所有数据推入 ClientDataSet 并对其进行迭代,但我认为将数据复制到 CDS 正是您当前正在做的事情 - 循环和分配。

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

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

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

于 2011-11-04T23:03:00.603 回答
3

你无法避免循环。“很长时间”是相对的,但如果检索 20000 条记录花费的时间太长,就会出现问题。

  • 检查您的查询;也许可以改进 SQL(缺少索引?)
  • 显示您将项目添加到组合框的循环代码。也许它可以被优化。FieldByName(在循环中重复调用?使用变体检索字段值?)
  • 确保ComboBox.Items.BeginUpdate;在循环之前和ComboBox.Items.EndUpdate之后调用。
  • 使用分析器查找瓶颈。
于 2011-11-04T23:13:37.507 回答
3

您的查询是否也与某些数据感知控件或 TDataSource 相关联?如果是这样,请在 DisableControls 和 EnableControls 块内进行循环,这样您的视觉控件就不会在每次移动到新记录时更新。

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

于 2011-11-05T04:15:41.223 回答
3

其他人提出了一些很棒的性能建议,您应该在 Delphi 中实施。你应该考虑他们。我将专注于 ADO。

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

ADO 记录集

在 ADO 中,有一个 RecordSet 对象。在这种情况下,该 RecordSet 对象基本上就是您的 ResultSet。遍历 RecordSet 的有趣之处在于它仍然与提供者耦合。

光标类型

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

因此,对于您所做的只是读取结果集以填充组合框的 TADOQuery,并且它不太可能发生更改,您应该使用静态游标类型来避免检查更新的记录。

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

并非每个提供程序都支持所有游标类型。

缓存大小

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

当您发出像 Next(在 ADO 中实际上是 MoveNext)这样的命令时,缓存大小为 1,RecordSet 仅在内存中拥有当前记录,因此当它获取下一条记录时,它必须再次从提供程序请求它。如果游标不是静态的,则提供者有机会在返回下一条记录之前获取最新数据。因此,对于 Keyset 或 Dynamic,大小 1 是有意义的,因为您希望提供者能够为您获取更新的数据。

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

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

于 2011-11-05T15:34:28.357 回答
1

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

   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;
于 2012-05-03T18:25:38.383 回答
0

不确定这是否有帮助,但我的建议是不要直接添加到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;
于 2011-11-05T01:12:37.560 回答