TClientDataSet为字符串字段使用了太多内存

问题描述 投票:6回答:2

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

我最近开始注意到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.

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

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

对于最后一点,我碰巧找到this question,其中隐藏在评论中,提到了vgLib,但我发现的只有破坏的链接,我甚至不知道它是否能解决这个问题。显然,MidasLib的C ++代码现已上市,但由于它是1.5MB的晦涩代码,我认为在我深入研究之前可能值得一提。 ;)

delphi delphi-10-seattle tclientdataset
2个回答
2
投票

blob字段(备忘录)和常规字段存储和检索其数据的方式之间存在差异。 Blob字段不会将数据存储在记录缓冲区中(请参阅TBlobField.GetDataSize),并且在存储或检索该数据时它们使用不同的方法集。

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

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

那么,回答你的问题:

  • 不能将TClientDataSet配置为以不同方式执行此操作。
  • 它可以通过其他字段类型解决,但它们必须从TBlobField继承。缓冲区大小预先分配,因此常规字段不能包含不同的大小,具体取决于其内容。
  • 我不确定更换的费用。 Dev Express有一个dxMemData,但我不知道它是否会遇到同样的问题,或者它是否是替代品。

2
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.