Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Как избежать ошибки "Out of Memory" при извлечении большого количества файлов из BLOB в SQL Server с помощью Delphi

Delphi , Базы данных , Ошибки БД

 

При работе с большими объемами данных, хранящихся в BLOB полях SQL Server, часто возникает задача извлечения этих данных в файлы. В Delphi, при использовании компонентов доступа к данным, таких как FireDAC (FDQuery), можно столкнуться с ошибкой "Out of Memory", особенно при обработке десятков тысяч файлов. В этой статье мы рассмотрим причины возникновения этой ошибки и предложим несколько решений, ориентированных на Delphi и Object Pascal.

Проблема:

Программа пытается извлечь большое количество файлов из BLOB колонки в SQL Server. Файлы успешно сохраняются в директорию, но после обработки около 30,000 файлов возникает ошибка "Out of Memory". Предполагается, что ресурсы освобождаются после обработки каждого файла, и используются опции выборки данных для больших наборов.

Причины возникновения ошибки "Out of Memory":

  • Чрезмерная агрегация данных в памяти: Использование FDQuery1.FetchOptions.Mode := fmAll; приводит к тому, что FireDAC пытается загрузить все данные из запроса в память сразу. Для больших наборов данных это быстро исчерпывает доступную память.
  • Неэффективное управление памятью: Даже при попытках освобождения ресурсов, утечки памяти в коде или неоптимальное использование памяти могут приводить к постепенному увеличению потребления памяти и, как следствие, к ошибке.
  • 32-битная архитектура приложения: 32-битные приложения имеют ограничение на объем используемой памяти (обычно 2-4 ГБ). Если объем данных, обрабатываемых приложением, превышает этот лимит, возникает ошибка "Out of Memory".

Решение:

Основная проблема в предоставленном коде - использование FDQuery1.FetchOptions.Mode := fmAll;. Это заставляет FireDAC загружать все данные в память сразу. Вместо этого, следует использовать режим fmOnDemand и обеспечить последовательную обработку данных.

Предлагаемое решение (с исправлениями):

procedure TForm1.ExtractInvoicesFromBlobs(AMinInvNo, AMaxInvNo: Integer);
var
  PrintDate, FileName, BaseFolder, TargetFolder: string;
  FileStream: TFileStream;
  BlobStream: TStream;
  RecordCount: Integer;
begin
  // init
  RecordCount := 0;
  Memo1.Clear;

  // retrieve data from db
  FDQuery1.SQL.Clear;
  FDQuery1.SQL.Text := 'SELECT ID, PrintedFile, Filename, PrintDateTime ' +
                       'FROM InvoiceStorage ' +
                       'WHERE Filename BETWEEN :MinFilename AND :MaxFilename';
  FDQuery1.ParamByName('MinFilename').AsString := Format('%d.pdf', [AMinInvNo]);
  FDQuery1.ParamByName('MaxFilename').AsString := Format('%d.pdf', [AMaxInvNo]);

  // Важно: Установите Unidirectional и удалите fmAll перед Open
  FDQuery1.FetchOptions.Unidirectional := True;
  //FDQuery1.FetchOptions.Mode := fmAll; // УДАЛИТЬ ЭТУ СТРОКУ!

  FDQuery1.Open;

  while not FDQuery1.Eof do
  begin
    try
      PrintDate := Copy(FDQuery1.FieldByName('PrintDateTime').AsString, 1, 4);
      BaseFolder := IncludeTrailingPathDelimiter(Edit3.Text);
      TargetFolder := Format(BaseFolder + '%s', [PrintDate]);
      ForceDirectories(TargetFolder);
      BlobStream := FDQuery1.CreateBlobStream(FDQuery1.FieldByName('PrintedFile'), bmRead);
      try
        FileName := IncludeTrailingPathDelimiter(TargetFolder) + FDQuery1.FieldByName('Filename').AsString;
        FileStream := TFileStream.Create(FileName, fmCreate);
        try
          FileStream.CopyFrom(BlobStream, BlobStream.Size);
        finally
          FileStream.Free;
        end;
      finally
        BlobStream.Free;
        BlobStream := nil; // Важно обнулять ссылки после освобождения памяти
      end;

      // Each 1,000 files: log and provide some breathing air to the system
      Inc(RecordCount);
      if RecordCount mod 1000 = 0 then
      begin
        Memo1.Lines.Add(Format('%d files processed...', [RecordCount]));
        TrimMemo;
        Application.ProcessMessages;
        Sleep(100);
      end;
    except
      on E: Exception do
        Memo1.Lines.Add(Format('Error while processing record %d: %s', [RecordCount, E.Message]));
    end;
    FDQuery1.Next;
  end;
  FDQuery1.Close;
