We have been running a stored procedure to extract BLOBS
from a database every day for roughly the last 5 years. We generally extract around 25 files each night, with the majority being approximately 500KB in size and a handful nearing 10,000KB.
The process has never been the fastest but after we moved data centres this process can take upwards of 12 hours. That in itself is shocking when you are only extracting ~55MB or so. We've engaged all the relevant teams to take a look at the performance of Oracle, disk I/O etc and they claim that everything is perfect.
I've been reading about UTL_FILE
and DBMS_LOB.read
, seen people talk about resetting the pos
after each loop etc. To be honest I can't seem to figure out what any of this means, and the general consensus is that there are far better ways of achieving the same outcome.
Unfortunately we do not have the liberty of refactoring this, so can anyone see anything glaringly wrong with our procedure? I'm just struggling with something I do not fully understand and those who maintain our infrastructure blame it all on this code and wash their hands of it.
CREATE OR REPLACE PROCEDURE PKG_EXTRACT (l_brand IN VARCHAR2) AS
l_file UTL_FILE.FILE_TYPE;
l_buffer RAW(32767);
l_amount BINARY_INTEGER := 32767;
l_pos INTEGER;
l_blob BLOB;
l_blob_len INTEGER;
x NUMBER;
l_file_name VARCHAR2(200);
l_count INTEGER := 1;
v_code NUMBER;
v_errm VARCHAR2(64);
log_file UTL_FILE.FILE_TYPE;
rec_num number;
BEGIN
DECLARE
CURSOR extract_cur IS
SELECT DATA, BIN_NAME
FROM STAGING
WHERE UPPER(EXTRACTED)='N';
BEGIN
log_file := UTL_FILE.fopen('DATA_DOWNLOAD_DIR','pkg_extract.log','a', 32767);
UTL_FILE.put_line(log_file,'Logging is being done in 24 hours format - V1.5 ',TRUE);
UTL_FILE.put_line(log_file,'Extract procedure started on Date-Time = '|| TO_TIMESTAMP(LOCALTIMESTAMP, 'DD-MON-RR,HH24.MI.SSXFF'),TRUE);
select count(1) into rec_num from staging;
UTL_FILE.put_line(log_file,'Total Number of records found = ' || rec_num , TRUE);
select count(1) into rec_num from staging where UPPER(EXTRACTED)='N';
UTL_FILE.put_line(log_file,'Total Number of records matching criteria = ' || rec_num , TRUE);
dbms_output.put_line('Loop through records and write them to file');
FOR extract_rec IN extract_cur
LOOP
l_pos := 1;
l_blob := extract_rec.DATA;
l_blob_len := DBMS_LOB.getlength(l_blob);
-- Save blob length.
x := l_blob_len;
l_file_name := extract_rec.BIN_NAME ;
-- Open the destination file.
dbms_output.put_line('Open the destination file:- ' || l_file_name);
l_file := UTL_FILE.fopen('DATA_DOWNLOAD_DIR',l_file_name,'wb', 32767);
dbms_output.put_line('File opened');
-- Read chunks of the BLOB and write them to the file until complete.
dbms_output.put_line('l_pos:- ' || l_pos);
dbms_output.put_line('l_blob_len:- ' || l_blob_len);
WHILE l_pos <= l_blob_len
LOOP
dbms_output.put_line('DBMS_LOB.read from position: ' || l_pos);
DBMS_LOB.read(l_blob, l_amount, l_pos, l_buffer);
dbms_output.put_line('UTL_FILE.put_raw');
UTL_FILE.put_raw(l_file, l_buffer, TRUE);
dbms_output.put_line('Written ' || l_amount || ' bytes of data starting at position: ' || l_pos);
-- Set the start position for the next cut.
l_pos := l_pos + l_amount;
--updating the extract field
dbms_output.put_line(extract_rec.BIN_NAME);
END LOOP;
l_count := l_count + 1;
-- Close the file.
dbms_output.put_line('Close the file:- ' || l_file_name);
UTL_FILE.fclose(l_file);
update staging set extracted='Y', extract_timestamp=sysdate where bin_name=extract_rec.BIN_NAME;
commit;
END LOOP;
UTL_FILE.put_line(log_file,'Extract procedure Completed on Date-Time = '|| TO_TIMESTAMP(LOCALTIMESTAMP, 'DD-MON-RR,HH24.MI.SSXFF'),TRUE);
IF UTL_FILE.is_open(log_file) THEN
UTL_FILE.fclose(log_file);
end if;
END;
EXCEPTION
WHEN OTHERS THEN
v_code := SQLCODE;
v_errm := SUBSTR(SQLERRM, 1, 64);
dbms_output.put_line('Error code ' || v_code || ': ' || v_errm);
UTL_FILE.put_line(log_file,'--------------------------------------' ,TRUE);
UTL_FILE.put_line(log_file,'Error Occurred while executing '||'Error code ' || v_code || ': ' || v_errm ,TRUE);
UTL_FILE.put_line(log_file,'Extract procedure Completed with errors - '|| TO_TIMESTAMP(LOCALTIMESTAMP, 'DD-MON-RR,HH24.MI.SSXFF'),TRUE);
UTL_FILE.put_line(log_file,'--------------------------------------' ,TRUE);
-- Close the file if something goes wrong.
IF UTL_FILE.is_open(l_file) THEN
UTL_FILE.fclose(l_file);
IF UTL_FILE.is_open(log_file) THEN
UTL_FILE.fclose(log_file);
end if;
END IF;
RAISE;
END;
/
EDIT
Execution Plan for the CURSOR extract_cur
.
Plan hash value: 3428151562
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 678 | 9 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| STAGING | 6 | 678 | 9 (0)| 00:00:01 |
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(UPPER("S"."EXTRACTED")='N')