对于大于32kb的文件,BLOB导出速度很慢

时间:2013-06-04 16:53:15

标签: performance oracle11g export blob utl-file

我需要从Oracle 11g(11.2.0.3)系统导出BLOB。该过程对于< BLOB(JPG照片)非常有用。 32,767字节。我可以使用dbms_log.read&amp ;;在5秒内将约4000张照片导出到数据库服务器上的本地目录中。 utl_file.put_raw。如果文件超过读缓冲区的32,767字节限制,那么这就是性能问题的开始。我已经看过关于确切性能问题的类似帖子,但所提供的解决方案已经被研究过但没有成功。基于监视工具,CPU,I / O和内存在导出期间不会受到压力。我试图理解为什么较大的BLOB(所有它们都在100K以下)必须以32,767字节增量拼接在一起,与32,767字节以下的BLOB相比,具有如此巨大的输出缓慢。导出大型BLOB时,每个文件最多可能需要15秒才能导出。

Related Post of slow blob extraction

Related Post of BLOB export tuning

有没有人遇到文件大于32,767字节的BLOB导出缓慢?

DECLARE

  CURSOR cur_photo IS
    select  substr(c.custnum, -7, length(c.custnum)) custnum,
            cp.cust_id,
            cp.photo 
    from customer c
    inner join customer_photo cp
      on c.cust_id = cp.cust_id
    inner join customer_def_grp_value cdv
      on c.cust_id = cdv.cust_id;

  select_sql varchar2(225);
  l_file      UTL_FILE.FILE_TYPE;
  l_buffer    RAW(32767);
  l_amount    PLS_INTEGER := 32767;
  l_pos       PLS_INTEGER := 1;
  l_blob      BLOB;
  l_blob_len  PLS_INTEGER;
  l_filename  varchar2(225);
  error_number varchar2(225);
  error_message varchar2(225);

BEGIN
  --dbms_output.put_line('Starting at: ' || to_char(systimestamp, 'DD-MON-YYYY HH:MI:SS.FF6'));
  --DBMS_OUTPUT.ENABLE (buffer_size => NULL); 
  FOR custphoto IN cur_photo LOOP
    --dbms_output.put_line('In the loop ' || custphoto.cust_id);

    select_sql := 'SELECT photo FROM customer_photo WHERE cust_id = :cust_id';
    --dbms_output.put_line('Statement: ' || select_sql);

    EXECUTE IMMEDIATE select_sql INTO l_blob using custphoto.cust_id;

    l_blob_len := DBMS_LOB.getlength(l_blob);
    --dbms_output.put_line('BLOB length: ' || l_blob_len);

    -- Set the filename
    l_filename := custphoto.custnum || '.jpg';
    --dbms_output.put_line('Filename: ' || l_filename);

    -- Open the destination file.
    l_file := UTL_FILE.fopen('jpeg', l_filename, 'wb', 32767);

    --dbms_output.put_line('Start Export at: ' || to_char(systimestamp, 'DD-MON-YYYY HH:MI:SS.FF6'));

    IF l_blob_len < 32767 then
      --dbms_output.put_line('BLOB < 32767 bytes');
      DBMS_LOB.read(l_blob, l_blob_len, l_pos, l_buffer);
      UTL_FILE.put_raw(l_file, l_buffer, TRUE);
    ELSE -- write in pieces
      --dbms_output.put_line('BLOB >= 32767 bytes');
      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;
    END IF;

    -- Close the file.
    UTL_FILE.fclose(l_file);

    -- Reset the pos for the next jpg file
    l_pos := 1;

  END LOOP;

EXCEPTION
  WHEN OTHERS THEN
    -- Close the file if something goes wrong.
    error_number := sqlcode; 
    error_message := substr(sqlerrm, 1, 100); 
    dbms_output.put_line('Error Number: ' || error_number); 
    dbms_output.put_line('Error Message: ' || error_message); 
    utl_file.fclose_all;
  RAISE;

END;

提前感谢您对BLOB导出的任何见解。

2 个答案:

答案 0 :(得分:5)

您还需要重置l_amount

l_amount := 32767;

dbms_lob.read的第二个参数是IN OUT参数。

http://docs.oracle.com/cd/E11882_01/appdev.112/e25788/d_lob.htm#i999170

DBMS_LOB.READ (
   lob_loc   IN             BLOB,
   amount    IN OUT  NOCOPY INTEGER,
   offset    IN             INTEGER,
   buffer    OUT            RAW);

如果你运气不好,它只剩下1个字节来读取,然后你逐个字节地逐步执行下一个blob:

  

     

要读取的字节数(对于BLOB)或字符(对于CLOB)或已读取的数字。

我完成了整个作业,可以重现缓慢的表现。我创建了一些testdata,如下所述:Prepare test data on Oracle with blob column

然后我用我自己的测试程序尝试了它:

create or replace directory outdir as '/home/oracle/pngs';
set serveroutput on
declare
  l_file      utl_file.file_type;
  l_buffer    RAW(32767);
  l_amount    PLS_INTEGER := 32767;
  l_pos       PLS_INTEGER := 1;
  l_blob_len  PLS_INTEGER;
begin
  --l_amount := 1; -- this wrecked the performance
  for c in (select * from demo.blob_test) loop
    l_file := UTL_FILE.fopen('OUTDIR', 'blob'||c.id||'.png', 'wb', 32767);
    l_blob_len := DBMS_LOB.getlength(c.data);
    IF l_blob_len < 32767 then
      dbms_output.put_line(systimestamp||' BLOB < 32767 bytes');
      DBMS_LOB.read(c.data, l_blob_len, l_pos, l_buffer);
      UTL_FILE.put_raw(l_file, l_buffer, TRUE);
      dbms_output.put_line(systimestamp||' done');
    ELSE 
      dbms_output.put_line(systimestamp||' BLOB >= 32767 bytes len '||l_blob_len);
      WHILE l_pos < l_blob_len LOOP
        --dbms_output.put_line(systimestamp||' l_pos '||l_pos||' l_amount '||l_amount);
        DBMS_LOB.read(c.data, l_amount, l_pos, l_buffer);
        UTL_FILE.put_raw(l_file, l_buffer, TRUE);
        l_pos := l_pos + l_amount;
      END LOOP;
      dbms_output.put_line(systimestamp||' done');
    END IF;
    l_pos := 1;
    l_amount := 32767; -- this handled it
    utl_file.fclose(l_file);
  end loop;
end;

答案 1 :(得分:0)

如果您在Oracle数据库中安装了Java运行时,请尝试使用Java过程将BLOB下载到文件中。

使用DBMS_LOB包需要45分钟才能下载10'000个PDF文档。 使用Java过程需要15秒才能下载相同数量和大小的文档。

这些BLOBS存储在带有NOCACHE的Oracle 11.2.0.3表安全文件中。