Oracle/DB2016. 8. 9. 14:13
728x90

1. dumper.c 수정하기
dumper.c 중에서

#include <time.h>

/* Oracle Type definitions, such as TRUE, FALSE sb1 etc. */
#include "/ORACLE/INF/app/oracle/product/9.2.0/rdbms/demo/oratypes.h"

/* LDA and CDA struct declarations */
#include "/ORACLE/INF/app/oracle/product/9.2.0/rdbms/demo/ocidfn.h"

/* ANSI-style function declarations/prototypes */
#include "/ORACLE/INF/app/oracle/product/9.2.0/rdbms/demo/ocikpr.h"

* ORACLE_HOME 을 적절하게 수정한다.


2. dumper 실행 파일 만들기

[sysdba01@infodb1 /EXP/SAM/SAM/VER]$ cat  dumper.mk.32bit.ibm
#export OBJECT_MODE=32 (for aix)
export LD_LIBRARY_PATH=/ORACLE/TST/app/oracle/product/9.2.0/lib:/ORACLE/TST/app/oracle/product/9.2.0/lib32:/usr/lib
#SQLLIB=" -lclntsh -lcommon -lcore4 -lnlsrtl3 "
cc -I$ORACLE_HOME/precomp/public -I$ORACLE_HOME/rdbms/demo -L$ORACLE_HOME/lib32 -lclntsh -lclntst9 -lpthreads -lodm  dumper.c -o dumper
#cc -I$ORACLE_HOME/precomp/public -I$ORACLE_HOME/rdbms/demo $SQLLIB -lpthread dumper.c -o dumper


3. 소스 테이블 선정하기

[sysdba01@infodb1 /EXP/SAM/SAM]$ cat target_tbl
TBFKK001
TBFKD002

4. owner 선정하기

[sysdba01@infodb1 /EXP/SAM/SAM]$ more  gen_ctl_main.sh
# *****************************************************************************
# Gen DDL :  Generate Shell Control File and Main Shell Script Using target_tbl
# *****************************************************************************

# -----------------------------------------------------------------------------
# >>>>>>>>>>>>> You Must Editing Shell Variable Below for Your Site <<<<<<<<<<<
# -----------------------------------------------------------------------------

SH_TARGET_TAB_FILE=./target_tbl              # Describe Table List File to Send

SH_SAMFILE_PATH=./CSV

# SH_SAMFILE_PATH=`pwd`
# SH_SAMFILE_PATH=/ettfs01/csv_qm

SH_DB_OWNER=DONGBU


SH_DB_USER=SVRCON
SH_DB_PASSWD=SVRCON

5. gen_ctl_main.sh 실행하여 dumper_YYYYMMDD.HHMM.sh 만들기
# sh gen_ctl_main.sh

6. dumper_YYYYMMDD.HHMM.sh  실행하기

* delimiter 는 dumper.c 중에 COMMA 부분으로 적절하게 수정함

'Oracle > DB' 카테고리의 다른 글

[Database] Oracle Wait Event 모니터링  (0) 2016.08.09
[SQL] Oracle Built-in Function  (0) 2016.08.09
[Database] Session Level에서 Trace Level 주기  (0) 2016.08.09
[Tuning] Usage tkprof  (0) 2016.08.09
[Database] RMAN Command  (0) 2016.08.09
Posted by swanseo
Oracle/DB2016. 8. 9. 14:13
728x90
Oracle Wait Event 모니터링
글: 안진철 (jcahn@warevalley.com)
現 ㈜웨어밸리 책임 컨설턴트
前 LG-EDS 기술 연구부문 DB 팀
- 솔루션 컨설팅 경력 6년

연재 순서
[1] Oracle Wait Event 모니터링 (2003년 1월 28일)
[2] Enqueue와 Latch (2003년 2월 12일)
[3] Shared Pool 관련 Wait Event (2003년 2월 26일)
[4] buffer cache 관련 Wait Event (2003년 3월 12일)
[5] redo log 관련 Wait Event (2003년 3월 26일)
[6] Top SQL 튜닝 (2003년 4월 9일)
[1] Oracle Wait Event 모니터링
흔히 DBA를 3D업종이라고 부르는 이유 가운데 하나는 몸은 고달픈데 반해 그 성과가 별로 티가 나지 않는다는 사실 때문일 것이다. 실제로, DBA가 수행해야 하는 일상적인 관리 업무들은 몸은 다소 피곤하게 만들지 몰라도 어느 정도 경험이 쌓이면 그리 부담을 주는 일은 아니다. 우리가 한단계 업그레이드된 전문가로서 인정 받는 DBA가 되기 위해서는 장애상황 혹은 유사 장애 상황에서 DB 모니터링 작업을 수행하고 분석할 수 있어야 한다. 시스템이 갑자기 느려지고 업무가 마비되는 상황에 맞닥뜨렸을 때 문제의 원인이 무엇인지를 집어낼 수 있는 능력이 있어야 하며 최소한 오라클의 문제인지 아닌지를 판단할 수는 있어야 몸으로 야간작업이나 때우는 DBA가 아니라 조직에 없어서는 안될 전문가로서의 나의 존재가치를 인정 받을 수 있을 것이다.
이 글에서는 오라클 Wait Event에 대하여 간단히 알아보고 일시적인 성능저하 상황에서 Wait Event를 모니터링하고 그 원인을 찾아가는 방법에 대하여 다루어 보고자 한다. 짧은 지면 위에 다룰 수 있는 내용도 제한되어 있고 글쓴이의 지식 또한 일천하지만 오라클 전문가가 되기 위해 같은 길을 가고 있는 동료로서 가진 지식 몇 가지 공유한다는 취지로 이 글을 쓴다.
오라클의 Wait Event 정보는 V$SYSTEM_EVENT, V$SESSION_EVENT, V$SESSION_WAIT 등이 있는데, 이 가운데 V$SESSION_WAIT는 각 세션이 현재 Waiting 하고 있는 Event나 마지막으로 Wait한 Event 정보를 보관하고 있으며, V$SYSTEM_EVENT와 V$SESSION_EVENT는 시스템이 Startup된 이후 각각 시스템 전체, 혹은 세션별로 발생한 Wait Event 정보를 누적하여 기록하고 있다.
오라클의 Wait Event는 성격에 따라 Network교신이나 IO를 위해 대기하는 일상적인 Wait와 특정 자원에 대해 여러 프로세스가 동시에 액세스하고자 할 때 발생하는 Wait, 별달리 할 일이 없어 대기하고 있는 Idle Wait 등 세가지 유형으로 구분할 수 있는데 그 유형에 따라 해석방법도 달라진다. 일단, Idle Wait는 일반적인 관심의 대상에서 제외되며 IO나 Network 관련 Wait는 작업량이 증가하면 같이 증가하는 Wait이므로 전체 서비스 시간(CPU time)과 비교하여 상대적으로 평가해야 하며 총 Wait time보다는 평균 Wait Time에 관심을 두고 분석을 해야 할 것이다. 시스템 자원에 대한 Wait는 데이터베이스 서버 튜닝시 가장 주된 관심 대상이 되며 이들 Wait에 대해서는 평균 Wait Time뿐만 아니라 총 Wait Time에도 관심을 가지고 분석해야 할 것이다. 유형별로 대표적인 Wait Event를 살펴본다면 아래와 같다.

[주요 Wait Event] 구분 이벤트명 설 명
일상적인 Wait Event
   db file scattered read - Full Scan시 OS에 I/O를 요청해놓고 대기
   db file sequential read -  Index Scan시 OS에 I/O를 요청해놓고 대기
(IO, Network)
   log file sync - 변경 log buffer를 log file에 반영하는 동안 대기
   DFS lock handle - OPS 환경에서 노드간 분산 Lock 교환에 따른 대기
   global cache cr request - OPS 환경에서 노드간 Buffer Block 교환에 의한 대기
자원 경합에 따른 Wait Event
    enqueue - Type에 따라 세분화 (24개의 enqueue type (9i))
    latch free - Name에 따라 세분화 (239개의 latch가 존재 (9i))
    buffer busy waits - 동일블록에 대한 동시 액세스에 따른 경합
    free buffer waits - free buffer를 할당위해 DBWR의 Write를 대기
    Log buffer space - Log buffer를 할당 받기 위해 LGWR의 write를 대기
    library cache lock - SGA내의 library cache를 참조하기 위한 대기(검색)
    row cache lock - SGA내의 dictionary cache를 참조하기 위한 대기
Idle Event
    SQL*Net message from client -   Client로부터의 작업요청을 대기
    Pmon timer - PMON이 할일 없을 때 대기하는 Event
   
업무시간대에 시스템이 갑자기 느려졌다면서 오라클 서버에 문제가 없는지 문의가 들어오면 글쓴이는 우선 아래의 SQL을 수행시켜본다.
select /*+ ordered / distinct /* 속도를 위해 v$sql을 조인할 경우 중복되는 레코드 제거 */
           s.sid SID, s.username, s.program, p.spid "OS-Pid",w.seconds_in_wait as "W_time(Sec)",
          decode(w.wait_time,0,'Wai-ting', 'Waited') Status, w.ename event,
--              p1text || ':' || decode(event,'latch free',p1raw, to_char(p1)) ||','||
--              p2text || ':' || to_char(p2) ||','|| p3text || ':' || to_char(p3) "Additional Info",
           q.sql_text
from ( select a.*, decode(a.event,'latch free', 'latch free (' ||b.name||')',
                                         'row cache lock', 'row cache lock (' || c.parameter || ')',
                                         'enqueue', 'enqueue ('||chr(bitand(p1, -16777216)/16777215)||
                                                                             chr(bitand(p1,16711680)/65535)||':'||
                                                decode(bitand(p1,65535), 1, 'N', 2, 'SS',3,'SX',4,'S',5,'SSX',6,'X') ||')',
                              a.event ) ename
           from v$session_wait a, v$latchname b, v$rowcache c
         where a.p2 = b.latch#(+) and a.p1 = c.cache#(+) and c.type(+) = 'PARENT'
           and a.event not in ('rdbms ipc message','smon timer','pmon timer','slave wait','pipe get','null event',
                                     'SQL*Net message from client', 'SQL*Net message to client','PX Idle Wait',
                                      'PX Deq: Execution Msg', 'KXFQ: kxfqdeq - normal deqeue',
                                         'ges remote message', 'wakeup time manager', /* idle event 적절히 수정 */
                                         'lock manager wait for remote message', 'single-task message')
        ) w, v$session s, v$process p, v$sql q
where w.sid = s.sid and s.paddr = p.addr
and s.sql_hash_value = q.hash_value(+) and s.sql_address = q.address(+)
order by w.ename;
SQL의 구체적인 내용이야 필요한 정보와 개인적 취향에 따라 달라지겠지만, 중요한 것은 일단 V$SESSION_WAIT 뷰로부터 실시간 Wait Event 정보를 얻어낸다는 것이다. 위 SQL을 수행했을 때 나타나는 결과가 없다면 일단 오라클 측면에서 업무성능을 심각하게 마비시키는 Waiting이 발생하고 있지 않다고 봐도 큰 무리가 없을 것이다.
일반적인 상태에서는 주로 'db file sequential read'나 'db file scattered read' 가 나타날 텐데, 이러한 Wait Event는 보통 짧은 시간 동안 지속되며 대상 자원(블록)을 바꿔가며 Wait가 반복되는 형태로 나타날 것이다. 이는 작업 처리량이 많을 때 일상적으로 발생하는 IO관련 Wait Event이므로 해당 세션에서 IO를 제법 많이 유발하고 있다는 정도로 이해하고 넘어가면 될 것이다. 물론, Wait의 지속시간이 길거나 지나치게 빈번히 나타나는 SQL에 대해서는 비효율적인 실행계획을 수립하고 있지 않은지 검토해서 튜닝해 주어야 한다.
성능저하의 원인이 오라클 쪽에 있는 경우에는 특정 자원에 대한 Waiting이 상당히 오랫동안 지속되어 현재까지 Waiting이 진행 중인 세션들(STATUS가 'Wai-ting' (wait_time=0)이며 'W_time(sec)' (seconds_in_wait) 값이 상당히 큰 세션)이 존재할 가능성이 높다. 오라클의 내부적인 작업들은 매우 짧은 기간에 처리되어야 하므로, Idle event(where절에서 not in으로 처리한 부분, 버전에 따라 달라질 수 있다.) 이외의 특정 Wait Event가 눈에 띌 정도로 검출된다는 것은 오라클 내부적으로는 훨씬 더 많은 Waiting이 발생하고 있다고 생각해야 한다. 바로 이런 세션들이 문제의 범인들이며 이제부터 DBA는 이들 Wait Event에 대한 원인을 파악하여 조치하는 작업을 해주어야 한다. 각각의 Wait Event에 따라 원인을 추적하고 조치하는 방법은 달라질 것이다.
다음 호에서는, 자주 경험하는 몇가지 대표적인 Wait Event들에 대하여 SGA 영역별로 구분하여 좀 더 자세히 살펴보고, 그에 앞서 Lock 또는 Latch Event의 이해를 위해 필요한 Enqueue와 Latch의 개념을 간단히 알아보도록 하겠다.

