【问题标题】:Loading millions of records into a stringlist can be very slow将数百万条记录加载到字符串列表中可能会非常慢
【发布时间】:2011-12-07 11:18:12
【问题描述】:

如何快速将 tadotable 中的数百万条记录加载到字符串列表中?

procedure TForm1.SlowLoadingIntoStringList(StringList: TStringList);
begin
  StringList.Clear;
  with SourceTable do
  begin
    Open;
    DisableControls;
    try
      while not EOF do
    begin
      StringList.Add(FieldByName('OriginalData').AsString);
      Next;
    end;
   finally
   EnableControls;
   Close;
  end;
end;

【问题讨论】:

  • 这里的瓶颈可能是数据库和/或 ado 驱动程序而不是字符串列表,尽管您可以尝试分析以确定。如果得到确认,您将需要更快的数据库或更好的驱动程序来加快速度。
  • 你可以尝试设置 Stringlist.Capacity := SourceTable.RecordCount;这将节省大量底层数组的重新分配
  • 这里比较慢的是FieldByName('OriginalData')
  • 并尝试使用直接链接到 OleDB 提供程序来绕过 ADO 层。例如,请参阅我们的Open Source unit
  • 清理我之前的评论 - 我刚刚尝试了一个非常简单的 1000 万条记录循环,它只是稍微快了一点(大约 15%)。

标签: delphi tstringlist tadotable


【解决方案1】:

在您的循环中,您将获得该字段。 在循环外搜索字段

procedure TForm1.SlowLoadingIntoStringList(StringList: TStringList); 
var
  oField: TField;
begin
  StringList.Clear;   
  with SourceTable do   
  begin     
    Open;     
    DisableControls;     
    try       
      oField:= FieldByName('OriginalData');
      if oField<>Nil then
      begin
        while not EOF do
        begin       
          StringList.Add(oField.AsString);       
          Next;     
        end;   
      end; 
    finally    
      EnableControls;    
      Close;   
    end; 
  end;  
end;

【讨论】:

  • 我还会添加BeginUpdateEndUpdate,用于有人将作为此方法的输入传递的情况,例如TListBox.Items :) 不,说真的,Kamyar,加载这么多行的原因是什么?您是否将这些行保存为文件?还是进行一些动态搜索?如果是动态搜索,那么您应该让数据库服务器为您执行此操作。
  • 如果输入的是TStringList参数,则不能传递TStrings。
【解决方案2】:

很遗憾,您无法快速完成此操作。这是一个本质上很慢的操作,需要大量的 CPU 时间和内存带宽才能实现。你可以投入更多的硬件,但我怀疑你应该重新考虑你的任务。

【讨论】:

    【解决方案3】:

    对于“数百万条记录”,您可以考虑: 1/ 从

    更改您的查询
    SELECT * FROM MYTABLE;
    

    SELECT OriginalData FROM MYTABLE;
    

    您将使用更少的内存并提高效率。

    2/ 根据需要查找除 TStringList 之外的其他组件。

    3/ 看看以前所有的好建议,主要是:

    • 不要使用 FieldByName
    • 直接链接到 OleDB 提供程序

    【讨论】:

      【解决方案4】:

      排序了吗?

        // Turn off the sort for now
        StringList.Sorted := False;
        // Preallocate the space
        StringList.Capacity := recordCount;
        // Now add the data with Append()
        ...
        // Now turn the sort back on
        StringList.Sorted := True;
      

      【讨论】:

        【解决方案5】:

        真的吗?字符串列表中有数百万条记录?

        好的,假设您确实需要采用这种方法...

        已经发布了一些很好的建议。

        如果您想尝试不同的方法,您可以考虑连接各个记录服务器端(通过存储过程),然后将连接的数据作为 blob(或者可能是 nvarchar(max))返回,这基本上是列表由回车分隔的串联字符串(假设这是满足您需要的合理分隔符)。

        然后您可以简单地将返回值分配给 TStringList 的 Text 属性。

        即使您不能一次完成所有字符串,您也可以一次按 1000 个为一组完成。

        这应该可以节省您在每个记录客户端循环的大量时间。

        【讨论】:

          【解决方案6】:

          扩展@Ravaut123 的答案,我建议使用以下代码:

          确保您的查询未连接到任何可视化其他组件,并且没有设置任何在行更改时触发的事件,因为这将导致它更新活动记录中的每个更改,从而减慢速度。
          您可以使用disablecontrols 禁用可视控件,但不能禁用事件和非可视控件。

          ...
          SQLatable:= 'SELECT SingleField FROM atable ORDER BY indexedfield ASC';
          AQuery:= TAdoQuery.Create(Form1);
          AQuery.Connection:= ....
          AQuery.SQL.Text:= SQLatable;  
          

          使用查询可确保您只选择 1 个字段,按照您想要的顺序,这样可以减少网络流量。一个表获取所有字段,导致更多的开销。

          function TForm1.LoadingAllIntoStringList(AQuery: TAdoQuery): TStringList;  
          var 
            Field1: TField; 
          begin 
            Result:= nil;
            try
              if not(AQuery.Active) then begin
                AQuery.Open;
              end else begin
                AQuery.First;
              end;
              AQuery.DisableControls;
              AQuery.Filtered:= false;                    //Filter in the SQL `where` clause
              AQuery.FetchAll;                            //Preload all data into memory
              Result:= TStringlist.Create;
            except
              {ignore error, will return nil}
            end;
            try
              Result.Sorted:= false;                      //Make sure you don't enable sorting
              Result.Capacity:= AQuery.RecordCount;       //Preallocate the needed space     
              Field1:= AQuery.FieldByName('SingleField'); //Never use `fieldbyname` in a loop!
              while not AQuery.EOF do begin
                Result.Add(Field1.AsString);
                AQuery.Next;
              end; {while} 
              AQuery.EnableControls;
            except
              FreeAndNil(Result);
            end;   
          

          如果您想将数据加载到字符串列表中进行一些处理,请考虑在 SQL 语句中执行此操作。数据库可以使用字符串列表无法使用的索引和其他优化。
          如果您想将该数据保存到 CSV 文件中,请考虑为此使用内置 DB 函数。
          例如MySQL 有:

          SELECT X FROM table1 INTO OUTFILE 'c:/filename_of_csv_file.txt'
          

          这将为您创建一个 CSV 文件。
          许多数据库都有类似的功能。

          【讨论】:

          • +1,关于让服务器端完成艰苦工作的好处。无论如何,我会将输出字符串列表声明为参数而不是函数结果;)
          猜你喜欢
          • 2017-06-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-05-31
          • 1970-01-01
          • 2011-02-07
          • 2019-08-06
          相关资源
          最近更新 更多