TClientDataSet为字符串字段使用过多的内存

Gol*_*rol 6 delphi tclientdataset delphi-10-seattle

当试图通过MCVE 支持该问题时,我被问到这个问题

我最近开始注意到TClientDataSet很快用完了内存。我在生产中遇到一个问题,它无法加载大约60.000的数据集,这对我来说似乎很低。客户端数据集通过提供程序与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.
Run Code Online (Sandbox Code Playgroud)

因此,问题本身有点广泛,但是我想为此提供解决方案,并能够通过使用字符串空间更有效地加载更大的数据集。字段很大的原因是因为它们可以包含注释。对于大多数记录而言,它们将是空的或较短的,因此这是巨大的空间浪费。

  • 可以以不同的方式配置TClientDataSet吗?我浏览了它的属性,但是找不到与此相关的任何东西。
  • 可以通过使用其他字段类型来解决吗?我虽然使用ftMemo,但是还有其他一些缺点,例如未将大小用于截断,以及一些显示问题,例如TDBGrid将其显示为(MEMO),而不是实际值。
  • 是否有用于解决此问题的TClientDataSet的替代产品?它不仅涉及内存部分,而且还涉及通过TProvider与ADO组件的通信,这是我在本项目中使用它的主要方式,因此没有任何内存数据集可以解决问题。

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

Gra*_*ter 3

Blob 字段(备注)和常规字段存储和检索数据的方式有所不同。Blob 字段不在记录缓冲区中存储数据(请参阅 参考资料TBlobField.GetDataSize),并且在存储或检索该数据时使用一组不同的方法。

每条记录的大小通过调用返回TField.GetDataSize。对于TStringField,这是所需的字符串大小 + 1。

TCustomClientDataSet.InitBufferPointers使用它作为计算的一部分,其值FRecBufSize用作为 中的每个记录分配的内存大小TCustomClientDataSet.AllocRecordBuffer

所以,回答你的问题:

  • 无法将 TClientDataSet 配置为以任何不同的方式执行此操作。
  • 它可以通过其他字段类型来解决,但它们必须从 TBlobField 派生。缓冲区大小是预先分配的,因此常规字段不能根据其内容包含不同的大小。
  • 我不确定替代品是否会减少。Dev Express 有一个 dxMemData,但我不知道它是否遇到同样的问题,或者是否是替代品的下降。