当我试图用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.
所以问题本身有点宽泛,但我想为此提供一个解决方案,并且能够通过更有效地使用字符串空间来加载更大的数据集。字段很大的原因是因为它们可以包含注释。对于大多数记录来说,这些都是空的或短暂的,所以这是一个巨大的空间浪费。
对于最后一点,我碰巧找到this question,其中隐藏在评论中,提到了vgLib,但我发现的只有破坏的链接,我甚至不知道它是否能解决这个问题。显然,MidasLib的C ++代码现已上市,但由于它是1.5MB的晦涩代码,我认为在我深入研究之前可能值得一提。 ;)
blob字段(备忘录)和常规字段存储和检索其数据的方式之间存在差异。 Blob字段不会将数据存储在记录缓冲区中(请参阅TBlobField.GetDataSize
),并且在存储或检索该数据时它们使用不同的方法集。
调用TField.GetDataSize
返回每条记录的大小。对于TStringField
,这是必需的字符串大小+ 1。
TCustomClientDataSet.InitBufferPointers
使用它作为FRecBufSize
值的计算的一部分,TCustomClientDataSet.AllocRecordBuffer
用作为qazxswpoi中的每条记录分配的内存大小。
那么,回答你的问题:
每当我在CDS中需要相当长的“字符串”字段时,我倾向于创建备忘录。除了前面提到的显示问题(可以解决相当无痛),几乎没有其他限制,所以我有自定义cds后代。 hyperbase(不是vglib)内部字符串格式是相同的,所以在这方面它不会改变任何东西。顺便说一句,有dacs(如firedac)允许自定义和选择目标字段类型映射。不确定是否可以修补/增强ado组件以实现类似的功能。此外,iirc firedac数据集可以选择控制内部字符串字段布局(“内联”行内缓冲区或仅指向动态分配的指针),但不是cds的1:1替换。