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

Оптимизация обработки больших BLOB-данных в Delphi: решение проблемы "Out of Memory"

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

 

При работе с базами данных, содержащими большие объемы данных в формате BLOB (Binary Large Object), часто возникает проблема нехватки памяти (Out of Memory), особенно при извлечении и обработке большого количества таких объектов. В контексте Delphi и Pascal, это может быть особенно актуально, учитывая ограничения 32-битной архитектуры (хотя и 64-битная архитектура не гарантирует полное отсутствие проблем при работе с очень большими объемами данных).

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

Проблема:

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

Пример кода, демонстрирующий проблему (как предоставлено в вопросе):

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]);
  FDQuery1.Open;

  // these 2 lines are to prevent "out of memory" after approximately 30,000 pdf
  FDQuery1.FetchOptions.Mode := fmAll;
  FDQuery1.FetchOptions.Unidirectional := True;

  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;

Решение:

Основная проблема кроется в неправильном использовании FetchOptions компонента FDQuery. Установка FDQuery1.FetchOptions.Mode := fmAll; приводит к тому, что все записи (включая все BLOB-данные) загружаются в память сразу после открытия запроса. Это, очевидно, приводит к быстрому исчерпанию памяти при большом количестве записей.

Правильное решение:

  1. Удалить строку FDQuery1.FetchOptions.Mode := fmAll;. По умолчанию используется fmOnDemand, что означает загрузку данных только при необходимости.
  2. Убедиться, что FDQuery1.FetchOptions.Unidirectional := True; установлено до открытия запроса (FDQuery1.Open;). Однонаправленный режим позволяет драйверу базы данных оптимизировать использование памяти, не сохраняя данные для повторного доступа.
  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]);

  FDQuery1.FetchOptions.Unidirectional := True; // Важно установить ДО открытия!

  FDQuery1.Open;

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

  • Пакетная обработка: Если даже с fmOnDemand и Unidirectional возникают проблемы, можно разбить задачу на несколько этапов, обрабатывая, например, по 10 000 файлов за раз. Это позволит снизить пиковую нагрузку на память.

     procedure TForm1.ExtractInvoicesFromBlobs(AMinInvNo, AMaxInvNo, APackageSize: Integer);
    var i: Integer;
    begin
    for i := AMinInvNo to AMaxInvNo step APackageSize do
    begin
    ExtractInvoicePackage(i, Min(i + APackageSize - 1, AMaxInvNo));
    end;
    end;

    procedure ExtractInvoicePackage(AMinInvNo, AMaxInvNo: Integer);
    // ... (Код из ExtractInvoicesFromBlobs, но с использованием AMinInvNo и AMaxInvNo)
    end;

  • Оптимизация SQL запроса: Убедитесь, что SQL запрос максимально эффективен. Используйте индексы для полей, используемых в WHERE условии. Избегайте SELECT *, выбирайте только необходимые поля.

  • Увеличение доступной памяти: Если возможно, скомпилируйте приложение как 64-битное. Это позволит использовать больше памяти. Однако, это потребует перекомпиляции всех используемых библиотек и компонентов.
  • Ручное управление памятью (осторожно!): В крайнем случае, можно попробовать использовать ручное управление памятью с помощью GetMem и FreeMem, но это требует очень аккуратного подхода, чтобы избежать утечек памяти. В большинстве случаев, стандартных средств Delphi достаточно.
  • Использование потоков (Threads): Распараллеливание задачи извлечения файлов с помощью потоков может улучшить производительность, но также требует внимательного управления ресурсами и синхронизации. Неправильное использование потоков может привести к нестабильности программы.
  • Профилирование памяти: Используйте инструменты профилирования памяти (например, встроенный в Delphi или сторонние инструменты) для выявления утечек памяти или неэффективного использования памяти.

Заключение:

Проблема "Out of Memory" при обработке больших BLOB-данных в Delphi решается, прежде всего, правильной настройкой FetchOptions компонента FDQuery. Использование fmOnDemand и Unidirectional позволяет загружать данные только при необходимости и оптимизировать использование памяти. Дополнительные оптимизации, такие как пакетная обработка, оптимизация SQL запроса и переход на 64-битную архитектуру, могут еще больше улучшить производительность и стабильность приложения. Важно помнить о необходимости аккуратного управления ресурсами и избегать утечек памяти.

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

В статье рассматривается проблема нехватки памяти при извлечении больших объемов BLOB-данных из SQL Server в Delphi и предлагаются решения, включающие правильную настройку FetchOptions, пакетную обработку и оптимизацию SQL-запросов.


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

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




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


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


реклама


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

Время компиляции файла: 2024-12-22 20:14:06
2025-09-10 23:13:07/0.0067310333251953/0