[다음]
[2] Enqueue와 Latch 개념 이해하기
DBMS의 가장 주된 기능 중에 하나는 동일 자원에 대한 동시 액세스를 관리하는 것이며, 이를 위해 오라클이 사용하는 대표적인 제어 구조가 Enqueue와 Latch이다.
Enqueue 와 Latch는 모두 특정 자원에 대한 접근을 serialize하는 것이 목적이라는 점에서는 같은 Lock의 일종이지만 관리방식이나 용도에서 차이가 있다. Enqueue는 이름에서 보듯 Queue를 통해 관리된다. 대상 자원에 대한 Owner, Waiter, Converter Queue를 관리하면서 먼저 요청한 순서대로 Lock을 획득하도록 하는 구조이며, Exclusive 모드 뿐 아니라 다양한 수준의 공유를 허용한다. 대표적인 것이 테이블 데이터를 Update할 때 사용되는 TM, TX enqueue이다.
반면에, Latch는 Enqueue에 비해 훨씬 단순한 구조로서 매우 짧은 시간 내에 획득되고 해제된다. Queue를 통해 관리되지 않으므로 먼저 Request한 프로세스가 먼저 latch를 획득한다는 보장이 없으며, 대부분의 경우 Exclusive모드로만 획득된다. Latch는 주로 SGA의 특정 메모리 구조체에 대한 액세스(library cache latch, cache buffers chains latch) 혹은 메모리 할당 시 (shared pool latch) 사용되거나 오라클의 중요한 코드가 동시에 수행되지 않도록 하기 위한 용도로(redo writing latch) 사용된다. Latch는 Enqueue보다는 하위 level에서 Locking 자체의 부하를 최소화하며 작동하는 제어 메커니즘이라고 할 수 있으며, 실제로 Enqueue 역시 내부적으로는 Latch (enqueues, enqueue hash chains latch )에 의해 운영된다는 점을 생각하면 둘 사이의 차이를 쉽게 이해할 수 있을 것이다.
■ Enqueue
Enqueue 정보는 내부적으로 Enqueue Resource 배열과 Enqueue Lock 배열에 저장된다. 특정 자원에 대한 Lock이 요청되면 대상을 하나의 Resource로 정의하여 할당하고 그 Resource에 대해 관련 Lock 정보를 Owner, Waiter, Converter가운데 하나로서 Link시키는 방식으로 운영되며, 이러한 정보는 V$RESOURCE와 V$LOCK 뷰를 통해 조회해 볼 수 있다. V$RESOURCE와 V$LOCK은 1:M 관계로 하나의 Resource에 대하여 여러 건의 Lock 레코드가 Owner (LMODE>0, REQUEST=0), Waiter (LMODE=0 ,REQUEST>0), Converter (LMODE>0, REQUEST>0) 중 하나로서 대응된다.
Enqueue Wait이 발생하는 것은 다른 세션이 이미 나보다 먼저 해당 자원에 대한 Lock을 잡고 있으므로 인해 내가 원하는 모드로 Lock을 할당 받을 수 없기 때문이다. 자신이 필요로 하는 Lock의 획득에 실패한 세션은 Owner가 작업을 완료하고 자신을 깨워줄 때까지(세마포어를 포스트해줄 때까지) Waiter 혹은 Converter Queue에서 대기하게 되며, 기다려도 소식이 없으면 3초 간격으로 timeout에 의해 일어나 혹시 Deadlock 상황이 아닌지 점검해 본 후 다시 Sleep에 빠져들기를 반복하게 된다. 튜닝관련 자료를 보다 보면 가끔 Enqueue에 대한 Wait이 많은 경우에 Enqueue_resource나 Enqueue_lock 파라미터를 증가시켜 주어야 한다는 가이드를 보게 되는 경우가 있는데 이 파라미터들은 Enqueue resource와 lock 배열의 크기를 늘려줄 뿐 특정 Enqueue 자원에 대한 동시 경합을 해소시키는 것과는 상관이 없다. Enqueue Wait를 해소하기 위한 구체적인 방법은 Enqueue type에 따라 달라지지만 결국은 Enqueue를 불필요하게 요청하는 경우가 없는지를 살펴 Enqueue에 대한 요청을 최소화하고 Enqueue를 점유하는 시간을 최대한 단축시키는 것이다. TX Enqueue에 대한 Wait은 대상 자원에 대한 Lock을 소유하고 있는 세션과 그 세션이 수행 중인 SQL을 찾아 트랜잭션이 장시간 지속되고 있는 이유가 무엇인지 애플리케이션 측면에서 조사해야 하며, SQ enqueue는 Sequence 값 할당 시 발생하는 경합이므로 cache값을 늘려줌으로써 완화시킨다거나 ST Enqueue의 경합이 존재할 경우에는 Locally managed tablespace를 사용하거나 Initial, Next 등의 extent 크기를 적당한 값으로 조정하여 실시간 공간할당을 감소시켜주는 등의 방법들이 Enqueue Wait에 대처하는 대표적인 사례이다. 지난 호에서 소개한 Session Waiter 스크립트는 Enqueue Wait 이벤트에 대해서 Enqueue type과 모드를 함께 표시하여 주도록 하고 있으며, 참고로 Enqueue type별 누적 Wait현황을 확인하고자 하면 아래 SQL을 수행하면 된다.

select q.ksqsttyp type,
           q.ksqstget gets,
           q.ksqstwat waits,
            round(q.ksqstwat/q.ksqstget,3) waitratio
       from sys.x$ksqst q
where q.inst_id = userenv('Instance')
      and q.ksqstget > 0
order by waits desc
/

■ Latch
오라클 운영 시에 하위레벨에서 내부적으로 처리되는 다양한 조작들이 latch의 관할 하에 수행되는데 V$LATCHNAME을 조회해보면 (9i 기준으로) 239 종류나 되는 Latch가 존재하는 것을 확인할 수 있다. 이 가운데 우리가 자주 접하게 되는 latch는 다음과 같은 정도이며 각 Latch의 기능은 관련 SGA별 Wait를 다룰 때 간단하게나마 소개하도록 하겠다.
Shared pool -  library cache latch, shared pool latch, row cache objects
Buffer Cache - cache buffers chains latch, cache buffers lru latch, cache buffer handle
Redo log - redo allocation latch, redo copy latch, redo writing latch
OPS - dlm resource hash list

▷ Willing to wait 모드와 No-wait 모드
Latch 획득 방식은 No-wait과 Willing to wait 의 두 가지 모드로 구분할 수 있다. Willing to wait 모드는 Latch의 획득에 실패하면 좀더 시간을 끌면서 해당 Latch를 잡을 때까지 재시도를 해보는 방식을 말한다. 일차적으로는 CPU를 놓지 않고 정해진 횟수만큼 Spinning을 한 후 재시도를 해보다가 그래도 실패하면 CPU를 놓고 Sleep하다가 timeout되어 재시도하는 작업을 반복하면서 Latch의 획득을 노력하게 된다. Latch가 sleep에 들어가게 되면 'latch free' wait event 대기가 시작된다. sleep의 지속시간은 sleep 횟수가 늘어갈수록 점점 길어지게 되는데, 따라서 V$LATCH의 Gets와 Sleeps의 비율과 함께 Sleep1~sleep4 항목에서 몇차 Sleep까지 발생했는지 여부도 각 Latch Wait의 심각성을 판단하는 요소 가운데 하나가 된다.
No-wait 모드는 Willing to wait과는 달리 더 이상 미련을 두지 않고 해당 Latch에 대한 획득을 포기하는 것이다. No-wait 모드가 사용되는 경우는 두 가지가 있는데, 하나는 동일한 기능을 하는 Latch가 여러 개 존재하여 그 중에 하나만 획득하면 충분하여서 특정 Latch에 미련을 가질 필요가 없는 경우이다. 물론, 이 때에도 같은 기능의 모든 Latch에 대한 시도가 실패로 끝날 경우에는 Willing to wait 모드로 요청을 할 것이다. No-wait 모드가 사용되는 다른 한가지 경우는 dead lock을 피하기 위해서 이다. 오라클은 기본적으로 latch dead lock 상황을 피하기 위하여 모든 Latch에 level을 부여하여 정해진 순서를 따라서만 Latch를 획득하도록 하고 있는데, 필요에 의해 이 규칙을 어기고 Latch를 획득하고자 할 경우 일단 No-wait 모드로 시도를 해보는 것이다. 다행히 Latch를 잡으면 좋은 것이고 비록 latch를 잡을 수 없더라도 무한정 기다림으로써 dead lock 상태에 빠지는 일은 피할 수 있는 것이다. No-wait 모드의 Latch작업에서는 당연히 Latch 관련 wait이 발생하지 않으며, redo copy latch를 제외하고는 Willing to wait 모드로 Latch를 획득하는 경우가 훨씬 많다.
▷ Parent latch와 Child latch
Latch 가운데에는 동일 기능을 하는 Child latch들의 set으로 운영되는 Latch도 있으며 하나의 Latch로만 운영되는 Latch도 있다. 전자의 대표적인 예로는 cache buffers chains (버퍼캐쉬 블록 들을 같은 이름의 다수의 Latch가 나누어 담당)가 있으며, 후자의 예로는 shared pool latch (shared pool내에서 메모리 할당을 위해 획득해야 하는 Latch로 시스템에 하나만 존재)가 있다. 이와 같은 Latch 관련 통계 정보는 Parent latch와 Child latch의 개념으로 관리가 되는데 Latch set에서 개별 Child latch에 대한 통계정보는 V$LATCH_CHILDREN 뷰를 통해 조회할 수 있으며, 단일 Latch 혹은 Latch set의 마스터 Latch (parent)에 대한 통계정보는 V$LATCH_PARENT 뷰를 통해 조회할 수 있다.
지금까지 한 회 분량을 할애하여 Enqueue와 Latch에 대해 요약해본 이유는, 많은 Waiting이 SGA내의 공유자원 (Block, Cursor 등)에 대한 경합으로 인해 발생하며 이러한 경합은 다시 해당 자원에 대한 동시 액세스를 제어하는 Enqueue와 Latch에 대한 경합으로 흔히 드러나게 되므로 오라클의 Wait Event를 모니터링하기 위해서는 Enqueue와 Latch의 구조와 작동원리에 대해 이해하는 것이 필수적이기 때문이다.

