将600MB文件插入sql server表示为文件流数据时出现内存不足错误

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错误.因此,我认为即使我现在没有时间对其进行验证,我接受的答案也确实会遇到问题.

Cos*_*und 5

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)