Ошибка нехватки памяти при вставке файлов 600 МБ в sql server express как данные файлового потока

(пожалуйста, прочтите раздел обновлений ниже, я также оставляю исходный вопрос для ясности)

Я вставляю много файлов в базу данных SQL Server, настроенную для файлового потока.

Я вставляю в цикл файлы из папки в таблицу базы данных.

Все идет нормально, пока я не попробую вставить файл 600 МБ.

Когда он вставляет его, в диспетчере задач используется память +600 МБ, и у меня есть ошибка.

Размер БД составляет ‹1 ГБ, а общий размер документов - 8 ГБ, я использую SQL Server Express R2, и, согласно документации, у меня могут возникнуть проблемы только при попытке вставить документ размером более 10 ГБ (ограничение Express ) - Текущий размер БД.

Может ли кто-нибудь сказать мне, почему у меня эта ошибка? Для меня это очень важно.

ОБНОВЛЕНИЕ НАПРАВЛЕНИЯ:

Я предложил 150, потому что для меня это очень важно!

Похоже, это ограничение Delphi memory Manager, пытаясь вставить документ размером более 500 МБ, я не проверял точный порог в любом случае, он составляет от 500 до 600 МБ). Я использую компоненты 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 Edition (из-за ограничений в 1 ГБ оперативной памяти), в любом случае у меня была ошибка в версии Enterprise, и это была ошибка «delphi», а не ошибка сервера sql. Поэтому я думаю, что ответ, который я принял, действительно решает проблему, даже если у меня сейчас нет времени проверять его.


person LaBracca    schedule 14.07.2010    source источник
comment
Помещать такие большие файлы в базу данных - действительно плохая идея.   -  person Fosco    schedule 14.07.2010
comment
Приложение написано для вставки данных, обычно имеет смысл вставлять файлы размером от 1 до 10 МБ, конечно, вставлять 600 МБ - плохая практика, но это зависит от пользователя, например, писать книгу в блокноте - это плохо, но это зависит от пользователя. В любом случае, согласно спецификации sql server 2008, должны поддерживаться файлы размером 600 МБ.   -  person LaBracca    schedule 14.07.2010
comment
Помните, что ограничение 4/10 ГБ в экспресс-версии не включает справку по данным в столбцах FILESTREAM, поэтому вы можете сходить с ума и хранить 1000 ГБ данных, если действительно хотите   -  person Kevin Ross    schedule 15.07.2010
comment
Да, поэтому я решил использовать файловый поток. раньше я использовал внешний каталог, но было слишком много минусов. Теперь, когда я делаю резервную копию, я уверен, что все данные в безопасном месте. Более того, подключаясь с внешнего IP-адреса, я могу получать документы прямо из базы данных, без необходимости настраивать ftp-сервер или сервер документов.   -  person LaBracca    schedule 15.07.2010
comment
Такое хранение больших двоичных объектов в БД - плохая практика. Более распространенной практикой в ​​этой ситуации является сохранение данных в файловой системе и сохранение пути в БД. Гибкость также, так что вы можете оптимизировать процедуру резервного копирования для БД и Filedata по отдельности, например для больших двоичных объектов в файловой системе вы можете проверить дату изменения файла, чтобы знать, что нужно создать.   -  person Caleb Hattingh    schedule 27.07.2010
comment
Я знаю, но я не хочу разрабатывать это, поскольку приложение предназначено для хранения небольших файлов, поэтому пользователи, которые хранят файлы размером более 20 МБ, составляют 0,5%. Я не хочу создавать все это только для управления граничным условием, которое почти никогда не возникает.   -  person LaBracca    schedule 29.07.2010
comment
Другое решение - позволить файлу в базе данных храниться в виде нескольких меньших фрагментов. Пример: если ваша таблица имеет поля ID_DOC_FILE, SEQUENCE, DOCUEMNT, вы можете хранить обычные небольшие файлы как одну запись с одним ID_DOC_FILE и одной SEQUENCE, а также вы можете хранить файлы большего размера как несколько фрагментов с последовательными номерами SEQUENCE. Первичный ключ в этой таблице будет (ID_DOC_FILE, SEQUENCE), а не только ID_DOC_FILE.   -  person Cosmin Prund    schedule 30.07.2010
comment
@Cosmin Каким-то образом разделение файла на части и их сохранение действительно могло бы решить мою проблему и даже быть хорошей идеей. Как вы думаете, проблема с диспетчером памяти решима или нет? Для разделения файлов я нашел этот проект с открытым исходным кодом, который уже делает это (raymarron.com/delphi/rmsplit.zip). Если вы ответите обычным образом, я награжу вас наградой. Спасибо.   -  person LaBracca    schedule 30.07.2010
comment
Какую версию Delphi вы используете? Если это версия старше Delphi 2006, пробовали ли вы FastMM? Если у вас Delphi 2006 или выше, FastMM включен по умолчанию. Похоже, проблема в том, как файл отправляется в БД из приложения; файл читается с использованием файлового потока внутри LoadFromFile, но затем весь файл копируется в вариантный массив и отправляется драйверу БД.   -  person vcldeveloper    schedule 01.08.2010
comment
У меня 2009 год. Так что FastMM уже используется.   -  person LaBracca    schedule 02.08.2010