[3] Shared Pool 관련 Wait
■ Share pool과 성능문제
오라클이 공유 메모리(SGA)를 사용하는 가장 큰 이유는 기본적으로 메모리 사용을 최소화하면서 처리성능은 최대화하기 위한 것이다. 한번 액세스된 블록을 Database buffer cache에 캐쉬 함으로써 비용이 큰 Disk I/O를 최소화하는 것처럼, 한번 처리된 SQL의 실행 정보를 Shared Pool에 공유함으로써 파싱 작업을 위한 CPU, 메모리 자원의 사용을 최소화하고 SQL 수행속도를 증가시킬 수 있다. Shared Pool에는 SQL이나 PL/SQL을 수행하기 위한 각종 정보 - SQL구문 및 실행계획, PL/SQL 소스, 테이블, 뷰 등의 각종 오브젝트와 오브젝트 상호간의 의존관계, 권한관계 등 - 가 저장되어 있다. 지면 관계상 이 글에서 Shared Pool의 관리 메커니즘을 상세히 기술할 수는 없지만 몇 가지 내재적인 특징으로 인해 Shared Pool은 오라클의 메모리 영역 가운데에서도 가장 성능문제의 요소가 많은 곳이면서도 효과적인 튜닝이 수월치 않은 영역이기도 하다.
무엇보다, Shared Pool에서 가장 문제가 되는 것은 메모리의 조각화(Fragmentation)이다. Shared Pool에서 라이브러리 캐쉬 오브젝트를 위해 할당되는 메모리 단위를 chunk라고 부르는데 chunk의 크기는 수 바이트에서 수 K바이트에 이르기까지 필요에 의해 다양하게 할당된다. 새로운 chunk의 할당이 필요하게 되면, 프로세스는 이미 존재하는 chunk로부터 필요한 만큼의 크기만을 떼어내어 사용하므로 시간이 흐를수록 점차 메모리가 조각화 되는 것을 피할 수 없다. ( 이는, Pctincrease가 0가 아닌 테이블스페이스에서 익스텐트의 할당과 해제가 반복됨에 따라 공간의 조각화가 심해지는 것을 떠올리면 이해가 쉬울 것이다. ). 어느 정도 정형화된 패턴의 애플리케이션이 수행되는 환경이 아니라, 공유가 불가능한 다양한 형태의 SQL(대표적으로 Literal SQL)이 빈번히 요청되는 환경이라면 Shared Pool 메모리 조각화에 따른 문제는 더욱 심각해진다.
또한, Shared Pool은 일반적인 메모리 캐쉬와는 달리 메모리에 저장되었던 정보를 잠시 기록해둘 대응되는 디스크 공간이 없으므로 한번 flush된 라이브러리 캐쉬 오브젝트를 reload하기 위해서는 해당 정보를 재생성 해야만 한다. 이 과정에서 관련 오브젝트 정보의 검색 및 참조, locking, 메모리 할당 등의 작업을 위해 많은 비용이 들기 때문에 결국 Shared Pool 관련 튜닝의 최대 과제는 SQL 공유를 최대화하여 새로운 파싱 요청과 메모리 요청을 최소화하는 것이라고 할 수 있다. 헌데, 이는 애플리케이션의 설계와 연계되는 영역으로서 이미 개발이 완료된 운영서버에서는 변경작업이 여의치 않은 것이 현실이다. 앞서, Shared Pool이 DBA로서 튜닝이 수월치 않은 영역이라고 표현한 이유 가운데 하나가 여기에 있다.
■ Shared Pool 관련 오해 바로잡기
Shared Pool과 관련하여 판단이 쉽지 않은 부분 가운데 하나가 과연 shared_pool_size를 얼마나 할당할 것인가 하는 것이다. 오라클은 Shared Pool 메모리를 최대한 효율적으로 활용하기 위하여 다양한 기법을 동원하고 있는데, 이러한 메모리 관리 메커니즘에 대해 정확히 알지 못하여 Shared Pool 크기를 지나치게 크게 할당함으로써 오히려 문제를 악화시키는 경우도 드물지 않다. 이러한 오해를 바로잡기 위해 Shared Pool의 메모리 할당과정을 간단하게나마 살펴보도록 하겠다.
새로운 메모리 Chunk가 할당되는 과정을 살펴보면, 우선 프로세스는 Free List를 검색하여 자신이 필요로 하는 크기의 Free Chunk를 찾고, 그러한 Free Chunk가 없으면 원하는 크기보다 한단계 큰 Free Chunk를 찾아서 필요한 크기만큼 분할하여 사용하게 된다. 만약 Free List에서 충분한 크기의 Free Chunk를 찾을 수 없다면, 이미 사용되었으나 현재는 사용되고 있지 않는(unpinned) Chunk들의 LRU List를 검색하여 오래된 것부터 8개씩 flush시켜 Free Chunk로 만든 후 자신이 필요한 크기를 할당하여 사용하게 된다. 만약 이 과정에서 현재 사용중인(pinned) Chunk가 대부분이거나, 너무 메모리 조각화가 많이 일어나서 기존 Chunk를 Flush시킨 후 인접한 Free Chunk들을 병합해보아도 원하는 크기의 Free Chunk를 얻어낼 수 없다면 오라클은 ORA-4031 에러를 발생시키는데, 그 이전에 한가지 최후의 비밀무기가 더 숨어 있다. 바로 Spare Free 메모리라는 것인데 오라클은 인스턴스 기동 후 처음에는 전체 Shared Pool의 50% 가량은 Free List에 올려놓지 않고 아예 숨겨두었다가 앞서와 같이 도저히 피할 수 없는 순간이 되면 조금씩 해제 시켜 사용하도록 한다. 그야말로 메모리의 조각화를 최소화하기 위한 오라클의 눈물 나는 노력이라고 할 수 있을 것이다. 물론 이 영역까지 다 소모한 후에 flush를 통해서도 필요한 Chunk를 확보할 수 없는 상황이 되면 결국 ORA-4031 에러가 발생할 것이다.
많은 이들이 Shared Pool의 남아있는 Free memory의 크기가 작으면 shared_pool_size를 증가시켜주어야 한다고 믿고 있는데 이는 잘못된 것이다. Shared Pool은 정보의 재사용을 위해 운영하는 것이므로 SQL 실행이 끝났다고 해서 해당 Chunk를 Free List로 반납하지 않는다. 즉, Free Memory가 남아있는 한 계속 소모 시키는 방식으로 사용되므로 오랜 시간동안 운영되어온 시스템에서 Shared Pool의 Free Memory가 매우 적게 남아 있는 것은 그 자체로는 문제가 되지 않으며, 오히려 피크타임이 지난 후에도 많은 양의 Free Memory가 남아있다면 이는 Spare Free 메모리도 다 소모하지 않은 상태로서 불필요하게 많은 메모리가 할당되어 낭비되고 있음을 의미한다. 더구나, Shared Pool 크기가 지나치게 크면 Free Memory를 다 사용할 때까지의 기간이 연장되는 효과는 얻을 수 있겠지만, 시간이 지날수록 Memory의 조각화가 더욱 심해지고 Free List의 길이가 길어져 Free Chunk의 검색과 할당에 걸리는 시간이 지연되므로 오히려 성능이 악화되는 결과를 초래할 것이다.
또한, 메모리 조각화에 따른 영향을 줄이기 위해 오라클은 5000 bytes가 넘는 큰 사이즈의 Chunk만을 위해 전체 Shared Pool의 5% 정도를 따로 관리하는 방법을 사용하고 있는데, 경험적으로 보면 이 공간은 거의 사용되지 않고 버려지고 있는 경우가 많다. 이는 V$SHARED_POOL_RESERVED 뷰의 USED_SPACE 값을 확인해 보면 알 수 있으며, 5000 bytes 이상의 large chunk가 거의 요구되지 않는 환경에서는 오히려 이 크기를 줄여주는 것이 나을 것이다.

