프로시저 FOR LOOP문 질문드립니다. 0 13 4,887

by 쿼리어려워 [PL/SQL] ORACLE PL/SQL PROCEDURE [2022.10.24 13:57:52]


안녕하세요 

혼자서 이것저것 찾아보다가 진전이 없어 질문드립니다...

운영 데이터와 로컬 데이터를 동기화 하기위한 프로시저를 짜서 실행을 보았었는데 한시간반이나 돌아가다가 너무 오래걸리는것 같아서 중단 시킨경우가 있엇습니다...

그 때 롤백이 되면서도 1시간반이라는 시간이 걸렸었습니다.

테이블도 30개인데 대부분 그렇게 크기가 크지 않은 테이블이 대다수입니다. 

보통 쿼리로 직접 하나하나 해보면 한시간까지도 안걸리고 전체 다 하는데 20분정도 걸렸었습니다...

혼자서 해결해보려고 인터넷도 찾아봤는데도 도저히 어느부분이 잘못된건지 모르겠어서 질문드립니다...

앞서 설명을 드리자면 30개의 운영테이블의 데이터를 백업하고  백업한 테이블의 데이터를 가지고 로컬테이블에 넣어서 데이터 동기화를 시키려고 합니다.   


순서는 이러합니다.

1.  30개의 운영테이블의 데이터를 DB링크와 LOOP문을 사용하여 CREATE TABLE 구문으로 로컬에 백업테이블을 생성(데이터 전체를 백업)

2. 동기화할 로컬의 테이블을 DELETE를 통해 데이터를 지운 후 INSERT로 백업테이블 데이터를 삽입

1번과 2번의 프로시저를 나눠서 만들었고 두개의 프로시저가 완성되면 후엔 추가적으로 조건문을 달아 필요한 월별 데이터만 가져오는 프로시저를 만들예정입니다...

30개의 테이블명이 각각 다르기 때문에 FOR LOOP문을 돌릴 때 TEMP_SYN_COPY테이블의 TABLE_NM컬럼에 각각의 테이블명을 저장해두었습니다.

또한 각각의 테이블명을 가져오기 위한부분에서 WHERE GROUP_NM = 'LTV' 부분은 필요한 테이블만 걸러내기 위해 사용되었습니다.

테이블명의 길이제한이 30인데 백업테이블을 생성시 테이블 명 뒤에 테이블명_221024 같이 붙게 되는데 본래 테이블의 길이가 길면 테이블명_2210 이런식으로

만들기 위해 IF문으로 분기를 태웠습니다. 

아래에는 프로시저 부분입니다.

1번 CREATE 부분입니다.
 

CREATE OR REPLACE PROCEDURE MVP_MNG.PROC_SYN_COPY_ALL_1(
	P_DATE IN VARCHAR2
	
)	
IS
	V_SQL VARCHAR(500);
	V_CNT NUMBER(1);
	V_TABLE_NM VARCHAR(30);
	V_TABLE_SUB_NM VARCHAR(7);
	V_TABLE_GROUP_NM VARCHAR(30);

	USER_DEFINE_EXCEPTION EXCEPTION; 
BEGIN
DBMS_OUTPUT.PUT_LINE('운영 테이블 DB 전체 복사');

--백업테이블 중복명칭 존재하는지 체크
FOR TABLE_LIST IN (
	SELECT TABLE_NM
	FROM TEMP_SYN_COPY
	WHERE TABLE_GROUP ='LTV'
	)
LOOP 
	-- 테이블 명칭을 내부변수에 넣어서 사용
	V_TABLE_NM	:= TABLE_LIST.TABLE_NM;
	
	-- 테이블명 길이제한에 의한 IF문
	IF LENGTH(V_TABLE_NM) > 23 THEN 
		V_TABLE_SUB_NM := '_' || TO_CHAR(SYSDATE,'YYMM');
	ELSE
		V_TABLE_SUB_NM := '_' || P_DATE;
	END IF;

	SELECT COUNT(1) INTO V_CNT FROM ALL_TABLES WHERE TABLE_NAME = V_TABLE_NM || V_TABLE_SUB_NM;
	--백업테이블 중복명칭이 존재했을 시
	IF V_CNT > 0 THEN
		RAISE USER_DEFINE_EXCEPTION;
	END IF;

