【问题标题】:Delphi ClientDataSet - Delete Operation - Why is it so slow?Delphi ClientDataSet - 删除操作 - 为什么这么慢?
【发布时间】:2016-08-14 18:42:54
【问题描述】:

我在一个应用程序中使用 TClientDataSet 来管理从多个 CSV 文件导入的大量数据。这总共可以是一百万个或更多条目。我希望能够删除与特定 CSV 文件关联的所有数据集条目,但删除大量项目的时间非常慢。

为了测试我是否在做一些愚蠢的事情,我创建了一个简单的控制台应用程序。它所做的只是:

  • 创建一个定义了 1 个字段 (ID) 的 TClientDataSet 实例:

    CDS := TClientDataSet.Create(nil); CDS.FieldDefs.Add('ID', ftInteger); CDS.CreateDataSet; CDS.LogChanges := False;

  • 追加 100,000 个项目(需要 0.1 秒):

    for i := 1 to 100000 do begin CDS.AppendRecord([i]); end;

  • 删除 50,000 个项目(大约需要 4 秒,或使用 LogChanges=TRUE 大约需要 4.4 秒):

    CDS.First; while CDS['ID'] <= 50000 do CDS.Delete;

如果我的数据集中有 150 万个项目,并且想要删除 0.50 万个条目,那么通过这种方法删除项目需要很长时间,我什至无法测量它。

作为目前的解决方法,我必须创建一个新数据集,然后将我想要保留的所有项目复制到新副本并删除原始副本。除非我只从原始数据集中删除一小部分条目,否则此方法要快得多。

也许我没有使用最合适的方法来尝试从数据集中删除项目?我猜我删除的每个项目都会触发一堆内部处理。有什么方法可以一次删除我丢失的一系列项目吗?或许我可以根据该索引设置一个索引和一个范围,然后一次操作删除当前范围内的所有项目?

也许问题出在 ClientDataSet 而不是我?也许我需要使用不同的组件。有什么建议吗?

【问题讨论】:

  • 可能你忘了关掉LogChanges。
  • 有趣,应该有区别,因为维护增量并不便宜。
  • 正如您提到的聚合和索引,这些可能是罪魁祸首;尝试在批量删除操作之前禁用它们并在之后重新启用它们(如果需要)。 (问题可能是它们在每次删除后都会更新,这是完全没有必要的。)
  • 我认为这是意料之中的。删除需要移动文件中的数据。
  • Delphi 的哪个版本?你试过 Andreas Hausladen 的 MidasSpeedFix 吗?

标签: delphi tclientdataset


【解决方案1】:

我想我不妨提供我自己的(临时/可能是永久的?)解决方法的详细信息,以防遇到同样问题的人感兴趣。

问题:与添加项目的初始时间(40 倍或更多)相比,使用 Delete 操作从大型 TClientDataSet(100k 或更多记录)中删除大部分记录需要很长时间。

解决方案:将您不想删除的所有记录复制到新数据集,然后删除原始数据集。 [缺点:更改日志丢失,需要额外的 RAM?]

var
 CDS: TClientDataSet;

// initialize new CDS instance
function CreateNewCDSInstance: TCLientDataSet;
begin
 Result := TClientDataSet.Create(nil);
 Result.FieldDefs.Add('ID', ftInteger);
 Result.CreateDataSet;
 Result.LogChanges := False;
end;

// close + free CDS instance
procedure CloseCDS;
begin
 CDS.EmptyDataSet;
 CDS.Close;
 CDS.Free;
end;

// delete current record?
function CanDeleteCurrentRecord: boolean;
begin
 Result := CDS['ID'] < 50001; //in this simple example
 // in my application it would be more like:
 // "CDS['FILE_ID'] = AFileIDToDelete"
end;

// delete block of records:
procedure DeleteRecords;
var
 aNewCopy: TClientDataSet;
begin
 aNewCopy := CreateNewCDSInstance;
 CDS.First;
 while not CDS.EoF do
 begin
  if not CanDeleteCurrentRecord then
  begin
   // NB: AppendRecord takes array of values corresponding to field defintions
   aNewCopy.AppendRecord([CDS['ID']]);
  end;
  CDS.Next;
 end;
 CloseCDS;
 CDS := aNewCopy;
 //NB: If you have any aggregates/indexes defined, they must be redefined
end;

使用上面给出的示例,这种删除 50k 项的方法需要 94 毫秒而不是大约 4 秒。

但是,在提出这个问题并阅读 cmets 时,我发现这个解决方案更像是一种绷带而不是治疗方法。更大的问题是我正在开发的系统并没有很好地设计来处理它需要的数据量。也许与其说是“TClientDataSet 的问题”,不如说是“我们如何使用 TClientDataSet 的问题”!即使修复了删除速度,在导入越来越大和数量越来越大的文件以及管理这些数据时,性能仍然存在问题。

也许(在下雨天!)我将开始研究一种类似于 SilverWarior 建议的方法,即使用单独的数据集来保存每个导入的文件,而不是将所有数据投入到一个巨大的内存表中。然后删除文件就变成了删除数据集——还有其他潜在的好处。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-15
    • 2011-10-26
    • 2021-09-03
    • 2015-03-05
    • 1970-01-01
    • 2021-09-05
    相关资源
    最近更新 更多