将 600MB 文件作为文件流数据插入 SQL Server Express 时出现内存不足错误

发布于 2024-09-09 23:52:52 字数 1204 浏览 2 评论 0原文

(请阅读下面的更新部分,为了清楚起见,我也保留了原来的问题)

我将许多文件插入到为文件流配置的 SQL Server 数据库中。

我正在循环中将文件从文件夹插入到数据库表中。

一切都很顺利,直到我尝试插入 600 MB 的文件。

当它插入时,任务管理器中的内存使用量为 +600MB,并且出现错误。

DB 大小 < 1 GB,文档的总大小为 8 GB,我使用的是 SQL Server Express R2,根据文档,只有在尝试插入大于 10 GB 的文档(Express 限制)时才会出现问题 - 当前数据库大小。

谁能告诉我为什么会出现这个错误?这对我来说非常重要。

赏金更新

我提供了 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;

SDAC 团队告诉我这是 Delphi 内存管理器的限制。现在,由于 SDAC 不支持文件流,我无法执行第一个答案中 c# 中建议的操作。唯一的解决方案是向 Embarcadero 报告并要求修复错误吗?

最终更新

真的感谢所有回答我的人。当然,对于 Express 版来说,插入大 blob 可能会是一个问题(因为 1 GB 内存的限制),无论如何,我在企业版上遇到了错误,而且这是一个“delphi”错误,而不是 sql server 错误。所以我认为我接受的答案确实解决了问题,即使我现在没有时间验证它。

(please read the update section below, I leave the original question too for clarity)

I am inserting many files into a SQL Server db configured for filestream.

I am inserting in a loop the files from a folder to a database table.

Everything goes fine until I try to insert a 600 MB file.

As it inserts it there is a +600MB memory usage in task manager and I have the error.

The DB size is < 1 GB and the total size of documents is 8 GB, I am using SQL Server Express R2, and according to the documentation I could have problems only if trying to insert a document that is greater than 10 GB (Express limitation) - Current DB Size.

Can anyone tell me why do I have this error? It is very crucial for me.

UPDATE FOR BOUNTY:

I offered 150 because it is very crucial for me!

This seems to be a limitation of Delphi memory Manager, trying to insert a document bigger than 500MB, I didn't check the exact threshold anyway it is between 500 and 600MB). I use SDAC components, in particular a TMSQuery (but I think the same can be done with and TDataset descendant), to insert the document in a table that has a PK (ID_DOC_FILE) and a varbinary(max) field (DOCUMENT) I do:

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;

SDAC team told me this is a limitation of Delphi memory manager. Now since SDAC doesn't support filestream I cannot do what has been suggested in c# in the first answer. Is the only solution reporting to Embarcadero and ask a bug fix?

FINAL UPDATE:

Thanks, really, to all you that answered me. For sure inserting big blobs can be a problem for the Express Edition (because the limitations of 1 GB of ram), anyway I had the error on the Enterprise edition, and it was a "delphi" error, not a sql server one. So I think that the answer that I accepted really hits the problem, even if I have no time to verify it now.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

想挽留 2024-09-16 23:52:52

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;

对于整数、日期和小字符串来说工作得很好,但是当 V2 是一个 600 Mb 变体数组时,赋值显然会产生一个完整的副本!现在考虑一下未在“3G”模式下运行的 32 位应用程序的可用内存空间。只有 2 Gb 的寻址空间可用。其中一些空间是保留的,一些空间用于可执行文件本身,然后是库,然后是为内存管理器保留的一些空间。在进行第一个 600 Mb 分配后,可能没有足够的可用寻址空间来分配其他 600 Mb 缓冲区!因此,可以放心地将其归咎于内存管理器,但话又说回来,为什么 DB 内容到底需要 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;

SDAC team told me this is a limitation of Delphi memory manager

