首页 > 解决方案 > TClientDataSet 对字符串字段使用过多内存

问题描述

当我试图用 MCVE支持这个问题时,我被触发提出这个问题。

我最近开始注意到 TClientDataSet 很快就会耗尽内存。我在生产中遇到了一个问题,它无法加载大约 60.000 的数据集,这对我来说似乎低得惊人。客户端数据集通过提供程序与 ADODataSet 连接,该 ADODataSet 加载良好。我单独运行该查询并将结果输出到 CSV,这给了我一个 < 30MB 的文件。

所以我做了一个小测试,在客户端数据集中最多可以加载大约165K条记录,其中有一个大小为4000的字符串字段。该字段的实际值只有3个字符,但这似乎没有事关结果。

看起来每条记录至少占用了这 4000 个字符。4000 x 2 字节 x 165K 记录 = 1.3GB,因此开始接近 32 位内存限制。如果我把它变成一个备忘录字段,我可以轻松添加 500 万行。

program ClientDataSetTest;
{$APPTYPE CONSOLE}
uses SysUtils, DB, DBClient;

var
  c: TClientDataSet;
  i: Integer;
begin
  c := TClientDataSet.Create(nil);
  c.FieldDefs.Add('Id', ftInteger);
  c.FieldDefs.Add('Test', ftString, 4000); // Actually claims this much space...
  //c.FieldDefs.Add('Test', ftMemo); // Way more space efficient (and not notably slower)
  //c.FieldDefs.Add('Test', ftMemo, 1); // But specifying size doesn't have any effect.
  c.CreateDataSet;

  try
    i := 0;
    while i < 5000000 do
    begin
      c.Append;
      c['Id'] := i;
      c['Test'] := 'xyz';
      c.Post;

      if (i mod 1000) = 0 then
        WriteLn(i, c['Test']);

      Inc(i);
    end;

  except
    on e: Exception do
    begin
      c.Cancel;
      WriteLn('Error adding row', i);
      Writeln(e.ClassName, ': ', e.Message);
    end;
  end;

  c.SaveToFile('c:\temp\output.xml', dfXML);
  Writeln('Press ''any'' key');
  ReadLn;
end.

所以问题本身有点宽泛,但我想有一个解决方案,并且能够通过更有效地使用字符串空间来加载更大的数据集。该字段很大的原因是因为它们可以包含注释。但是对于大多数记录来说,这些记录是空的或很短,所以这是对空间的巨大浪费。

对于最后一点,我碰巧发现了这个问题,隐藏在评论中,提到了 vgLib,但我发现的只是链接断开,我什至不知道它是否能解决这个问题。显然 MidasLib 的 C++ 代码现在可用,但由于它是 1.5MB 的晦涩代码,我认为在深入研究之前可能值得在这里询问。;)

标签: delphidelphi-10-seattletclientdataset

解决方案


每当我在 CDS 中需要相当长的“字符串”字段时,我倾向于创建一个备忘录。除了前面提到的显示问题(可以很轻松地解决),几乎没有其他限制,所以我有自定义 cds 后代。hyperbase(不是 vglib)内部字符串格式是相同的,所以在这方面它不会改变任何东西。顺便说一句,有允许自定义和选择目标字段类型映射的 DAC(例如 firedac)。不确定是否可以修补/增强 ado 组件以实现类似的功能。此外,iirc fireac 数据集可以选择控制内部字符串字段布局(“内联”行内缓冲区或只是指向动态分配的缓冲区的指针),但不是 cds 的 1:1 替代品。


推荐阅读