대량의 DML 작업에대한성능개선방안 엑셈컨설팅본부 /DB 컨설팅팀박준연 개요 대량의데이터를변경해야하는작업은그자체만으로도큰부담으로다가온다. 하지만변경작업자체에만국한되는것이아니라변경되기전데이터와변경이후데이터를각각저장관리해야하는메커니즘이라면성능을개선해야하는입장에서는더욱큰부담일것이다. 말그대로대량의데이터를변경해야하는작업의특성상 SQL Tuning 만으로성능을개선할여지는많지않을뿐더러개선을한다하더라도극적인효과를기대하기는어렵다. 더욱이필연적으로발생하는 redo 와 undo 데이터로인한추가적인부하는성능을개선해야하는입장에서는오라클의합리적인동시성보장메커니즘이오히려원망스럽게느껴질법도하다. 따라서, 이번주제는대량의 DML 을처리할때, 파티션테이블의구성및변경이나 Direct Path Insert 를유도하는방법등을통하여 DML 작업자체의성능개선은물론이고, 시스템의 Down Time 최소화에도기여할수있는방법에대한이야기를하고자한다. 대량의 Delete 월단위혹은특정기간단위의데이터를주기적으로삭제하는업무는어느시스템에서나쉽게 볼수있는작업중하나이다. 익숙한작업이긴하나그작업의성능을개선하기란쉽지않다. 대량의 Delete 작업은 Table Full Scan 이나 Index Scan 등과같은해당테이블을 Access 하는방법에의한비효율보다는필연적으로발생할수밖에없는 redo, undo 데이터생성과더불어해당테이블에인덱스가존재한다면인덱스의개수에따라추가적인 Overhead 가발생하는것이성능저하의주된이유가되기때문이다. 인덱스에서발생하는추가적인 Overhead 란, 삭제대상이되는데이터를인덱스에서도삭제해 야하는것과동시에, 이과정역시 redo 와 undo 데이터가생성됨을의미한다. Part 1 ORACLE 95
위와같은부하를억제하면서대량의 Delete 작업의수행속도를개선하려면어떤방법이있을까? 답은 Partition Table 구성에있다. 주기적으로삭제해야하는기간이정해져있다면, 해당칼럼을 Key 로하는 Range Partition 을 구성하고, 삭제대상이되는기간에해당하는 Partition Table 을 Alter Table [Partition_Name] Drop Partition [Partition_Name] 명령을통해 DML 이아닌 DDL 로작업을대체할수있다. DDL 로의 Delete 작업의대체는, redo 데이터가생성되지않고단순히해당 Partition 을 Drop 하는작업만수행하므로속도의차이는비교할수없을만큼크고, 많은양의 redo / undo 데이 터가발생되지않으므로 Down Time 최소화에도큰기여를할수있다. 아래테스트를통해그차이에대해서상세히알아보자. 테이블생성 DDL (Partition_test) CREATE TABLE partition_test (TIME_ID DATE, DAY_NAME VARCHAR2(9), DAY_NUMBER_IN_WEEK NUMBER(1,0), DAY_NUMBER_IN_MONTH NUMBER(2,0), CALENDAR_WEEK_NUMBER NUMBER(2,0), FISCAL_WEEK_NUMBER NUMBER(2,0), WEEK_ENDING_DAY DATE, WEEK_ENDING_DAY_ID NUMBER, CALENDAR_MONTH_NUMBER NUMBER(2,0), FISCAL_MONTH_NUMBER NUMBER(2,0), CALENDAR_MONTH_DESC VARCHAR2(8), CALENDAR_MONTH_ID NUMBER, FISCAL_MONTH_DESC VARCHAR2(8), FISCAL_MONTH_ID NUMBER, DAYS_IN_CAL_MONTH NUMBER, DAYS_IN_FIS_MONTH NUMBER, END_OF_CAL_MONTH DATE, END_OF_FIS_MONTH DATE, CALENDAR_MONTH_NAME VARCHAR2(9), FISCAL_MONTH_NAME VARCHAR2(9), CALENDAR_QUARTER_DESC CHAR(7), CALENDAR_QUARTER_ID NUMBER, FISCAL_QUARTER_DESC CHAR(7), 96 2013 기술백서 White Paper
FISCAL_QUARTER_ID NUMBER, DAYS_IN_CAL_QUARTER NUMBER, DAYS_IN_FIS_QUARTER NUMBER, END_OF_CAL_QUARTER DATE, END_OF_FIS_QUARTER DATE, CALENDAR_QUARTER_NUMBER NUMBER(1,0), FISCAL_QUARTER_NUMBER NUMBER(1,0), CALENDAR_YEAR NUMBER(4,0), CALENDAR_YEAR_ID NUMBER, FISCAL_YEAR NUMBER(4,0), FISCAL_YEAR_ID NUMBER, DAYS_IN_CAL_YEAR NUMBER, DAYS_IN_FIS_YEAR NUMBER, END_OF_CAL_YEAR DATE, END_OF_FIS_YEAR DATE ) PARTITION BY RANGE( time_id )( PARTITION partition_test1 VALUES less than (to_date('1998-12-31','yyyy-mm-dd')), PARTITION partition_test2 VALUES less than (to_date('1999-12-31','yyyy-mm-dd')), PARTITION partition_test3 VALUES less than (to_date('2000-12-31','yyyy-mm-dd')), PARTITION partition_test4 VALUES less than (to_date('2001-12-31','yyyy-mm-dd')), PARTITION partition_test5 VALUES less than (to_date('2003-12-31','yyyy-mm-dd')) ); 테이블생성 DDL (Partition_test2) CREATE TABLE partition_test2 (TIME_ID DATE, DAY_NAME VARCHAR2(9), DAY_NUMBER_IN_WEEK NUMBER(1,0), DAY_NUMBER_IN_MONTH NUMBER(2,0), CALENDAR_WEEK_NUMBER NUMBER(2,0), FISCAL_WEEK_NUMBER NUMBER(2,0), WEEK_ENDING_DAY DATE, WEEK_ENDING_DAY_ID NUMBER, CALENDAR_MONTH_NUMBER NUMBER(2,0), FISCAL_MONTH_NUMBER NUMBER(2,0), CALENDAR_MONTH_DESC VARCHAR2(8), CALENDAR_MONTH_ID NUMBER, FISCAL_MONTH_DESC VARCHAR2(8), FISCAL_MONTH_ID NUMBER, DAYS_IN_CAL_MONTH NUMBER, DAYS_IN_FIS_MONTH NUMBER, END_OF_CAL_MONTH DATE, Part 1 ORACLE 97
END_OF_FIS_MONTH DATE, CALENDAR_MONTH_NAME VARCHAR2(9), FISCAL_MONTH_NAME VARCHAR2(9), CALENDAR_QUARTER_DESC CHAR(7), CALENDAR_QUARTER_ID NUMBER, FISCAL_QUARTER_DESC CHAR(7), FISCAL_QUARTER_ID NUMBER, DAYS_IN_CAL_QUARTER NUMBER, DAYS_IN_FIS_QUARTER NUMBER, END_OF_CAL_QUARTER DATE, END_OF_FIS_QUARTER DATE, CALENDAR_QUARTER_NUMBER NUMBER(1,0), FISCAL_QUARTER_NUMBER NUMBER(1,0), CALENDAR_YEAR NUMBER(4,0), CALENDAR_YEAR_ID NUMBER, FISCAL_YEAR NUMBER(4,0), FISCAL_YEAR_ID NUMBER, DAYS_IN_CAL_YEAR NUMBER, DAYS_IN_FIS_YEAR NUMBER, END_OF_CAL_YEAR DATE, END_OF_FIS_YEAR DATE ); 데이터입력 ( 두테이블동일 ) INSERT /*+ append */ INTO partition_test SELECT a.* FROM sh.times a, ( SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000 ) b; 일반테이블 (Partition_test2) 의데이터삭제 SQL> delete partition_test2 2 where time_id<= (to_date('2000-12-31','yyyy-mm-dd')); 10960000 행이삭제되었습니다. Execution Plan 98 2013 기술백서 White Paper
---------------------------------------------------------- Plan hash value: 3385385008 -------------------------------------------------------------------------------------- Id Operation Name Rows Bytes Cost (%CPU) Time -------------------------------------------------------------------------------------- 0 DELETE STATEMENT 5718 51462 141K (1) 00:28:15 1 DELETE PARTITION_TEST2 * 2 TABLE ACCESS FULL PARTITION_TEST2 5718 51462 141K (1) 00:28:15 -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("time_id"<=to_date(' 2000-12-31 00:00:00', 'syyyy-mm-ddhh24:mi:ss')) Note ----- - dynamic sampling used for this statement (level=2) Statistics ---------------------------------------------------------- 5516 recursive calls 13289330 db block gets 521869 consistent gets 520081 physical reads 5831072716 redo size 565 bytes sent via SQL*Net to client 544 bytes received via SQL*Net from client 3 SQL*Net roundtrips to/from client 7 sorts (memory) 0 sorts (disk) 10960000 rows processed 파티션테이블 (Partition_test) 의데이터삭제 SQL> alter table partition_test drop partition partition_test1; 테이블이변경되었습니다. SQL> alter table partition_test drop partition partition_test2; 테이블이변경되었습니다. SQL> alter table partition_test drop partition partition_test3; Part 1 ORACLE 99
테이블이변경되었습니다. 대량의 INSERT 대량의데이터를 Insert 하는작업은위에서소개한 Delete 작업에비해성능개선의여지가많은편이다. 이는데이터입력대상테이블의속성변경 (Nologging) 과힌트 (/*+ append */) 만으로 Direct Path Insert 를유도하여 redo 발생을억제하는방법이있기때문이다. 하지만여기에또다른암초가존재하는데, 이는인덱스의존재다. 이는대량의 Insert 작업시데이터가테이블에입력되는작업보다도인덱스의개수와인덱스의 성격에따라성능이좌우된다고해도무리가아니기때문이다. Direct Path Insert 가가능한환경에서두가지경우에대한대량의 Insert 작업의성능차이와 이를개선하는방법에대해서알아보고자한다. 인덱스의개수에따른 Direct Path Insert 성능차이 Insert 대상이되는테이블에인덱스가다수생성되어있다면그만큼의 redo 발생량이뒤따를 것이므로자연스레 Insert 작업이더뎌질것이다. 아래테스트를통해인덱스개수에따라 Direct Path Insert 작업의속도에어느정도영향을미 치는지알아보자. 테이블생성 DDL 생략 ( 위대량의 Delete 의 Partition_test 테이블 DDL 참조 ) 세개의인덱스가존재하는경우의대량의 Insert SQL> alter table partition_testnologging; 테이블이변경되었습니다. SQL> INSERT /*+ append */ 100 2013 기술백서 White Paper
2 INTO partition_test 3 SELECT a.* 4 FROM sh.times a, 5 ( 6 SELECT LEVEL 7 FROM dual 8 CONNECT BY LEVEL <= 10000 9 ) b; 18260000 개의행이만들어졌습니다. 경과 : 00:08:50.77 Statistics ---------------------------------------------------------- 37399 recursive calls 4600992 db block gets 323059 consistent gets 493284 physical reads 5177833936 redo size 564 bytes sent via SQL*Net to client 636 bytes received via SQL*Net from client 3 SQL*Net roundtrips to/from client 3 sorts (memory) 3 sorts (disk) 18260000 rows processed 기존의인덱스를삭제하고 Insert 이후인덱스생성 SQL> truncate table partition_test; 테이블이잘렸습니다. 경과 : 00:00:00.74 SQL> drop index idx_test1; 인덱스가삭제되었습니다. 경과 : 00:00:00.09 SQL> drop index idx_test2; 인덱스가삭제되었습니다. 경과 : 00:00:00.03 Part 1 ORACLE 101
SQL> drop index idx_test3; 인덱스가삭제되었습니다. 경과 : 00:00:00.03 SQL> INSERT /*+ append */ 2 INTO partition_test 3 SELECT a.* 4 FROM sh.times a, 5 ( 6 SELECT LEVEL 7 FROM dual 8 CONNECT BY LEVEL <= 10000 9 ) b; 18260000 개의행이만들어졌습니다. 경과 : 00:00:24.49 Statistics ---------------------------------------------------------- 13820 recursive calls 532991 db block gets 4210 consistent gets 7 physical reads 1975520 redo size 566 bytes sent via SQL*Net to client 636 bytes received via SQL*Net from client 3 SQL*Net roundtrips to/from client 20 sorts (memory) 0 sorts (disk) 18260000 rows processed SQL> create index idx_test1 on partition_test(time_id, day_name) local; 인덱스가생성되었습니다. 경과 : 00:00:26.87 SQL> create index idx_test2 on partition_test(day_number_in_week,day_number_in_month) local; 인덱스가생성되었습니다. 경과 : 00:00:27.42 SQL> create index idx_test3 on partition_test(week_ending_day,week_ending_day_id) 102 2013 기술백서 White Paper
local; 인덱스가생성되었습니다. 경과 : 00:00:26.72 위테스트결과와같이대량의데이터가입력될때, 해당테이블의인덱스가얼마나많이존재하는지에따라성능에큰영향을주는것으로나타났다. 이는단순히대량의데이터입력시에인덱스의존재에따른성능차이를보여주는테스트이므로대량의데이터가입력되는모든작업에인덱스를 Drop 하거나 Unusable 상태로변경하고작업하라는의미는아니다. 다만, 온라인사용자가없는시간대의배치작업이라면충분히고려할수있는방법이라고생각 한다. 수행속도는물론이거니와 redo 발생의최소화에도큰기여를할수있는장점이있기때문 이다. 빈번한 Index Split에따른 Direct Path Insert 성능차이항상증가하는값을 Insert 해야하는성격의칼럼이존재할수있다. 대표적인예는날자칼럼에 SYSDATE 가입력되는상황일것이다. 만약, 이칼럼에 Index 가생성되어있다고가정하고, 대량의 Insert 작업이수행된다면 9:1 Index Split 이빈번하게발생하는현상이자연스레나타날것이다. 앞서언급했듯이, Index Split 은결코가볍지만은않다. 왜가볍지않은지그과정을살펴보자. 우선, 9:1 Split 이란 Max 값이입력되는상황에서우측최하위 Index Leaf Block 에더이상저장할공간이없는경우 Free List 의 Free Block 을가지고와서기존최하위 Index Leaf Block 의최소한의데이터를새로운 Free Block 에옮겨담고새로저장되는데이터를해당 Block 에입력하는과정을말한다. 이는데이터가증가하는과정에서필연적으로 Index Size 가증가하며, Index Split 은이과정 에서발생하는자연스러운현상이다. 하지만, Index Split 과정은공짜가아니다. Part 1 ORACLE 103
Index Split 의전과정은 redo 가발생하고 Split 이시작되고완료되는시점까지 Locking 을통 해이작업을보호한다. 이 Locking 은, Enq : TX Index Contention 이라는 Wait Event 로 Split 이완료되는시점까지 Split 과정을보호하게된다. 따라서, 항상증가하는성격의칼럼에인덱스가생성되어있고해당테이블에대량의데이터가 입력되는경우, Index Split 에의한 Wait Time 증가와더불어 redo 발생등으로 Insert 속도의 저하는필연적으로발생한다. 그렇다면이를개선할수있는방안이있을까? 결론부터이야기하면문제가되는 Index 에다른 칼럼을추가하여재생성하는방법이존재한다. 이방법의목적은대량의데이터가입력될때, 기존우측최하위 Index Leaf Block 에경합이집중되던것이결합인덱스로인하여그경합이어느정도분산될것이기때문이다. 새로결합할 Index 구성대상칼럼의 NDV(Number of Distinct Value) 값이클수록 Index Split 감소의효과는더욱커질것이다. 다만, 이부분에서주의할것은 Index Clustering Factor 가크게감소할수있으므로 NDV 값이 큰칼럼을우선순위로구성할필요는없다. Array Processing 대량의 DML 작업은데이터를 Load 하거나 Update(Delete) 할때일반적으로 LOOP 문을사용하여건건이처리하는경우가많다. 이방법은매 Loop 마다 1 회씩 DML 작업이수행되어그만큼의 DBMS CALL 이발생하기때문에성능상불리하다. 하지만 Array Processing 을이용한 Bulk SQL 을사용하면 Loop 없이단한번의 SQL 수행만으로처리가가능하다. 즉, 대량의 DML 을단한번 (Limit 예약어를사용하여한번에처리할수있는건수를제한하지않는경우 ) 에처리할수있으므로 DBMS CALL 을감소시켜 Loop 로처리되던방법에비해큰성능개선효과를볼수있다. 아래테스트를통해그성능차이를비교해보자. 104 2013 기술백서 White Paper
Array Processing 을활용하여 Insert 하는 Procedure Source - P_BULK_INSERT_TEST1 CREATE OR REPLACE PROCEDURE P_BULK_INSERT_TEST1 IS CURSOR sales_cur IS SELECT PROD_ID, CUST_ID, TIME_ID, CHANNEL_ID, PROMO_ID, QUANTITY_SOLD, AMOUNT_SOLD FROM SH.SALES; TYPE SALES_TBL_TYPE IS TABLE OF sales_cur%rowtype INDEX BY BINARY_INTEGER; SALES_TBL SALES_TBL_TYPE; BEGIN OPEN sales_cur; LOOP FETCH sales_cur BULK COLLECT INTO sales_tbl LIMIT 1000; -- PGA 소모를작게유지하기위해 1000 건씩만처리 FOR i IN sales_tbl.first..sales_tbl.last LOOP --각종계산은여기에서 sales_tbl(i).amount_sold := sales_tbl(i).amount_sold * 1.5; END LOOP; -- FETCH 된 1000 건을 SQL 1 회수행으로처리 FORALL i IN sales_tbl.first..sales_tbl.last INSERT INTO SALES2 VALUES (sales_tbl(i).prod_id, sales_tbl(i).cust_id, sales_tbl(i).time_id, sales_tbl(i).channel_id, sales_tbl(i).promo_id, sales_tbl(i).quantity_sold, sales_tbl(i).amount_sold ); EXIT WHEN sales_cur%notfound; END LOOP; CLOSE sales_cur; COMMIT; EXCEPTION Part 1 ORACLE 105
WHEN OTHERS THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20001,'RAISE_APPLICATION_ERROR : ' SQLCODE ':' SUBSTR(SQLERRM,1,200)); END; / 건건이 Loop 를통해 Insert 하는 Procedure Source - P_BULK_INSERT_TEST2 CREATE OR REPLACE PROCEDURE P_BULK_INSERT_TEST2 IS CURSOR sales_cur IS SELECT PROD_ID, CUST_ID, TIME_ID, CHANNEL_ID, PROMO_ID, QUANTITY_SOLD, AMOUNT_SOLD FROM SH.SALES; TYPE SALES_TBL_TYPE IS TABLE OF sales_cur%rowtype INDEX BY BINARY_INTEGER; SALES_TBL SALES_TBL_TYPE; BEGIN OPEN sales_cur; LOOP FETCH sales_cur BULK COLLECT INTO sales_tbl LIMIT 1000; -- PGA 소모를작게유지하기위해 1000 건씩만처리 FOR i IN sales_tbl.first..sales_tbl.last LOOP --각종계산은여기에서 sales_tbl(i).amount_sold := sales_tbl(i).amount_sold * 1.5; END LOOP; -- LOOP 를사용하는방법 ; INSERT SQL 1000 번수행 FOR i IN sales_tbl.first..sales_tbl.last LOOP -- FORALL 예약어배제 INSERT INTO SALES2 VALUES (sales_tbl(i).prod_id, sales_tbl(i).cust_id, sales_tbl(i).time_id, sales_tbl(i).channel_id, sales_tbl(i).promo_id, sales_tbl(i).quantity_sold, sales_tbl(i).amount_sold ); 106 2013 기술백서 White Paper
END LOOP; EXIT WHEN sales_cur%notfound; END LOOP; CLOSE sales_cur; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20001,'RAISE_APPLICATION_ERROR : ' SQLCODE ':' SUBSTR(SQLERRM,1,200)); END; / P_BULK_INSERT_TEST2 Vs. P_BULK_INSERT_TEST2 SQL> exec P_BULK_INSERT_TEST1; PL/SQL 처리가정상적으로완료되었습니다. 경과 : 00:00:02.20 SQL> SQL> SQL> exec P_BULK_INSERT_TEST2; PL/SQL 처리가정상적으로완료되었습니다. 경과 : 00:00:35.08 참고 : Array Processing 을활용한 Update Procedure Source CREATE OR REPLACE PROCEDURE P_BULK_INSERT_TEST2 IS CURSOR sales_cur IS SELECT PROD_ID, CUST_ID, TIME_ID, CHANNEL_ID, PROMO_ID, QUANTITY_SOLD, AMOUNT_SOLD FROM SH.SALES; Part 1 ORACLE 107
TYPE SALES_TBL_TYPE IS TABLE OF sales_cur%rowtype INDEX BY BINARY_INTEGER; SALES_TBL SALES_TBL_TYPE; BEGIN OPEN sales_cur; LOOP FETCH sales_cur BULK COLLECT INTO sales_tbl LIMIT 1000; -- PGA 소모를작게유지하기위해 1000 건씩만처리 FOR i IN sales_tbl.first..sales_tbl.last LOOP --각종계산은여기에서 sales_tbl(i).amount_sold := sales_tbl(i).amount_sold * 1.5; END LOOP; -- UPDATE / DELETE 도가능 FORALL i IN sales_tbl.first..sales_tbl.last UPDATE SALES SET AMOUNT_SOLD = sales_tbl(i).amount_sold WHERE PROD_ID = sales_tbl(i).prod_id AND CUST_ID = sales_tbl(i).cust_id AND TIME_ID = sales_tbl(i).time_id AND CHANNEL_ID = sales_tbl(i).channel_id AND PROMO_ID = sales_tbl(i).promo_id; EXIT WHEN sales_cur%notfound; END LOOP; CLOSE sales_cur; COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20001,'RAISE_APPLICATION_ERROR : ' SQLCODE ':' SUBSTR(SQLERRM,1,200)); END; / 108 2013 기술백서 White Paper
또다른난관 앞서소개한방법들의적용이가능하다면대량의 DML 작업에대한성능개선 Process 를가져갈 수있을것이다. 그러나또하나의극복해야할난관이아직기다리고있다. 이는 Partition Table 로의변경이필요한부분에해당하는것으로방대한양의데이터를저장하 고있는기존테이블을 Partition Table 로변경하는작업이만만치않다는것이다. 일정시간 Offline 이가능하다고하면변경작업을시도해볼수도있겠으나, 항상서비스가되어 야하는환경에서 Partition Table 로변경해야한다고권고한다면, 원론적으로는옳다고할수 있을지라도실무를담당하고있는 DBA 에게는큰당혹감만을안겨주는결과를낳을수있다. 하지만이어려워보이는문제도극복할수있는방법이있다. 이는바로 Partition Exchange( 이하 PE) 기능을이용하는것이다. PE 기능은말그대로일반테이블을 Partition Table 로의변경이가능함을의미한다. 또한, 추가적인공간도필요없어상당히효율적인방법으로 Partition Table 로의전환을이룰수있다. 아래테스트는 PE 기능을활용하여일반테이블을 Partition Table 로의손쉬운전환이가능함 을증명해준다. 기존에사용하던일반테이블 : Partition_test2 : Delete 테스트의테이블생성스크립트와데이터입력스크립트참조 Partition Table 로전환할 Partition Table 생성 CREATE TABLE partition_test (TIME_ID DATE, DAY_NAME VARCHAR2(9), DAY_NUMBER_IN_WEEK NUMBER(1,0), DAY_NUMBER_IN_MONTH NUMBER(2,0), CALENDAR_WEEK_NUMBER NUMBER(2,0), FISCAL_WEEK_NUMBER NUMBER(2,0), WEEK_ENDING_DAY DATE, WEEK_ENDING_DAY_ID NUMBER, CALENDAR_MONTH_NUMBER NUMBER(2,0), FISCAL_MONTH_NUMBER NUMBER(2,0), CALENDAR_MONTH_DESC VARCHAR2(8), Part 1 ORACLE 109
CALENDAR_MONTH_ID NUMBER, FISCAL_MONTH_DESC VARCHAR2(8), FISCAL_MONTH_ID NUMBER, DAYS_IN_CAL_MONTH NUMBER, DAYS_IN_FIS_MONTH NUMBER, END_OF_CAL_MONTH DATE, END_OF_FIS_MONTH DATE, CALENDAR_MONTH_NAME VARCHAR2(9), FISCAL_MONTH_NAME VARCHAR2(9), CALENDAR_QUARTER_DESC CHAR(7), CALENDAR_QUARTER_ID NUMBER, FISCAL_QUARTER_DESC CHAR(7), FISCAL_QUARTER_ID NUMBER, DAYS_IN_CAL_QUARTER NUMBER, DAYS_IN_FIS_QUARTER NUMBER, END_OF_CAL_QUARTER DATE, END_OF_FIS_QUARTER DATE, CALENDAR_QUARTER_NUMBER NUMBER(1,0), FISCAL_QUARTER_NUMBER NUMBER(1,0), CALENDAR_YEAR NUMBER(4,0), CALENDAR_YEAR_ID NUMBER, FISCAL_YEAR NUMBER(4,0), FISCAL_YEAR_ID NUMBER, DAYS_IN_CAL_YEAR NUMBER, DAYS_IN_FIS_YEAR NUMBER, END_OF_CAL_YEAR DATE, END_OF_FIS_YEAR DATE ) PARTITION BY RANGE( time_id )( PARTITION part1 VALUES less than (to_date('2002-12-31','yyyy-mm-dd')) ); -- Partiton Table 로전환될테이블의 Max 값을입력 Partition Exchange 수행 SQL> ALTER TABLE partition_test exchange partition part1 WITH TABLE partition_test2 without validation ; 테이블이변경되었습니다. 경과 : 00:00:00.03 기존테이블삭제후 Rename 110 2013 기술백서 White Paper
SQL> DROP TABLE partition_test2; 테이블이삭제되었습니다. 경과 : 00:00:00.06 SQL> RENAME partition_test to partition_test2; 테이블이름이변경되었습니다. 경과 : 00:00:00.03 위와같이수행하면기존의테이블은간단히 Partition Table 로변경할수있음을알수있다. 다 만주의할점은기존일반테이블에서가지고있던제약조건 (Primary Key) 이나 Index 구성은 모두동일하게생성하여야정상적인 PE 수행이가능하다. 결론 앞서소개한대량의 DML 작업의성능을개선하는방법은테이블의구조자체를변경하거나발생가능한 redo 의양을최대한감소시키는방법그리고 Index 의구조변경과 Drop 등으로정리할수있다. 하지만이러한방법은자칫실무를담당하는 DBA 에게는그저뜬구름잡는이야기에불과할수있다. 머리로는충분히이해하고공감할수있는좋은방법을제시하였다하더라도현실에적용할수없다면그공허함은더큰무게로다가올것이다. 하지만위에소개한내용들은실질적으로적용할수있고또한적용한사례가있다. 물론업무의 성격에따라수학공식처럼항상같은방식으로대입하여적용할수있는것은아니지만, 그환 경에맞는솔루션은항상존재한다고믿는다. 비현실적이라고치부해버리는순간성능개선의여지는요원해진다. 위의내용들의핵심을잘정 리하고있다면해당하는상황에대해여러가지성능개선아이디어를제시할수있고, 그중가장 손쉽게적용할수있는방법을협의를통해찾을수있을것이다. 대량의 DML 작업성능개선과시스템의 Down Time 최소화라는두마리토끼를잡는것은것은 어렵지만은않다. Part 1 ORACLE 111