■ Shared Pool 관련 wait
Shared Pool과 관련하여 흔히 발생하는 Wait은 라이브러리 캐쉬 오브젝트에 대한 동시 액세스와 메모리 할당에 따른 관련 Lock 또는 Latch에 대한 경합이 대부분이며, 구체적인 이름은 다음과 같다. (Latch free 이벤트시 괄호 안의 관련 latch 이름은 v$session_wait의 p2값과 v$latchname의 latch#를 조인하여 얻어낼 수 있다. 1회 SQL 참조)
Latch                                   Lock
latch free ( library cache )           library cache lock, library cache pin
latch free ( library cache load lock)  library cache load lock
latch free ( row cache objects )       row cache lock
latch free ( shared pool )             -      
Library cache lock, library cache pin, library load lock은 각각 특정 라이브러리 캐쉬 오브젝트에 대한 검색이나 변경 및 실행 또는 로드 시에 대상 오브젝트에 대해 할당되며, 이러한 Locking 작업은 library cache latch와 library cache load lock latch의 관할 하에 처리된다. Shared pool latch는 Free List나 LRU List를 검색하거나 메모리를 할당하는 작업에 사용되며, row cache lock과 row cache objects latch는 Data dictionary cache 오브젝트에 대한 동시 액세스를 제어하는데 사용된다.
Latch의 개수는 시스템 전체적으로 하나 또는 제한된 개수가 존재하는 것이고 Lock은 대상 오브젝트 각각 대해 할당되는 것이므로, 엄밀하게 말해서 Lock에 대한 경합은 직접적으로는 특정 라이브러리 캐쉬 오브젝트에 대한 동시 액세스로 인해 유발되는 것인 반면에, Latch에 대한 경합은 시스템 전체적으로 관련 오퍼레이션(즉, SQL 파싱) 자체가 지나치게 많이 발생하거나, 짧은 시간 내에 처리되지 못함으로 인해 유발되는 것이라고 구분해볼 수 있다. 그러나, 결국 이 모든 경합은 근본적으로 Shared Pool의 조각화(Fragmentation)에 따른 문제가 주된 원인이며 다시 이러한 조각화는 요청되는 SQL들이 공유되지 못하고 지속적으로 새롭게 파싱되고 메모리가 할당됨으로 인해 발생하는 것이다. 따라서, 이러한 문제를 해결하는 가장 효과적인 방법은 Literal SQL을 바인드 변수를 사용하도록 수정하거나, SQL작성 표준을 마련하고, HOLD_CURSOR/ RELEASE_CURSOR, SESSION_CACHED_CURSORS, CURSOR_SPACE_FOR_TIME, CURSOR_SHARING 등의 파라미터를 활용하는 등의 방법을 통해 SQL의 공유도를 높여주는 것이며, 또한 자주 사용되는 PL/SQL에 대해서는 DBMS_SHARED_POOL 패키지를 사용하여 메모리에서 Flush되지 않도록 보존하는 등의 조치를 취해주면 도움이 될 것이다. SQL의 수정이 어려운 환경이거나 시스템에 요청되는 SQL의 절대량이 확보된 메모리 공간에 비해 많은 상황이라면 주기적으로 피크타임을 피해 Shared Pool을 직접 Flush(alter system flush shared_pool 명령을 사용한다.) 시켜주는 것도 권장할 만한 관리 방법이다. 많은 이들이 우려하는 바와는 달리 Shared Pool을 직접 flush 시키는 것이 심각한 성능상 문제를 야기하지는 않으며 특히 중요한 패키지나 SQL cursor, Sequence 등이 keep되어 있는 경우라면 더욱 그러하다.
가끔 버그를 포함한 특수한 상황에서 특정 라이브러리 캐쉬 오브젝트에 대한 lock이 장시간 해제되지 못하고 있는 경우도 있는데 이때는 X$KGLLK 뷰를 조회하면 library cache lock에 대한 holder/waiter를 확인하여 조치할 수 있다. 또한, Row cache lock에 대한 경합은 Locally managed tablespace를 도입하거나, DML이 빈번한 테이블에 대한 인덱스의 개수를 줄여주는 등의 조치를 통해 완화될 수 있을 것이다.
부연하자면, Shared Pool과 관련된 Wait는 특정 오브젝트 자원에 대한 경합에 의해 발생하기 보다는 애플리케이션의 설계, 보다 단순화시켜 표현하면 Literal SQL에 의한 메모리 조각화에 의해 발생하는 경우가 많다. 따라서, Shared Pool관련 Wait가 많이 발생하여 오라클이 그로 인한 성능상의 문제를 드러낼 때 눈에 띄는 하나의 주범을 찾아내려는 노력은 별 효과를 거두지 못하는 경우가 많으며, 그러한 시점에 DBA가 즉각적으로 취할 수 있는 조치로는 직접 Shared Pool을 Flush 시키는 정도가 있을 것이다. 결국, 평소에 꾸준한 모니터링을 통해 Shared Pool의 적절한 크기와 관련 파라미터 값을 찾아가는 것, 그리고 무엇보다 애플리케이션 측면에서 튜닝 및 수정 작업을 진행함으로써 성능문제를 사전에 예방하는 것이 최선이다.
[3] Buffer Cache 관련 Wait
■ Buffer Cache 구조
Buffer Cache의 기본적인 기능은 여러 프로세스에 의해 공통으로 자주 액세스 되는 데이터베이스 블록을 메모리에 캐쉬하여 물리적인 디스크 IO를 최소화함으로써 더 빠른 액세스 속도를 제공하기 위한 것이다. 복잡한 설명은 생략하고, Buffer Cache 의 기본구조를 이해하기 위한 몇 가지 핵심 용어들을 간단히 정리해 보도록 하겠다.
▷ Buffer header
모든 버퍼 블록들은 각자의 buffer header를 통해 액세스되고 관리된다. 즉, 메모리에 캐쉬된 특정 데이터 블록에 대한 액세스는 먼저 해쉬 알고리즘을 통해 cache chain 상의 buffer header를 찾고 해당 buffer header에 기록된 데이터 블록의 메모리상 주소를 찾아가 원하는 정보를 읽는 방식으로 이루어진다. Buffer header에 기록되는 주요정보는 다음과 같으며 Buffer header의 내용은 V$bh 뷰를 통하여 조회해볼 수 있다.
     - 메모리상에서의 해당 버퍼블록의 주소
     - 해당 버퍼 블록(실제로는 버퍼헤더)가 포함되어 있는 hash chain
     - LRU, LRUW, CKPTQ와 같은 리스트상에서의 해당 버퍼블록의 위치
     - 해당 버퍼블록에 대한 User, Waiter와 상태를 나타내는 각종 Flag
▷ Hash Buckets/ Hash Chains
Buffer Cache의 모든 블록은 해쉬 알고리즘을 통해 관리된다. 곧, 데이터 블록의 DBA, Class 값으로 Hash Function을 적용하여 해당 블록이 속하는 hash buckets을 할당하며, 동일한 hash buckets에 할당되는 데이터 블록의 버퍼헤더들은 linked list형태로 hash chain을 이루게 된다. Hash buckets/hash chains는 특정 데이터 블록을 찾아가기 위한 수단을 제공한다. 각각의 hash buckets에는 자신에 속한 hash chain을 보호하기 위한 latch(cache buffers chains)가 할당된다.
▷ LRU
LRU는 두개의 리스트, 즉 LRUW와 LRU 리스트의 쌍으로 구성된다. LRUW(LRU Write list)는 dirty list와 같은 말이며, 수정되어 디스크에 반영되어야 할 블록들의 리스트이다. LRU(Least recently used list)는 LRUW에 올라가지 않은 나머지 버퍼 블록들이 등록되어 있다. Buffer cache 상의 버퍼블록은 반드시 LRU나 LRUW 둘 중의 하나에 등록되며, 두 리스트에 동시에 포함되는 경우는 없다. LRU는 Free Buffer를 찾기 위한 수단을 제공한다. 경합을 피하기 위해 버퍼캐쉬 블록들을 여러 개의 LRU쌍으로 나누어 관리할 수 있으며, 각 LRU리스트를 보호하기 위해 Latch(Cache buffers lru chain)가 하나씩 할당된다.
■ Buffer Cache 운영규칙
▷ 메모리상의 특정 버퍼블록을 찾아가거나, 특정 블록이 메모리에 캐쉬 되어 있는지를 확인하기 위해서 오라클은 hash bucket/hash chain 구조를 사용한다.
▷새로운 데이터블록을 디스크로부터 메모리로 읽어 들이기 위한 free buffer를 확보하기 위해 오라클은 LRU 리스트를 사용한다.
▷ 버퍼블록은 LRU나 LRUW 둘 가운데 하나에 등록된다.
▷ 하나의 블록에 대해 시간대가 다른 여러 개의 복사본이 존재할 수 있으며, 그 가운데 오직 CURRENT 버퍼만이 변경될 수 있다.
▷하나의 버퍼블록은 한번에 오직 하나의 프로세스에 의해서만 변경될 수 있다.
■ Buffer Cache 관련 Waits
버퍼캐쉬와 관련되어 흔히 발생하는 대표적인 Wait 이벤트는 다음과 같다.
▷ buffer busy waits
여러 세션이 동시에 같은 블록을 읽으려고 하거나 여러 세션이 같은 블록에 대한 변경작업이 완료되기를 기다리고 있는 경우에 발생하며, 특정 블록에 대한 경합을 해소하기 위한 조치는 블록의 유형에 따라 달라진다. Data block에 대한 경합이 많은 경우는 Pct free나 Pct used 값을 사용하여 블록 당 로우수를 줄이거나, 특정 블록에 로우 입력이 몰리는 구조의 인덱스(right-hand-index)일 경우는 reverse key index의 사용을 검토하는 등의 방법이 있으며, segment header의 경합이 많은 경우는 freelist 수를 늘리거나 Extent의 크기를 증가시키는 등의 방법이 있고, undo header나 undo block에 대한 경합은 롤백세그먼트의 개수나 크기를 증가시키는 것이 전형적인 조치 방법이다. v$waitstat과 x$kcbfwait을 이용하며 Class 또는 file별로 wait 발생상황을 판단할 수 있다.
▷ free buffer waits/write complete waits
DBWR가 dirty buffer를 write하는 동안 서버 프로세스가 대기하고 있는 경우 발생한다. 곧, 너무나 많은 dirty buffer가 생겨나거나 DBWR의 쓰기 속도가 충분히 튜닝 되지 못한 경우에 발생한다. 점검 포인트는 물리적 디스크의 속성(stripe size, layour, cache size) 최적화, Raw device의 활용, Async IO나 multi-DBWR(db_writer_processes) 활용여부 등이다.
위와 같은 버퍼 블록에 대한 경합 역시 비효율적인 실행계획을 통해 수행되는 애플리케이션에 의하여 불필요하게 많은 블록이 메모리로 올라오는 것이 원인일 경우가 많으므로 경합이 빈번한 블록이 속하는 테이블/인덱스 명을 찾아낼 수 있다면 관련 SQL을 찾아내어 보다 효과적인 튜닝작업이 이루어질 수 있을 것이다. v$session_wait의 p1,p2 컬럼에 각각 file#, block#값을 표시하여 주므로 이 값을 이용하여 아래의 SQL문으로 현재 어떤 오브젝트에 대하여 해당 wait가 발생하고 있는지를 추적할 수 있다. ( 1회에 소개한 SQL문에서는 Additional Info 값을 참조. )
     select segment_name, segment_type
     from dba_extents
     where file_id = :file#
     and :block# between block_id and block_id + blocks -1

▷ cache buffers chains latch
SGA내에 캐쉬된 데이터블록을 검색할 때 사용된다. 버퍼캐쉬는 블록들의 chain을 이루고 있으므로 각각의 chain은 이 Latch의 child들에 의해 보호된다. 이 Latch에 대한 경합은 특정 블록에 대한 대량의 동시 액세스가 발생할 때 유발된다. 애플리케이션을 검토해 보아야 한다.
Ø cache buffers lru chain latch
버퍼캐쉬의 버퍼를 LRU 정책에 따라 이동시켜야 할 필요가 있는 경우 프로세스는 이 Latch 획득하게 된다. 이 Latch에 대한 경합은 Multiple buffer pool을 사용하거나 DB_BLOCK_LRU_LATCHES 를 증가시켜 LRU Latch의 개수를 늘려서 해소할 수 있다. SQL문을 튜닝하면 해당 프로세스에 의해 액세스 될 블록의 수가 줄어들 것이므로 당연히 효과를 거둘 수 있다.
위와 같이 버퍼캐쉬를 관리하는 Latch에 대한 경합은 경합이 집중되는 특정 Child Latch에 의해 관리되는 버퍼블록을 찾아 해당 블록이 속한 세그먼트 정보를 알아낸다면 보다 효과적인 조치가 가능할 것인데, latch free wait일 경우 v$session_wait의 p1raw 값이 해당 Latch address를 의미한다. 이 값을 x$bh의 hladdr 값과 조인하면 관련 오브젝트 이름을 추적해볼 수 있다.
     select file#, dbarfil, dbablk, obj, o.name
     from x$bh bh, obj$ o
     where bh.hladdr = :latch_address
     and bh.obj = o.obj#;

5] Redo buffer 관련 Wait
■ Redo buffer 구조
오라클 리두 구조의 핵심은 모든 트랜잭션 정보를 OS 파일에 기록해 둠으로써 시스템 장애가 발생해도 트랜잭션 단위의 일관성을 잃지 않고 데이터베이스를 복구할 수 있도록 하겠다는 것이다. 리두버퍼(redo buffer)는 이처럼 데이터베이스에 가해진 모든 변경내역을 파일에 기록 하기 위해 잠시 사용되는 메모리 영역이며 리두버퍼에 기록된 리두 정보는 다시 리두로그 파일에 기록되어짐으로써 향후 시스템 복구 작업이 필요할 때에 사용하게 된다. 오라클의 리두 구조를 이해하기 위한 핵심적인 개념을 간단히 정리해보면 다음과 같다.
데이터베이스에 대한 변경내역은 블록단위로 저장된다. 물론 변경되는 모든 블록의 복사본을 통째로 저장하는 것은 아니고 블록별로 어떠한 오퍼레이션을 수행하는가, 그리고 그러한 블록별 오퍼레이션을 어떠한 순서로 수행하는가를 기록한다. 이러한 블록별 단위액션을 change vector라고 부르며 change vector가 순차적으로 모여 하나의 의미 있는 redo record가 된다. 리두로그는 시스템내의 모든 프로세스들에 의해 생성되는 redo record를 SCN 순서대로 저장해놓은 것이다. 이때 리두로그에 기록되는 내용에는 테이블이나 인덱스 등의 데이터 블록 뿐만 아니라 UNDO 블록 또는 UNDO 세그먼트 헤더블록에 대한 변경내용을 포함하는 모든 버퍼캐쉬 블록에 대한 변경내역이 대상이 된다.
리두 정보는 항상 실제 변경작업보다 먼저 보관되어야 어떤 상황에서도 복구가 가능해진다. 따라서 트랜잭션을 수행하는(데이터베이스 블록에 변경을 가하는) 프로세스는 우선 자신의 메모리 영역 내에서 수행하고자 하는 작업에 대한 리두 레코드를 만들며, 이를 먼저 로그버퍼에 기록하고 난 후에 실제 버퍼블록에도 리두 레코드에 담긴 내용을 따라 적용하게 된다. 또한 같은 이유로 오라클은 변경된 버퍼캐쉬 블록을 디스크에 기록하기 전에 먼저 관련된 로그버퍼를 로그파일에 기록하는 작업을 처리하게 된다. 따라서, 리두 버퍼 또는 리두 파일 (아카이브 파일을 포함해서)에 대한 쓰기 작업에 병목이 생기면 시스템에 대한 모든 작업 수행이 대기 상태로 빠지게 될 것이다.
트랜잭션 커밋을 요청한 프로세스는 우선 해당 트랜잭션에 대한 로그버퍼가 리두로그 파일에 기록되는 작업이 완료된 후에야 커밋 완료 메세지를 받을 수 있다. 그렇게 함으로써 버퍼캐쉬 변경내역을 모두 디스크에 반영하지 않고도 시스템의 비정상 종료시 리두파일에 저장된 리두 레코드로부터 커밋 트랜잭션을 보존할 수 있게 된다.
■ 리두 버퍼관련 Wait 이벤트
일반적으로는 로그버퍼 관련해서 심각한 Waiting이 발생하는 경우는 드물지만, 가끔 볼 수 있는 리두 관련 Wait 이벤트로는 다음과 같은 것들이 있다.
▷ Log file parallel write
LGWR가 OS에 리두 버퍼를 로그파일에 기록하도록 요청해 둔 상태에서 대기하고 있는 이벤트이다. 이 경우에는 DML 작업시 nologging 옵션 등을 사용하여 시스템에서 발생하는 리두 레코드의 절대량을 줄이거나 하드웨어적으로 DISK IO를 개선시켜주는 것이 방안이다.
▷Log buffer space
프로세스가 로그버퍼를 할당하기 위해 대기하는 이벤트인데 LGWR가 로그버퍼를 비우는 것보다 더 빠른 속도로 프로세스들이 리두 레코드를 생성하고 있다는 것을 의미한다. 로그버퍼의 크기를 늘려주거나, DISK IO의 속도를 개선시켜 주어야 할 것이다. 로그버퍼는 로그파일에 대응되는 블록이 맵핑이 된 후에 사용될 수 있으므로 로그 스위치 발생시에도 log buffer space 이벤트에 대한 대기가 발생할 수 있다. 로그 스위치가 너무 잦다면 리두 로그 파일의 크기를 증가시켜주는 것이 좋다.
▷ Log file sync
프로세스가 커밋이나 롤백을 수행할 경우 우선 LGWR에게 해당 트랜잭션까지의 로그버퍼를 Write하도록 요청하게 되는데 이때 사용자 프로세스는 LGWR가 쓰기 작업을 완료할 때까지 log file sync 이벤트를 대기하게 된다. 버전 8i 이전에서는 DBWR가 쓰기 작업을 수행하다가 아직 관련 로그버퍼가 파일에 쓰여지지 않을 경우에도 LGWR에 쓰기를 요청하고 log file sync 이벤트에 대기하였으나 8i 이상에서는 log file sync에 대기하는 대신 deferred write queue에 등록한다. 따라서 버전 8i 이상에서 log file sync 이벤트는 사용자 프로세스에 의해 요청되는 커밋, 롤백 처리 시에 발생하며 결국, 시스템 전체적으로 커밋, 롤백이 지나치게 자주 수행되거나 상대적으로 LGWR의 쓰기 속도가 느린 것이 원인일 것이다. 또는, 로그 버퍼가 너무 커서 LGWR가 백그라운드로 flush 시켜주기 전( 보통 3초 간격 및 1/3 이상의 로그버퍼가 찬 경우)에 커밋에 의한 쓰기 요청이 이루어지므로 커밋 시점에 써야 할 양이 많아 대기시간이 길어지는 경우도 있는데 이 경우엔 리두 버퍼의 크기를 오히려 줄여주어야 할 것이다. 또는, LGWR wait for redo copy 이벤트가 많이 나타난다면 redo copy latch가 너무 많아 LGWR이 사용자 프로세스가 버퍼 쓰기 작업을 마칠 때까지 기다리는 일이 잦은 경우를 뜻하며 이 경우엔 _LOG_SIMULTANEOUS_COPIES 파라미터를 사용하여 copy latch의 수를 줄여주는 조치가 필요할 것이다.
시스템에 따라서 언급한 외의 다양한 이벤트 대기와 원인이 존재할 수 있고, 더구나 버전에 따라 redo copy latch와 redo allocation latch를 포함한 리두 운영 방식상 상이한 부분이 많이 존재하여 그에 따른 추가적인 튜닝요소가 있으나 이 글에서는 지면 관계상 8i를 기준으로 간략히 정리해 보았다.
[6] Top SQL 튜닝하기 (맺음)
■ Top SQL 튜닝의 필요성
지난 회까지 실시간 Wait Event 모니터링과 이벤트별 원인분석에 대해서 간단히 살펴보았다. 일시적 성능장애 시 재빨리 원인을 찾아내는 것도 중요하지만 보다 바람직한 것은 이러한 성능문제를 사전에 최대한 예방하는 것임은 두말할 필요도 없다. 오라클 성능문제를 다루는데 있어 강조하고 싶은 한가지는 시스템 자원의 배분을 변경하거나 증가를 고려하기 전에, 불필요한 작업을 최소화함으로써 자원요구 횟수와 자원점유 시간을 줄여주는 노력이 선행되어야 한다는 점이다. Wait Event에 대한 모니터링과 분석이 DBMS의 자원에 대한 경합과 관련된 성능문제를 파악하는데 유용한 방법임에 틀림없지만, 이 같은 정보는 데이터베이스 혹은 그 하위 레벨의 구조적 비효율성을 드러내어줄 뿐 애플리케이션 레벨의 문제를 직접적으로 알려주지는 않는다. 간단히 말하자면, DBMS 튜닝을 위해 정성을 쏟기 이전에 애플리케이션 튜닝에 더 많은 투자를 하라는 것이다. 이런 관점에서 DBA가 비교적 손쉽게 수행할 수 있는 것이 오라클 메모리로부터 악성 SQL을 추출하여 튜닝하는 Top SQL 튜닝이다.
■ Top SQL 추출기준
사용자로부터 요청되어 오라클 내에서 처리되는 모든 SQL은 오라클의 공유 메모리 영역 가운데 shared pool내에 캐쉬 되어 지며 이렇게 캐쉬 되어 있는 SQL과 관련 통계정보는 V$SQL 또는 V$SQLAREA 뷰를 통해서 조회할 수 있다. 이때, Top SQL을 추출하는데 중요하게 사용되는 항목은 buffer_gets, disk_reads, executions, rows_processed 등이며 일반적으로 아래와 같은 기준으로 Top SQL을 추출한다.
▷ Buffer gets 수치가 높은 SQL
Buffer gets은 해당 SQL이 처리되는 과정에서 액세스한 메모리 블록의 수(Logical IO)를 의미한다. 물론 이 값이 높다고 해서 무조건 악성 SQL임을 의미하는 것은 아니다. 즉, 이러한 SQL들 중에는 실제로 요구되는 작업량이 많아서 액세스 블록수가 많은 SQL도 있을 것이며 불필요한 처리를 수행하느라 액세스 블록수가 많은 SQL도 있을 것이다. 어느 경우이든 이 SQL들이 현재 오라클 서버에 부하를 많이 유발하고 있는 SQL들이라는 것만은 분명하며 사소한 비효율적 요소에 의해서도 서버에 큰 영향을 미칠 잠재적인 가능성이 있는 SQL들이므로 일차적으로 점검해 볼 필요가 있다.
악성 SQL여부를 판단하기 위한 Buffer gets의 수치에 절대적인 기준은 없으며 시스템의 데이터 규모와 트랜잭션량에 따라 다르다. buffer gets값을 기준으로 역순으로 정렬한 후 패턴을 살펴 적절한 추출기준을 선택하는 것이 좋을 것이다. 만일, 상위 몇 개의 SQL들과 나머지 SQL들 간의 buffer gets의 편차가 매우 크게 나타난다면 상위 몇 개의 SQL에 대해서만 튜닝을 수행해 주어도 큰 효과를 볼 수 있을 것이다. 일반적으로 시스템에서 수행되는 SQL 가운데 심각한 부하를 야기하는 SQL은 소수에 불과한 경우가 많으며 뒤에 기술될 다른 조건들과 조합하여 최대 Top 50건 정도를 추출하여 효율성을 검증하고 튜닝을 통해 개선하는 작업을 수행하여 주면 충분하다.
▷Buffer gets/Execution 수치가 높은 SQL
SQL의 단위 수행당 buffer gets 수치를 의미한다. 단위 수행당 buffer gets 값이 높다는 것은 해당 SQL의 처리가 비효율적일 가능성이 높음을 의미한다. 액세스 블록수가 비정상적으로 많다는 것은 rows_processed 값과 비교하여 상대적으로 평가되어야 할 부분이다. 실제로 반환하는 로우수가 매우 많은 배치성 SQL이거나 혹은 반환되는 로우수가 1건이라도 Group Function이 사용된 Summary성 SQL이라면 처리과정에서 많은 수의 블록을 액세스하는 것은 불가피하며 이 자체가 문제가 될 수는 없기 때문이다.
▷Execution 수치가 높은 SQL
Executions는 해당 SQL이 수행된 횟수를 의미한다. 수행횟수가 잦은 SQL은 buffer gets가 높을 경우가 많다. 일반적으로 십만 ~ 백만 회 이상 빈번하게 수행되는 SQL이라면 buffer gets/executions 값이 2자리 수 이내의 값을 나타내어야 정상이며 단위 수행당 속도는 0.1초 이내로 매우 빨라야 한다. 따라서, 이러한 SQL의 경우 SQL단위로 보면 튜닝의 효과를 체감하기도 어렵고 필요성을 느끼지 못할 수도 있으나 튜닝을 통해 아주 적은 차이라도 개선을 가져올 수 있다면 시스템 전체적인 관점에서는 매우 큰 효과를 가져다 줄 수 있다는 점이 중요하다. 하루에 백만번 수행되는 SQL에 대하여 0.01초를 개선한다면 시스템 시간으로 하루에 일만초를 절약한 셈이 될 것이다. 이러한 SQL에 대해서는 현재 빠르게 수행되고 있다고 해도 더 빠르게 처리할 여지가 없는지 점검하고 가능한 모든 방안을 동원하여 개선시키도록 노력해야 한다.
▷disk_reads 수치가 높은 SQL
disk_reads는 SQL이 처리되는 과정에서 물리적인 IO를 통해 액세스한 블록의 수를 의미한다. 물리적 IO의 발생여부는 원하는 블록이 메모리에 캐쉬되어 있는지 여부에 따라 달라지므로 수행되는 횟수와 수행되는 시간대의 데이터베이스 캐쉬 상황에 따라 유동적이라고 할 수 있다. 그러나, buffer gets의 값과 비교하여 disk_reads의 비율이 높은 SQL은 Full Scan을 수행하는 SQL일 가능성이 큰데 그 이유는 Full Scan을 통해 액세스되는 블록들은 기본적으로 DB buffer Cache의 LRU 알고리즘에 의해 관리되지 않으므로 작업 후에 곧바로 메모리로부터 밀려나 버릴 가능성이 높기 때문이다. 반면에 인덱스를 통하여 액세스하는 경우, 일상적으로 액세스되는 테이블에 대해서는 인덱스의 root block과 branch block은 항상 메모리에 캐쉬 되어 있을 확률이 높으므로 물리적 IO를 유발하는 비율이 낮을 수 밖에 없다. 
■ Top SQL 추출기준
글을 맺기 전에 마지막으로 언급하고 싶은 것 하나는, 문제가 발생했을 때 문제의 원인이 bug로 인한 것일 가능성을 항상 염두에 두어야 헛된 고생을 덜한다는 것이다. 오라클도 사람이 만든 프로그램이므로 버그가 없을 수 없으나 다행히 오라클의 버그 및 패치 관리는 매우 훌륭한 편이다. 오라클 메타링크를 활용하여 유사한 문제가 보고된 적은 없는지 관련 버그에 대한 정보는 없는지 살펴보아야 하며, 평소에 정기적으로 패치 및 버전 관리를 해주는 것이 바람직하다.