Ответы (6)


Команда SDAC сказала мне, что это ограничение диспетчера памяти Delphi.

Мне это показалось упрощенным ответом, и я исследовал. У меня нет компонентов SDAC, и я также не использую SQL Server, мои фавориты - Firebird SQL и набор компонентов IBX. Я попытался вставить большой двоичный объект размером 600 МБ в таблицу с помощью IBX, а затем попытался сделать то же самое с помощью ADO (охватывающий две технологии подключения, оба являются потомками TDataSet). Я обнаружил, что правда где-то посередине, это не совсем диспетчер памяти, это не вина SDAC (ну ... они в состоянии что-то с этим поделать, если многие другие люди попытаются вставить капли 600 Мб в баз данных, но это не имеет отношения к данному обсуждению). «Проблема» в коде БД в Delphi. Как оказалось, Delphi настаивает на использовании одного варианта для хранения любого типа данных, которые можно загрузить в параметр. И это имеет смысл, ведь мы можем загружать много разных вещей в параметр для INSERT. Вторая проблема заключается в том, что Delphi хочет рассматривать этот вариант как тип VALUE: он копирует его в список дважды, а может быть, и трижды! Первая копия создается сразу после загрузки параметра из файла. Вторая копия создается, когда параметр готов к отправке в ядро ​​базы данных.

Написать это легко:

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

и отлично работает для целых чисел, дат и небольших строк, но когда V2 представляет собой массив Variant 600 МБ, это назначение, по-видимому, создает полную копию! Теперь подумайте о пространстве памяти, доступном для 32-битного приложения, которое не работает в режиме «3G». Доступно всего 2 Гб адресного пространства. Часть этого пространства зарезервирована, часть этого пространства используется для самого исполняемого файла, затем есть библиотеки, а затем есть место, зарезервированное для диспетчера памяти. После выделения первых 600 Мбайт доступного адресного пространства может не хватить для выделения другого буфера 600 Мбайт! Из-за этого можно с уверенностью винить в этом диспетчер памяти, но опять же, почему именно БД нужна другая копия монстра 600 Мб?

Одно возможное исправление

Попробуйте разделить файл на более мелкие, более управляемые части. Настройте таблицу базы данных, чтобы иметь 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;
person Cosmin Prund    schedule 30.07.2010
comment
Спасибо за ответ и за код. Я, вероятно, выберу этот подход, спасибо также за четкое объяснение проблемы. - person LaBracca; 02.08.2010

