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

发布于 2024-12-13 22:40:52 字数 347 浏览 3 评论 0原文

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

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

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

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

We're trying to find out some performance fixes reading from a TADOQuery. Currently, we loop through the records using 'while not Q.eof do begin ... Q.next method. For each, we read ID and Value of each record, and add each to a combobox list.

Is there a way to convert all values of a specified field into a list in one shot? Rather than looping through the dataset? It would be really handy if I can do something like...

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

I know that's not a real command, but that's the concept I'm looking for. Looking for a fast response and solution (as always but this is to fix a really urgent performance issue).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

笑红尘 2024-12-20 22:40:52

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

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

从字段检索值时,调用 AsStringAsInteger 或返回您要查找的数据类型的其他方法。如果您从 TField.Value 属性读取数据,则您会在 variant 转换上浪费时间。

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

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

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

最后,米凯尔·埃里克森的评论绝对值得关注!

Looking at your comment, here are a few suggestions:

There are a few things that are likely to be a bottleneck in this situation. The first is looking up the fields repeatedly. If you're calling FieldByName or FindField inside your loop, you're wasting CPU time recomputing a value that's not going to change. Call FieldByName once for each field you're reading from and assign them to local variables instead.

When retrieving values from the fields, call AsString or AsInteger, or other methods that return the data type you're looking for. If you're reading from the TField.Value property, you're wasting time on variant conversions.

If you're adding a bunch of items to a Delphi combo box, you're probably dealing with a string list in the form of the Items property. Set the list's Capacity property and make sure to call BeginUpdate before you start updating, and call EndUpdate at the end. That can enable some internal optimizations that makes loading large amounts of data faster.

Depending on the combo box you're using, it could have some trouble dealing with large numbers of items in its internal list. See if it has a "virtual" mode, where instead of you loading everything up-front, you simply tell it how many items it needs, and when it gets dropped down, it calls an event handler for each item that's supposed to be shown on screen, and you give it the right text to display. This can really speed up certain UI controls.

Also, you should make sure your database query itself is fast, of course, but SQL optimization is beyond the scope of this question.

And finally, Mikael Eriksson's comment is definitely worthy of attention!

梦幻的心爱 2024-12-20 22:40:52

您可以使用 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']);

You can use Getrows. You specify the column(s) you are interested in and it will return an array with values. The time it takes to add 22.000 rows to a combo box goes from 7 seconds with the while not ADOQuery1.Eof ... loop to 1.3 seconds in my tests.

Sample code:

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;

If you want more than one column in the array you should use a variant array as the third parameter.

V := ADOQuery1.Recordset.GetRows(adGetRowsRest, EmptyParam, 
       VarArrayOf(['ColumnName1', 'ColumnName2']);
万水千山粽是情ミ 2024-12-20 22:40:52

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

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

ADO RecordSet

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

游标类型

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

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

如果您不知道光标是什么,当您调用 Next 之类的函数时,您正在移动光标,它代表当前记录。

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

CacheSize

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

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

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

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

There are some great performance suggestions made by other folks that you should implement in Delphi. You should consider them. I will focus on ADO.

You haven't specified what the back end database server is, so I can't be too specific, but there are some things that you should know about ADO.

ADO RecordSet

In ADO, there is a RecordSet object. That RecordSet object is basically your ResultSet in this case. The interesting thing about iterating through the RecordSet is that it's still coupled with the provider.

Cursor Type

If your cursor type is Dynamic or Delphi's default Keyset, then each time the RecordSet requests a new row from the provider, the provider will check to see if there were any changes before it returns the record.

So, for the TADOQuery where all you're doing is reading the result set to populate the combobox, and it's not likely to have changed, you should use the Static cursor type to avoid checking for updated records.

In case you don't know what a cursor is, when you call a function like Next, you are moving the cursor, which represents the current record.

Not every provider supports all of the cursor types.

CacheSize

Delphi's and ADO's default cache size for a RecordSet is 1. That's 1 record. This works in combination with the cursor type. The cachesize tells the RecordSet how many records to fetch and store at a time.

When you issue a command like Next (really MoveNext in ADO) with a cache size of 1, the RecordSet only has the current record in memory, so when it fetches that next record, it must request it from the provider again. If the cursor is not Static, that gives the provider the opportunity to get the latest data before returning the next record. So, a size of 1 makes sense for Keyset or Dynamic, because you want the provider to be able to get you the updated data.

Obviously, with a value of 1, there's communication between the provider and RecordSet each time move the cursor. Well, that's overhead that we don't want if the cursor type is static. So, increasing your cache size will reduce the number of round trips between the RecordSet and the provider. This also increases your memory requirements, but it should be faster.

Also note that with a cache size greater than 1 for Keyset cursors, if the record that you want is in the cache, it won't request it from the provider again, which means that you won't see the updates.

南城旧梦 2024-12-20 22:40:52

你无法避免循环。 “非常长的时间”是相对的,但如果检索 20000 条记录花费的时间太长,则说明出现了问题。

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

You can't avoid looping. "Very long time" is relative but if retrieving 20000 records takes too long there's something wrong.

  • Check your query; perhaps the SQL can be improved (missing index?)
  • Show the code of your loop where you add items to the combobox. Maybe it can be optimized. (calling FieldByName repeatedly in a loop? using variants to retrieve field values?)
  • Make sure to call ComboBox.Items.BeginUpdate; before the loop and ComboBox.Items.EndUpdate after.
  • Use a profiler to find the bottleneck.
离笑几人歌 2024-12-20 22:40:52

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

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

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

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

You could try pushing all data into a ClientDataSet and iterating this, but I think copying the data to the CDS does exactly what you are currently doing - looping and assigning.

What I did once was concatenating values on the server, transmitting it in one bulk to the client and splitting it again. This actually made the system faster because it reduced the communication necessary between client and server.

You have to look careful where the performance bottleneck is. It could as well be the combobox if you don't block GUI updates while adding values (ecpecially when we are talking about 20K values - that's a lot to scroll).

Edit: When you cannot change the communication then you perhaps could make it asynchronous. Request new data in a thread, keep the GUI responsive, fill the combobox when the data is there. It means the user sees an empty combobox for 5 seconds, but at least he can do something else in the meantime. Doesn't change the amount of time needed, though.

终陌 2024-12-20 22:40:52

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

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

Is your query also tied to some data aware controls or a TDataSource? If so, do your looping inside an DisableControls and EnableControls block so your visual controls don't update each time you move to a new record.

Is the list of items fairly static? If so, consider creating a non-visual instance of a combobox at application startup, maybe inside a separate thread, and then assign your non-visual combobox to the visual combobox when your form is created.

美男兮 2024-12-20 22:40:52

尝试使用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;

try use DisableControls and EnableControls for increase performance of linear process in dataset.

   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;
浮生未歇 2024-12-20 22:40:52

不确定这是否有帮助,但我的建议是不要直接添加到 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;

Not sure if this will help, but my suggestion would be not to add directly to the ComboBox. Load to a local TStringList instead, make that as fast as possible, and then use TComboBox.Items.AddStrings to add them all at once:

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文