You need to reset l_amount
also.
l_amount := 32767;
the second parameter of dbms_lob.read is an IN OUT
parameter.
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);
If you are unlucky it will be just 1 byte left to read and then you step through the big next blob byte by byte:
amount
Number of bytes (for BLOBs) or characters (for CLOBs) to read, or number that were read.
I have done the whole homework and could reproduce the slow performance. I created some testdata as described here: Prepare test data on Oracle with blob column
Then I tried it with my own test program:
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;