END LOOP;


-- CREATE TABLE LOOP
FOR TABLE_LIST IN (
	SELECT	TABLE_NM
			, GROUP_NM 
	FROM TEMP_SYN_COPY
	WHERE TABLE_GROUP ='LTV'
	)
LOOP 
	-- 테이블 명칭, 그룹명을 내부변수에 넣어서 사용
	V_TABLE_NM := TABLE_LIST.TABLE_NM;
	V_TABLE_GROUP_NM := TABLE_LIST.GROUP_NM;

	-- 테이블명 길이제한에 의한 IF문
	IF LENGTH(V_TABLE_NM) > 23 THEN 
		V_TABLE_SUB_NM := '_' || TO_CHAR(SYSDATE,'YYMM');
	ELSE
		V_TABLE_SUB_NM := '_' || P_DATE;
	END IF;
	
	--CREATE 구문 
	V_SQL	:=	'CREATE TABLE '|| V_TABLE_NM || V_TABLE_SUB_NM  || ' AS SELECT * FROM MVP_MNG.'|| V_TABLE_NM ||'@DL_MVP_LIVE';
	EXECUTE IMMEDIATE V_SQL;

	DBMS_OUTPUT.PUT_LINE(V_TABLE_GROUP_NM || ' ' || V_TABLE_NM || V_TABLE_SUB_NM || ' 복사 완료');

END LOOP;

DBMS_OUTPUT.PUT_LINE('테이블 생성 완료');
COMMIT;


EXCEPTION
WHEN USER_DEFINE_EXCEPTION THEN
	DBMS_OUTPUT.PUT_LINE(V_TABLE_NM || '의 백업테이블이 존재합니다.');
WHEN OTHERS THEN
	ROLLBACK;
	DBMS_OUTPUT.PUT_LINE(V_TABLE_NM || ' 테이블 복사중 실패하였습니다.');

END PROC_SYN_COPY_ALL_1;

 

2번의 DELETE&INSERT 부분입니다.

 

CREATE OR REPLACE PROCEDURE MVP_MNG.PROC_SYN_MIG_ALL_2(
	P_DATE IN VARCHAR2
)	
IS
	V_SQL VARCHAR(500);
	V_TABLE_NM VARCHAR(30);
	V_TABLE_SUB_NM VARCHAR(7);
	V_TABLE_GROUP_NM VARCHAR(30);
BEGIN
	DBMS_OUTPUT.PUT_LINE('운영 백업 테이블 => 개발기 테이블 DB 전체 이전');
	
	FOR TABLE_LIST IN (
		SELECT	TABLE_NM
				, GROUP_NM
		FROM TEMP_SYN_COPY
		WHERE TABLE_GROUP = 'LTV'
		)
	LOOP
		-- 테이블 명칭, 그룹명을 내부변수에 넣어서 사용
		V_TABLE_NM	:= TABLE_LIST.TABLE_NM;
		V_TABLE_GROUP_NM := TABLE_LIST.GROUP_NM;
	
		--개발기 테이블 DELETE
		V_SQL	:= 'DELETE FROM MVP_MNG.'|| V_TABLE_NM || ' WHERE 1=1';
		EXECUTE IMMEDIATE V_SQL;
		
		-- 테이블명 길이제한에 의한 IF문
		IF LENGTH(V_TABLE_NM) > 23 THEN 
			V_TABLE_SUB_NM := '_' || TO_CHAR(SYSDATE,'YYMM');
		ELSE
			V_TABLE_SUB_NM := '_' || P_DATE;
		END IF;
	
		--운영백업 테이블 => 개발기 테이블 INSERT
		V_SQL	:=	'INSERT INTO MVP_MNG.' || V_TABLE_NM || ' SELECT * FROM ' || V_TABLE_NM || V_TABLE_SUB_NM;
	
		EXECUTE IMMEDIATE V_SQL;
		DBMS_OUTPUT.PUT_LINE(V_TABLE_GROUP_NM || ' ' || V_TABLE_NM || V_TABLE_SUB_NM || ' INSERT 완료');
	
	END LOOP;
	
	COMMIT;
	DBMS_OUTPUT.PUT_LINE('운영 백업 테이블 => 개발기 테이블 DB 전체 이전 완료');
	
	EXCEPTION
	WHEN OTHERS THEN 
	ROLLBACK;
	DBMS_OUTPUT.PUT_LINE(V_TABLE_NM || V_TABLE_SUB_NM || '데이터 이전중 오류가 발생하였습니다.');