오라클 시스템을 운영하다 보면 현실에서는 다양한 문제가 복잡하게 얽혀 나타나므로 명백한 원인을 파악하기가 쉽지 않을 때가 많지만, 운영 시 자신의 시스템에서 자주 발생하는 Wait 패턴 또한 분명히 존재하므로 굵은 가지들부터 하나씩 이해하고 해결해 나가다 보면 오라클이 우리가 보인 애정에 보답해줄 날이 올 것이라 믿는다. 지면 관계상 OPS(RAC) 관련 Wait을 비롯한 기타 Wait 이벤트에 대해 다루지 못한 점, 그리고 각 Wait 이벤트별로 좀더 친절한 설명과 사례를 제시하지 못한 점이 아쉬움으로 남지만, 누구든 아주 작은 것 하나라도 이 글을 통해 새로이 얻을 수 있었다면 그 이상 바랄 것은 없다.

'Oracle > DB' 카테고리의 다른 글

[Database] UNIX- 오라클 테이블 SAM 내리기  (0) 2016.08.09
[SQL] Oracle Built-in Function  (0) 2016.08.09
[Database] Session Level에서 Trace Level 주기  (0) 2016.08.09
[Tuning] Usage tkprof  (0) 2016.08.09
[Database] RMAN Command  (0) 2016.08.09
Posted by swanseo
Oracle/DB2016. 8. 9. 14:12
728x90

1. 숫자함수
ABS(n) : ABS함수는 절대값을 계산하는 함수입니다.
CEIL(n) : CEIL함수는 주어진 값보다는 크지만 가장 근접하는 최소값을 구하는 함수입니다.
EXP(n) : EXP함수는 주어진 값의 e의 승수를 나타냅니다.  e는 2.171828183..입니다.
FLOOR(n) : FLOOR함수는 주어진 값보다 작거나 같은 최대 정수값을 구하는 함수입니다. (CEIL 함수와 비교해 보세요. )
LN(n) : LN함수는 주어진 값의 자연로그 값을 반환합니다.
MOD(m, n) : MOD함수는 m을 n으로 나누어 남은 값을 반환한다. n이 0일 경우 m을 반환합니다.
POWER(m, n) : POWER함수는 m의 n승 값을 계산합니다.
ROUND(n, [m]) : ROUND함수는 n값의 반올림을 하는 함수로 m은 소숫점 아래 자릿수를 나타낸다.
SIGN(n) : SIGN함수는 n<0일 경우 -1DFM N=0일 경우 0을 N>0일 경우 1을 반환합니다.
SQRT(n) : SQRT함수는 n값의 루트값을 계산한다. n은 양수여야 합니다.
TRUNC(n, m) : TRUNC함수는 n값을 m 소숫점 자리로 반내림한 값을 반환합니다. (ROUND 함수와 비교해 보세요.)


2. 문자열 처리함수
CONCAT(char1, char2) : CONCAT 함수는 Concatenation의 약자로 두 문자를 결합하는 역할을 합니다. "||" 연산자와 같은 역할을 합니다.
INITCAP(char) : 주어진 문자열의 첫 번째 문자를 대문자로 변환시켜 줍니다.
LOWER(char) : 문자열을 소문자로 변환 시켜 줍니다.
UPPER(char) : 문자열을 대문자로 변환 시켜 줍니다.
LPAD(char1, n [,char2]) :왼쪽에 문자열을 끼어 놓는 역할을 합니다. n은 반환되는 문자열의 전체 길이를 나타내며, char1의 문자열이 n보다 클 경우 char1을 n개 문자열 만큼 반환합니다.
RPAD(char1, n [,char2]) : LPAD와 반대로 오른쪽에 문자열을 끼어 놓는 역할을 합니다.
SUBSTR(char, m ,[n]) : SUBSTR함수를 이용하여 m 번째 자리부터 길이가 n개인 문자열을 반환한 합니다. m이 음수일 경우에는 뒤에서 M번째 문자부터 반대 방향으로 n개의 문자를 반환합니다.
LENGTH(char1) : 문자열의 길이를 리턴 합니다.
REPLACE(char1, str1, str2) : REPLACE는 문자열의 특정 문자를 다른 문자로 변환 합니다.
INSTR : 문자열이 포함되어 있는지를 조사하여 문자열의 위치를 반환합니다. 지정한 문자열이 발견되지 않으면 0이 반환 됩니다.
TRIM : 특정한 문자를 제거 합니다. 제거할 문자를 입력하지 않으면 기본적으로 공백이 제거 됩니다.  리턴값의 데이터타입은 VARCHAR2 입니다.

 -- 두개의 문자를 연결한다.
SQL>SELECT CONCAT('Oracle', ' Korea') NAME FROM dual;
-- 문자좌측에 *를 붙인다. 문자 길이는 10개로 제한
SQL>SELECT LPAD('JUNG-SICK', 10, '*') NAME FROM dual;
-- 문자우측에 *를 붙인다. 문자 길이는 11개로 제한
SQL>SELECT RPAD('JUNG-SICK', 11, '*') NAME FROM dual;
-- 앞에서부터, 뒤에서부터 예제...
SQL>SELECT SUBSTR('JUNG-SICK', 3, 3) NAME FROM dual;
SQL>SELECT SUBSTR('JUNG-SICK', -3, 3) NAME FROM dual;
-- 문자의 길이를 구함
SQL>SELECT LENGTH('JUNG-SICK') TEST FROM dual;
-- 대소문자 구별합니닷!!
SQL>SELECT REPLACE('JACK and JUE','J','BL') "Changes" FROM dual;
SQL>SELECT REPLACE('JACK and JUE','j','BL') "Changes" FROM dual;
-- 지정한 문자 OK가 발견되지 않아서 0이 반환 됩니다.
SQL>SELECT INSTR('CORPORATE FLOOR','OK')  "Instring" FROM dual;
-- OR이 있는 위치 2를 반환 합니다. 왼쪽부터 비교를 한다는 것을 알 수 있습니다.
SQL>SELECT INSTR('CORPORATE FLOOR','OR')  "Instring" FROM dual;
-- 0을 제거 합니다.
SQL>SELECT TRIM(0 FROM 0009872348900)  "TRIM Example" FROM dual;
-- 어떤 문자도 입력하지 않으면 기본적으로 공백이 제거 됩니다.
-- TRIM을 사용한 위에 예제와 사용하지 않은 아래 예제의 결과 값이 다르게 나오는 것을 알 수 있습니다.
-- NVL(a,b) 함수는 a가 NULL일 경우 b로 바꿔주는 함수입니다.
SQL>SELECT NVL(TRIM ('  '),'공백')  "TRIM Example"  FROM dual;


