Извлечение Oracle BLOB очень медленно

У меня возникают проблемы с производительностью при извлечении BLOBS из базы данных oracle 10gR2 10.2.05, которую я администрирую. У меня есть около 400 файлов, хранящихся в виде больших двоичных объектов, которые мне нужно записать в файловую систему. Ниже мой код. Когда я выполняю эту процедуру, первые 8 или около того файлов записываются в течение пары секунд, а затем все замедляется в геометрической прогрессии, где-то около 1 файла каждые 40 секунд после первых 8. Для меня это не имеет никакого смысла, зачем первые 8 файлов быстро но потом все тормозит. Я попытался запустить это как хранимую процедуру, изменив UTL_FILE.fopen на «wb» (записать двоичный файл), а также используя файловую систему, смонтированную NFS, чтобы не препятствовать производительности базы данных. Ничего из этого не оказало никакого влияния. При такой скорости мне потребуется 6 часов, чтобы извлечь 400 файлов, каждый из которых в среднем занимает около 1,5 МБ. Кто-нибудь видит что-то не так с моим кодом или знает лучший способ сделать это? Кстати, я использовал этот пример кода, найденный здесь http://www.oracle-base.com/articles/9i/ExportBlob9i.php в качестве отправной точки.

Спасибо за любую помощь!

DECLARE
  TYPE comment_text IS TABLE OF documents.comment_text%TYPE;
  TYPE localdata IS TABLE OF documents.localdata%TYPE;
  l_file UTL_FILE.FILE_TYPE;
  l_buffer RAW(32767);
  l_amount BINARY_INTEGER := 32767;
  l_pos INTEGER := 1;
  l_blob localdata;
  l_fname comment_text;
  l_blob_len INTEGER;
  l_x NUMBER := 1;
BEGIN
  SELECT comment_text, localdata
  BULK COLLECT INTO l_fname, l_blob
  FROM documents
  WHERE user_id='BILLYBOB';
  IF SQL%ROWCOUNT =0 THEN
    DBMS_OUTPUT.PUT_LINE('No records found!');
  ELSE
    FOR i IN l_fname.FIRST .. l_fname.LAST
    LOOP
      l_blob_len := DBMS_LOB.getlength(l_blob(i));
      DBMS_OUTPUT.PUT_LINE(l_blob_len);
      l_file := UTL_FILE.fopen('BLOBS',l_x || '_' || l_fname(i),'w', 32767);
      l_pos := 1;
      l_x := l_x + 1;
      WHILE l_pos < l_blob_len
      LOOP
        DBMS_LOB.read(l_blob(i), l_amount, l_pos, l_buffer);
        UTL_FILE.put_raw(l_file, l_buffer, TRUE);
        l_pos := l_pos + l_amount;
      END LOOP;
      UTL_FILE.fclose(l_file);
    END LOOP;
  END IF;
END;

person spaghettiwestern    schedule 08.12.2010    source источник


Ответы (1)


Я почти уверен, что вам не следует извлекать все BLOB-объекты в массив в начале процедуры. Поскольку вы читаете данные BLOB и фактически никогда не закрываете локатор больших объектов, Oracle должен хранить всю эту информацию в памяти. Я предполагаю, что это случай переполнения памяти.

Попробуйте это вместо этого:

CURSOR cc IS (SELECT ...)
BEGIN
   OPEN cc;
   LOOP
      FETCH cc
         INTO l_fname, l_blob;
      EXIT WHEN cc%NOTFOUND;
      l_blob_len := DBMS_LOB.getlength(l_blob);
      DBMS_OUTPUT.PUT_LINE(l_blob_len);
      l_file := UTL_FILE.fopen('BLOBS', l_x || '_' || l_fname, 'w', 32767);
      l_pos  := 1;
      l_x    := l_x + 1;
      WHILE l_pos < l_blob_len LOOP
         DBMS_LOB.READ(l_blob, l_amount, l_pos, l_buffer);
         UTL_FILE.put_raw(l_file, l_buffer, TRUE);
         l_pos := l_pos + l_amount;
      END LOOP;
      UTL_FILE.fclose(l_file);
   END LOOP;
   CLOSE cc;
