Ошибка нехватки памяти при вставке 600 МБ файлов на сервер SQL Express в виде данных файлового потока - PullRequest
5 голосов
/ 14 июля 2010

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

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

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

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

При вставке файла в задаче используется + 600 МБ памятиУ меня и менеджера есть ошибка.

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

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

ОБНОВЛЕНИЕ ДЛЯ BOUNTY :

Я предложил 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 Server.Поэтому я думаю, что ответ, который я принял, действительно затрагивает проблему, даже если у меня нет времени, чтобы проверить это сейчас.

Ответы [ 6 ]

5 голосов
/ 31 июля 2010

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

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

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

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

и отлично работает для Integer и Date, а также для небольших строк, но когда 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;
3 голосов
/ 14 июля 2010

Вот пример, который читает файл на диске и сохраняет его в столбце FILESTREAM.(Предполагается, что у вас уже есть транзакция Context и 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()
3 голосов
/ 14 июля 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
        }
2 голосов
/ 31 июля 2010

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

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

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

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

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

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

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

2 голосов
/ 31 июля 2010

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

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

Единственное верное исправление - 64-битное, и у нас пока нет этой опции.

1 голос
/ 01 августа 2010

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

...