Вы можете перебрать байтовый поток объекта, который вы пытаетесь вставить, и по существу буферизовать его часть за раз в своей базе данных, пока вы не сохраните весь ваш объект.

Я бы посмотрел на метод Buffer.BlockCopy (), если вы используете .NET.

На мой взгляд, метод анализа вашего файла может выглядеть примерно так:

        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
        }
person George Johnston    schedule 14.07.2010

Вот пример, который читает файл с диска и сохраняет его в столбце FILESTREAM. (Предполагается, что у вас уже есть контекст транзакции и FilePath в переменных «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()
person jacob sebastian    schedule 14.07.2010
comment
Если вы сделаете что-то подобное, вам нужно будет убедиться, что вы настроили доступ к системным файлам из SQL, который не включен по умолчанию. - person George Johnston; 14.07.2010
comment
Что ж, на самом деле нужно активировать не «доступ к системным файлам из sql». FILESTREAM - это гибридная функция, которую необходимо включить на уровне администратора Windows, а также на уровне SQL Server. Прежде всего, необходимо включить FILESTREAM для «потокового доступа к файловому вводу-выводу». Это можно сделать либо во время установки, либо с помощью диспетчера конфигурации SQL Server (после установки). В дополнение к этому уровень доступа FILESTREAM необходимо настроить (для полного доступа) на уровне экземпляра SQL Server. Это можно сделать либо с помощью TSQL SSMS. - person jacob sebastian; 18.07.2010

Вероятно, вы где-то сталкиваетесь с проблемами фрагментации памяти. Игра с действительно большими блоками памяти, особенно в любой ситуации, когда их может потребоваться перераспределение, имеет тенденцию вызывать ошибки нехватки памяти, когда теоретически у вас достаточно памяти для выполнения работы. Если ему нужен блок 600 Мбайт, и он не может найти дыру шириной 600 Мбайт, значит, нехватка памяти.

Хотя я никогда не пробовал это сделать, я предпочитаю обходной путь создать очень минимальную программу, которая выполняет ТОЛЬКО одну операцию. Сделайте это максимально простым, чтобы минимизировать выделение памяти. Когда вы сталкиваетесь с такой рискованной операцией, обратитесь за помощью к внешней программе. Программа запускается, выполняет одну операцию и завершает работу. Дело в том, что новая программа находится в собственном адресном пространстве.

Единственное настоящее исправление - 64-разрядная версия, и у нас пока нет такой возможности.

person Loren Pechtel    schedule 30.07.2010

Недавно у меня возникла аналогичная проблема при запуске DBCC CHECKDB на очень большой таблице. Я бы получил такую ​​ошибку:

Недостаточно системной памяти во внутреннем пуле ресурсов для выполнения этого запроса.

Это было на SQL Server 2008 R2 Express. Интересно то, что я мог контролировать возникновение ошибки, добавляя или удаляя определенное количество строк в таблице.

После обширных исследований и обсуждений с различными экспертами по SQL Server я пришел к выводу, что проблема связана с комбинацией нехватка памяти и ограничение памяти 1 ГБ SQL Server Express.

Мне была дана рекомендация:

  1. Приобретите машину с большим объемом памяти и лицензионной версией SQL Server или ...
  2. Разделите таблицу на большие части, которые может обработать DBCC CHECKDB.

Из-за сложной природы синтаксического анализа этих файлов в объекте FILSTREAM я бы рекомендовал filesystem и просто использовать SQL Server для хранения местоположения файлов.

person 8kb    schedule 31.07.2010

«Хотя нет никаких ограничений на количество поддерживаемых баз данных или пользователей, оно ограничено использованием одного процессора, 1 ГБ памяти и 4 ГБ файлов базы данных (файлы базы данных 10 ГБ из SQL Server Express 2008 R2)». Важен не размер файлов базы данных, а «1 ГБ памяти». Попробуйте выплюнуть файл размером 600MB +, но поместить его в поток.

person CarneyCode    schedule 01.08.2010