To me that looked like an simplistic answer, and I investigated. I don't have the SDAC components and I also don't use SQL Server, my favorites are Firebird SQL and the IBX component set. I tried inserting an 600Mb blob into a table, using IBX, then tried the same using ADO (covering two connection technologies, both TDataSet descendants). I discovered the truth is somewhere in the middle, it's not really the memory manager, it's not SDAC's fault (well... they are in a position to do something about it, if many more people attempt inserting 600 Mb blobs into databases, but that's irrelevant to this discussion). The "problem" is with the DB code in Delphi. As it turns out Delphi insists on using an single Variant to hold whatever type of data one might load into an parameter. And it makes sense, after all we can load lots of different things into an parameter for an INSERT. The second problem is, Delphi wants to treat that Variant like an VALUE type: It copies it around at list twice and maybe three times! The first copy is made right when the parameter is loaded from the file. The second copy is made when the parameter is prepared to be sent to the database engine.

Writing this is easy:

var V1, V2:Variant;
V1 := V2;

and works just fine for Integer and Date and small Strings, but when V2 is an 600 Mb Variant array that assignment apparently makes a full copy! Now think about the memory space available for a 32 bit application that's not running in "3G" mode. Only 2 Gb of addressing space are available. Some of that space is reserved, some of that space is used for the executable itself, then there are the libraries, then there's some space reserved for the memory manager. After making the first 600 Mb allocation there just might not be enough available addressing space to allocate an other 600 Mb buffer! Because of this it's safe to blame it on the memory manager, but then again, why exactly does the DB stuff need an other copy of the 600 Mb monster?

One possible fix

Try splitting up the file into smaller, more manageable chunks. Set up the database table to have 3 fields: ID_DOCUMENT, SEQUENCE, DOCUMENT. Also make the primary key on the table to be (ID_DOCUMENT, SEQUENCE). Next try this:

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;
故事↓在人 2024-09-16 23:52:52

您可以循环遍历要插入的对象的字节流,并一次将其中一部分缓冲到数据库中,直到存储整个对象为止。

如果您使用 .NET,我会看一下 Buffer.BlockCopy() 方法。

在我的脑海中,解析文件的方法可能如下所示:

        var file = new FileStream(@"c:\file.exe");
        byte[] fileStream;
        byte[] buffer = new byte[100];
        file.Write(fileStream, 0, fileStream.Length);
        for (int i = 0; i < fileStream.Length; i += 100)
        {
            Buffer.BlockCopy(fileStream, i, buffer, 0, 100);
            // Do database processing
        }

You could loop through the byte stream of the object you are trying to insert and essentially buffer a piece of it at a time into your database until you have your entire object stored.

I would take a look at the Buffer.BlockCopy() method if you're using .NET

Off the top of my head, the method to parse your file could look something like this:

        var file = new FileStream(@"c:\file.exe");
        byte[] fileStream;
        byte[] buffer = new byte[100];
        file.Write(fileStream, 0, fileStream.Length);
        for (int i = 0; i < fileStream.Length; i += 100)
        {
            Buffer.BlockCopy(fileStream, i, buffer, 0, 100);
            // Do database processing
        }
长梦不多时 2024-09-16 23:52:52