END PROC_SYN_MIG_ALL_2;

이상입니다.

제가 짠 프로시저 부분에서 잘못된 부분이 있는데 어디인지 잘모르겠어서 질문드려요...

잘부탁드리겠습니다ㅜㅜ

by 마농 [2022.10.24 17:11:03]

Delete 보다는 Truncate 가 좋을 듯 합니다.


by 쿼리어려워 [2022.10.25 11:19:17]

 

답변감사합니다!! 이 프로시저뿐만 아니라 추 후에 조건에 해당하는 월별만 동기화시키는 프로시저와 합칠 예정이라 TRUCATE 말고 DELETE를 사용했습니다! 혹시 제가 잘못알고 있는거라면 고치겠습니다!! 마농님이 보셨을 때 다른 문제는 없어 보이시나요??


by 쿼리어려워 [2022.10.25 14:25:18]

현재 계속 찾아보고 있는데 혹시 savepoint와 관계가 있을까요? 선배님들 시간 괜찮으시면 도움좀 주시면 감사하겠습니다ㅠㅠ


by 포동푸우 [2022.10.25 17:11:44]
--  select 및 insert 하는 부분을 병렬로 처리 하시면 어떤가요 ? 

-- PROC_SYN_MIG_ALL_2 프로시저의 선언부에 
PARALLEL_ENABLE 
-- PROC_SYN_MIG_ALL_2 프로시저의 실행부에 
insert /*+ prallel ( insert_table 4 ) */ ........... select /*+ prallel ( insert_table 4 ) */ .......

 


by 포동푸우 [2022.10.25 17:13:42]

오타입니다

select /*+ prallel ( select_table 4 ) */ .......

 


by 쿼리어려워 [2022.10.25 17:52:55]

포동푸우님 답변해주셔서 감사합니다 ㅠㅠ LOOP문에서 병렬처리하는 방법을 인터넷에 찾아봤지만 나오질 않아서 안된다고 생각하고 있었습니다..  알려주신 방법으로 시도해보겠습니다 감사합니다!! 복받으세요!!


by 우리집아찌 [2022.10.26 13:27:59]

월별 관리면 partitioning table 사용하는것도 방법일것같습니다.

또 oracle 19c 부터는 오브젝트명이 30byte 이상도 됩니다.


by 쿼리어려워 [2022.10.26 13:57:39]

우리집아찌님 답변 정말 감사합니다!! 제가 partitoning table에 대해서 처음 듣기도 하고 추후에 월별관리할 때 사용해보려고 찾아봤습니다만 제가 루프문으로 돌린 30개의 테이블중에는 월별기준이 되는 컬럼이 있는 테이블도 있고 없는 테이블도 있기도 하고 또한 각각의 테이블 데이터의 양이 그리 크지도 않아서 사용하기 애매한것같습니다... 후에 사용할만한 일이 있을 때 우리집아찌님이 알려주신 partitioning table을 꼭 기억해서 사용하겠습니다~~다시 한번 감사드립니당 혹시 그 부분말고는 우리집아찌님이 보셨을 때 무언가 이상한 부분이 없을까요? 