end;

Альтернативные решения и дополнительные рекомендации:

  • Пакетная обработка: Разделите задачу на несколько этапов, обрабатывая, например, по 10,000 файлов за раз. После обработки каждой партии, закройте и снова откройте запрос. Это позволит освободить память между итерациями.
procedure TForm1.ExtractInvoicesFromBlobs(AMinInvNo, AMaxInvNo: Integer; ABatchSize: Integer);
var
  CurrentMinInvNo, CurrentMaxInvNo: Integer;
begin
  CurrentMinInvNo := AMinInvNo;
  while CurrentMinInvNo <= AMaxInvNo do
  begin
    CurrentMaxInvNo := CurrentMinInvNo + ABatchSize - 1;
    if CurrentMaxInvNo > AMaxInvNo then
      CurrentMaxInvNo := AMaxInvNo;

    ExtractInvoicesFromBlobsBatch(CurrentMinInvNo, CurrentMaxInvNo); // Отдельная процедура для обработки пакета
    CurrentMinInvNo := CurrentMaxInvNo + 1;
  end;
end;

procedure TForm1.ExtractInvoicesFromBlobsBatch(AMinInvNo, AMaxInvNo: Integer);
// ... (код из предыдущего примера, адаптированный для обработки пакета)
begin
  // ...
  try
    FDQuery1.Open;
    // ... (обработка данных)
  finally
    FDQuery1.Close;
  end;
end;
  • Перекомпиляция в 64-битное приложение: Перекомпилируйте приложение под 64-битную архитектуру. Это позволит использовать больший объем памяти. В Delphi это можно сделать в настройках проекта.
  • Использование потоков (Threads): Разделите задачу извлечения файлов на несколько потоков. Каждый поток будет отвечать за обработку определенной части данных. Это позволит более эффективно использовать ресурсы процессора и памяти. (Более сложный вариант).
  • Профилирование памяти: Используйте инструменты профилирования памяти, чтобы выявить утечки памяти в коде. Delphi IDE предоставляет встроенные инструменты для профилирования.
  • Оптимизация SQL запроса: Убедитесь, что SQL запрос оптимизирован для быстрого извлечения данных. Индексы могут значительно улучшить производительность.
  • Проверка наличия достаточного места на диске: Убедитесь, что на диске, куда сохраняются файлы, достаточно места. Нехватка места на диске также может приводить к ошибкам.

Важные замечания:

  • Обнуление ссылок: После освобождения объектов (например, BlobStream.Free;) рекомендуется обнулять ссылки на эти объекты (BlobStream := nil;). Это помогает избежать случайного повторного использования освобожденной памяти.
  • Обработка исключений: Тщательно обрабатывайте исключения, чтобы предотвратить утечки ресурсов в случае ошибок. Используйте блоки try...finally для гарантированного освобождения ресурсов.
  • Application.ProcessMessages и Sleep: Использование Application.ProcessMessages позволяет приложению обрабатывать события Windows (например, перерисовку экрана), что делает его более отзывчивым. Sleep дает системе небольшую передышку. Однако, злоупотребление этими функциями может снизить производительность.

Заключение:

Ошибка "Out of Memory" при извлечении больших объемов данных из BLOB полей в Delphi может быть вызвана различными факторами. Использование правильных опций выборки данных, эффективное управление памятью, перекомпиляция в 64-битную архитектуру и пакетная обработка данных - это основные методы, позволяющие избежать этой ошибки и успешно обработать большие объемы данных. Не забывайте про профилирование памяти и оптимизацию SQL запросов для достижения максимальной производительности.

Создано по материалам из источника по ссылке.

Контекст описывает причины возникновения ошибки "Out of Memory" при извлечении больших объемов данных из BLOB полей SQL Server в Delphi и предлагает решения, такие как использование режима `fmOnDemand`, пакетная обработка, перекомпиляция в 64-битную архи


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: Ошибки БД ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-09-07 18:49:19/0.0042710304260254/0