下面是读取磁盘文件并将其保存到 FILESTREAM 列中的示例。 (它假设您已经在变量“filepath”和“txContext”中拥有事务上下文和文件路径。

'Open the FILESTREAM data file for writing
Dim fs As New SqlFileStream(filePath, txContext, FileAccess.Write)

'Open the source file for reading
Dim localFile As New FileStream("C:\temp\microsoftmouse.jpg",
                                FileMode.Open,
                                FileAccess.Read)

'Start transferring data from the source file to FILESTREAM data file
Dim bw As New BinaryWriter(fs)
Const bufferSize As Integer = 4096
Dim buffer As Byte() = New Byte(bufferSize) {}
Dim bytes As Integer = localFile.Read(buffer, 0, bufferSize)

While bytes > 0
    bw.Write(buffer, 0, bytes)
    bw.Flush()
    bytes = localFile.Read(buffer, 0, bufferSize)
End While

'Close the files
bw.Close()
localFile.Close()
fs.Close()

Here is an example that reads a disk file and saves it into a FILESTREAM column. (It assumes that you already have the transaction Context and FilePath in variables "filepath" and "txContext".

'Open the FILESTREAM data file for writing
Dim fs As New SqlFileStream(filePath, txContext, FileAccess.Write)

'Open the source file for reading
Dim localFile As New FileStream("C:\temp\microsoftmouse.jpg",
                                FileMode.Open,
                                FileAccess.Read)

'Start transferring data from the source file to FILESTREAM data file
Dim bw As New BinaryWriter(fs)
Const bufferSize As Integer = 4096
Dim buffer As Byte() = New Byte(bufferSize) {}
Dim bytes As Integer = localFile.Read(buffer, 0, bufferSize)

While bytes > 0
    bw.Write(buffer, 0, bytes)
    bw.Flush()
    bytes = localFile.Read(buffer, 0, bufferSize)
End While

'Close the files
bw.Close()
localFile.Close()
fs.Close()
各空 2024-09-16 23:52:52

您可能在某个地方遇到了内存碎片问题。使用非常大的内存块,尤其是在可能需要重新分配内存的任何情况下,当理论上您有足够的内存来完成这项工作时,往往会导致内存不足错误。如果它需要一个 600mb 的块,但找不到 600mb 宽的洞,那就说明内存不足了。

虽然我从未尝试过,但我倾向于创建一个非常小的程序,执行一个操作。保持尽可能简单,以尽量减少内存分配。当面临这样的危险操作时,调用外部程序来完成这项工作。程序运行,执行一项操作并退出。关键是新程序位于它自己的地址空间中。

唯一真正的解决办法是 64 位,但我们还没有这个选项。

You're probably running into memory fragmentation issues somewhere. Playing around with really large blocks of memory, especially in any situation where they might need to be reallocated tends to cause out of memory errors when in theory you have enough memory to do the job. If it needs a 600mb block and it can't find a hole that's 600mb wide that's it, out of memory.

While I have never tried it my inclination for a workaround would be to create a very minimal program that does ONLY the one operation. Keep it absolutely as simple as possible to keep the memory allocation minimal. When faced with a risky operation like this call the external program to do the job. The program runs, does the one operation and exits. The point is the new program is in it's own address space.

The only true fix is 64 bit and we don't have that option yet.

弥繁 2024-09-16 23:52:52

我最近在一个非常大的表上运行 DBCC CHECKDB 时遇到了类似的问题。我会得到这个错误:

系统内存不足
资源池“内部”来运行它
查询。

这是在 SQL Server 2008 R2 Express 上进行的。有趣的是,我可以通过向表中添加或删除一定数量的行来控制错误的发生。

经过与各种 SQL Server 专家的广泛研究和讨论,我得出的结论是,问题是 内存压力SQL Server Express 的 1 GB 内存限制

给我的建议是要么

  1. 购买一台具有更多内存和
    SQL Server 的许可版本或...
  2. 将表分区为相当大的
    DBCC CHECKDB 可以的块
    由于

将这些文件解析为 FILSTREAM 对象的复杂性,我建议使用 文件系统 方法,并简单地使用 SQL Server 来存储文件的位置。

I recently experienced a similar problem while running DBCC CHECKDB on a very large table. I would get this error:

There is insufficient system memory in
resource pool 'internal' to run this
query.

This was on SQL Server 2008 R2 Express. The interesting thing was that I could control the occurrence of the error by adding or deleting a certain number of rows to the table.

After extensive research and discussions with various SQL Server experts, I came to the conclusion that the problem was a combination of memory pressure and the 1 GB memory limitation of SQL Server Express.

The recommendation given to me was to either

  1. Acquire a machine with more memory and a
    licensed edition of SQL Server or...
  2. Partition the table into sizeable
    chunks that DBCC CHECKDB could
    handle

Due the complicated nature of parsing these files into the FILSTREAM object, I would recommend the filesystem method and and simply use SQL Server to store the locations of the files.

萌梦深 2024-09-16 23:52:52

“虽然支持的数据库或用户数量没有限制,但仅限于使用一个处理器、1 GB 内存和 4 GB 数据库文件(SQL Server Express 2008 R2 中的 10 GB 数据库文件)。”问题不是数据库文件的大小,而是“1 GB 内存”。尝试吐出 600MB 以上的文件,但将其放入流中。

"While there are no limitations on the number of databases or users supported, it is limited to using one processor, 1 GB memory and 4 GB database files (10 GB database files from SQL Server Express 2008 R2)." It is not the size of the database files that is an insue but "1 GB memory". Try spitting the 600MB+ file but putting it in the stream.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文