3. 날짜 처리함수
LAST_DAY(d) : LAST_DAY함수는 달의 마지막 날의 날짜를 반환합니다.
ADD_MONTHS(a, b) : ADD_MONTHS 함수는 a의 날짜에 b의 달을 더한 값을 반환 합니다.
MONTH_BETWEEN(a1, a2) : MONTH_BETWEEN은 a1과 a2 사이의 달의 수를 NUMBER형 타입으로 반환 합니다.
ROUND(d[,F]) : ROUND 함수는 F에 지정된 단위로 반올림 합니다. F가 연도라면 연도 단위로 반올림 합니다.
 -- 오늘과 이달의 마지막날 반환
SQL>SELECT SYSDATE TODAY, LAST_DAY(SYSDATE) LASTDAY FROM dual;
-- 오늘에서 3개월을 더한 후, RRRR/MM/DD 형식으로 반환
SQL>SELECT TO_CAHR(ADD_MONTHS(SYSDATE,3),'YYYY/MM/DD') "date" FROM dual;
-- 두 날짜 사이의 개월수를 반환
SQL>SELECT MONTHS_BETWEEN(TO_DATE('2000/06/05') , TO_DATE('2000/09/23')) "Date" FROM dual;
-- '1998/09/11' 의 년도인 1998을 반환
SQL>SELECT ROUND(TO_DATE('1998/09/11'), 'YEAR') FROM dual;


4. 변환함수
TO_CHAR : TO_CHAR함수는 DATE형, NUMBER형을 VARCHAR2형으로 바꿔줍니다.
TO_DATE : TO_DATE함수는 CHAR, VARCHAR2형을 DATE 타입으로 변환합니다.
TO_NUMBER : TO_NUMBER함수는 CHAR, VARCHAR2의 데이터 타입을 숫자형식으로 변환합니다.

-- 오늘의 월만 반환
SQL>SELECT TO_CHAR(sysdate, 'MONTH') CHARTEST FROM dual;
-- 문자형이 날짜형으로 변환
SQL>SELECT TO_DATE('2000/06/16','YYYY/MM/DD') FROM dual;
-- '1210616'의 문자열이 숫자형으로 변환
SQL>SELECT TO_NUMBER('1210616') FROM dual;


1. 기타함수
NVL
- NVL 함수는 NULL값을 다른 값으로 바꿀 때 쓰입니다.
- 모든 데이터 타입에 적용 가능합니다.
- 전환되는 값의 데이터 타입을 일치시켜야 합니다.
DECODE
- DECODE 함수는 데이터 들을 다른 값으로 바꾸어 줍니다.
- 형식 DECODE(VALUE, IF1, THEN1, IF2, THEN2...)
- VALUE 값이 IF1일경우에 THEN1값으로 바꾸어 주고 VALUE값이 IF2일경우에는 THEN2값으로 바꾸어 줍니다.
DUMP : DUMP는 바이트 크기와 해당 데이터 타입 코드를 반환합니다.
GREATEST : GREATEST함수는 검색값 중에서 가장 큰 값을 반환 합니다.
LEAST : LEAST함수는 GREATEST함수와 반대로 가장 작은 값을 반환합니다.
UID : 현재 사용자의 유일한 ID번호를 반환 합니다.
USER : 현재 오라클을 사용하는 사용자를 VARCHAR2형식으로 반환 합니다.
USERENV : USERENV 함수는 현재 세션의 환경 정보를 반환합니다.
  - ENTRYID : 사용 가능한 Auditing entry Identifier를 반환합니다.
  - LABEL : 현재 세션의 Label을 반환합니다.
  - LANGUAGE : 현재 세션에서 사용중인 언어와 테리토리 값을 반환합니다.
  - SESSIONID : Auditing(감사) Session ID를 반환 합니다.
  - TERMINAL : 현재 세션 터미널의 OS ID를 반환 합니다.
VSIZE : 해당 문자의 BYTE수를 반환 합니다. 해당 문자가 NULL이면 NULL값이 반환 됩니다.

-- 컬럼 comm의 값이 NULL일 경우, 0을 반환합니다.
SQL>SELECT empno, NVL(comm, 0) FROM emp WHERE deptno = 30;
-- 컬럼 deptno의 값이 10이며 'ACCOUNTING'으로
-- 컬럼 deptno의 값이 20이며 'RESEARCH'으로
-- 컬럼 deptno의 값이 30이며 'SALES'으로
-- 컬럼 deptno의 값이 40이며 'OPERATIONS'으로
SQL>SELECT deptno, DECODE(deptno, 10 , 'ACCOUNTING', 20 , 'RESEARCH', 30 , 'SALES', 40 , 'OPERATIONS') FROM emp;
-- 만약에 16대신 8을 넣으면 8진수로, 10를 넣으면 10진수로 변환이 됩니다.
-- 16, 10, 8, 17이 올수 있는데요 17은 단일 문자열을 반환합니다.
-- 결과값 중, Len은 ename의 해당 byte수 입니다.
SQL>SELECT ename, DUMP(ename, 16) "16진수" FROM emp WHERE ename = 'ALLEN'
-- 가장 큰수/가장 작은수 반환
SQL>SELECT GREATEST(10, 100, 5, -7) FROM dual;
SQL>SELECT LEAST(10, 100, 5, -7) FROM dual;
-- 현재 사용자의 이름과 ID값을 반환
SQL> SELECT USER, UID FROM DUAL;
-- 현재 세션의 환경정보를 반환
SQL> SELECT USERENV('LANGUAGE') FROM dual;
-- 현재 문자의 byte 수를 반환
SQL> SELECT VSIZE(ename), ename FROM emp  WHERE deptno = 30;

'Oracle > DB' 카테고리의 다른 글

[Database] UNIX- 오라클 테이블 SAM 내리기  (0) 2016.08.09
[Database] Oracle Wait Event 모니터링  (0) 2016.08.09
[Database] Session Level에서 Trace Level 주기  (0) 2016.08.09
[Tuning] Usage tkprof  (0) 2016.08.09
[Database] RMAN Command  (0) 2016.08.09
Posted by swanseo
Oracle/DB2016. 8. 9. 14:12
728x90

alter session set events '10046 trace name context forever,level 12';

'Oracle > DB' 카테고리의 다른 글

[Database] Oracle Wait Event 모니터링  (0) 2016.08.09
[SQL] Oracle Built-in Function  (0) 2016.08.09
[Tuning] Usage tkprof  (0) 2016.08.09
[Database] RMAN Command  (0) 2016.08.09
[Database] Change Archive Mode  (0) 2016.08.09
Posted by swanseo
Oracle/DB2016. 8. 9. 14:12
728x90
Usage: tkprof tracefile outputfile [explain= ] [table= ] [print= ] [insert= ] [sys= ] [sort= ] table=schema.tablename Use 'schema.tablename' with 'explain=' option. explain=user/password Connect to ORACLE and issue EXPLAIN PLAN. print=integer List only the first 'integer' SQL statements. aggregate=yes|no insert=filename List SQL statements and data inside INSERT statements. sys=no TKPROF does not list SQL statements run as user SYS. record=filename Record non-recursive statements found in the trace file. waits=yes|no Record summary for any wait events found in the trace file. sort=option Set of zero or more of the following sort options: prscnt number of times parse was called prscpu cpu time parsing prsela elapsed time parsing prsdsk number of disk reads during parse prsqry number of buffers for consistent read during parse prscu number of buffers for current read during parse prsmis number of misses in library cache during parse execnt number of execute was called execpu cpu time spent executing exeela elapsed time executing exedsk number of disk reads during execute exeqry number of buffers for consistent read during execute execu number of buffers for current read during execute exerow number of rows processed during execute exemis number of library cache misses during execute fchcnt number of times fetch was called fchcpu cpu time spent fetching fchela elapsed time fetching fchdsk number of disk reads during fetch fchqry number of buffers for consistent read during fetch fchcu number of buffers for current read during fetch fchrow number of rows fetched userid userid of user that parsed the cursor

---------------

[Sample]

tkprof erpdev_ora_7893168_8703122.trc erpdev_ora_7893168_8703122.prf explain=etrade/etrade sort='exeela,fchela,prsela'
tkprof stxerp_ora_16781534.trc stxerp_ora_16781534.prf explain=apps/apps sort="prsela,exeela,fchela"
tkprof a.trc a.prf explain=apps/apps sort="prsela,exeela,fchela"


alter system set "_optimizer_push_pred_cost_based"=false
alter session set "_unnest_subquery"=FALSE


alter session set "_optimizer_connect_by_cost_based"=true; 


'Oracle > DB' 카테고리의 다른 글

[SQL] Oracle Built-in Function  (0) 2016.08.09
[Database] Session Level에서 Trace Level 주기  (0) 2016.08.09
[Database] RMAN Command  (0) 2016.08.09
[Database] Change Archive Mode  (0) 2016.08.09
[SQL] PL/SQL WRAPPER (PL/SQL 소스 암호화)  (0) 2016.08.09
Posted by swanseo
Oracle/DB2016. 8. 9. 14:10
728x90

RMAN


$ rman TARGET /


[ CONFIGURE ]

http://cafe.daum.net/_c21_/bbs_search_read?grpid=1Njrd&fldid=FrKk&datanum=10&docid=1NjrdFrKk1020110816154200

RMAN> SHOW ALL;

# 보유정책(retention policy)
RMAN> CONFIGURE RETENTION POLICY TO RECOVERY WINDOW OF 3 DAYS; # recovery window 옵션을 사용하는 보유정책(retention policy)의 설정: 3일이상 지난 RMAM 백업은 데이터베이스 복구를 위해 저장 해야 한다는 것을 의미한다
RMAN> CONFIGURE RETENTION POLICY TO REDUNDANCY 7; # redundancy 옵션을 사용하는 보유정책(retention policy)의 설정: 7일이상 지난 RMAN 백업은 더이상 저장할 필요가 없다
RMAN> REPORT OBSOLETE; # 보유정책 확인
RMAN> DELETE OBSOLETE; # 보유정책 위반 하는 백업제거
RMAN> CONFIGURE RETENTION POLICY CLEAR; # 기존에 설정된 보유정책 제거
RMAN> CONFIGURE BACKUP OPTIMAIZATION ON; # 이미백업한 파일이 존재할경우 백업하지 않는다



[ BACKUP ]

RMAN> BACKUP AS BACKUPSET FULL DATABASE PLUS ARCHIVELOG TAG 'SIDM_RCU';
RMAN> BACKUP CURRENT CONTROLFILE TAG 'SIDM_RCU';



[ RESTORE / RECOVERY ]

RMAN> STARTUP FORCE MOUNT; # MOUNT 단계
RMAN> VALIDATE DATABASE;    # 데이터베이스의 유효성 검사
RMAN> REPORT SCHEMA;    # 데이터베이스 파일의 확인
RMAN> RESTORE DATABASE PREVIEW SUMMARY;    # 복구 준비
RMAN> RESTORE DATABASE;   # 전체 백업 데이터를 대상 데이터베이스로 복사
RMAN> RECOVER DATABASE;   # 복사된 백업 데이터를 복구함
RMAN> ALTER DATABASE OPEN RESETLOGS;

RMAN> LIST BACKUP SUMMARY;
RMAN> LIST BACKUPSET;
RMAN> LIST BACKUPSET OF CONTROLFILE;
RMAN> LIST BACKUP OF DATABASE BY BACKUP;
RMAN> DELETE BACKUPSET <숫자>; # Delete BackupSet
RMAN> DELETE BACKUPSET TAG=''; # Delete BackupSet by TAG
delete expired backupset;
delete noprompt expired backupset;
delete noprompt ARCHIVELOG UNTIL TIME 'SYSDATE-1'


RMAN> CROSSCHECK BACKUPSET; 전체 backupset 과 target 서버에 저장된 backupset의 정보 비교하기


RMAN> RESTORE CONTROLFILE;


