LaB*_*cca 5 sql-server delphi out-of-memory filestream
(请阅读下面的更新部分,为了清楚起见,我也保留原始问题)
我将许多文件插入到为文件流配置的SQL Server数据库中.
我在循环中插入从文件夹到数据库表的文件.
在我尝试插入600 MB文件之前一切正常.
在插入它时,任务管理器中的内存使用量为+ 600MB,我有错误.
数据库大小<1 GB,文档总大小为8 GB,我使用的是SQL Server Express R2,根据文档,我只有在尝试插入大于10 GB的文档时才会出现问题(快速限制) ) - 当前数据库大小.
谁能告诉我为什么会有这个错误?这对我来说非常重要.
更新为BOUNTY:
我提供150,因为它对我来说非常重要!
这似乎是Delphi内存管理器的限制,试图插入一个大于500MB的文件,我没有检查确切的阈值,无论如何它在500到600MB之间).我使用SDAC组件,特别是TMSQuery(但我认为可以使用和TDataset后代相同),将文档插入到具有PK(ID_DOC_FILE)和varbinary(max)字段(DOCUMENT)的表中:
procedure UploadBigFile;
var
sFilePath: String;
begin
sFilePath := 'D:\Test\VeryBigFile.dat';
sqlInsertDoc.ParamByName('ID_DOC_FILE').AsInteger := 1;
sqlInsertDoc.ParamByName('DOCUMENT').LoadFromFile(sFilePath, ftblob);
sqlInsertDoc.Execute;
sqlInsertDoc.Close;
end;
Run Code Online (Sandbox Code Playgroud)
SDAC团队告诉我这是Delphi内存管理器的限制.现在,由于SDAC不支持文件流,我无法在第一个答案中执行c#中的建议.唯一的解决方案是向Embarcadero报告并要求修复错误吗?
最终更新:
真的,谢谢所有回答我的人.为了确保插入大blob可能是Express Edition的一个问题(因为1 GB ram的限制),无论如何我在企业版上有错误,这是一个"delphi"错误,而不是一个sql server错误.因此,我认为即使我现在没有时间对其进行验证,我接受的答案也确实会遇到问题.
SDAC团队告诉我这是Delphi内存管理器的限制
对我来说,这看起来像一个简单的答案,我调查.我没有SDAC组件,我也不使用SQL Server,我最喜欢的是Firebird SQL和IBX组件集.我尝试使用IBX将600Mb blob插入表中,然后使用ADO尝试相同(包括两种连接技术,两者都是TDataSet后代).我发现事实是在中间的某个地方,它不是真正的内存管理器,它不是SDAC的错(嗯......如果有更多的人尝试将600 Mb blob插入到数据库中,他们可以对此做些什么,但是这与这次讨论无关)."问题"在于Delphi中的DB代码.事实证明,Delphi坚持使用单个Variant来保存可能加载到参数中的任何类型的数据.这是有道理的,毕竟我们可以将许多不同的东西加载到INSERT的参数中.第二个问题是,Delphi希望将Variant视为VALUE类型:它将列表复制两次,可能是三次!从文件加载参数时,第一个副本正确.当参数准备发送到数据库引擎时,将生成第二个副本.
写这个很容易:
var V1, V2:Variant;
V1 := V2;
Run Code Online (Sandbox Code Playgroud)
并且对于Integer和Date以及小字符串工作得很好,但是当V2是一个600 Mb变体数组时,它的分配显然可以完整复制!现在考虑可用于32位应用程序的内存空间,该应用程序未在"3G"模式下运行.只有2 Gb的寻址空间可用.一些空间是保留的,一些空间用于可执行文件本身,然后是库,然后为内存管理器保留一些空间.在进行第一次600 Mb分配后,可能没有足够的可用寻址空间来分配其他600 Mb缓冲区!因此,将它归咎于内存管理器是安全的,但话又说回来,为什么数据库内容确实需要600 Mb怪物的其他副本?
尝试将文件拆分为更小,更易于管理的块.将数据库表设置为包含3个字段:ID_DOCUMENT,SEQUENCE,DOCUMENT.还要使表上的主键为(ID_DOCUMENT,SEQUENCE).接下来试试这个:
procedure UploadBigFile(id_doc:Integer; sFilePath: String);
var FS:TFileStream;
MS:TMemoryStream;
AvailableSize, ReadNow:Int64;
Sequence:Integer;
const MaxPerSequence = 10 * 1024 * 1024; // 10 Mb
begin
FS := TFileStream.Create(sFilePath, fmOpenRead);
try
AvailableSize := FS.Size;
Sequence := 0;
while AvailableSize > 0 do
begin
if AvailableSize > MaxPerSequence then
begin
ReadNow := MaxPerSequence;
Dec(AvailableSize, MaxPerSequence);
end
else
begin
ReadNow := AvailableSize;
AvailableSize := 0;
end;
Inc(Sequence); // Prep sequence; First sequence into DB will be "1"
MS := TMemoryStream.Create;
try
MS.CopyFrom(FS, ReadNow);
sqlInsertDoc.ParamByName('ID_DOC_FILE').AsInteger := id_doc;
sqlInsertDoc.ParamByName('SEQUENCE').AsInteger := sequence;
sqlInsertDoc.ParamByName('DOCUMENT').LoadFromStream(MS, ftblob);
sqlInsertDoc.Execute;
finally MS.Free;
end;
end;
finally FS.Free;
end;
sqlInsertDoc.Close;
end;
Run Code Online (Sandbox Code Playgroud)