2016-12-07 2 views
0

Мы выполняем хранимую процедуру для извлечения BLOBS из базы данных каждый день примерно за последние 5 лет. Обычно мы извлекаем около 25 файлов каждую ночь, причем большинство составляет примерно 500 КБ в размере, а количество пользователей около 10 000 КБ.Извлечение из базы данных BLOB получает медленнее

Этот процесс никогда не был самым быстрым, но после перемещения центров обработки данных этот процесс может занять 12 часов. Это само по себе шокирует, когда вы только извлекаете ~ 55 МБ или около того. Мы привлекли все соответствующие команды, чтобы взглянуть на производительность Oracle, диск ввода-вывода и т. Д., И они утверждают, что все идеально.

Я читал о UTL_FILE и DBMS_LOB.read, видел, как люди говорят о сбросе pos после каждого цикла и т.д. Честно говоря, я не могу показаться, чтобы выяснить, что все это значит, и общее мнение состоит в том, что являются гораздо лучшими способами достижения такого же результата.

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

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

Выполнение плана по 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') 
+0

Вы пытались собрать статистику на столе 'staging'? – GurV

+0

@GurwinderSingh, который обычно делается в базе данных каждый месяц. Это не особенно большая база данных ... 25 нечетных записей в день, и мы сохраняем только 30-дневную стоимость. – hshah

+1

Вы можете получить план выполнения запроса курсора и вставить вопрос? – GurV

ответ

0

нормально, так что я изменил код, попытайтесь избежать этих вещей select count(1) into rec_num from staging потому, что оракул делает здесь он преобразует PL/SQL в SQL и SQL в PL/SQL, так лучше использовать курсор, вот измененный код. И попробуйте добавить индекс create index extracted_idx on staging(extracted asc);, так что здесь есть модифицированный код: я не знаю, если он компилируется, потому что я не могу его проверить, но он должен работать.

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; 

    CURSOR extract_cur IS 
    SELECT DATA, 
       BIN_NAME 
      FROM STAGING 
     WHERE EXTRACTED = 'N'; 

    cursor c_count is 
    select count(1) cnt 
      from staging; 

    cursor c_rec_num is 
    select count(1) cnt 
     from staging 
     where 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); 

    open c_cnt; 
    fetch c_cnt into rec_num; 
    close c_cnt; 

    open c_rec_num; 
    fetch c_rec_num into rec_num; 
    close c_rec_num; 

    UTL_FILE.put_line(log_file, 
        'Total Number of records found = ' || rec_num, 
        TRUE); 
    --dont know why you doing this again 
    open c_rec_num; 
    fetch c_rec_num into rec_num; 
    close c_rec_num; 

    UTL_FILE.put_line(log_file, 
        'Total Number of records matching criteria = ' || 
        rec_num, 
        TRUE); 

    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. 

    l_file := UTL_FILE.fopen('DATA_DOWNLOAD_DIR', l_file_name, 'wb', 32767); 

    -- Read chunks of the BLOB and write them to the file until complete. 

    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); 

     -- Set the start position for the next cut. 
     l_pos := l_pos + l_amount; 

    --updating the extract field 

    END LOOP; 

    l_count := l_count + 1; 
    -- Close the file. 
    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); 
     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; 
END; 
/
+0

Я только что пережил это, чтобы попытаться понять изменения. Это не моя сильная сторона, но я думаю, что добираюсь туда ... ведь одна из целей просить о помощи здесь - это учиться. У меня есть вопрос о коде вокруг комментария «не знаю, почему вы делаете это снова». Это выглядит не совсем правильно, потому что оригинал получает общее количество строк и назначает его rec_num перед его выводом, затем присваивает счету «N» той же переменной и выводит ее. Ваш открывающий «c_cnt», который, я думаю, должен быть «c_count», поскольку это то, что определено в верхней части. – hshah

+0

Мне определенно нужно переименовать «cnt», потому что я вижу только одно слово каждый раз, когда я его читаю;) – hshah

+0

Вам удалось взглянуть на это? Я на правильных линиях? – hshah

Смежные вопросы