by 우리집아찌 [2022.10.27 09:18:10]

파티션은 성능개선 보다는 관리를 위한 목적이 더강합니다. 

월별 관리면 해당 파티션만 truncate 되니까 좀 더 시간이 절약됩니다.

insert 시 table에 nologging 처리후 append hint도 도움이 됩니다.


by 쿼리어려워 [2022.10.28 10:03:38]

아.. 감사합니다.. 파티션에 대해 더 공부해보겠습니다!! insert시 append hint도 추가했습니다! 부족한 부분을 채워주셔서 다시 한번  감사합니다ㅠㅠ


by 포동푸우 [2022.10.27 02:30:28]
-- 아마도 마농 님 말씀처럼, Delete 일량을 줄이는 것이 최우선이 되어야 할 것 같습니다. 
-- 아찌님 이야기 하신 partition 처리가 가능할 것 같네요
  
-- 프로시저 수행 Trace 를 분석해,  개선방향을 도출해 볼 수 있습니다.  
-- 아래 참조 하세요 

-- 1. 아래는 커서를 오픈하여 데이터를 건바이건 으로 INSERT한 후 바로 커밋을 수행하는 프로그램 예시입니다.  < 프로젝트에서 강한 오라클 PL/SQL 프로그래밍, 장정수 저, 비팬북스, 에서 인용 > 

DECLARE
	l_time1 NUMBER;
	l_time2 NUMBER;
	l_trace_file_name VARCHAR2(200);
	-- 커서 정의
	CURSOR C1 IS (SELECT *
					FROM SALES
					WHERE ROWNUM <= 100000
	);
BEGIN
	DBMS_OUTPUT.ENABLE (buffer_size => NULL);
	SELECT test_pkg.trace_file_name_func
	  INTO l_trace_file_name
	  FROM DUAL;
	l_time1 := DBMS_UTILITY.GET_TIME;
	test_pkg.trace_on_proc(10046);
	-- 건건이 읽어 INSERT 후 COMMIT 수행
	FOR REC IN C1 LOOP
		INSERT INTO SALES_TEMP VALUES REC;
		COMMIT;
	END LOOP;
	l_time2 := DBMS_UTILITY.GET_TIME;
	DBMS_OUTPUT.PUT_LINE(‘TRACE FILE : ’ || l_trace_file_name);
	DBMS_OUTPUT.PUT_LINE(‘EXECUTION TIME : ’ || TO_CHAR(l_time2 - l_time1)/100);
END ;
/

TRACE FILE : c:\oracle\diag\rdbms\test\test\trace/test_ora_7564.trc  <-- Trace 파일명 
EXECUTION TIME : 89.06  <-- 실행시간

-- 2. 위 trace 파일을 분석해서,,  실행과정과 일량, 소요시간 등을 확인할 수 있습니다. 

tkprof sort=fchela, exeela sys=no aggregate=yes trace={경로}/test_ora_7564.trc output= test_ora_7564.txt  

~~> tkprof 로 trace 분석 하는 방법은, DB구르비에 잘 정리되어 있습니다. 

--
--

 


by 포동푸우 [2022.10.27 02:34:05]

좌측 "구루비" 검색에 tkprof 조회 하시면, 많은 자료를 확인할 수 있습니다. 

SQL Trace와 TKPROF 유틸리티
  - http://www.gurubee.net/lecture/1842 

10046 Trace 사용 방법
  - http://www.gurubee.net/lecture/2130 

TKPROF
  - http://wiki.gurubee.net/pages/viewpage.action?pageId=1507504 
 


by 쿼리어려워 [2022.10.28 10:07:48]

포동푸우님 답변 감사합니다!! 아직 배운지 얼마 되지 않아 trace라는 개념을 잘 모르겠습니다.. 주말에 제대로 공부해보겠습니다!! 견문을 넓혀주셔서 감사합니다 포동푸우님~

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입