*******************************************************************************************
DB 생성 후 다음과 같이 하지 않으면 backupset이 Filesystem에 저장되어 Restore 시에 문제 발생.
SQL> ALTER SYSTEM SET DB_CREATE_FILE_DEST='+DATA_KRV2' SID='*';
SQL> ALTER SYSTEM SET DB_RECOVERY_FILE_DEST_SIZE=100G SID='*';
SQL> ALTER SYSTEM SET DB_RECOVERY_FILE_DEST='+RECO_KRV2' SID='*';

---------------

http://radiocom.kunsan.ac.kr/lecture/oracle/backup_restore/rman.html

백업카피 파일을 사용한 데이터베이스 복구
http://radiocom.kunsan.ac.kr/lecture/oracle/backup_restore/backup_as_copy_restore.html

1) 백업카피가 저장되어질 위치를 변경한다.
RMAN> configure channel device type disk format '/export/home0/oracle/backup/bk_%t_%s';
+DATA_KRV2/sidm

2) disk backup type을 기본적으로 copy로 설정한다.
RMAN> configure device type disk backup type to copy;

3) parallelism 옵션을 1보다 크게 설정한다.
RMAN> configure device type disk parallelism 3;

4) backup 명령을 사용하여 백업카피를 생성하는 과정을 수행한다.
RMAN> BACKUP AS COPY database;

5) switch database to copy 명령의 실행
RMAN> shutdown immediate
RMAN> startup mount
RMAN> switch database to copy;


[ DROP DATABASE ]

RMAN> CONNECT TARGET /
RMAN> STARTUP FORCE MOUNT
RMAN> SQL 'ALTER SYSTEM ENABLE RESTRICTED SESSION';
RMAN> DROP DATABASE INCLUDING BACKUPS NOPROMPT;

------------------------

http://ss64.com/ora/rman_backup.html

RMAN Backup Clause

Back up database files, archive logs, backups, or copies.

Syntax:

   BACKUP FULL Options
   BACKUP FULL AS (COPY | BACKUPSET) Options
   BACKUP INCREMENTAL LEVEL [=] integer Options
   BACKUP INCREMENTAL LEVEL [=] integer AS (COPY | BACKUPSET) Options
   BACKUP AS (COPY | BACKUPSET) Options
   BACKUP AS (COPY | BACKUPSET) (FULL | INCREMENTAL LEVEL [=] integer) Options

Options:
   [backupOperand [backupOperand]…]
      backupSpec [backupSpec]…
        [PLUS ARCHIVELOG [backupSpecOperand [backupSpecOperand]…]];

backupOperand::=
   { FORMAT [=] 'format_string' [, 'format_string']…
   | CHANNEL ['] channel_id [']
   | CUMULATIVE
   | MAXSETSIZE [=] integer [ K | M | G ]
   | TAG [=] ['] tag_name [']
   | keepOption
   | SKIP { OFFLINE | READONLY | INACCESSIBLE }
   | VALIDATE
   | NOT BACKED UP [SINCE TIME [=] 'date_string']
   | COPIES [=] integer
   | DEVICE TYPE deviceSpecifier
   .
   .
   .
   }

backupSpec::=
   [(]
   { BACKUPSET
     { {ALL | completedTimeSpec }
     | primary_key) [, primary_key]…
     }
   | COPY OF { DATABASE
             | TABLESPACE ['] tablespace_name ['] [, ['] tablespace_name
   [']]…
             | DATAFILE datafileSpec [, datafileSpec]…
             }
   | DATAFILE datafileSpec [, datafileSpec]…
   | DATAFILECOPY 'filename' [, 'filename']…
   | DATAFILECOPY FROM TAG [=] ['] tag_name ['] [, ['] tag_name [']]…
   | DATAFILECOPY { ALL | LIKE 'string_pattern' }
   | TABLESPACE ['] tablespace_name ['] [, ['] tablespace_name [']]…
   | DATABASE
   | archivelogRecordSpecifier
   | CURRENT CONTROLFILE [FOR STANDBY]
   | CONTROLFILECOPY 'filename'
   | SPFILE
   }
   [backupSpecOperand [backupSpecOperand]…]

backupSpecOperand::=
   { FORMAT [=] 'format_string' [, 'format_string']…
   | CHANNEL ['] channel_id [']
   | CUMULATIVE
   | MAXSETSIZE [=] integer [ K | M | G ]
   | TAG [=] ['] tag_name [']
   | keepOption
   | SKIP { OFFLINE | READONLY | INACCESSIBLE }
   | NOT BACKED UP [ SINCE TIME [=] 'date_string'
                   | integer TIMES
                   ]
   | DELETE [ALL] INPUT
   .
   .
   .
   }
   .

You should configure default devices and channels in advance of running RMAN Backup.

Examples

Back up the database, and then the control file:
 (which contains a record of the backup)
 RMAN> BACKUP DATABASE;
 RMAN> BACKUP CURRENT CONTROLFILE;

Backup datafiles:
 RMAN> BACKUP AS BACKUPSET DATAFILE
        'ORACLE_HOME/oradata/trgt/users01.dbf',
        'ORACLE_HOME/oradata/trgt/tools01.dbf';

Backup all datafiles in the database:
  (bit-for-bit copies, created on disk)
 RMAN> BACKUP AS COPY DATABASE;

Backup archive logs:
 RMAN> BACKUP ARCHIVELOG COMPLETION TIME BETWEEN  'SYSDATE-28' AND 'SYSDATE-7';

Backup tablespace:
 RMAN> BACKUP TABLESPACE system, users, tools;

Backup controlfile:
 RMAN> BACKUP CURRENT CONTROLFILE TO '/backup/cntrlfile.copy';

Backup parameter file:
 RMAN> BACKUP SPFILE;

Backup everything:
 RMAN> BACKUP BACKUPSET ALL;

Create a consistent backup and keep the backup for 1 year:
 (exempt from the retention policy)
 RMAN> SHUTDOWN;
 RMAN> STARTUP MOUNT;
 RMAN> BACKUP DATABASE UNTIL 'SYSDATE+365' NOLOGS;

Backup Validation confirms that a backup could be run, by confirming that all database files exist and are free of physical and logical corruption, this does not generate any output.
Example:

 RMAN> BACKUP VALIDATE DATABASE ARCHIVELOG ALL;

"It's a very sobering feeling to be up in space and realize that one's safety factor was determined by the lowest bidder on a government contract" - Alan Shepherd

-------------
cd $ORACLE_HOME/oui/bin
./runInstaller -silent -detachHome ORACLE_HOME="/u01/DBMS/EIDM/product/11.2.0" ORACLE_HOME_NAME="OraDb11g_home2"

------------------------------------------------------

INCREMENTAL MERGE BACKUP & RECOVERY
=====================================

1) 개요

RMAN을 이용하여 database의 Image copy를 Backup하고 그 Backup에 retention policy만큼의 Incremental Backup을 적용하여서 복구의 시간을 단축한 Backup의 방법입니다.
즉 retention policy에 설정되어있는 시간 전의 최후의 시간까지 Image Copy를 보장하는 방법이며 Disk Space는 Incremental Backup만 사용하는 방법보다는 Image Copy를 사용하는 것임으로 많이 소요됩니다.
물론 Backup & Recovery 정책에 따라서 Retention Policy를 Recovery Window나 Redundancy 2이상으로 설정할 수 있으나, Incremental Merge Backup & Recovery의 장점을 극대화 하기 위해서는 Redundancy 1 (Default)로 설정하는 것을 권해드립니다.


2) 장점

- Recovery 시에 최근 Incremental Backup의 정보들이 적용이 되어있기 때문에 최소한의 시간으로 Recovery 작업을 진행하실 수 있습니다. 물론 장점을 극대화 하시기 위해서는 Retention Policy를 redundancy 1로 설정해 주시길 권해드립니다.
- Datafile들의 Destination을 Backup된 Image Copy가 있는 곳으로 Switch 하여 Restore하는 시간을 단축하셔서 Recovery를 진행하실 수도 있으나 Recovery 후의 관리의 용이성을 위해 부득히한 경우를 재외하고는 권장하지 않습니다.

3) Syntax

- Backup
RMAN> BACKUP INCREMENTAL LEVEL 1
FOR RECOVER OF COPY
WITH TAG WEEKLY DATABASE;

위 명령어를 사용하시면 WEEKLY라는 Tag로 생성된 Copy가 없을 시에는 Image Copy를 Weekly라는 Tag로 구분하여 Backup을 받게 됩니다.
* 여기서 Copy라 함은 FOR RECOVER OF COPY라는 구문을 사용하여서 만든 Image Copy를 뜻합니다.

RMAN> RECOVER COPY OF DATABASE WITH TAG WEEKLY;

위 명령어를 사용하시면 현재 받았던 Incremental Backup을 Weekly라는 Tag를 가지고 있는 Database Copy에 적용을 하게 됩니다.

RMAN> Delete Obsolete;

위 명령어를 사용하시면 현재의 Image Copy에 적용된 Incremental Backup을 재외한 나머지 Incremental Backup들이 Delete됩니다.

-Recovery

RMAN> switch database to copy;

문제가 발생하셨을 때 위 명령어를 사용하시면 모든 datafile의 pointer들은 image backup copy를 바라보게 됨으로 Restore의 시간을 절약할 수 있습니다.


4) Retention Policy에 따른 Syntax 변화

* 기본적으로 INCREMENTAL MERGE BACKUP & RECOVERY의 장점인 Recovery 시간을 극대화 하기 위해서는 Retention Policy를 redundancy 1(Default)로 설정하시는 것을 권장해드립니다.

하지만 User가 Backup & Recovery 계획을 변경하여 retention policy를 recovery window 또는 redundancy 1 이상으로 설정해 주시면 Obsolete한 Backupset을 Delete하는 부분과 Backupset을 적용시키는 부분을 수정해 주셔야합니다.

- Recovery Window of 2

Recovery Window를 2일로 설정해 놓으시면 Backup시에는 똑같은 Syntax를 사용하실 수 있으나 Recover copy of database 명령어는 아래와 같이 바뀌어야 합니다.

RMAN> BACKUP INCREMENTAL LEVEL 1
FOR RECOVER OF COPY
WITH TAG WEEKLY DATABASE;
RMAN> RECOVER COPY OF DATABASE WITH TAG WEEKLY until time 'sysdate-2';

왜냐하면 retention policy에 마춰서 image copy도 2일전의 상태로 보관되어야하며 그에 따라서 2일동안의 Backupset들도 보관되어야하기 때문입니다. 만약 until time을 사용하지 않으시면 incremental backup들이 obsolete 상태로 안변하게 됩니다.

- Redundancy 2 이상

Redundancy 2이상으로 설정해 주시면 2개 이상의 Tag를 보관하는 효과로서 역시 예상대로 Obsolete로 안 변하는 현상이 발생됩니다.

DAY 1)
RMAN> BACKUP INCREMENTAL LEVEL 1 FOR RECOVER OF COPY WITH TAG DAILY DATABASE;

-위와 같이 실행하시면 한개의 Database Copy (Tag DAILY)가 생성됩니다.

DAY 2)
RMAN> BACKUP INCREMENTAL LEVEL 1 FOR RECOVER OF COPY WITH TAG DAILY DATABASE;
RMAN> BACKUP INCREMENTAL LEVEL 1 FOR RECOVER OF COPY WITH TAG WEEKLY DATABASE;

-위와 같이 실행하시면 한개의 Database Copy (Tag WEEKLY)와 한개의 Backupset (Tag DAILY)가 생성됩니다.

DAY 3)
RMAN> BACKUP INCREMENTAL LEVEL 1 FOR RECOVER OF COPY WITH TAG DAILY DATABASE;
RMAN> BACKUP INCREMENTAL LEVEL 1 FOR RECOVER OF COPY WITH TAG WEEKLY DATABASE;
RMAN> BACKUP INCREMENTAL LEVEL 1 FOR RECOVER OF COPY WITH TAG MONTHLY DATABASE;

-위와 같이 실행하시면 한개의 Database Copy (Tag MONTHLY)와 두개의 Backupset (Tag DAILY, Tag WEEKLY)가 생성됩니다.

DAY 4)
RMAN> Delete Obsolete;

- 위와 같이 실행하시면 DAILY Tag와 관련된 부분이 Delete 됩니다. (최신 2개의 Tag관련 Backup만 보관)

RMAN> Delete Obsolete redundancy 1

- 위와 같이 실행하시면 DAYLY Tag와 WEEKLY Tag 관련된 부분이 Delete 됩니다. (Redundancy 1로 설정한 것과 똑같은 효과)

Reference:

Article-ID: Note 351455.1
Title: Oracle Suggested Strategy & Backup Retention

Article-ID: Note 303861.1
Title: Incrementally Updated Backup In 10G

--------------

13 Creating and Updating Duplicate Databases with RMAN
http://docs.oracle.com/cd/B19306_01/backup.102/b14191/rcmdupdb.htm#sthref943

Posted by swanseo
Oracle/DB2016. 8. 9. 14:10
728x90

SQL> ARCHIVE LOG LIST
Database log mode              No Archive Mode
Automatic archival             Disabled
Archive destination            +RECO
Oldest online log sequence     1
Current log sequence           1