END;
person Vincent Malgrat    schedule 08.12.2010
comment
Я проверил ваш код, и нет никакой разницы. Однако DBMS_LOB.close выдает следующую ошибку: 22289. 00000 - невозможно выполнить операцию %s с неоткрытым файлом или большим объектом * Причина: файл или большой объект не открыт для выполнения требуемой операции. *Действие: предшествовать текущей операции успешную операцию открытия файла или большого объекта. - person spaghettiwestern; 09.12.2010
comment
@spaghettiwestern: Похоже, моя догадка была ошибочной. Однако я попытался воспроизвести вашу настройку и не заметил, что запись стала медленнее (моя установка включала 1000 файлов размером около 1,5 МБ каждый). В вашем случае, поскольку первые 8 операций записи кажутся удовлетворительными с точки зрения производительности, я бы предложил разделить работу на небольшие партии. - person Vincent Malgrat; 09.12.2010
comment
Спасибо за ваш вклад и результаты тестирования, очень признателен!!! Интересно, как у вас, кажется, нет такой же проблемы, какую версию Oracle вы используете? Когда я изменяю свою процедуру так, чтобы она принимала аргумент и позволяла извлекать только один BLOB за раз, а затем в блоке plsql использовала цикл для управления моей процедурой, у меня не было проблемы с замедлением. На самом деле он завершает извлечение 700 МБ примерно за 40 секунд, тогда как исходный пример кода, который я разместил здесь, занял бы несколько часов. - person spaghettiwestern; 09.12.2010
comment
@spaghettiwestern: я провел тест на 10.2.0.3 дБ. Можете ли вы попробовать с явным курсором вместо неявного курсора? (Я обновил свой ответ, хотя вам придется настроить раздел DECLARE). Мне интересно, может быть, скрытая пакетная выборка неявного курсора (функция 10g) извлекает здесь слишком много данных. Кстати, я тестирую на не занятом тестовом сервере с большим количеством доступной памяти, возможно, поэтому я не могу воспроизвести ваш результат. - person Vincent Malgrat; 09.12.2010
comment
@Vincent - явный курсор дает те же результаты, он работает медленно. Я также использую простаивающий сервер разработки с большим количеством оперативной памяти и процессора. Я думаю, что проблема, которую я вижу, как-то связана с DBMS_LOB.read, например, она заполняет какой-то буфер или что-то в этом роде. Если я закомментирую UTL_FILE.put_raw в моем исходном примере кода, все будет работать медленно, поэтому я знаю, что это как-то связано с DBMS_LOB.read. - person spaghettiwestern; 09.12.2010
comment
Это довольно серьезное замедление только из-за проблемы с буферизацией. Вы пытались профилировать его с помощью sql_trace? - person JOTN; 09.12.2010
comment
@JOTN - я попробую sql_trace завтра, когда у меня будет шанс. - person spaghettiwestern; 10.12.2010
comment
Что-то, что я заметил, это то, что когда я запускаю свой исходный код, но условно извлекаю только файлы размером менее 100 КБ, я не сталкиваюсь с проблемой производительности, только с большими файлами, скажем, 1200 КБ, я сталкиваюсь с проблемой производительности. Я запустил sql_trace, и, на мой взгляд, это говорит мне о чрезмерном дисковом вводе-выводе, кто-то поправит меня, если я ошибаюсь, вот итоги; ЦП = 166,06, прошедшее = 162,76, диск = 1704593, запрос = 11373096 - person spaghettiwestern; 11.12.2010
comment
Я запустил DBMS_PROFILER с моим исходным кодом, и узкое место, похоже, связано с тем, что DBMS_LOB.read занимает наибольшее значение total_time почти в 3 раза больше, чем UTL_FILE.put_raw total_time. - person spaghettiwestern; 11.12.2010