【问题标题】:BLOB exports are slow for files larger than 32kb对于大于 32kb 的文件,BLOB 导出速度很慢
【发布时间】:2013-05-31 04:13:35
【问题描述】:

我需要从我们的 Oracle 11g (11.2.0.3) 系统导出 BLOB。该过程非常适合小于 32,767 字节的 BLOB(JPG 照片)。我可以使用 dbms_log.read 和 utl_file.put_raw 在 5 秒内将约 4000 张照片导出到数据库服务器上的本地目录。如果文件超过了读取缓冲区的 32,767 字节限制,这就是性能问题的开始。我看过关于确切性能问题的类似帖子,但提供的解决方案已经过研究但没有成功。根据监控工具,CPU、I/O 和内存在导出期间没有受到压力。我试图理解为什么与 32,767 字节以下的 BLOB 相比,必须以 32,767 字节增量拼凑在一起的较大 BLOB(所有这些 BLOB 的大小都低于 100K)具有如此巨大的导出速度。导出大型 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 导出的任何见解。

【问题讨论】:

  • 速度慢的原因是 WHILE 子句中的 dbms_lob.read 调用。

标签: performance oracle11g export blob utl-file


【解决方案1】:

您还需要重置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),或已读取的数量。

我已经完成了整个作业,并且可以重现缓慢的性能。我创建了一些测试数据,如下所述: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;

【讨论】:

  • 优秀。我很高兴这确实是问题所在。它仍然是一个谜。感谢您的接受和点赞。
  • 谢谢!!!!也许在 12c 他们可以只支持 blob 导出,又是哪一年?
【解决方案2】:

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

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

这些是使用 NOCACHE 存储在 Oracle 11.2.0.3 表安全文件中的 BLOBS。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 2013-01-15
    • 2017-07-09
    • 1970-01-01
    • 2011-02-26
    • 1970-01-01
    相关资源
    最近更新 更多