SQL> STARTUP NOMOUNT

SQL> ALTER SYSTEM SET LOG_ARCHIVE_START=TRUE SCOPE=SPFILE;

SQL> ALTER SYSTEM SET LOG_ARCHIVE_FORMAT='%t_%s_%r.arc' SCOPE=SPFILE;

SQL> SHUTDOWN IMMEDIATE

SQL> STARTUP MOUNT

SQL> ALTER DATABASE ARCHIVELOG;

SQL> ALTER DATABASE OPEN;

SQL> ARCHIVE LOG LIST
Database log mode              Archive Mode
Automatic archival             Enabled
Archive destination            +RECO
Oldest online log sequence     1
Next log sequence to archive   1
Current log sequence           1

SQL> ALTER SYSTEM SWITCH LOGFILE;

ASMCMD [+RECO/PRD/ARCHIVELOG/2014_07_16] > ls -l
Type        Redund  Striped  Time             Sys  Name
ARCHIVELOG  MIRROR  COARSE   JUL 16 10:00:00  Y    thread_1_seq_1.258.853066651

'Oracle > DB' 카테고리의 다른 글

[Tuning] Usage tkprof  (0) 2016.08.09
[Database] RMAN Command  (0) 2016.08.09
[SQL] PL/SQL WRAPPER (PL/SQL 소스 암호화)  (0) 2016.08.09
[Database] Snapshot too old 회피 방법  (0) 2016.08.09
[Tuning] View 사용에서의 힌트사용  (0) 2016.08.09
Posted by swanseo
Oracle/DB2016. 8. 9. 14:09
728x90

PL/SQL Wrapper

PL/SQL procedure, function, trigger 는 Data dictionary 에 저장이 된다.
그러므로 어떤 유저든 해당 procedure, function, trigger 의 source 를 조회하고 edit 하여 변경할 수 있게 된다.
PL/SQL Wrapper 는 Security Utility 라고 할 수 있다.
위에서 언급한 단점을 보완하기 위하여 oracle 에서 제공하는 Security Utility 이다.
PL/SQL Wrapper 는 PL/SQL source code 를 hexadecimal digits 로 convert 한다.
convert 된 source 자체로 procedure compile 이 가능하다.
또한 user_source view 에도 hexadecimal digits 로 보게된다.



ADVANTAGES:

  1. source code 를 확인할 수 없으므로 오용을 막는다.
  2. Wrapped source code 는 portable 하다.
  3. Platform independent 하다.
  4. Import/Export 에서 Wrapped file 을 지원한다.


Syntax :

사용법은 다음과 같다.
  • unix 의 경우 : wrap 이란 파일이 $ORACLE_HOME/bin 에 위치.
  • client 의 경우 : wrap(n) - n 은 version number -- 이란 파일이 $ORACLE_HOME/bin 에 위치
WRAP80 INAME= input_filename [ONAME= output_filename]
예) wrap23 iname=dept.sql



Input and Output file content:

PL/SQL wrapper 는 반드시 다음 문장중에 하나로 시작되는 파일이어야만 정상적으로wrapping 이 되며 만약 그렇지 않다면 에러가 발생하거나 wrapping 없이 output 파일을 생성하게 된다.
CREATE [OR REPLACE] PACKAGE
CREATE [OR REPLACE] PACKAGE BODY
CREATE [OR REPLACE] FUNCTION
CREATE [OR REPLACE] PROCEDURE
  • 일반적으로 output 파일이 input 파일보다 크기가 증가한다.
  • 디폴트 output 파일의 확장자는 .plb 이다.
  • 일단 wrapped output 파일은 edit 할 수 없다.
  • 모든 DBMS Package 가 사용가능하다. (web toolkit 용 package 는 사용불가)


Sample

input file = dept.sql
CREATE OR REPLACE PROCEDURE dept_sel IS
  t_dept varchar2(8);
BEGIN
  select dname into t_dept from dept
  where loc='BOSTON';
END;
output file = dept.plb
CREATE OR REPLACE PROCEDURE dept_sel wrapped 



0
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
7
95a025ff
1
4
0
9
8 DEPT_SEL:
6 T_DEPT:
8 VARCHAR2:
1 8:
5 DNAME:
4 DEPT:
3 LOC:
1 =:
6 BOSTON:
0


0
0
25
2
0 1d 9a a3 b4 55 6a a0
51 a5 1c 81 b0 a0 ac :2 a0
b2 ee a0 7e 6e b4 2e ac
e5 d0 b2 e9 b7 a4 b1 11
68 4f 17 b5
25
2
0 3 c 31 7 19 1d 21
4 25 26 2d b 38 a 3c
40 44 45 4c 50 53 57 58
5d 5e 63 67 68 8 6d 71
73 7e 82 84 8b
25
2
0 1 b 1 0 :2 1 9 12
11 :2 9 1 :2 b 16 22 1d 22
b e f :2 e 1d :4 4 :8 1
25
2
0 :2 1 3 0 :2 1 :6 3 :6 6 :5 7
:5 6 :2 5 :6 1
8d
2
:4 0 4 :3 0 10 20 7 5 1
:a 0 21 1 4 :2 0 21 2 5
:2 0 3 :3 0 3 7 9 :5 0 c
a 0 1f 2 :6 0 5 :3 0 2
:3 0 6 :3 0 9 11 17 0 18
:3 0 7 :3 0 8 :2 0 9 :3 0 d
14 16 :4 0 1a 1b :4 0 e 12
0 b 0 19 :2 0 1d :3 0 20
12 20 1f 1d 1e :6 0 21 0
2 5 20 23 :2 0 1 21 24
:6 0
14
2
:3 0 1 8 1 3 1 d 1
10 1 f 2 13 15 1 1c
1 b
1
4
0
23
0
1
14
1
2
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0
2 0 1
3 1 0
0


/


'Oracle > DB' 카테고리의 다른 글

[Database] RMAN Command  (0) 2016.08.09
[Database] Change Archive Mode  (0) 2016.08.09
[Database] Snapshot too old 회피 방법  (0) 2016.08.09
[Tuning] View 사용에서의 힌트사용  (0) 2016.08.09
[Database] FGA(Fine Grained Auditing)  (0) 2016.08.09
Posted by swanseo
Oracle/DB2016. 8. 9. 14:08
728x90

Snapshot too old 회피 방법

  • 과거 버전에는 UNDO 세그먼트를 수동으로 관리 했으므로 Snapshot too old 에러 발생을 최소화하기 위해 System Application 특성에 맞는 UNDO 튜닝을 DBA가 했음
  • 9i부터 AUM( Automatic Undo Management)이 도입돼 UNDO segment 크기와 개수를 오라클이 동적으로 자동 조절해 주기 때문에 Snapshot too old에러가 발생할 가능성도 줄었음
  • Snapshot too old에러 발생 가능성을 줄이기 위한 Application 측변에서의 솔루션

1. 불필요한 커밋을 자주 수행하지 않는다.
2. fetch across commit 형태의 프로그램 작성을 피해 다른 방식으로 구현현다. ANSI 표준에 따르면 커밋 이전에 열려 있던 커서는 더는 Fetch하면 안 된다.
다른 방식으로 구현하기 어렵다면 커밋 횟수를 줄여본다.
3. 트랜잭션이 몰리는 시간대에 오래 걸리는 쿼리가 같이 수행되지 않도록 시간을 조정한다.
4. 큰 테이블을 일정 범위로 나누어 읽고 단계적으로 실행 할 수 있도록 코딩한다. Snapchot too old 발생 가능성을 줄일 뿐 아니라 문제가 발생했을 때 특정 부분부터 다시 시작할 수 있어 유리하다. 물론 그렇게 해도 읽기 일관성에 문제가 없을때에만 적용해야 한다.
5. 오랜 시간에 걸쳐 같은 블록을 여러번 방문하는 Nested loop형태의 조인문 또는 인덱스를 경유한 테이블 액세스를 수반하는 프로그램이 있는지 체크하고 이를 회피할 수 있는 방법을( 조인 메소드 변경, Full Table Scan등)을 찾는다.
6. 소트 부하를 감수하더라도 order by 등을 강제로 산입해 소트 연산이 발생하도록 한다. 많은 데이터를 오랜 시간에 걸쳐 Fetch하는 동안 Undo 정보를 지속적으로 참조하기 때문에 문제가 발생하는 것이므로, 서버내에서 빠르게 데이터를 읽어 Temp 세그먼트에 저장하는 데에만 성공하면 이후에는 같은 블록을 아무리 재방문 하더라도 더는 에러가 발생할까 걱정하지 않아도 된다.
7. 만약 delayed 블록 클린아웃에 의해 Snspshot too old가 발생하는 것으로 의심되면 대량 업데이트 후에 곧바로 해당 테이블에 대해 Full Scan 하도록 쿼리를 날리는 것도 하나의 해결 방법이 될 수 있다. 인덱스 블록에서 문제가 발생한다고 되면 인덱스 리프 블록을 모두 스캔하도록 쿼리한다.


출처: http://www.gurubee.net/display/DBSTUDY/Snapshot+too+old

'Oracle > DB' 카테고리의 다른 글

[Database] Change Archive Mode  (0) 2016.08.09
[SQL] PL/SQL WRAPPER (PL/SQL 소스 암호화)  (0) 2016.08.09
[Tuning] View 사용에서의 힌트사용  (0) 2016.08.09
[Database] FGA(Fine Grained Auditing)  (0) 2016.08.09
[Database] LogMiner - logmnr  (0) 2016.08.09
Posted by swanseo
Oracle/DB2016. 8. 9. 14:07
728x90
-- 1. 뷰를 액세스하는 쿼리
SELECT EDV.EMPNO
FROM    EMP_DEPT_V EDV
WHERE  EDV.DEPTNO = 20;
-- 2. EMP_DEPT_V
SELECT E.EMPNO
           , E.ENAME
           , D.DEPTNO
           , D.DNAME
FROM    EMP E, DEPT D
WHERE  E.DEPTNO = D.DEPTNO;
이렇게 EMP_DEPT_V가 있을 때 이 뷰를 액세스하는 쿼리가 있다고 가정해 보겠습니다.
이 때, 1번처럼 쿼리를 사용했는데 우리가 원하는 실행계획으로 풀리지 않을 때 뷰에 힌트를
주고 싶을 때가 있습니다.
이럴 경우 대부분의 개발자들은 뷰 안에 원하는 힌트를 주어 컴파일 한뒤 사용을 하고 있습니다. 하지만 이렇게 뷰에 힌트를 넣어 변경할 경우 이 뷰를 사용하는 프로그램이 1번쿼리가 사용되는 프로그램에서만 사용되면 문제가 없겠지만 이 뷰를 참조하는 프로그램이 100개라고 한다면 나머지 99개는 비효율적인 실행계획을 생성할 확률이 높아집니다.
그렇지만 1번쿼리가 사용되는 곳에서는 반드시 이 힌트를 사용해야 하는 경우가 발생할경우, 이를 해결하기 위해 어떻게 해야할까요?
바로 Select-List에서 Alias.Column처럼 접근하듯이 힌트도 이렇게 접근할 수가 있습니다.
예를들어 /*+ LEADING(D) */ 힌트를 주어 DEPT 테이블부터 드라이빙이 되도록 하려고 합니다.
이를 뷰 안에 하지 않고 뷰를 사용하는 쿼리에다가 주고자 할 때는 다음과 같이 사용하시면 됩니다.
SELECT /*+ LEADING(EDV.D) */             EDV.EMPNO
FROM    EMP_DEPT_V EDV
WHERE  EDV.DEPTNO = 20;
이렇게 하면 힌트의 내용이 해당 뷰 안에까지 침투하여 원하는 실행계획을 유도할 수 있습니다.
이처럼 Alias.Alies... 로 접근을 하면 가장 큰 장점은 뷰를 사용할 때 해당 뷰를 수정하지 않고도 해당 화면에 최적화된 실행계획을 유도할 수 있다는 것입니다. 즉, 뷰를 여러곳에서 사용하지만 각각의 사용되는 곳에서 목적에 맞게 뷰를 컨트롤 할 수 있다는 뜻이죠.
인덱스의 경우는 해당 테이블을 참조하는 화면이 100개일 경우 이 100개에서 액세스되는 패턴을 모두 분석해서 최소공배수의 인덱스 전략을 세워야 하지만 힌트는 Objects로 생성하지 않고도 해당 어플리케이션에서 필요할 때마다 호출하여 사용할 수 있는게 가장 큰 매력입니다.
또 하나의 장점은 뷰의 Depth에 제한이 없다는 것입니다.
예를 들어, 뷰 -> 뷰 -> 뷰 -> 뷰 -> 뷰 처럼 5단계로 Depth가 있는 뷰일 경우 이 5단계 아래의 뷰를 호출하려면 계속 Alias.Alias... 처럼 접근하여 핸들링하면 컨트롤이 가능합니다.


Posted by swanseo