블로그 이미지
redkite

카테고리

분류 전체보기 (291)
00.SI프로젝트 산출물 (0)
00.센터 운영 문서 (0)
01.DBMS ============.. (0)
01.오라클 (117)
01.MS-SQL (15)
01.MySQL (30)
01.PostgreSql (0)
01.DB튜닝 (28)
====================.. (0)
02.SERVER ==========.. (0)
02.서버-공통 (11)
02.서버-Linux (58)
02.서버-Unix (12)
02.서버-Windows (2)
====================.. (0)
03.APPLICATION =====.. (11)
====================.. (0)
04.ETC =============.. (0)
04.보안 (5)
====================.. (0)
05.개인자료 (1)
06.캠핑관련 (0)
07.OA관련 (1)
Total
Today
Yesterday

달력

« » 2024.5
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

공지사항

최근에 올라온 글

'01.DB튜닝'에 해당되는 글 28건

  1. 2013.02.20 [오라클]SQL_PROFILE을 통한 비효율SQL 문장 plan fix
  2. 2013.02.20 [오라클]AWR Report 생성 및 분석
  3. 2012.12.19 [DB튜닝]Cursor PIN S Wait On X 대기 이벤트
  4. 2012.12.19 [DB튜닝]AWR 활용한 정보 얻기
  5. 2012.12.19 [DB튜닝]DBMS_XPLAN 이용한 정보 얻기
  6. 2012.12.19 [DB튜닝]AWR을 이용한 튜닝 대상 SQL 추출
  7. 2012.12.19 [DB튜닝]튜닝 로드맵
  8. 2012.12.19 [오라클]2-2단계 Redo 로그 파일
  9. 2012.12.19 [오라클]3-3단계 Lock의 경합
  10. 2012.12.19 [오라클]3-2단계 Temp 세그먼트
  11. 2012.12.19 [오라클]3-1단계 Undo 세그먼트
  12. 2012.12.19 [오라클]2-3단계 아카이브 파일
  13. 2012.12.19 [오라클]2-1단계 데이터 파일
  14. 2012.12.19 [오라클]1-5단계 래치와 경합
  15. 2012.12.19 [오라클]1-4단계 자바풀 영역
  16. 2012.12.19 [오라클]1-3단계 로그버퍼 영역
  17. 2012.12.19 [오라클]1-2단계 버퍼캐쉬 영역
  18. 2012.12.19 [오라클]1-1단계 공유풀 영역
  19. 2012.12.19 [오라클]네트워크 튜닝 – 시스템의 과부화
  20. 2012.12.19 [오라클]네트워크 튜닝 – 세션 수의 제한
  21. 2012.12.19 [오라클]네트워크 튜닝 – 프로세스의 경합
  22. 2012.12.19 [오라클]네트워크 튜닝 – 서버프로세스의 활성화 지연
  23. 2012.12.19 [오라클]Log File Sync 튜닝 포인트
  24. 2012.12.19 [오라클]SQL 튜닝-SELECT 문의 성능저하
  25. 2012.12.19 [오라클]ORA-01467 처리
  26. 2012.12.19 [오라클]INDEX 제대로 사용하자!!
  27. 2012.12.19 [오라클]센터 쿼리 튜닝 히스토리 - 1
  28. 2012.12.19 [DB튜닝]서비스 로직 흐름을 변경하여 DB 튜닝

10g부터 소개된 SQL_PROFILE의 기능에 대한 소개를 하고자 한다.


SQL_PROFILE이란, 특정 SQL_ID의 실행계획이 비효율적으로 생성될 경우, SQL 구문 변경 없이 실행계획만 변경시키거나 고정시키기 위해 사용한다.

일반적으로 SQL 튜닝을 하기 위해서는 아래와 같은 절차를 통해 개선을 한다.
  - 튜닝 대상 SQL에 대한 구문 변경
  - 튜닝 대상 SQL에 대한 힌트 사용
  - 튜닝 대상 프로그램에 대한 로직 변경
  - 필요할 경우 인덱스 생성  

문제는 위와 같은 튜닝을 하기 위해서는 DB Object 변경이 수반되어야 한다는 점이다.
운영 상태에서 DB Object 변경은 상당한 Risk(Object Invalidation, Re-Parsing 등)를 가지고 있기 때문에, 해당 이슈를 처리하다가 장애까지 이어질 수 있기 때문에 이와 같은 튜닝을 적용하기에는 무리가 있다.

이 경우, SQL 구문 변경 없이(Object 변경 없이) 원하는 실행계획을 유도할 경우, 긴급하게 이슈 대응이 가능하기 때문에 튜너라면 반드시 이 기능을 숙지하고 있어야 한다.

1. SQL_PROFILE 사용 예제


1-1) 오라클 버전 확인

SELECT * FROM V$VERSION;

 

BANNER                                                              

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

Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit    

PL/SQL Release 10.2.0.3.0 - Production                              

 

1-2) 테이블 생성

DROP TABLE DEPT;

 

CREATE TABLE DEPT

       (DEPTNO NUMBER(2),

        DNAME VARCHAR2(14),

        LOC VARCHAR2(13) );

 

1-3) 데이터 생성

INSERT INTO DEPT VALUES (10, 'ACCOUNTING', 'NEW YORK');

INSERT INTO DEPT VALUES (20, 'RESEARCH',   'DALLAS');

INSERT INTO DEPT VALUES (30, 'SALES',      'CHICAGO');

INSERT INTO DEPT VALUES (40, 'OPERATIONS', 'BOSTON');

 

COMMIT;

/

 

1-4) 인덱스 생성 및 통계정보 생성

CREATE UNIQUE INDEX USER.DEPT_U1 ON USER.DEPT (DEPTNO);

 

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'DEPT', CASCADE => TRUE);

 

1-5) 데이터 확인

SELECT * 

FROM   DEPT

;

 

DEPTNO DNAME         LOC        

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

    10 ACCOUNTING    NEW YORK   

    20 RESEARCH      DALLAS     

    30 SALES         CHICAGO    

    40 OPERATIONS    BOSTON     

;

 

1-6) 실행계획 확인

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = :B1

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT            |         |     1 |    20 |     1   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    20 |     1   (0)| 00:00:01 |

|*  2 |   INDEX UNIQUE SCAN         | DEPT_U1 |     1 |       |     0   (0)| 00:00:01 |

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

                                                                                       

Predicate Information (identified by operation id):                                    

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

                                                                                       

   2 - access("D"."DEPTNO"=TO_NUMBER(:B1))                                             

;



지금까지 DEPT 테이블을 만든 후 데이터, 인덱스 및 통계정보를 생성한 뒤, 실행계획을 살펴보았다.

실행계획은 당연히 DEPTNO 조건이 들어오기 때문에 이 컬럼으로 구성된 DEPT_U1 인덱스를 사용한 것을 볼 수 있다.


이제 이 실행계획을 SQL_PROFILE을 통해 FULL SCAN을 하도록 유도하려고 하는데, 방식은 아래와 같다.



즉, DEPT_U1 인덱스를 사용하는 SQL을 A라 하고, FULL SCAN 하는 SQL을 B라 할 때, SQL_PROFILE을 통해 B로 변경을 하는 방식이다.


1-7) B 실행계획 유도

EXPLAIN PLAN FOR

SELECT /*+ FULL(D) */ -- 힌트추가(KJS)

       *

FROM   DEPT D

WHERE  D.DEPTNO = :B1

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT  |      |     1 |    20 |     5   (0)| 00:00:01 |

|*  1 |  TABLE ACCESS FULL| DEPT |     1 |    20 |     5   (0)| 00:00:01 |

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

                                                                          

Predicate Information (identified by operation id):                       

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

                                                                          

   1 - filter("D"."DEPTNO"=TO_NUMBER(:B1))                                

;



이제 원하는 B 실행계획이 나왔으므로, 이 힌트에 대한 OUTLINE FULL HINT를 통해 값을 가져오도록 한다.


1-8) B 실행계획 OUTLINE FULL HINT 

EXPLAIN PLAN FOR

SELECT /*+ FULL(D) */ -- 힌트추가(KJS)

       *

FROM   DEPT D

WHERE  D.DEPTNO = :B1

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, NULL, 'OUTLINE'));

 

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

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |  

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

|   0 | SELECT STATEMENT  |      |     1 |    20 |     5   (0)| 00:00:01 |  

|*  1 |  TABLE ACCESS FULL| DEPT |     1 |    20 |     5   (0)| 00:00:01 |  

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

                                                                            

Outline Data                                                                

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

                                                                            

  /*+                                                                       

      BEGIN_OUTLINE_DATA                                                    

      FULL(@"SEL$1" "D"@"SEL$1")                                            

      OUTLINE_LEAF(@"SEL$1")                                                

      ALL_ROWS                                                              

      OPT_PARAM('_optim_peek_user_binds' 'false')                           

      OPT_PARAM('_fast_full_scan_enabled' 'false')                          

      OPT_PARAM('_b_tree_bitmap_plans' 'false')                             

      OPTIMIZER_FEATURES_ENABLE('10.2.0.3')                                 

      IGNORE_OPTIM_EMBEDDED_HINTS                                           

      END_OUTLINE_DATA                                                      

  */                                                                        

                                                                            

Predicate Information (identified by operation id):                         

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

                                                                            

   1 - filter("D"."DEPTNO"=TO_NUMBER(:B1))                                  

;



위에서 나온 'Outline Data'를 SQL_PROFILE에 등록한다(등록 시 모두 가져올 필요는 없으며, 필요한 힌트만 취득하면 되나 테스트를 위해 전체를 가져옴)


SQL_PROFILE은 SQL_TEXT를 구분자로 인식하기 때문에 반드시 SQL_TEXT가 일치해야 하므로, 가급적 Shared Pool에 있는 SQL_TEXT를 이용하는 것이 좋다.

이를 이용하기 위해 해당 SQL을 수행토록 한다.



1-9) Shared Pool에 등록 및 SQL_ID 확인

※ 이 절차는 실제 운영 시에는 필요 없으며, 테스트를 위한 절차임

 

-- SQL 실행

var B1 NUMBER

EXEC :B1 := 10

 

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = :B1

;

 

    DEPTNO DNAME                        LOC      

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

        10 ACCOUNTING                   NEW YORK 

;

 

-- SQL_ID 및 FULL_TEXT 확인

SELECT SQL_ID,

       SQL_TEXT,

       SQL_FULLTEXT

FROM   V$SQL

WHERE  SQL_TEXT LIKE '%DEPT D%'

;

 

 

SQL_ID        SQL_TEXT                                     SQL_FULLTEXT            

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

abfnv4rva7df9 SELECT * FROM   DEPT D WHERE  D.DEPTNO = :B1 SELECT *                

                                                           FROM   DEPT D           

                                                           WHERE  D.DEPTNO = :B1   

;



SQL 실행 후 V$SQL에서 SQL_ID 및 SQL_FULLTEXT를 확인하였으므로, 이를 활용하여 SQL_PROFILE을 등록하도록 하자.


1-10) SQL_PROFILE 등록

DECLARE

    -- SQL_FULLTEXT 데이터타입이 CLOB이므로, 변수도 CLOB으로 선언

    V_SQL_TEXT CLOB;

BEGIN

    -- V$SQL의 SQL_FULLTEXT를 변수에 저장

    SELECT SQL_FULLTEXT

    INTO   V_SQL_TEXT

    FROM   V$SQL

    WHERE  SQL_ID = 'abfnv4rva7df9';

 

    -- DBMS_SQLTUNE.IMPORT_SQL_PROFILE 패키지를 이용하여 등록

    DBMS_SQLTUNE.IMPORT_SQL_PROFILE(

        NAME        => 'DEPT_PROFILE_1',

        DESCRIPTION => 'DEPT_PROFILE_1',

        CATEGORY    => 'DEPT_PROFILE_1',

        SQL_TEXT    => V_SQL_TEXT,

        PROFILE     => SQLPROF_ATTR('BEGIN_OUTLINE_DATA',                                                    

                                    'FULL(@"SEL$1" "D"@"SEL$1")',                                            

                                    'OUTLINE_LEAF(@"SEL$1")',                                                

                                    'ALL_ROWS',                                                                                           

                                    'IGNORE_OPTIM_EMBEDDED_HINTS',                                           

                                    'END_OUTLINE_DATA'  

                                    ),

        REPLACE     => TRUE

    );

END;

/

 

PL/SQL procedure successfully completed.

;



이제 해당 SQL_PROFILE이 DICTIONARY에 등록 되었으며, 해당 내용을 사용해 보도록 하자.



1-11) SQL_PROFILE 사용

-- 1. SQL_PROFILE 사용 전

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = :B1

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT            |         |     1 |    20 |     1   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    20 |     1   (0)| 00:00:01 |

|*  2 |   INDEX UNIQUE SCAN         | DEPT_U1 |     1 |       |     0   (0)| 00:00:01 |

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

                                                                                       

Predicate Information (identified by operation id):                                    

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

                                                                                       

   2 - access("D"."DEPTNO"=TO_NUMBER(:B1))                                             

;

 

-- 2. SQL_PROFILE 활성화

ALTER SESSION SET SQLTUNE_CATEGORY = DEPT_PROFILE_1 ;

 

Session altered.

;

 

-- 3. SQL_PROFILE 사용 후

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = :B1

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |  

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

|   0 | SELECT STATEMENT  |      |     1 |    20 |     5   (0)| 00:00:01 |  

|*  1 |  TABLE ACCESS FULL| DEPT |     1 |    20 |     5   (0)| 00:00:01 |  

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

                                                                            

Predicate Information (identified by operation id):                         

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

                                                                            

   1 - filter("D"."DEPTNO"=TO_NUMBER(:B1))                                  

                                                                            

Note                                                                        

-----                                                                       

   - SQL profile "DEPT_PROFILE_1" used for this statement                   

;



위의 결과처럼, Session-Level에서 'DEPT_PROFILE_1' SQL_PROFILE을 사용한 결과, 힌트 없이 FULL SCAN하는 것을 알 수 있으며, 'Note'에서도 'DEPT_PROFILE_1'을 사용했다는 것을 확인할 수 있다.


이와 같이 SQL_PROFILE은 SQL별로 등록할 수 있으며, 이에 대해 DBA_SQL_PROFILES 뷰에서도 확인이 가능하다.


1-12) DBA_SQL_PROFILES View

col NAME            format a30

col DESCRIPTION     format a30

col CATEGORY        format a30

col SQL_TEXT        format a100

col CREATED         format a30

col LAST_MODIFIED   format a30

col TYPE            format a30

col STATUS          format a30

col FORCE_MATCHING  format a30

 

SELECT NAME,

       DESCRIPTION,

       CATEGORY,

       SQL_TEXT,

       CREATED,

       LAST_MODIFIED,

       STATUS,

       FORCE_MATCHING

FROM   DBA_SQL_PROFILES

WHERE  NAME = 'DEPT_PROFILE_1'

;

 

NAME            DESCRIPTION     CATEGORY       SQL_TEXT              

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

CREATED         LAST_MODIFIED   STATUS         FORCE_MATCHING        

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

DEPT_PROFILE_1  DEPT_PROFILE_1  DEPT_PROFILE_1 SELECT *              

                                               FROM   DEPT D         

                                               WHERE  D.DEPTNO = :B1 

                                                                     

11-JAN-13       11-JAN-13       ENABLED        NO                    

;

1-13) CATEGORY 기능

- CATEGORY 기능은 2가지 측면에서 활용이 가능함.

  ① CATEGORY GROUPING

    - 'CATEGORY GROUPING'은 하나의 프로그램 내에 여러 SQL을 SQL_PROFILE을 통해 개선을 해야 하는 경우도 있다.

    - 만약, 하나의 프로그램에 5개의 SQL을 등록 후 사용할 경우 Session-Level에서 5개의 SQL_PROFILE을 모두 호출해야 하는데, 이를 CATEGORY에서 하나의 이름으로 등록할 경우, 이 이름으로 한 번만 호출하여 5개 SQL을 모두 사용할 수 있다.

    

  ② System-Level

    - 위의 'CATEGORY GROUPING'을 통해 여러 SQL을 하나의 CATEGORY로 묶을 수 있지만, 이를 사용하기 위해서는 어느 지점에서 반드시 'ALTER SESSION'을 해야 한다.

    - 하지만, 'ALTER SESSION'을 할 수 없는 상황일 경우, 이를 System-Level로 등록하여 세션 변경 없이 사용이 가능하다.

 

- System-Level로 등록하기 위해서는 값을 'Default'로 설정하면 된다.

DECLARE

    -- SQL_FULLTEXT 데이터타입이 CLOB이므로, 변수도 CLOB으로 선언

    V_SQL_TEXT CLOB;

BEGIN

    -- V$SQL의 SQL_FULLTEXT를 변수에 저장

    SELECT SQL_FULLTEXT

    INTO   V_SQL_TEXT

    FROM   V$SQL

    WHERE  SQL_ID = 'abfnv4rva7df9';

 

    -- DBMS_SQLTUNE.IMPORT_SQL_PROFILE 패키지를 이용하여 등록

    DBMS_SQLTUNE.IMPORT_SQL_PROFILE(

        NAME        => 'DEPT_PROFILE_1',

        DESCRIPTION => 'DEPT_PROFILE_1',

        CATEGORY    => 'DEFAULT',

        SQL_TEXT    => V_SQL_TEXT,

        PROFILE     => SQLPROF_ATTR('BEGIN_OUTLINE_DATA',                                                                                   'FULL(@"SEL$1" "D"@"SEL$1")',                                                                           'OUTLINE_LEAF(@"SEL$1")',                                                                               'ALL_ROWS',                                                                                             'IGNORE_OPTIM_EMBEDDED_HINTS',                                                                          'END_OUTLINE_DATA'  

                                    ),

        REPLACE     => TRUE

    );

END;

/




2. SQL_PROFILE 패키지 정리
1) DBMS_SQLTUNE Package
- SQL_PROFILE을 등록 또는 삭제하기 위해서는 DBMS_SQLTUNE 패키지를 사용해야 한다.
  => SQL_PROFILE 등록     : DBMS_SQLTUNE.IMPORT_SQL_PROFILE 
  => SQL_PROFILE 삭제     : DBMS_SQLTUNE.DROP_SQL_PROFILE(NAME =>'DEPT_PROFILE_1'); 
  => SQL_PROFILE DISABLE  : DBMS_SQLTUNE.ALTER_SQL_PROFILE (NAME =>'DEPT_PROFILE_1',ATTRIBUTE_NAME=>'STATUS',VALUE=>'DISABLED');

2) SQL_PROFILE 활성화
- 활성화   : ALTER SESSION SET SQLTUNE_CATEGORY = DEPT_PROFILE_1 ;
- 비활성화 : ALTER SESSION SET SQLTUNE_CATEGORY = FALSE;






3. FORCE_MATCH 기능
- SQL_PROFILE의 구분자는 위에서 설명한 것처럼 SQL 구문으로 체크를 한다.
- 그런데, 만약 바인드 변수가 아닌 Literal 값으로 들어오는 Dynamic SQL일 경우, SQL 구문이 모두 틀리기 때문에 SQL_PROFILE을 등록하여도 공유해서 사용을 할 수가 없다.
- 이 때, FORCE_MATCH 기능을 사용할 경우 CURSOR_SHARING 기능처럼 Literal 값을 바인드 변수로 자동변경 해주므로, 하나만 등록하여도 나머지 SQL을 공유해서 사용 할 수 있다.

1) 기존 SQL_PROFILE 삭제

EXEC DBMS_SQLTUNE.DROP_SQL_PROFILE(NAME =>'DEPT_PROFILE_1');

 

PL/SQL procedure successfully completed.

 

2) Literal SQL을 CATEGORY 'DEFAULT'로 하고, FORCE_MATCH 기능 없이 등록하여 공유가 되는지 체크

DECLARE

BEGIN

    -- DBMS_SQLTUNE.IMPORT_SQL_PROFILE 패키지를 이용하여 등록

    DBMS_SQLTUNE.IMPORT_SQL_PROFILE(

        NAME        => 'DEPT_PROFILE_1',

        DESCRIPTION => 'DEPT_PROFILE_1',

        CATEGORY    => 'DEFAULT', -- System-Level로 등록

        SQL_TEXT    => 'SELECT *

                        FROM   DEPT D

                        WHERE  D.DEPTNO = 10',

        PROFILE     => SQLPROF_ATTR('BEGIN_OUTLINE_DATA',                                                    

                                    'FULL(@"SEL$1" "D"@"SEL$1")',                                            

                                    'OUTLINE_LEAF(@"SEL$1")',                                                

                                    'ALL_ROWS',                                                                                           

                                    'IGNORE_OPTIM_EMBEDDED_HINTS',                                           

                                    'END_OUTLINE_DATA'  

                                    ),

        REPLACE     => TRUE

    );

END;

/

 

3) 실행계획 확인

 

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = 10

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0 | SELECT STATEMENT  |      |     1 |    20 |     5   (0)| 00:00:01 |

|*  1 |  TABLE ACCESS FULL| DEPT |     1 |    20 |     5   (0)| 00:00:01 |

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

                                                                          

Predicate Information (identified by operation id):                       

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

                                                                          

   1 - filter("D"."DEPTNO"=10)                                            

                                                                          

Note                                                                      

-----                                                                     

   - SQL profile "DEPT_PROFILE_1" used for this statement                 

;

 

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = 20

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     | 

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

|   0 | SELECT STATEMENT            |         |     1 |    20 |     1   (0)| 00:00:01 | 

|   1 |  TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    20 |     1   (0)| 00:00:01 | 

|*  2 |   INDEX UNIQUE SCAN         | DEPT_U1 |     1 |       |     0   (0)| 00:00:01 | 

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

                                                                                        

Predicate Information (identified by operation id):                                     

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

                                                                                        

   2 - access("D"."DEPTNO"=20)                                                          

;

 

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = 30

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     | 

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

|   0 | SELECT STATEMENT            |         |     1 |    20 |     1   (0)| 00:00:01 | 

|   1 |  TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    20 |     1   (0)| 00:00:01 | 

|*  2 |   INDEX UNIQUE SCAN         | DEPT_U1 |     1 |       |     0   (0)| 00:00:01 | 

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

                                                                                        

Predicate Information (identified by operation id):                                     

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

                                                                                        

   2 - access("D"."DEPTNO"=30)                                                          

;

 

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = 40

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     | 

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

|   0 | SELECT STATEMENT            |         |     1 |    20 |     1   (0)| 00:00:01 | 

|   1 |  TABLE ACCESS BY INDEX ROWID| DEPT    |     1 |    20 |     1   (0)| 00:00:01 | 

|*  2 |   INDEX UNIQUE SCAN         | DEPT_U1 |     1 |       |     0   (0)| 00:00:01 | 

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

                                                                                        

Predicate Information (identified by operation id):                                     

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

                                                                                        

   2 - access("D"."DEPTNO"=40)                                                          

;



위의 결과에서 보듯이, 'DEPT=10'에 대해서만 SQL_PROFILE을 등록하였기 때문에, 이 경우에만 FULL SCAN을 하고 나머지 20, 30, 40일 경우 INDEX SCAN을 사용하였다. 즉, 20, 30, 40인 경우에는 SQL_PROFILE을 사용하지 못하는 것을 확인할 수 있다.


이제는 FORCE_MATCH를 사용하여 CURSOR_SHARING 기능이 사용되는지 체크해보도록 하자.



4) Literal SQL을 CATEGORY 'DEFAULT'로 하고, FORCE_MATCH 기능 포함하여 등록한 후 공유가 되는지 체크

EXEC DBMS_SQLTUNE.DROP_SQL_PROFILE(NAME =>'DEPT_PROFILE_1');

 

PL/SQL procedure successfully completed.

 

DECLARE

BEGIN

    -- DBMS_SQLTUNE.IMPORT_SQL_PROFILE 패키지를 이용하여 등록

    DBMS_SQLTUNE.IMPORT_SQL_PROFILE(

        NAME        => 'DEPT_PROFILE_1',

        DESCRIPTION => 'DEPT_PROFILE_1',

        CATEGORY    => 'DEFAULT',-- System-Level로 등록

        SQL_TEXT    => 'SELECT *

                        FROM   DEPT D

                        WHERE  D.DEPTNO = 10',

        PROFILE     => SQLPROF_ATTR('BEGIN_OUTLINE_DATA',                                                    

                                    'FULL(@"SEL$1" "D"@"SEL$1")',                                            

                                    'OUTLINE_LEAF(@"SEL$1")',                                                

                                    'ALL_ROWS',                                                                                           

                                    'IGNORE_OPTIM_EMBEDDED_HINTS',                                           

                                    'END_OUTLINE_DATA'  

                                    ),

        REPLACE     => TRUE,

        FORCE_MATCH => TRUE  -- CURSOR_SHARING 기능 사용  

    );

END;

/

 

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = 10

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |                               

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

|   0 | SELECT STATEMENT  |      |     1 |    20 |     5   (0)| 00:00:01 |                               

|*  1 |  TABLE ACCESS FULL| DEPT |     1 |    20 |     5   (0)| 00:00:01 |                               

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

                                                                                                         

Predicate Information (identified by operation id):                                                      

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

                                                                                                         

   1 - filter("D"."DEPTNO"=10)                                                                           

                                                                                                         

Note                                                                                                     

-----                                                                                                    

   - SQL profile "DEPT_PROFILE_1" used for this statement    

;

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = 20

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |                               

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

|   0 | SELECT STATEMENT  |      |     1 |    20 |     5   (0)| 00:00:01 |                               

|*  1 |  TABLE ACCESS FULL| DEPT |     1 |    20 |     5   (0)| 00:00:01 |                               

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

                                                                                                         

Predicate Information (identified by operation id):                                                      

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

                                                                                                         

   1 - filter("D"."DEPTNO"=20)                                                                           

                                                                                                         

Note                                                                                                     

-----                                                                                                    

   - SQL profile "DEPT_PROFILE_1" used for this statement  

;

 

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = 30

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |                               

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

|   0 | SELECT STATEMENT  |      |     1 |    20 |     5   (0)| 00:00:01 |                               

|*  1 |  TABLE ACCESS FULL| DEPT |     1 |    20 |     5   (0)| 00:00:01 |                               

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

                                                                                                         

Predicate Information (identified by operation id):                                                      

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

                                                                                                         

   1 - filter("D"."DEPTNO"=30)                                                                           

                                                                                                         

Note                                                                                                     

-----                                                                                                    

   - SQL profile "DEPT_PROFILE_1" used for this statement  

;

 

EXPLAIN PLAN FOR

SELECT *

FROM   DEPT D

WHERE  D.DEPTNO = 40

;

 

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

 

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

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |                               

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

|   0 | SELECT STATEMENT  |      |     1 |    20 |     5   (0)| 00:00:01 |                               

|*  1 |  TABLE ACCESS FULL| DEPT |     1 |    20 |     5   (0)| 00:00:01 |                               

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

                                                                                                         

Predicate Information (identified by operation id):                                                      

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

                                                                                                         

   1 - filter("D"."DEPTNO"=40)                                                                           

                                                                                                         

Note                                                                                                     

-----                                                                                                    

   - SQL profile "DEPT_PROFILE_1" used for this statement                                                

;



이처럼, SQL_PROFILE에서 FORCE_MATCH 기능을 통해 Literal SQL도 극복할 수 있으므로, 아주 유용한 기능이라 할 수 있다.



4. 주의사항
1) SQL_PROFILE 체크는 SQL 구문으로 체크하기 때문에 Shared Pool에 등록된 SQL을 SQL_PROFILE 시 사용하는 것이 좋다.
2) 1)에서 이야기한 것 처럼 SQL 구문으로 인식하기 때문에, 튜닝 SQL이 힌트가 아닌 구문 변경일 경우 사용할 수 없다.
3) CATEGORY 등록 시 'DEFAULT' 즉, System-Level로 등록할 경우 시스템에 부하를 줄 수 있기 때문에 가급적 긴급 대응일 경우에만 사용하는 것이 좋다.

Posted by redkite
, |

DBA 계정으로 sqlplus 로그인하여 실행 
  
(성능통계 수집 기간 및 보관주기 보기) 
select snap_interval, retention from dba_hist_wr_control; 

  

(20분 간격으로 2일보관주기로 변경) 

begin 

  dbms_workload_repository.modify_snapshot_settings ( 

      interval => 20, 

      retention => 2*24*60 

  ); 

end; 



* AWR snapshot 생성 (시작과 종료시 아래 처럼 실행) 
- 1노드에 대해서 snapshot 됨 
execute dbms_workload_repository.create_snapshot; 
  
- 전체 노드에 대해서 snapshot 됨 
exec sys.dbms_workload_repository.create_snapshot('ALL'); 
  
  
* report 출력시 (해당 결과파일 local로 받아서 *.html형식으로) 
$ORACLE_HOME/rdbms/admin/awrrpt.sql 수행 
  실행시 입력 값 
  Enter value for report_type: text    << text format 으로 보고서 생성 
  Enter value for num_days: 1  << 최근 하루 동안의 snap 조회 
  Enter value for end_snap: 1271  << 09:08 ~ 14:00 구간에 대한 조회 요청 
  Enter value for report_name: awrrpt_1_1266_1271.txt  << 보고서 이름 지정 
  
  
* 분석 point 

1.  Load Profile 분석 
Snap 구간 동안의 DBMS 성능 통계를 보여준다. 기본적인 DBMS 성능의 Baseline을 제공한다. 
초당 Transactions  및 SQL 호출 수 등을 통해 DBMS의 Activity 를 분석한다. 
  
2.  메모리 성능 분석 
Shared Pool 및 Buffer Cache의 Hit Rate 등 메모리 활용의 적절성을 분석한다. 
  
3.  이벤트 분석 
CPU time이 높은 비율로 유지되어야 하며 기타 I/O 를 위한 Wait이나 Lock 발생여부를 분석한다. 
  
4.  TOP SQL 분석 
SQL ordered by Gets 항목 분석을 통해 I/O를 많이 유발하는 Bad SQL을 찾아서 튜닝한다 
============================================================================================== 
============================================================================================== 
Knowledge 등록 건 (LGCNS 공공 DA 김승철 차장) ========================================================== 
  
오라클10g의 AWR기능을 활용하면 튜닝에 필요한 많은 정보를 얻을 수 있다. 
MMON 백그라운드 프로세스와 여러 개의 슬레이브 프로세스를 통해 자동으로 매 시간마다 스냅샷 정보를 수집한다. 
수집된 데이타는 7일 후 자동으로 삭제된다. 스냅샷 주기와 보관 주기는 사용자에 의해 설정 가능하다. 

1. AWRRPT에서 SNAP_ID와 SQL_ID로 바인드 변수 찾기 
  ------------------------------------------------- 

  1.1 awrrpt의 레포트파일의 SNAP_ID 및 SQL_ID를 활용 
        SQL> select * 
                from dba_hist_sqlbind 
              where SNAP_ID=2099 
                and SQL_ID='92rpbbrrb3bqj'; 

        SQL> select * 
                from dba_hist_sqlbind 
              where SNAP_ID between 18797 and 18814  -- between [Begin Snap] and [End Snap] 
                and SQL_ID='1g8h29fbpv5yu'; 

  1.2 SQL_ID알고 최근 SNAP_ID를 구하여 활용 
        SQL> select max(SNAP_ID) from dba_hist_sqlbind where SQL_ID='92rpbbrrb3bqj'; 
                2099 

        SQL> select * 
              from dba_hist_sqlbind 
              where SNAP_ID=2099 
                and SQL_ID='92rpbbrrb3bqj'; 


2. 실행중인 SQL을 SID로 찾아 PLAN 보기 
  ------------------------------------ 

  SQL> select 'select * from TABLE(dbms_xplan.display_cursor('''||sql_id||''','||SQL_CHILD_NUMBER||')) ;' 
          from  v$session 
        where  sid = 4194; 

  결과 : select * from TABLE(dbms_xplan.display_cursor('bqxzbkrtt26gj',0)) ; 

  -- 결과를 실행 
  SQL> select * from TABLE(dbms_xplan.display_cursor('bqxzbkrtt26gj',0)) ;                                      


3. V$SESSION의 SQL_HASH_VALUE로 SQL 찾기 
  -------------------------------------------- 

  SQL> select sql_text 
          from v$sqltext 
        where hash_value = 2555467871 
        order by piece; 


4. /*+ gather_plan_statistics */ 힌트와 dbms_xplan.display_cursor 패키지를 이용한 플랜보기 
  --------------------------------------------------------------------------------------- 

  - statistics_level = all 인 경우에는 Hint 불필요 
  - SQL 실행 시 Row Source 레벨의 통계 정보 수집 
  - E-Rows(예측 Row 수)와 A-Rows(실제 Row 수)의 비교를 통해 통계정보의 오류를 파악할 수 있음 
  - Optimizer가 얼마나 합리적인 실행 계획을 세우느냐는 Cardinality, 즉 예상 Row수의 정확성에 달려 있음 

  SQL> select /*+ gather_plan_statistics */ * from tb_test where id < 1000; 
  또는 
  SQL> alter session statistics_level = ALL; 
  SQL> select * from tb_test where id < 1000; 

  SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST')); 
  ---------------------------------------------------------------------------------------------------------
  | Id  | Operation                  | Name            | Starts | E-Rows | A-Rows |  A-Time  | Buffers | 
  ---------------------------------------------------------------------------------------------------------
  |  1 |  TABLE ACCESS BY INDEX ROWID|  TB_TEST        |      1 |      1 |  1000 |00:00:00.01 |    140 | 
  |*  2 |  INDEX RANGE SCAN          |  TB_TEST_IDX    |      1 |      1 |  1000 |00:00:00.01 |      70 | 
              : 
  - 주요 항목 설명 
    . E-Rows: 예측 Row 수 
    . A-Rows: 실제 Row 수 
    . A-Time: 실제 소요 시간 
    . Buffers: Logical Reads 

참고) dbms_xplan.display_cursor(sql_id, child_number, format)의 format 종류 
  - Basic 
  - Typical 
  - Outline 
  - All 
  - Advanced 
  * allstats last 
  * +peeked_binds : 예) dbms_xplan.display_cursor(null,null,'ALLSTATS LAST +peeked_binds'); 


5. 스냅샷 직접 생성(수동) 
  --------------------- 

    SQL> execute dbms_workload_repository.create_snapshot; 

    SQL> SELECT snap_id, begin_interval_time begin, end_interval_time end FROM SYS.DBA_HIST_SNAPSHOT; 

    SQL> SELECT snap_id, startup_time FROM dba_hist_snapshot ORDER BY 1,2; 

            SNAP_ID    STARTUP_TIME 
            ---------- -------------------- 
            10        2007/12/19 10:27:32.000 <-- 삭제할 첫번째 스냅샷 
            11        2007/12/19 10:27:32.000 
            12        2007/12/19 10:27:32.000 
            13        2007/12/19 10:27:32.000 
            14        2007/12/19 10:27:32.000 
            15        2007/12/19 10:27:32.000 <-- 삭제할 마지막 스냅샷 
            16        2007/12/19 10:27:32.000 
            17        2007/12/19 10:27:32.000 

            12 rows selected. 


6. SNAP_ID 범위 지정하여 삭제 
  -------------------------- 

    SQL> exec dbms_workload_repository.drop_snapshot_range(10, 15); 


7. AWR 스냅샷 주기와 보관 주기 설정 
  -------------------------------- 

  1] 스냅샷주기(1시간,default) 및 보관주기(7일,default) 조회 
      SQL> SELECT snap_interval , retention FROM dba_hist_wr_control; 
            SNAP_INTERVAL              RETENTION 
            -------------------------- --------------------------- 
            +00000 01:00:00.0          +00007 00:00:00.0 

  2] 스냅샷주기(10분) 및 보관주기(15일)을 변경 
      SQL> execute DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS 
                                                  (interval  => 10,        -- 분단위 
                                                  retention => 15*24*60); -- 15일 

  3] 스냅샷주기(10분) 및 보관주기(15일) 조회 
      SQL> SELECT snap_interval , retention FROM dba_hist_wr_control; 
            SNAP_INTERVAL              RETENTION 
            -------------------------- --------------------------- 
            +00000 00:10:00.0          +00015 00:00:00.0 

8. AWR Report 생성 
  ---------------- 

  과거의 DB의 상태를 awrrpt를 이용하여 확인할 수 있다. 

  SQL> connect / as sysdba 

  SQL> @?/rdbms/admin/awrrpt.sql 실행 

            : 

  Enter value for report_type: html 입력 

            : 

  Enter value for num_days: 8 입력 

  Listing the last 8 days of Completed Snapshots 

                                                                                    Snap 
  Instance    DB Name        Snap Id    Snap Started        Level 
  ------------ ------------ --------- ------------------ ----- 
  DB_SID      DB_NAME      20159 01 Aug 2008 00:00        1 
                                          20160 01 Aug 2008 01:00      1 
                                          20161 01 Aug 2008 02:00      1 
                                          20162 01 Aug 2008 03:00      1 
                                          20163 01 Aug 2008 04:00      1 
                                          20164 01 Aug 2008 05:00      1 
                                          20165 01 Aug 2008 06:00      1 
                                          20166 01 Aug 2008 07:00      1 
                                          20167 01 Aug 2008 08:00      1 
                                          20168 01 Aug 2008 09:00      1 
                                              : 

                                          20333 08 Aug 2008 06:00      1 
                                          20334 08 Aug 2008 07:00      1 
                                          20335 08 Aug 2008 08:00      1 
                                          20336 08 Aug 2008 09:00      1  --- begin 
                                          20337 08 Aug 2008 10:00      1 
                                          20338 08 Aug 2008 11:00      1 
                                          20339 08 Aug 2008 12:00      1  --- end 
                                          20340 08 Aug 2008 13:00      1 
                                          20341 08 Aug 2008 14:00      1 
                                          20342 08 Aug 2008 15:00      1 

  Enter value for begin_snap: 20336 입력 --- begin 

  Enter value for end_snap  : 20339 입력  --- end 


  Enter value for report_name: awrrpt_20080808_09-12_DB_SID.html 입력 

    awrrpt_20080808_09-12_DB_SID.html 파일을 ftp로 pc로 다운로드 받은 후 
  열어서 SQL ordered by Elapsed Time 항목 등을 확인해 보시면 된다. 




Oracle have provided many performance gathering and reporting tools over the years. Originally the UTLBSTAT/UTLESTAT scripts were used to monitor performance metrics. Oracle8i introduced the Statspack functionality which Oracle9i extended. In Oracle 10g statspack has evolved into the Automatic Workload Repository (AWR). 
AWR Features 

The AWR is used to collect performance statistics including: 

Wait events used to identify performance problems. 
Time model statistics indicating the amount of DB time associated with a process from the V$SESS_TIME_MODEL and V$SYS_TIME_MODEL views. 
Active Session History (ASH) statistics from the V$ACTIVE_SESSION_HISTORY view. 
Some system and session statistics from the V$SYSSTAT and V$SESSTAT views. 
Object usage statistics. 
Resource intensive SQL statements. 
The repository is a source of information for several other Oracle 10g features including: 

Automatic Database Diagnostic Monitor 
SQL Tuning Advisor 
Undo Advisor 
Segment Advisor 
Snapshots 
By default snapshots of the relevant data are taken every hour and retained for 7 days. The default values for these settings can be altered using: 






BEGIN 
DBMS_WORKLOAD_REPOSITORY.modify_snapshot_settings( 

retention => 43200, -- Minutes (= 30 Days). Current value retained if NULL. 

interval => 30); -- Minutes. Current value retained if NULL. 

END; 


The changes to the settings are reflected in the DBA_HIST_WR_CONTROL view. Typically the retention period should capture at least one complete workload cycle. If you system has monthly archive and loads a 1 month retention time would be more beneficial that the default 7 days. An interval of "0" switches off snapshot collection, which in turn stops much of the self-tuning functionality, hence this is not recommended. Automatic collection is only possible if the STATISTICS_LEVEL parameter is set to TYPICAL or ALL. If the value is set to BASIC manual snapshots can be taken, but they will be missing some statistics. 







Extra snapshots can be taken and existing snapshots can be removed using: 


EXEC DBMS_WORKLOAD_REPOSITORY.create_snapshot; 

BEGIN 
DBMS_WORKLOAD_REPOSITORY.drop_snapshot_range ( 
low_snap_id => 22, 
high_snap_id => 32); 
END; 


Snapshot information can be queried from the DBA_HIST_SNAPSHOT view. 







Baselines 


A baseline is a pair of snapshots that represents a specific period of usage. Once baselines are defined they can be used to compare current performance against similar periods in the past. You may wish to create baseline to represent a period of batch processing like: 


BEGIN 

DBMS_WORKLOAD_REPOSITORY.create_baseline ( 
start_snap_id => 210, 
end_snap_id => 220, 
baseline_name => 'batch baseline'); 
END; 


The pair of snapshots associated with a baseline are retained until the baseline is explicitly deleted: 



BEGIN 

DBMS_WORKLOAD_REPOSITORY.drop_baseline ( 
baseline_name => 'batch baseline', 
cascade => FALSE); 
END; 


Baseline information can be queried from the DBA_HIST_BASELINE view. 







Workload Repository Views 


The following workload repository views are available: 

 V$ACTIVE_SESSION_HISTORY - Displays the active session history (ASH) sampled every second. 
V$METRIC - Displays metric information. 
V$METRICNAME - Displays the metrics associated with each metric group. 
V$METRIC_HISTORY - Displays historical metrics. 
V$METRICGROUP - Displays all metrics groups. 
DBA_HIST_ACTIVE_SESS_HISTORY - Displays the history contents of the active session history. 
DBA_HIST_BASELINE - Displays baseline information. 
DBA_HIST_DATABASE_INSTANCE - Displays database environment information. 
DBA_HIST_SNAPSHOT - Displays snapshot information. 
DBA_HIST_SQL_PLAN - Displays SQL execution plans. 
DBA_HIST_WR_CONTROL - Displays AWR settings. 
Workload Repository Reports 


Oracle provide two scripts to produce workload repository reports (awrrpt.sql and awrrpti.sql). They are similar in format to the statspack reports and give the option of HTML or plain text formats. The two reports give essential the same output but the awrrpti.sql allows you to select a single instance. The reports can be generated as follows: 


@$ORACLE_HOME/rdbms/admin/awrrpt.sql 



@$ORACLE_HOME/rdbms/admin/awrrpti.sql 


The scripts prompt you to enter the report format (html or text), the start snapshot id, the end snapshot id and the report filename. The resulting report can be opend in a browser or text editor accordingly. 







Enterprise Manager 


The automated workload repository administration tasks have been included in Enterprise Manager. The "Automatic Workload Repository" page is accessed from the main page by clicking on the "Administration" link, then the "Workload Repository" link under the "Workload" section. The page allows you to modify AWR settings or manage snapshots without using the PL/SQL APIs. 

Posted by redkite
, |

장애 시점으로 보이는 구간에 CURSOR PIN S WAIT ON X 대기 이벤트가 대량 발생하고 있었고, 담당자의 말에 의하면 이 시점에DB HANG이 걸린 것 같은 현상이 있었다는 것이었다.

 

단순히 파라미터 수정을 통해 문제는 해결이 된다는 것으로 답변을 하였지만, 담당자가 원하는 답변은 그런 것이 아니었고CURSOR PIN S WAIT ON X 대기에 대한 구체적인 내용을 원하는 것이었다. 하지만 아직 이 문제에 대해 나름대로 정리를 해본 적이 없었고 설명을 대략적으로 하려 하였으나 결국 제대로 이해시키지 못하였다.

 

해당 대기 이벤트는 ORACLE에서 LOCK 메커니즘 중 MUTEX라는 기능을 10R2부터 디폴트로 사용함으로써 발생되는 문제이며, 현재 내가 지원하는 고객사 중 ORACLE 10.2.0.3을 운영하는 곳에서 종종 발견되는 이벤트였다. (물론 장애를 포함하여서..)

 

근본적인 해결 책을 아닌 것 같지만 현재 해당 문제는 _kks_use_mutex_pin 라는 ORACLE 히든 파라미터의 수정을 통해 이벤트를 해소 할 수 있는 것으로 알려져 있다. 


뮤텍스란 무엇인지 살펴보면,  

<font size="2"> </font>
뮤텍스(MUTual EXclusion)(상호배제) 
Critical Section을 가진 Thread들의 running time이 서로 겹치지 않게, 각각 단독으로 실행되게 하는 기술이다.
* Critical Section : 프로그램 상에서 동시에 실행될 경우 문제를 일으킬 수 있는 부분.
만약 어느 Thread에서 Critical Section을 실행하고 있으면 다른 Thread들은 그 Critical Section에 접근할 수 없고 앞의 Thread 가 Critical Section을 벗어나기를 기다려야 한다.
 
<font size="2"></font>

뮤텍스의 특징

 

-       다수의 프로세스가 동일한 리소스를 공유할 수 있게 해줌

-       시스템으로부터 프로그램이 요청한 리소스를 위한 mutex을 하나 생성하고

-       시스템이 고유id를 부여한다. ( no wait mode  latch와 비슷함)

-       latch와 달리 mutex은 시스템이 관장

-       latch spin을 수행하지 않기 때문에 가벼울 수 있다.

-       문제발생시 oracle해결범위를 넘어선다.

-       mutex의 경우 복구가 안 된다.

 

아마도 뮤텍스라는 기능을 이용함으로 인해 mutex pin을 사용하는 과정에서 아래와 같은 현상이 발생하였고 mutex pinexclusive로 획득 중인 세션이 비정상적인 이유로 인해 계속해서 pin을 획득하게 되어 mutex pin을 획득하기 위해 다른 세션의 대기가 지속 되는 것으로 예측이 되었다.

<v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f><o:lock aspectratio="t" v:ext="edit"></o:lock>



 

오라클 버그에도 등록이 되어 있는 내용을 보면 특정 hp장비에 (CAS기능을 지원하지 않는 장비) 드물게 이러한 문제가 있으며, 아직 해당 문제는 해결이 되지 않았으며 근본적으로 11g에서 해결이 된다고 한다.

 

시간을 투자하여 나름대로 관련 정보를 검색한 후 정리를 하고 나니 비로소 담당자가 이해를 잘 하였다. 그리고 고객사 담당자의 DB HANG이라는 말은 확인을 해본 결과 개발자의 얘기였으며, 엄밀히 말하면 HANG의 상태는 아니었고 CURSOR PIN S WAIT ON X대기 이벤트를 대기하는 세션의 작업이 수행이 되지 않아 그렇게 얘기 한 듯 하였다. 담당자에게 TOTAL SESSION의 증가 구간을 확인 할 것을 권고 하였고 실제로 맥스게이지 로그를 통해 확인해 보니 문제 발생 이 후 세션의 증가 구간이 있었다.

 

결국 이와 같은 문제는 _kks_use_mutex_pin = false 로 설정하면서 mutex 기능을 사용 해제 하면 해결이 되나 히든 파라미터인 관계로 oracle에 확인 후 설정을 하시는 것이 좋다는 권고만 할 수 있었다.

 

지원은 시간이 많이 걸리긴 했지만 답변을 줄 수 있었고 이러한 경험을 통해 명확히 문제에 대한 지식이 없을 경우, 지원의 시간이 많이 걸릴 뿐 아니라 담당자가 이해하기까지도 어려움이 많음을 느꼈다.

Posted by redkite
, |

오라클10g의 AWR기능을 활용하면 튜닝에 필요한 많은 정보를 얻을 수 있다.

AWR은 MMON 백그라운드 프로세스와 여러 개의 슬레이브 프로세스를 통해 자동으로

매 시간마다 스냅샷 정보를 수집한다.

기본적으로 수집된 데이타는 7일 후 자동으로 삭제된다.

스냅샷 주기와 보관 주기는 사용자에 의해 설정 가능하다.

1. AWRRPT에서 SNAP_ID와 SQL_ID로 바인드 변수 찾기
   -------------------------------------------------
   1.1 awrrpt의 레포트파일의 SNAP_ID 및 SQL_ID를 활용
         SQL> select *
                from dba_hist_sqlbind
               where SNAP_ID=2099
                 and SQL_ID='92rpbbrrb3bqj';

         SQL> select *
              from dba_hist_sqlbind
             where SNAP_ID between 2090 and 2100
                 and SQL_ID='1g8h29fbpv5yu';

   1.2 SQL_ID알고 최근 SNAP_ID를 구하여 활용 
        SQL> select max(SNAP_ID)  as SNAP_ID

               from dba_hist_sqlbind

              where SQL_ID='92rpbbrrb3bqj';

              SNAP_ID

        -------
        2099

        SQL> select *
               from dba_hist_sqlbind
              where SNAP_ID=2099
                and SQL_ID='92rpbbrrb3bqj';

2. 스냅샷 직접 생성(수동)
   ---------------------
    SQL> execute dbms_workload_repository.create_snapshot;

    SQL> SELECT snap_id, begin_interval_time begin, end_interval_time end FROM SYS.DBA_HIST_SNAPSHOT;

    SQL> SELECT snap_id, startup_time FROM dba_hist_snapshot ORDER BY 1,2;

             SNAP_ID    STARTUP_TIME
             ---------- --------------------
             10         2007/12/19 10:27:32.000 <-- 삭제할 첫번째 스냅샷
             11         2007/12/19 10:27:32.000
             12         2007/12/19 10:27:32.000
             13         2007/12/19 10:27:32.000
             14         2007/12/19 10:27:32.000
             15         2007/12/19 10:27:32.000 <-- 삭제할 마지막 스냅샷
             16         2007/12/19 10:27:32.000
             17         2007/12/19 10:27:32.000

             12 rows selected.


3. SNAP_ID 범위 지정하여 삭제
   --------------------------
   SQL> exec dbms_workload_repository.drop_snapshot_range(10, 15);


4. AWR 스냅샷 주기와 보관 주기 설정
   --------------------------------
   1] 스냅샷주기(1시간,default) 및 보관주기(7일,default) 조회
       SQL> SELECT snap_interval , retention FROM dba_hist_wr_control;
            SNAP_INTERVAL              RETENTION 
            -------------------------- ---------------------------
            +00000 01:00:00.0          +00007 00:00:00.0 

   2] 스냅샷주기(10분) 및 보관주기(15일)을 변경
       SQL> execute DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS
                                                  (interval  => 10,        -- 분단위
                                                   retention => 15*24*60); -- 15일

   3] 스냅샷주기(10분) 및 보관주기(15일) 조회
       SQL> SELECT snap_interval , retention FROM dba_hist_wr_control;
            SNAP_INTERVAL              RETENTION 
            -------------------------- ---------------------------
            +00000 00:10:00.0          +00015 00:00:00.0

5. AWR Report 생성
   ----------------
   과거의 DB의 상태를 awrrpt를 이용하여 확인할 수 있다.

   SQL> connect / as sysdba

   SQL> @?/rdbms/admin/awrrpt.sql 실행

            :

   Enter value for report_type: html 입력

            :

   Enter value for num_days: 8 입력

   Listing the last 8 days of Completed Snapshots

                                                                                    Snap
   Instance     DB Name        Snap Id    Snap Started         Level
   ------------ ------------ --------- ------------------ -----
   DB_SID       DB_NAME       20159 01 Aug 2008 00:00         1
                                          20160 01 Aug 2008 01:00      1
                                          20161 01 Aug 2008 02:00      1
                                          20162 01 Aug 2008 03:00      1
                                          20163 01 Aug 2008 04:00      1
                                          20164 01 Aug 2008 05:00      1
                                          20165 01 Aug 2008 06:00      1
                                          20166 01 Aug 2008 07:00      1
                                          20167 01 Aug 2008 08:00      1
                                          20168 01 Aug 2008 09:00      1
                                               :

                                          20333 08 Aug 2008 06:00      1
                                          20334 08 Aug 2008 07:00      1
                                          20335 08 Aug 2008 08:00      1
                                          20336 08 Aug 2008 09:00      1  --- begin
                                          20337 08 Aug 2008 10:00      1
                                          20338 08 Aug 2008 11:00      1
                                          20339 08 Aug 2008 12:00      1  --- end
                                          20340 08 Aug 2008 13:00      1
                                          20341 08 Aug 2008 14:00      1
                                          20342 08 Aug 2008 15:00      1

   Enter value for begin_snap: 20336 입력 --- begin

   Enter value for end_snap   : 20339 입력  --- end


   Enter value for report_name: awrrpt_20080808_09-12_DB_SID.html 입력

   awrrpt_20080808_09-12_DB_SID.html 파일을 ftp로 pc로 다운로드 받은 후
   열어서 SQL ordered by Elapsed Time 항목 등을 확인해 보시면 된다.

 

Posted by redkite
, |

DBMS_XPLAN 패키지의 장점은 포맷을 자유로이 설정한다는것
작년 겨울에 About DBMS_XPLAN - 1.실행계획 이라는 글에서 실행계획의 세부항목을 소개한바 있다.
이 패키지의가장 뛰어난 특징은 사용자가 출력 포맷을 설정하여 원하는 정보만 얻을수 있다는 것이다.
따라서 이번에는 DBMS_XPLAN 패키지 사용시 포맷설정을 자유롭게 하기 위한 Format Controller를 소개하려 한다.

Format Controller는 아래와 같이 3가지 종류가 있다.

1) 기본 Format Controller : 반드시 적용되어야 하는 기본적인 Controller 이다.
적용하지 않더라도 자동으로 기본값으로 적용된다.

2)세부 Format Controller: 기본 포맷정보에 의해서 표시되거나 생략되는 되는 세부적인 포맷을 Control 한다.
이 Control은 + 표시로 추가하거나 - 표시로 생략이 가능하다.

3)실행통계 Format Controller: 이 Control을 적용하면 실행시의 PGA 통계를 출력한다.

이제 한가지씩 상세히 살펴보자.

1) 기본 Format Controller
1.basic
: 가장 기본적인 포맷으로서 id, Operation, Object Name을 출력한다.
2.typical : basic 옵션에서 한발더 나아가서 옵티마이져가 에상할수 있는 모든것들을 보여준다.
출력되는 정보로는 예상 row, 예상 bytes, 예상 temporary space 사용량, cost, 예상시간,
Predicate Information(Operation 별로 access 및 filter 정보) 이다.
3.serial : typical 과 같으나 parallel 쿼리사용시 관련 정보가 나오지 않는다.
4.all : plan 정보는 typical 과 같으나 plan 이외의 정보중에서 Outline Data 정보를 제외하고 전부 출력한다.
5.advanced : all 과 같지만 Peeked Binds, Outline Data, note 등을 더보여준다.

2) 세부 Format Controller

1.alias :Operation 별로 쿼리블럭명과 object alias 를 control 한다.
plan 의 하단에 위치하며 쿼리변형이 발생하거나 복잡한 쿼리 튜닝의 경우 유용하다.
2.bytes : plan 상의 E-Bytes 정보를 control 한다.
3.cost :plan 상의 Cost (%CPU)를 control 한다.
4.note : 결과중 가장 마지막에 위치하며 여러가지 유용한 정보를 보여준다.
예를 들면 dynamic sampling 이 사용되었는지의 혹은 plan_table 이 old 버젼이므로 새로만들어야
한다는 등의 유용한 정보를 나타낸다.
5.outline : Outline Data를 control 한다. USER 가 작성한 힌트와 옵티마이져가 추가한 내부적인 힌트들이
포함된다. 쿼리변형이 발생하거나 복잡한 쿼리 튜닝의 경우 유용하다.
6.parallel : PARALLEL 쿼리인경우 TQ, IN-OUT, and PQ Distrib 등의 정보를 control 한다.
7.partition :파티션 ACCESS 가 포함된경우 Pstart(시작 파티션) and Pstop(종료 파티션) 등의 정보를 control 한다.
8.peeked_binds : BIND 변수의 값을 control 한다. 단 _optim_peek_user_binds 파라미터의 값이 TRUE 로
되어있는 경우만 해당되며 파라미터는 세션단위로 수정이 가능하다. EXPLAIN PLAN 을
사용한 경우에는 나타나지 않는다.
9.predicate : Predicate Information을 control 한다. Operation 별로 access 및 filter 정보를 나타낸다.
일반적인 튜닝시 가장 눈여겨 보아야할 정보이다.
10.projection : projection information을 control 한다. Operation 별로 select 되는 컬럼정보를 나타낸다.
11.remote : DBLINK 를 사용힐때 REMOTE 쿼리의 수행정보를 control 한다.
12.rows : plan 상의 E-Rows수를 control 한다.

3) 실행통계 Format Controller

이정보들은 DBMS_XPLAN.DISPLAY 함수에는 적용되지 않는다. 왜냐하면 explain plan 은 쿼리가 실제 수행되는것이 아니므로 실행통계정보가 없기 때문이다.
또한 DBMS_XPLAN.DISPLAY_CURSOR 나 DBMS_XPLAN.DISPLAY_AWR 등의 함수 수행시에도 GATHER_PLAN_STATISTICS 힌트를 주거나 아니면 파라미터 STATISTICS_LEVEL = ALL 로 되어 있어야 출력이 가능하다.

1.allstats : I/O 통계정보(Buffers, Reads, Writes)와 PGA 통계정보(OMem, 1Mem , Used-Mem, Used-Tmp,
Max-Tmp 등)를 동시에 control 한다.
2.iostats : I/O 통계정보(Buffers, Reads, Writes)를 control 한다.
3.last : 실행통계 출력시 이 control을 명시하면 가장마지막에 수행된 실행통계를 출력한다.
이 control을 명시하지 않으면 실행통계의 누적치를 출력하므로 주의가 필요하다.
4.memstats :PGA 통계정보(OMem, 1Mem , Used-Mem, Used-Tmp, Max-Tmp 등)를 control 한다.
5.runstats_last : iostats control 과 last control 을 합친것과 같다.
이 control은 Oracle 10g Release 1 에서만 사용할수 있다.
6.runstats_tot : iostats control과 동일하다. 이 control은 Oracle 10g Release 1 에서만 사용할수 있다.

주의사항 : runstats_last 와 runstats_tot 를 제외한 4가지의 control은 Oracle 10g Release 2 에서만 사용할수 있다.


그럼 이제 적용해볼까?
위에서 설명한 Controller 를 이용하여 Format 을적용해보자.

SElECT /*+ gather_plan_statistics */ *
FROM EMP E
WHERE E.DEPTNO = :B1
AND ROWNUM <= 100
ORDER BY EMPNO;

이후로는 위의 SQL 은 동일하므로 생략된다.


 

SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL, 'advanced allstats last' ) );


아래의 plan 은 지면관계상 잘려서 2줄로 나타내었음을 이해해주기 바란다.

----------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows |E-Bytes|E-Temp |
----------------------------------------------------------------------------------
| 1 | SORT ORDER BY | | 1 | 100 | 2600 | 153M|
|* 2 | COUNT STOPKEY | | 1 | | | |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 2002K| 49M| |
|* 4 | INDEX RANGE SCAN | EMP_N1 | 1 | 2003K| | |
----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
Cost (%CPU)| E-Time | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------
19034 (1)| 00:03:49 | 9 |00:00:00.01 | 4 | 2048 | 2048 | 2048 (0)|
| | 9 |00:00:00.01 | 4 | | | |
4126 (1)| 00:00:50 | 9 |00:00:00.01 | 4 | | | |
989 (1)| 00:00:12 | 9 |00:00:00.01 | 3 | | | |
-----------------------------------------------------------------------------------


Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
3 - SEL$1 / E@SEL$1
4 - SEL$1 / E@SEL$1

Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('10.2.0.4')
OPT_PARAM('_optim_peek_user_binds' 'false')
OPT_PARAM('_bloom_filter_enabled' 'false')
OPT_PARAM('_optimizer_connect_by_cost_based' 'false')
OPT_PARAM('optimizer_index_cost_adj' 25)
OPT_PARAM('optimizer_index_caching' 90)
FIRST_ROWS(1)
OUTLINE_LEAF(@"SEL$1")
INDEX_RS_ASC(@"SEL$1" "E"@"SEL$1" ("EMP"."DEPTNO"))
END_OUTLINE_DATA
*/

Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(ROWNUM<=100)
4 - access("E"."DEPTNO"=:B1)

Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=1) "E"."EMPNO"[NUMBER,22], "E"."DEPTNO"[NUMBER,22], "E"."EMPNO_VARCHAR"[VARCHAR2,40], "E"."JOB"[VARCHAR2,2], "E"."HIREDATE"[DATE,7]
2 - "E"."EMPNO"[NUMBER,22], "E"."EMPNO_VARCHAR"[VARCHAR2,40], "E"."JOB"[VARCHAR2,2], "E"."HIREDATE"[DATE,7], "E"."DEPTNO"[NUMBER,22]
3 - "E"."EMPNO"[NUMBER,22], "E"."EMPNO_VARCHAR"[VARCHAR2,40], "E"."JOB"[VARCHAR2,2], "E"."HIREDATE"[DATE,7], "E"."DEPTNO"[NUMBER,22]
4 - "E".ROWID[ROWID,10], "E"."DEPTNO"[NUMBER,22]

'advanced allstats last' 포맷은 출력되는 정보가 너무많아
'advanced allstats last' 포맷을 적용하였으므로 DBMS_XPLAN.DISPLAY_CURSOR 가 보여줄수 있는 모든
정보를 출력 하였다. 단 지면 관계상 가장 처음에 나오는 SQL TEXT 와 sql_id, child number, plan_hash_value 등은 생략하였다. 많은정보를 생략하였음에도 불구하고 일반적인 튜닝시 필요가 없는 정보가 모두 출력되고 말았다.

이제 위에서 정의된 각 Controller 를 이용하여 여러분만의 Format 을 만들어보자.
필자의 경우 가장 선호하는 포맷은 아래의 두가지 이다.

권장되는 포맷유형 2가지

1.쿼리변형이 없는 단순 쿼리 튜닝의 경우:

SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL, 'allstats last -rows +predicate'));

포맷을 'allstats last -rows +predicate' 로 주었으므로 예측 row 수(E-row) 가 생략되고 실행통계와
Predicate Information 만을 출력한다.
아래의 plan 또한 너무길어 지면관계상 2줄로 나타내었다.

-------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time |
-------------------------------------------------------------------------------
| 1 | SORT ORDER BY | | 1 | 9 |00:00:00.01 |
|* 2 | COUNT STOPKEY | | 1 | 9 |00:00:00.01 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 9 |00:00:00.01 |
|* 4 | INDEX RANGE SCAN | EMP_N1 | 1 | 9 |00:00:00.01 |
-------------------------------------------------------------------------------

-------------------------------------
Buffers | OMem | 1Mem | Used-Mem |
-------------------------------------
4 | 2048 | 2048 | 2048 (0)|
4 | | | |
4 | | | |
3 | | | |
-------------------------------------


Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(ROWNUM<=100)
4 - access("E"."DEPTNO"=:B1)


깔끔하게 꼭필요한 정보만 출력 되었다.


2.쿼리변형이 발생하거나 복잡한 쿼리 튜닝의 경우.

SELECT * FROM
TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL, 'allstats last -rows +alias +outline +predicate'));


'allstats last -rows +alias +outline +predicate' 포맷을 사용하면 Query Block Name / Object Alias 정보와 Outline Data 정보가 추가로 출력된다.
아래의 plan 도 지면관계상 2줄로 나타내었다.

-------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time |
-------------------------------------------------------------------------------
| 1 | SORT ORDER BY | | 1 | 9 |00:00:00.01 |
|* 2 | COUNT STOPKEY | | 1 | 9 |00:00:00.01 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 9 |00:00:00.01 |
|* 4 | INDEX RANGE SCAN | EMP_N1 | 1 | 9 |00:00:00.01 |
-------------------------------------------------------------------------------

-------------------------------------
Buffers | OMem | 1Mem | Used-Mem |
-------------------------------------
4 | 2048 | 2048 | 2048 (0)|
4 | | | |
4 | | | |
3 | | | |
-------------------------------------


Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
3 - SEL$1 / E@SEL$1
4 - SEL$1 / E@SEL$1

Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('10.2.0.4')
OPT_PARAM('_optim_peek_user_binds' 'false')
OPT_PARAM('_bloom_filter_enabled' 'false')
OPT_PARAM('_optimizer_connect_by_cost_based' 'false')
OPT_PARAM('optimizer_index_cost_adj' 25)
OPT_PARAM('optimizer_index_caching' 90)
FIRST_ROWS(1)
OUTLINE_LEAF(@"SEL$1")
INDEX_RS_ASC(@"SEL$1" "E"@"SEL$1" ("EMP"."DEPTNO"))
END_OUTLINE_DATA
*/

Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(ROWNUM<=100)
4 - access("E"."DEPTNO"=:B1)

쿼리변형이 발생한경우나 뷰(혹은 인라인뷰) 등을 튜닝할경우 아주 적합한 옵션이다. 하지만 특이한경우
Column Projection Information 이 필요한경우도 있다. 이경우는 +projection 포맷을 추가해주면 된다.

자신만의 적절한 포맷이 필요해
출력되는 정보의 양이 너무 많으면 소화 하기가 힘들고 너무 적으면 튜닝하기가 어려워진다. SQL이 아무리 복잡하고 다양한 경우가 있더라도 2~3 가지의 Format 조합으로도 충분하다. 여러분 각자의 입맛에 맞는 Format 을 개발해보길 바란다. 물론 그러기 위해서는 각각의 Controller 들과 친해질 필요가 있다.

 

 

오라클10g의 dbms_xplan 패키지를 활용하면 튜닝에 필요한 보다 많은 정보를 얻을 수 있다.

 

1. /*+ gather_plan_statistics */ 힌트와

   dbms_xplan.display_cursor 패키지를 이용한 플랜보기
   ---------------------------------------------------------------------------------------
   - statistics_level = all 인 경우에는 Hint 불필요
   - SQL 실행 시 Row Source 레벨의 통계 정보 수집
   - E-Rows(예측 Row 수)와 A-Rows(실제 Row 수)의 비교를 통해 통계정보의 오류를

     파악할 수 있음
   - Optimizer가 얼마나 합리적인 실행 계획을 세우느냐는 Cardinality, 즉 예상 Row수의

     정확성에 달려 있음

   SQL> select /*+ gather_plan_statistics */ * from tb_test where id < 1000;
   또는
   SQL> alter session statistics_level = ALL;
   SQL> select * from tb_test where id < 1000; 

   SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
   ---------------------------------------------------------------------------------------------------------
   | Id  | Operation                   | Name            | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
   ---------------------------------------------------------------------------------------------------------
   |   1 |  TABLE ACCESS BY INDEX ROWID|  TB_TEST        |      1 |      1 |   1000 |00:00:00.01 |     140 |
   |*  2 |   INDEX RANGE SCAN          |  TB_TEST_IDX    |      1 |      1 |   1000 |00:00:00.01 |      70 |
               :
   - 주요 항목 설명
     . E-Rows: 예측 Row 수
     . A-Rows: 실제 Row 수
     . A-Time: 실제 소요 시간
     . Buffers: Logical Reads

참고) dbms_xplan.display_cursor(sql_id, child_number, format)의 format 종류
   - Basic
   - Typical
   - Outline
   - All
   - Advanced
   * allstats last
   * +peeked_binds :

     예) dbms_xplan.display_cursor(null,null,'ALLSTATS LAST +peeked_binds');

 

2. 실행중인 SQL을 SID로 찾아 PLAN 보기
   ------------------------------------
   SQL> select 'select * from TABLE(dbms_xplan.display_cursor('''||sql_id||''','||SQL_CHILD_NUMBER||')) ;'
          from  v$session
         where  sid = 4194;

   결과 : select * from TABLE(dbms_xplan.display_cursor('bqxzbkrtt26gj',0)) ; 

   -- 결과를 실행
   SQL> select * from TABLE(dbms_xplan.display_cursor('bqxzbkrtt26gj',0)) ;

Posted by redkite
, |

SQL 튜닝을 위한 대상 선정을 하기 위하여 AWR을 이용하여

다음의 스크립트를 활용하여 추출할 수 있다.

추출조건(실행횟수는 제외)은 각 시스템의 규모나 성능에 맞도록 조정하여 사용하시길...

-------------------------------------------------------------------------------
1. 소요시간 2초 이상 OR

   읽은 블럭 10,000블럭 이상 SQL 추출(SQL_ID 중복없이 추출)

-------------------------------------------------------------------------------
SELECT 
       A.EXECUTIONS_DELTA,
       ROUND(NVL(A.ROWS_PROCESSED_DELTA,0) /

          DECODE(A.EXECUTIONS_DELTA,0,1,A.EXECUTIONS_DELTA),0) AS AVG_ROWS_PROCESSED,
       ROUND(NVL(A.ELAPSED_TIME_DELTA,0)   /

          DECODE(A.EXECUTIONS_DELTA,0,1,A.EXECUTIONS_DELTA)/1000000,2) AS AVG_ELAPSED,
       ROUND(NVL(A.BUFFER_GETS_DELTA,0)    /

          DECODE(A.EXECUTIONS_DELTA,0,1,A.EXECUTIONS_DELTA),0)        AS AVG_BUFFER_GETS,
       A.MODULE,
       A.ACTION,
       A.SQL_ID,
       A.PARSING_SCHEMA_NAME         AS USERNAME,
       B.SQL_TEXT                    AS SQL_TEXT,
       TO_CHAR(C.BEGIN_INTERVAL_TIME,'YYYY.MM.DD HH24:MI:SS')  AS SQL_BEGIN_SNAP_TIME,
       TO_CHAR(C.END_INTERVAL_TIME,'YYYY.MM.DD HH24:MI:SS')    AS SQL_END_SNAP_TIME,
       A.INSTANCE_NUMBER             AS INST_NO,
       A.SNAP_ID,
       A.PLAN_HASH_VALUE,
       A.BUFFER_GETS_DELTA,
       A.VERSION_COUNT,
       A.OPTIMIZER_MODE,
       A.ELAPSED_TIME_DELTA,
       A.ROWS_PROCESSED_DELTA
  FROM DBA_HIST_SQLSTAT  A,
       DBA_HIST_SQLTEXT  B,
       DBA_HIST_SNAPSHOT C
 WHERE A.SQL_ID          = B.SQL_ID(+)
   AND A.DBID            = B.DBID(+)
   AND A.SNAP_ID         = C.SNAP_ID
   AND A.DBID            = C.DBID
   AND A.INSTANCE_NUMBER = C.INSTANCE_NUMBER
   AND C.BEGIN_INTERVAL_TIME BETWEEN TO_DATE('20100405090000','YYYYMMDDHH24MISS')

                                 AND TO_DATE('20100406235959','YYYYMMDDHH24MISS') 
   AND (A.SNAP_ID,A.BUFFER_GETS_DELTA,A.EXECUTIONS_DELTA,A.ELAPSED_TIME_DELTA)
        = (SELECT MAX(A.SNAP_ID),

                  MAX(BUFFER_GETS_DELTA),

                  MAX(EXECUTIONS_DELTA),

                  MAX(ELAPSED_TIME_DELTA)
             FROM DBA_HIST_SQLSTAT

            WHERE A.DBID            = B.DBID

              AND A.INSTANCE_NUMBER = INSTANCE_NUMBER

              AND A.SQL_ID = SQL_ID)
   AND (A.ELAPSED_TIME_DELTA   >= 2000000 *

          DECODE(A.EXECUTIONS_DELTA, NULL, 1, 0, 1, A.EXECUTIONS_DELTA) OR
        A.BUFFER_GETS_DELTA    >=   10000 *

          DECODE(A.EXECUTIONS_DELTA, NULL, 1, 0, 1, A.EXECUTIONS_DELTA))
   AND A.PARSING_SCHEMA_NAME  = '사용자';

 

-------------------------------------------------------------------------------
2. BIND 변수 값 추출
-------------------------------------------------------------------------------

SELECT /*+ USE_NL(A B C) */
       B.SNAP_ID,
       B.DBID,
       B.INSTANCE_NUMBER,
       B.SQL_ID,
       B.POSITION,
       B.NAME,
       B.VALUE_STRING,
       B.DATATYPE_STRING,
       B.LAST_CAPTURED,
       A.MODULE,
       C.BEGIN_INTERVAL_TIME,
       C.END_INTERVAL_TIME
  FROM DBA_HIST_SQLSTAT  A,
       DBA_HIST_SQLBIND  B,
       DBA_HIST_SNAPSHOT C
 WHERE A.DBID               = B.DBID
   AND A.SNAP_ID            = B.SNAP_ID
   AND A.INSTANCE_NUMBER    = B.INSTANCE_NUMBER
   AND A.SQL_ID             = B.SQL_ID
   AND B.DBID               = C.DBID
   AND B.SNAP_ID            = C.SNAP_ID
   AND B.INSTANCE_NUMBER    = C.INSTANCE_NUMBER
   AND C.BEGIN_INTERVAL_TIME BETWEEN TO_DATE('20100405090000','YYYYMMDDHH24MISS') and TO_DATE('20100406235959','YYYYMMDDHH24MISS') 
   AND (A.ELAPSED_TIME_DELTA   >= 2000000 * DECODE(A.EXECUTIONS_DELTA, NULL, 1, 0, 1, A.EXECUTIONS_DELTA) OR
        A.BUFFER_GETS_DELTA    >=   10000 * DECODE(A.EXECUTIONS_DELTA, NULL, 1, 0, 1, A.EXECUTIONS_DELTA))
   AND A.PARSING_SCHEMA_NAME  LIKE '%WAS'
ORDER BY B.SNAP_ID, B.DBID, B.INSTANCE_NUMBER, B.SQL_ID, TO_NUMBER(POSITION);

 

Posted by redkite
, |

[DB튜닝]튜닝 로드맵

01.DB튜닝 / 2012. 12. 19. 15:32

진행상황 [#MEMORY의 효율저하현상] Server튜닝 Road-Map
ROAD1-1 SHARED-POOL 영역의 튜닝 -----------------
ROAD1-2 버퍼캐쉬 영역
ROAD2-1 데이터파일
ROAD2-3 아카이브파일
ROAD3-3 LOCK경합
ROAD1-2 버퍼캐쉬 영역
ROAD2-1 데이터파일
ROAD2-2 리두-로그파일
ROAD2-3 아카이브파일
ROAD3-1 UNDO-SEGMENT
ROAD3-2 TEMP-SEGMENT
ROAD3-3 LOCK경합
ROAD1-1 공유풀 영역
ROAD1-2 버퍼캐쉬 영역
ROAD1-3 로그버퍼영역
ROAD1-4 자바-풀 영역
ROAD1-5 래치와 경합
ROAD2-2 리두-로그파일
ROAD3-3 LOCK경합
## ## SERVER 튜닝의 마지막입니다.
다른 튜닝ROAD-MAP 으로 이동하거나 '그만하기 ' 을 클릭하십시오. ## ##

 

 

▼ 진행상황 [#SHARED SEVER PROCESS] Network/System튜닝 Road-Map
ROAD1-1 서버프로세스의 활성화 지연 튜닝 ----------
ROAD1-2 프로세스의 경합
ROAD1-3 세션수의 제한
ROAD1-4 시스템의 과부하
## ## NETWORK/SYSTEM 튜닝의 마지막입니다.
다른 튜닝ROAD-MAP 으로 이동하거나 '그만하기' 을 클릭하십시오. ## ##

Posted by redkite
, |

리두로그 파일과 아카이브 파일의 I/O 튜닝

사용자들이 UPDATE, INSERT, DELETE문을 실행하면 모든 변경 데이터는 리두로그 버퍼에 저장된 후 일정시점이 되면 LGWR 백그라운드 프로세스에 의해 리두로그 파일에 저장됩니다.

그리고, 아카이브 모드에서는 리두로그 시스템에 로그 스위치가 발생하면 ARCH 백그라운드 프로세스에 의해 리두로그 파일을 미리 지정된 경로로 복사 합니다.

이 메커니즘이 바로 오라클 데이터베이스의 백업 메커니즘입니다. 하지만, 이 메커니즘이 정상적으로 운영 되었을 경우에는 별 문제가 발생하지 않겠지만, 서로 연속적으로 발생하는 읽기,쓰기 작업이 순차적으로 진행되지 않으면 성능저하 현상이 발생하게 됩니다.

자, 그럼 보다 자세히 성능문제를 알아 봅시다.

리두로그 파일과 성능문제
만약, 여러 개의 리두로그 그룹들이 같은 디스크에 생성되어 있다면, 많은 변경 데이터를 저장할 때 연속적으로 여러 개의 리두로그 파일에 저장할 수 없는 문제가 발생합니다. 이유는 물리적 디스크 장치는 한번에 하나의 I/O 만을 유발시킬 수 있기 때문에 하나의 리두로그 파일에 변경 데이터를 저장한 후 다음 리두로그 파일에 저장하려고 할 때 다른 사용자들의 읽기,쓰기 작업으로 인해 연속적으로 쓰기 작업을 진행하지 못하고 일시적으로 대기해야 하는 문제가 발생하게 됩니다. 이러한 대기상태가 빈번하게 발생하면 데이터베이스 전체적인 성능저하 현상이 나타나게 됩니다.

< 해결방법-1 >
여러 개의 리두로그 파일들을 물리적으로 다른 여러 개의 디스크로 나누어 배치하게 되면 하나의 디스크에서 발생하는 집중현상을 여러 개의 디스크로 분산할 수 있습니다.

< 해결방법-2 >
리두로그 파일의 크기를 보다 크게 늘려 줍니다. 리두로그 파일의 크기가 너무 작으면 많은 변경 데이터를 여러 리두로그 파일에 나누어 저장해야 하기 때문에 불필요한 로그 스위치가 발생하기 때문입니다.

< 해결방법-3 >
리두로그 그룹의 수를 늘려 줍니다. 하나의 리두로그 파일의 크기를 너무 크게 설정해 두면 로그 스위치가 발생할 때 ARCH 프로세스에 의해 아카이브 되는데 소요되는 시간이 너무 길어질 수도 있습니다. 즉, 아카이브가 완료되지 않으면 로그 스위치에 의해 다음 리두로그 파일에 변경 데이터가 저장되지 못하고 일시적으로 대기해야 하는 문제가 발생하게 됩니다.
이런 경우를 최소화 하기 위해서는 리두로그 그룹 수를 적절히 늘려 주어야 합니다.
$HOME/ADMIN/BDUMP/alert_<SID>.ora 파일에 다음과 같은 메시지가 나타나면 LGWR 프로세스에 대기상태가 발생한 것 입니다.

"checkpoint not complete ; unable to allocate file"
아카이브 파일과 성능문제

메모리 영역인 리두로그 버퍼의 정보는 LGWR 프로세스에 의해 리두로그 파일에 저장됩니다. 하나의 리두로그 파일이 모두 쓰여지면 ARCH 프로세스에 의해 오프라인 또는 온라인 저장구조에 저장됩니다. 문제점은, ARCH 프로세스가 다른 저장구조에 저장하는데 소요되는 시간보다 LGWR 프로세스가 메모리 영역으로부터 리두로그 파일에 저장하는데 소요되는 시간이 훨씬 빠르다는 점입니다. 즉, 많은 변경 데이터가 보다 빠르게 리두로그 파일에 저장되려면 아카이브 작업이 빠르게 완료되어야 하는데, 시간이 많이 소요되게 되면 연속적인 쓰기 작업을 하지 못한다는 점입니다.

< 해결방법-1 >
log_archive_max_processes 파라메터의 값을 보다 높게 설정해 줍니다. ARCHIVE 모드로 데이터베이스 환경을 설정하게 되면 기본적으로 하나의 ARCH 프로세스가 활성화 됩니다. 동시에 많은 변경작업이 발생하는 경우 하나의 ARCH 프로세스로 아카이브를 하는 것 보다 여러 개의 ARCH 프로세스를 활성화하게 되면 보다 빠르게 아카이브 작업을 완료할 수 있습니다.

< 해결방법-2 >
아카이브 파일은 결국 리두로그 파일의 백업 정보이므로 리두로그 파일의 크기와 개수를 적절하게 조정하는 것이 아카이브 파일의 개수와 크기를 조절할 수 있는 방법입니다.

< 해결방법-3 >
아카이브 파일들이 저장되는 저장구조를 충분하게 확보하십시오. 아카이브 모드에서 파일들이 저장되는 저장구조에 여유공간이 없으면 아카이브 작업은 더 이상 진행되지 않습니다. 즉, LGWR 프로세스는 리두로그 영역의 데이터를 리두로그 파일에 저장하지 못하게 되고 궁극적으로 데이터베이스 전체적인 대기상태가 발생하게 됩니다.

체크포인트

오라클 데이터베이스의 백그라운드 프로세스 중 CKPT의 역할은 LGWR 프로세스가 리두로그 버퍼의 내용을 리두로그 파일에 저장하는 시점의 정보와 DBWR 프로세스가 데이터 버퍼 캐시영역의 내용을 데이터 파일에 저장하는 시점의 정보를 동기화 시켜주는 역할을 하게 됩니다.
하지만, 이러한 체크포인트가 너무 자주 실행되면 불필요한 읽기,쓰기 작업이 빈번하게 발생되기 때문에 데이터베이스의 전체적인 성능이 저하될 수 있습니다. 반대로, 자주 실행되면 인스턴스 문제로 인한 데이터베이스의 장애가 발생한 경우 빠르게 복구할 수 있는 장점을 가지고 있기도 합니다.

다음은 체크포인트가 발생하는 시점입니다.

- 로그 스위치가 발생할 때
- 데이터베이스가 정상적으로 종료될 때
- ALTER SYSTEM CHECKPOINT 명령어가 실행될 때
- ALTER SYSTEM SWITCH LOGFILE 명령어가 실행될 때
- 테이블스페이스가 ONLINE 상태에서 OFFLINE 상태로 변경될 때

다음은 체트포인트를 유발시키는 파라메터 들입니다.

1) LOG_CHECKPOINT_INTERVAL : 체크포인트가 발생하는 간격을 지정된 블록수로 결정합니다.리두로그 파일에 지정된 변경 데이터가 저장될 때 마다 체크포인트가 발생합니다.

2) LOG_CHECKPOINT_TIMEOUT : 체크포인트가 발생하는 간격을 지정된 시간으로 결정합니다. 이 시간이 지날 때 마다 LGWR과 DBWR 프로세스는 각 파일에 정보를 저장합니다.

3) FAST_START_IO_TARGET : 인스턴스를 복구할 때 복구해야 할 블록 수를 지정할 수 있습니다. 항상 지정된 크기의 정보가 메모리에 남게 되면 체크포인트가 발생합니다.

4) DB_BLOCK_DIRTY_TARGET : DBWR 프로세스는 지정된 블록수가 데이터 버퍼 캐시영역에서 확보되면 데이터 파일에 저장합니다.

5) FAST_START_MTTR_TARGET : 인스턴스를 복구할 때 복구시간을 지정할 수 있습니다. 지정된 복구 대상시간 만큼의 정보가 메모리에 남게 되면 체크포인트가 발생합니다.

결론적으로, 오라클 데이터베이스의 성능 튜닝을 할 때는 체크포인트가 자주 발생하지 않도록 다음과 같이 환경설정을 해야 합니다.

1) 체크포인트의 빈도를 줄인다는 말은 로그 스위치가 발생하는 빈도를 줄인다는 것과 같은 의미를 가지고 있으므로 리두로그 파일의 크기를 보다 크게 설정해야 합니다.

2) 빈번한 변경작업이 발생하는 데이터베이스 환경에서는 LOG_CHECKPOINT_INTERVAL 또는 LOG_CHECKPOINT_TIMEOUT과 같은 파라메터를 통해 체크포인트의 발생빈도를 줄일 수 있습니다.

Posted by redkite
, |

 

락 (LOCK)

DML문을 실행하면 해당 트랜잭션에 의해 발생한 데이터가 다른 사용자에 의해 변경이 발생하지 않도록 LOCK(잠금현상)을 발생시킵니다. 그 이유는 진행중인 트랜잭션의 작업이 완료될 때 까지는 다른 사용자들로부터 보호되어야 하기 때문입니다. 이러한 현상은 COMMIT 또는 ROLLBACK문이 실행되면 자동적으로 해제가 됩니다.

다음 내용은 잠금현상의 주요 특징입니다 .

- 데이터의 검색이 일관되게 해 줍니다.

- DML문장이 실행될 때 ROLLBACK을 위해 오라클 서버가 변경 전 데이터와 변경 후 데이터를 모두 보관합니다.

- 테이블을 직접 변경하고 있는 세션에서 만 변경중인 데이터를 확인할 수 있으며 다른 세션에서는 변경 중인 데이터를 확인할 수 없습니다.

- 어떤 사용자가 변경 중인 행을 다른 사용자가 변경할 수 없습니다. 변경 중인 사용자에 의해 COMMIT 또는 ROLLBACK문이 실행된 후 변경할 수 있습니다.

LOCK의 종류

오라클 사에서 제공하는 락의 종류는 행 -레벨 락과 테이블-레벨 락, 두 가지 종류가 있으며 데이터베이스를 설치하면 기본적으로 행-레벨 락이 기본 모드입니다.

다음은 현재 사용하고 있는 데이터베이스가 어떤 락 -모드인지 확인하는 방법입니다.

SQL > show parameter always_content
SQL > show parameter row_locking
NAME TYPE VALUE

row_locking string always ß 행-레벨 락 모드

ROW_LOCKING = INTENT ß 테이블-레벨 락 모드

1) ROW-LEVEL의 LOCK

행 -레벨 락이란 SELECT~FOR UPDATE, UPDATE, INSERT, DELETE문에 의해 해당 행에만 발생하는 락 종류입니다. 다음 예제를 따라 해 보십시오.

<세션1>

SQL > SELECT * FROM emp WHERE empno = 7844;
SQL > UPDATE emp SET sal = sal * 1.1 WHERE empno = 7844;
SQL>

<세션2>

SQL > UPDATE emp SET sal = sal * 1.1 WHERE empno = 7844;

ß Waiting 발생 : 세션1에서 변경 중인 행이기 때문에 ROW-LEVEL LOCK 발생

<세션1>

SQL > COMMIT;

<세션2>

SQL> ß 세션1에서 COMMIT이 실행되었기 때문에 LOCK 해제

또는

<세션1>

SQL > SELECT * FROM emp WHERE empno = 7844 FOR UPDATE;

ß SELECT ~ FOR UPDATE 문장은 SELECT문이지만 LOCK이 발생합니다.

검색되는 ROW들은 앞으로 변경되어질 행으로 미리 LOCK을 설정합니다.

SQL >

<세션2>

SQL > UPDATE emp SET sal = sal * 1.1 WHERE empno = 7844;

ß Waiting 발생 : 세션1에서 변경 중인 행이기 때문에 ROW-LEVEL LOCK 발생

<세션1>

SQL > ROLLBACK;

<세션2>

SQL >

ß 세션1에서 COMMIT이 실행되었기 때문에 LOCK이 해제되고 문장이 실행됨

2) TABLE-LEVEL의 LOCK

변경중인 데이터가 특정 행들이 아닌 테이블 전체인 경우에는 테이블 -레벨 락이 발생합니다.

(DML문이 실행되었지만 WHERE 조건절이 없는 경우) 오라클 사에서 제공하는 테이블-레벨 락에는 3가지 종류가 있습니다.

- S LOCK : 테이블 전체 행에 설정되는 락 종류이며 마스터-디테일(Master-Detail) 관계 또는 부모-자식(Paraent-Child) 관계를 가진 2개의 테이블에서 부모 테이블이 변경될 때 자식 테이블에 발생하는 테이블-레벨 락 입니다. 또는 다음과 같은 명령어에 의해 수동으로 발생시킬 수 있습니다.

< 문법 > LOCK TABLE [테이블명] IN SHARE MODE;

- SRX LOCK : S 락과 같은 경우에 발생하며, 차이점은 부모 테이블이 생성될 때 자식 테이블의 FOREIGN-KEY 컬럼에 대한 부모 테이블의 컬럼이 ON DELETE CASCADE 절에 의해 생성되어 있고 FOREIGN-KEY 컬럼에 인덱스가 생성되어 있지 않은 경우입니다.( 다음 페이지에서 자세히 소개됩니다.)

다음과 같은 명령어에 의해 수동으로 발생시킬 수 있습니다 .

< 문법 > LOCK TABLE [테이블명] IN SHARE ROW EXCLUSIVE MODE;

- X LOCK : 가장 강력한 범위의 테이블-레벨 락 입니다. X 락이 발생하면 어떤 종류의 다른 락을 발생시킬 수 없습니다.

< 문법 > LOCK TABLE [테이블명] IN EXCLUSIVE MODE;

<세션1>

SQL > DELETE emp; ß
SQL >

ß 전체 테이블을 삭제하기 때문에 TABLE-LEVEL LOCK이 발생

SQL >

<세션2>

SQL > UPDATE emp SET sal = sal * 1.1 WHERE deptno = 20;

ß Waiting 발생 : 세션1에서 삭제 중인 행이기 때문에 TABLE-LEVEL LOCK 발생

<세션1>

SQL > COMMIT;

<세션2>

0 행이 갱신되었습니다. ß 세션1에서 COMMIT이 실행되었기 때문에 EMP 테이블에는

ROW가 존재하지 않으므로 에러가 발생합니다.

또는

SQL > LOCK TABLE emp IN EXCLUSIVE MODE; ß TABLE-LEVEL LOCK을 유발시키는 명령어
SQL > UPDATE emp SET sal = sal * 1.1 WHERE empno = 7954;
SQL > ß 하나의 행 만 변경하지만 TABLE-LEVEL LOCK 발생

<세션2>

SQL > UPDATE emp SET sal = sal * 1.1 WHERE empno = 7954;

ß Waiting 발생 : 세션1에서 변경 중인 행이기 때문에 TABLE-LEVEL LOCK 발생

<세션1>

SQL > COMMIT;

<세션2>

SQL > ß 세션1에서 COMMIT이 실행되었기 때문에 LOCK 해제
S 락과 SRX 락의 튜닝

위 그림을 보면 , 마스터-디테일(Master-Detail) 관계 또는 부모-자식(Paraent-Child) 관계를 가진 2개의 테이블이 있습니다. 부서정보 테이블에 부서번호 컬럼은 식별키(Primary Key)이고 사원정보 테이블에 부서번호 컬럼은 부서정보 테이블의 부서번호 컬럼을 참조하는

외부키 (Foreign-Key) 입니다.

SQL >

CREATE TABLE DEPT
(DEPTNO NUMBER(2) PRIMARY KEY,
DNAME VARCHAR2(14),
LOC VARCHAR2(13) );

SQL >

CREATE TABLE EMP
(EMPNO NUMBER(4) NOT NULL,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7, 2),
COMM NUMBER(7, 2),
DEPTNO NUMBER(2) REFERENCES DEPT (DEPTNO) );

이와 같은 , 부모-자식 관계를 가진 2개의 테이블에서 부모 테이블의 컬럼이 변경될 때 자식 테이블에 테이블-레벨 락이 발생합니다.

SQL >

DELETE FROM dept WHERE deptno = 10;

SQL >

테이블 -레벨의 락이 발생하는 이유는 2개의 테이블에 저장되어 있는 공통 컬럼은 서로 참조하는 관계를 가지고 있기 때문에 부모 테이블에 저장되어 있지 않은 컬럼 값이 자식 테이블에 존재할 수 없기 때문입니다. 그래서, 부모 테이블에 대해 변경이 발생하면 자식 테이블 전체도 S 테이블 락을 발생하는 것 입니다.

문제점은 자식 테이블의 경우 , 실제적인 DML문이 아닌 부모 테이블로 인해 테이블-레벨 락이 발생했기 때문에 락이 해제될 때 까지 무작정 기다려야 한다는 점입니다.

자식 테이블에 대해 더 이상의 DML문을 수행할 수 없는 문제가 발생하게 됩니다.

이런 문제를 해결하기 위한 유일한 방법은 자식 테이블의 외부키 (Foreign-Key) 컬럼에 인덱스를 생성하는 방법입니다.

SQL >

CREATE INDEX I_emp_deptno ON dept(deptno);

외부키 컬럼에 인덱스를 생성하면 S 락 또는 SRX 락이 발생할 때 자식 테이블에 대해 테이블-레벨 락을 발생시키지 않고 인덱스에 락을 발생시키기 때문에 자식 테이블에 대해 계속적인 DML 작업을 수행할 수 있게 됩니다.

즉 , 이런 문제로 인한 대기상태를 피할 수 있으므로 성능이 개선될 수 있습니다.

락 모니터링

데이터베이스를 사용하다 보면 개발자에 의해 실행되는 수 많은 SQL문들이 반복적으로 락을

설정하고 또 해제하게 됩니다 . 많은 사용자들이 동시에 테이블을 입력, 수정, 삭제, 조회하게 되면 서로 걸고 걸리는 락 관계가 성립되게 됩니다.

그렇다면 , 개발자가 어떤 테이블을 조작하려는데 누군가가 락을 발생시킨 상태라면 더 이상작업을 진행시키지 못하게 되고, 락을 발생시킨 사용자가 COMMIT 또는 ROLLBACK 문을 수행할 때 까지 무작정 대기해야 하는 문제가 발생하게 됩니다.

이런 경우 , 락을 유발시킨 사용자가 누구인지 알 수 있다면 락 해제를 요청할 수 있을 것이고 또한, 어떤 사용자가 어떤 SQL문을 언제 실행하여 어떤 테이블, 어떤 행에 어떤 종류의 락을 유발하고 있는지도 알 수 있다면 보다 효과적으로 락을 관리할 수 있을 것입니다.

위 그림을 보면 , 세션번호가 10번인 사용자가 EMP 테이블의 2번째 행을 UPDATE문으로 변경하고 있습니다. 아직 COMMIT문을 실행하지 않은 상태에서 세션번호가 20번인 사용자가 같은 EMP 테이블의 2번째 행을 UPDATE 하려고 합니다. 이런 경우가 발생하면 2번째 사용자의 UPDATE문은 실행되지 못하고 대기상태에 빠지게 됩니다. 첫 번째 사용자가 트랜잭션을 빠르게 종료 시킨다면 문제가 없겠지만 그렇지 못한 경우에는 무한정 대기상태에 빠지게 됩니다.

이런 경우 , 오라클사에서 제공하는 UTLLOCKT.SQL 스크립트를 실행하면 어떤 세션에서, 어떤 종류의 락을, 어떤 문장으로 실행했고 그 문장으로 인해 대기상태에 빠진 세션은 어떤 정보을 가지고 있는지를 상세히 제공해 줍니다.

SQL > CONNECT system/manager
SQL >

@$HOME/rdbms/admin/utllockt.sql
USERNAME SID OBJECT SQL

SCOTT 10 EMP update emp set sal = sal * 1.1
where empno = 7934
SCOTT 20 EMP update emp set sal = sal * 1.1
where empno = 7934

ß 분석결과를 보면, 10번 세션번호를 가진 사용자에 의해 UPDATE문이 실행되었고 행-레벨의 락으로 인해 20번 세션번호를 가진 사용자의 세션이 대기상태에 있는 것을 확인할 수 있습니다.

락의 원인이 분석되었으면 신속히 락을 해제해야 하는데 락을 해제하는 방법은 2가지가 있습니다. 첫 번째 방법은 락을 유발한 세션에서 COMMIT 또는 ROLLBACK문을 실행하는 방법이고 두 번째 방법은 ALTER SYSTEM KILL SESSION 명령어로 강제로 세션을 종료시키는 방법입니다. 대부분의 경우는 COMMIT, ROLLBACK 문장에 의해 자동적으로 락을 해제하게 되고 데드-락(Dead-Lock)과 같이 더 이상 락을 해제할 수 없는 상황에는 ALTER SYSTEM KILL SESSION 명령어를 사용하게 됩니다.

< 문법 > SELECT sid, serial# FROM v$session;

ALTER SYSTEM KILL SESSION ‘[sid], [serial#]';

자 ~ 다음 예제를 따라 해 보십시오.

실습을 위하여 3개의 세션(session)을 Open하십시오. 2개 세션은 SCOTT 사용자로 나머지 하나는 “/as sysdba”로 접속합니다.

1) 첫 번째 SCOTT Session에서 한 사원의 월급을 변경(Update) 합니다.

먼저 , DEPT, EMP 테이블을 새롭게 생성하고 다음과 같이 제약조건을 설정하십시오

$ sqlplus scott/tiger
SCOTT(1).SQL> @$ORACLE_HOME/sqlplus/demo/demobld
$ sqlplus scott/tiger
SCOTT(1).SQL> alter table dept
add constraint dept_deptno_pk primary key(deptno) ;
SCOTT(1).SQL> alter table emp
add constraint emp_deptno_fk foreign key(deptno)
references dept(deptno);
SCOTT(1).SQL> update emp
set sal = sal * 1.1
where empno = 7934 ; ß

UPDATE문에 의해 LOCK이 발생합니다

2) SYS Sesseion에서 데이터베이스의 Lock 상태를 확인해 봅니다.

$sqlplus “/as sysdba”

SYS.SQL>

select username, sid
from v$session
where username = ‘SCOTT';

ß 현재 Lock을 유발하고 있는 사용자명과 Session-ID를 분석하십시오

<질문> 사용자명은 ?

세션 ID 는 ?

SYS.SQL>

SYS.SQL> select sid, type, id1, lmode, request
from v$lock
where sid = <Session ID>;

ß 조금 전 분석된 Session-ID

이번에는 Lock이 발생한 Object가 어떤 것 인지 알아 봅시다.

SYS.SQL>

select object_name
from dba_objects
where object_id in ( ID1 컬럼의 값 );

ß 위 문장 V$LOCK을 실행했을 때 해당 SID의 TYPE 컬럼이 ‘TM' lock이 걸린 행의

ID1 컬럼의 값을 괄호 안에 지정하십시오.

OBJECT_NAME

EMP ß EMP 테이블이 DML문에 의해 Lock 발생하고 있습니다.

다음과 같이 V$LOCKED_OBJECT 자료사전을 통해서도 알 수 있습니다.

SYS.SQL> col ORACLE_USERNAME format a10

SYS.SQL> col OS_USER_NAME format a12

SYS.SQL> select OBJECT_ID, SESSION_ID,

ORACLE_USERNAME, OS_USER_NAME, LOCKED_MODE

from v$locked_object

OBJECT_ID SESSION_ID ORACLE_USE OS_USER_NAME LOCKED_MODE

5285 7 SCOTT dba9i101 3

OBJECT_ID : Lock이 유발된 객체번호

SESSION_ID : Lock을 유발한 사용자의 세션번호

ORACLE_USER : Lock을 유발한 사용자명

OS_USER_NAME : Lock을 유발한 사용자의 O/S 계정

LOCKED_MODE : 1 SELECT ~ FOR UPDATE에 의한 Lock

2 INSERT에 의한 Lock

3 UPDATE에 의한 Lock

4 DELETE에 의한 Lock

3) 두번째 SCOTT Session 에서 EMP 테이블을 삭제합니다… 삭제가 될까요 ?

$ sqlplus scott/tiger

SCOTT(2).SQL>

drop table emp;
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified

ß 이 문장이 실행되기 위해서는 Exclusive Table Lock이 필요한데, 첫번째 SCOTT Session이 EMP 테이블에 대해 Row Exclusive Table Lock 잡고 있기 때문에 DROP 문장은 실행될 수 없습니다.

두번째 SCOTT Session 에서 EMP 테이블의 다른 행을 변경해 봅시다.

SCOTT(2).SQL>

update emp
set sal = sal * 1.1
where empno = 7902 ;

ß SYS Session애서 현재 데이터베이스에 어떤 Lock이 발생했는지 알아봅시다.

SCOTT(2).SQL>

select username, sid
from v$session
where username = ‘SCOTT' ;
USERNAME SID

SCOTT 7
SCOTT 9 새롭게 Lock이 발생한 세션

<질문> 사용자명은 ? 세션 ID 는 ?

SYS.SQL>

select sid, type, id1, lmode, request
from v$lock
where sid = <Session ID>; ß 조금 전 분석된 Session-ID

SYS.SQL>

select OBJECT_ID, SESSION_ID,

ORACLE_USERNAME, OS_USER_NAME, LOCKED_MODE

from v$locked_object;

OBJECT_ID SESSION_ID ORACLE_USE OS_USER_NAME LOCKED_MODE

5285 7 SCOTT dba9i101 3
5285 9 SCOTT dba9i101 3 ß 새로운 Lock 정보
두번째 SCOTT session 에서 다음과 같이 UPDATE문을 실행해 보십시오.
SCOTT(2).SQL> rollback;
SCOTT(2).SQL> update emp set sal = sal * 1.1 where empno = 7934 ;
이미 SCOTT(1) Session에서 UPDATE문을 진행하고 있기 때문에 Lock이 발생되어 있기 때문에 변경할 수 없습니다.( Waiting 상태 )

SYS Session 에서 어떤 lock이 발생했는지 알아봅시다.

SYS.SQL>

select sid, type, id1, lmode, request
from v$lock
where request <> 0; ß request <> 0 이면 waiting 중인 Lock정보를 검색합니다.
SID TY ID1 LMODE REQUEST

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

9 TX 131077 0 6

ß 세션ID 9번이 현재 Waiting 상태임을 알 수 있습니다.

또는 , 다음과 같은 방법으로 Waiting 중인 세션을 분석할 수 있습니다.

SYS.SQL> SQL> select XIDUSN, OBJECT_ID, SESSION_ID,

ORACLE_USERNAME, OS_USER_NAME, LOCKED_MODE

from v$locked_object

where XIDUSN = 0 ; ß XIDUSN = 0 이면 waiting 중 세션

XIDUSN OBJECT_ID SESSION_ID ORACLE_USE OS_USER_NAME LOCKED_MODE

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

0 5285 9 SCOTT dba9i101 3

이번에는 어떤 사용자의 어떤 세션에서 어떤 Object에 대해 어떤 SQL문을 실행하여 Lock이 발생하였는지를 분석하는 방법입니다.

SYS.SQL> @$ORACLE_HOME/rdbms/admin/utllockt.sql

SYS.SQL> col username format a8

SYS.SQL> col sid format 999

SYS.SQL> col object format a12

SYS.SQL> col sql format a50

SYS.SQL> SELECT b.username username, c.sid sid,

c.object object, a.sql_text SQL

FROM v$sqltext a,v$session b,v$access c

WHERE a.address = b.sql_address and

a.hash_value = b.sql_hash_value and

b.sid = c.sid and c.owner != 'SYS';

USERNAME SID OBJECT SQL

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

SCOTT 9 EMP update emp set sal = sal * 1.1

where empno = 7934

SCOTT 9 EMP update emp set sal = sal * 1.1

where empno = 7934

결과를 보고 lock을 잡고 있는 Session을 강제로 Kill하면 Lock이 풀리게 됨으로 다음 트랜잭션은 실행됩니다.

SQL> select sid, serial#, username from v$session where sid = ???;

SID SERIAL# USERNAME

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

9 81 SCOTT

SQL> alter system kill session ‘9, 81' ;

ß 위 문장에서 분석된 Session-ID와 Seria-No로 Kill하십시오.

SCOTT(2) Session으로 이동해서 Enter-Key를 눌러 보십시오.

이 세션은 강제로 Kill 되었습니다..

SCOTT(2).SQL> update emp

set sal = sal * 1.1

where empno = 7934 ; ß 새로운 SQL문 작성 후 Enter

ERROR at line 1:

ORA-00028: your session has been killed

때로는 , 특정 사용자가 실행한 트랜잭션이 계속해서 Lock을 유발하고 있을 때 DBA는 해당 작업을 분석하여 강제로 세션을 종료시켜야 합니다.

< 잠깐 > 기타 관련사전

1) DBA_WAITERS

이 자료사전은 다른 사용자의 DML문이 실행된 후 락이 발생한 행에 대해 또 다른 사용자가 DML문을 실행하게 되면 대기상태에 빠지게 되는데, 이러한 경우, 대기하는 사용자의 세션 정보를 제공합니다. 이 자료사전을 참조하기 전에 반드시 CATBLOCK.SQL 스크립트를 실행해야 합니다.

< 세션-1 >

SQL> @$HOME/rdbms/admin/catblock.sql

SQL> connect scott/tiger

SQL> update dept set loc = 'aa';

<세션-2>

$ sqlplus scott/tiger

SQL> update dept set loc = 'aa';

< 세션-1 >

SQL> SELECT WAITING_SESSION HOLDING_SESSION LOCK_TYPE, LOCK_ID1, LOCK_ID2

FROM DBA_WAITERS;

WAITING_SESSION HOLDING_SESSION LOCK_TYPE LOCK_ID1 LOCK_ID2

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

10 9 Transaction 131076 229

2) DBA_BLOCKERS : 현재 락을 유발하고 있는 모든 세션정보를 알 수 있습니다.

SQL >

SELECT * FROM DBA_BLOCKERS;
HOLDING_SESSION

9

 

Posted by redkite
, |
 

 
  

소트 영역의 튜닝 분석

 

대부분의 사용자들이 작성하는 SQL문은 실행 시 분류작업(SORTING)이 발생합니다. 분류작업이 발생하면 별도의 시간과 공간이 추가로 필요하기 때문에 분류작업이 발생하지 않는 경우보다 성능이 저하될 수밖에 없습니다. 또한, 별도의 공간이 분류작업을 하기에 충분하지 못하다면 성능은 더 저하될 수밖에 없는 것입니다. 사용자의 SQL문에서 분류작업이 발생하는 경우는 다음과 같습니다.

 

- 인덱스를 생성하는 문법을 사용하는 경우(CREATE INDEX ~문)
- 인덱스가 있는 테이블에 병렬로 데이터를 입력하는 경우(INSERT INTO~PARALLEL(DEGREE n)
- ORDER BY, GROUP BY을 사용하는 경우(SELECT ~ ORDER BY ~ GROUP BY ~)
- DISTINCT 키워드를 사용하는 경우(SELECT DISTINCT ~ )
- UNION, INTERSECT, MINUS 연결 연산자를 사용하는 경우(SELECT~ UNION SELECT ~)
- 인덱스가 없는 2개의 테이블을 조인하는 경우(SELECT ~ FROM a, b WHERE ~)
- ANALYZE 명령문을 사용하는 경우(ANALYZE TABLE ~)

 

이와 같은 SQL문이 실행된다면 내부적으로 분류작업을 통해 데이터를 리턴해 줍니다.

 

소트 메모리 영역(Sort Memory Area)

 

자~ 그럼 생각해 보세요. 이러한 분류작업은 어디서 실행될까요 ? "SQL문을 처리하는 과정"과 "DML문을 처리하는 과정"을 통해 데이터베이스의 구조를 알아보았는데 이러한 분류작업은 어떤 구조에서 처리되는 걸까요?

두 가지 경우에 대해서 알아보아야 할 것입니다. 첫 번째, 전용서버 환경에서는 서버 프로세스에 있는 PGA(PROGRAM GLOBAL AREA) 영역의 SORT AREA 영역에서 분류작업이 실행됩니다.
두 번째, 공유서버 환경에서는 SORT AREA 영역이 공유 풀 영역에 존재하기 때문에 모든 분류작업이 SGA 영역에서 실행됩니다.

그럼, 이 영역의 크기는 어떤 값에 의해 결정될까요 ? INIT<DB명>.ORA 파일에 있는 다음 파라메터 값에 의해 결정됩니다. 만약, 대용량 데이터에 대한 분류작업이 발생하는 경우에는 이 파라메터 값을 높게 설정하여 메모리 영역에서 보다 원활한 분류작업이 발생하도록 해야만 좋은 성능을 기대할 수 있습니다.

 

SORT_AREA_SIZE = [크기]

 

또한, 사용자의 SQL문에 의해 사용된 SORT_AREA_SIZE 영역이 분류작업 후 PGA 영역에 계속해서 할당되어 있으면 메모리의 낭비가 발생할 수 있기 때문에 다음과 같은 파라메터에 의해 SORT_AREA_SIZE 영역을 축소(SHRINK) 시킬 수 있습니다.

이 파라메터의 값은 SORT_AREA_SIZE 파라메터 값을 초과하여 정의할 수는 없습니다.

 

SORT_AREA_RETAINED_SIZE = [크기]

 

소트 디스크 영역(Sort Disk Area)

 

자~ 한가지 문제에 대해서 더 알아보겠습니다. 시스템 또는 데이터베이스를 위한 메모리 영역은 항상 크기가 제한되어 있습니다. 만약, 분류작업을 해야할 테이블이 100만 건의 행을 가지고 있다면 모든 행을 메모리 영역에 저장한 후 분류작업을 할 수는 없겠죠 ? 그래서, 필요한 영역이 TEMPORARY 테이블스페이스입니다. 메모리 영역인 SORT_AREA_SIZE에서 1차적인 분류작업이 발생하고 작업이 완료되지 못하면 디스크 영역에 생성되어 있는 TEMPORARY 테이블스페이스에 분류된 내용의 일부를 잠시 저장하게 됩니다. 이러한 연속적인 작업을 통해 대용량 데이터에 대한 분류작업을 실행하게 됩니다. 참~~ TEMPORARY 테이블스페이스는 오라클 유니버설 인스톨러에 의해 오라클 데이터베이스를 설치하면 기본적으로 생성되는 논리적 구조입니다.(보다 자세한 내용은 "오라클 9i의 생김새 알아보기"를 참조하십시오.)

예를 들어, 윈도우 시스템에서 [설정] --> [제어판] --> [시스템] --> [고급] --> [성능옵션]에 보면 가상 메모리를 설정하는 기능이 있습니다. 시스템의 메모리 영역에서 완료되지 못한 사용자 작업의 일부를 잠시 저장해 두는 디스크 상의 공간을 의미합니다. 이와 유사한 개념을 가진 데이터베이스의 논리적 구조가 TEMPORARY 테이블스페이스입니다.

TEMPORARY 테이블스페이스는 다음과 같은 문법으로 생성됩니다.

 
 

CREATE DATABASE ora90
LOGFILE GROUP 1 ('c:\oracle\oradata\ora90\redo01.log') size 10m,
GROUP 2 ('c:\oracle\oradata\ora90\redo02.log') size 10m,
GROUP 3 ('c:\oracle\oradata\ora90\redo03.log') size 10m
DATAFILE 'c:\oracle\oradata\ora90\system01.dbf' size 100m
UNDO TABLESPACE undo
DATAFILE 'c:\oracle\oradata\ora90\undo01.dbf' size 50m
DEFAULT TEMPORARY TABLESPACE temp
TEMPFILE 'c:\oracle\oradata\ora90\temp01.dbf' size 30m
EXTENT MANAGEMENT LOCAL UNIFORM size 1m
CHARACTER SET ko16ksc5601
NATIONAL CHARACTER SET ko16ksc5601
SET TIME_ZONE = 'Korea/Seoul';

 

이 문법은 오라클 유니버설 인스톨러에 의해 최초 만들어지는 TEMPORARY 테이블스페이스에 관련된 문법입니다.

데이터베이스 설치 후 모든 사용자들은 TEMP 테이블스페이스에서 모든 분류작업을 하게 됩니다. 또한, 이 테이블스페이스는 기본적으로 로컬리매니저 테이블스페이스 형태로 생성됩니다.

 

CREATE TEMPORARY TABLESPACE [테이블스페이스명]
TEMPFILE '[경로와 파일명] SIZE [크기]
EXTENT MANAGEMENT LOCAL UNIFORM SIZE [크기];

 

이 문법은 데이터베이스 생성 후 추가적으로 TEMPORARY 테이블스페이스를 생성하는 방법입니다. "사용자 관리" 및 "테이블스페이스 생성 시 주의사항"에서 언급했던 대로 좋은 성능을 위해서는 사용자별로 TEMPORARY 테이블스페이스를 생성하여 할당해야 합니다.

 
 

 

충분한 SORT 공간을 할당하라.

 

그럼, 분류영역에 대한 튜닝 분석방법을 알아보겠습니다. 데이터베이스가 생성되면 기본적으로 TEMPORARY 테이블스페이스가 생성되며 또한, 사용자는 추가적으로 TEMPORARY 테이블스페이스 생성하게 됩니다. 사용자가 분류하려는 테이블의 데이터가 너무 커서 현재 할당되어 있는 TEMPORARY 테이블스페이스 공간으로는 모든 분류작업을 할 수 없을 때 SQL문의 성능은 저하됩니다. 다음은 자료사전을 통해 분류영역을 분석하는 방법입니다.

 

SQL >

select disk.value "Disk",
mem.value "Mem",
(disk.value / mem.value) * 100 "Ratio"
from v$sysstat mem, v$sysstat disk
where mem.name = 'sorts (memory)'
and disk.name = 'sorts (disk)';

 

V$SYSSTAT 자료사전을 참조하면 이 영역에 대한 튜닝 여부를 확인할 수 있습니다.
"sorts (memory)"는 서버 프로세스가 PGA의 SORT AREA 영역에서 작업한 블록 수이며 "sorts (disk)"는 TEMPOR ARY 테이블스페이스의 디스크 공간에서 작업한 블록 수를 의미합니다. "sorts (disk)" 값이 "sorts (memory)" 값의 5% 미만일 때 분류작업 시 좋은 성능을 기대할 수 있습니다. 만약, 기준치에 적합하지 않다면 SORT_AREA_SIZE 파라메터 값이 너무 작아 성능이 저하되고 있으므로 파라메터의 값을 높게 설정해 주어야 합니다. 또한, V$SORT_USAGE 자료사전을 조회하여 세션별로 TEMPORARY 테이블스페이스를 얼마나 사용하고 있는지를 분석할 수도 있습니다.

다음 예제를 따라 해 보십시오.

SYSTEM 사용자로 접속하여 현재 시점의 SORTING 영역의 튜닝상태를 분석해 봅시다.

 
 

$ sqlplus scott/tiger

SQL >

select disk.value "Disk",
mem.value "Mem",
(disk.value / mem.value) * 100 "Ratio"
from v$sysstat mem, v$sysstat disk
where mem.name = 'sorts (memory)'
and disk.name = 'sorts (disk)';

 

Disk

Mem

Ratio

0

628

0

 

▲ 대부분의 분류작업은 PGA 영역에서 작업되고 있습니다

 
 

$ sqlplus scott/tiger

SQL >

alter session set sort_area_size=10000000;

 

▲ PGA 영역을 보다 크게 설정하는 방법입니다.

 

SQL >

SELECT * FROM big_emp
ORDER BY ename

 

▲ SORTING이 발생하는 SQL문을 실행하면 SORTING
정보를 메모리와 디스크 공간에 저장하게 될 것입니다.

 

SQL >

select disk.value "Disk",
mem.value "Mem",
(disk.value / mem.value) * 100 "Ratio"
from v$sysstat mem, v$sysstat disk
where mem.name = 'sorts (memory)'
and disk.name = 'sorts (disk)';

 

Disk

Mem

Ratio

1

660

.151515152

 

▲ 대부분의 분류작업은 PGA 영역에서 작업되고 있습니다.
디스크 공간에 SORTING 정보가 일부 저장되었군요.

 

SQL >

EXIT

 

이번에는, SORT_AREA_SIZE 파라메터를 낮게 설정하고 다시 분류영역의 상태를 분석해 봅시다.

 
 

$ sqlplus scott/tiger

SQL >

alter session set sort_area_size=100;

 

▲ 이전 상태보다 PGA 영역을 낮게 활성화 합니다.

 

SQL >

SELECT * FROM account ß SORTING
ORDER BY customer;

 

▲ SORTING이 발생하는 SQL문을 실행하면 SORTING
정보를 메모리와 디스크 공간에 저장하게 될 것 입니다.

 

SQL >

select disk.value "Disk",
mem.value "Mem",
(disk.value / mem.value) * 100 "Ratio"
from v$sysstat mem, v$sysstat disk
where mem.name = 'sorts (memory)'
and disk.name = 'sorts (disk)';

 

Disk

Mem

Ratio

3

674

.445103858

 

▲ 5% 미만이면 성능에 큰 영향을 미치지 않습니다.
하지만 , SORT_AREA_SIZE를 높게 설정하면 보다
좋은 성능을 기대할 수 있습니다.

 

다음은 디스크 영역에 존재하는 TEMPORARY 테이블스페이스의 현재 사용 현황을 분석하는 방법입니다. 또 다른 윈도우-창에서 데이터베이스에 접속한 다음 사용 중인 TEMPORARY 테이블스페이스의 튜닝상태를 분석하십시오.

 

SQL >

col TABLESPACE_NAME format a10

SQL >

select tablespace_name, current_users, total_extents,
used_extents,extent_hits, max_used_blocks, max_sort_blocks
from v$sort_segment;

SQL >

select tablespace_name, current_users,
extent_hits, max_used_blocks, max_sort_blocks
from v$sort_segment;

 

TABLESPACE

CURRENT_USERS

MAX_SORT_BLOCKS

TEMP

0

1024

 

TABLESPACE : TEMPORARY 테이블스페이스 이름
CURRENT_USERS : 분류작업을 실행하고 있는 사용자 수
MAX_SORT_BLOCKS : 분류작업에 의해 사용된 블록 수

 

다음은 자료사전은 어떤 사용자에 의해 어떤 TEMPORARY 테이블스페이스가 얼마나 사용되고 있는지를 분석하는 방법입니다.

먼저, 새로운 세션을 하나 더 생성하고 기존에 접속된 세션은 그대로 유지하십시오.

 
 

$ sqlplus scott/tiger

SQL >

SELECT * FROM big_emp
ORDER BY ename;

 

▲ 결과가 화면에 계속 출력 됩니다.

 

또 다른 윈도우-창에서 데이터베이스에 접속한 다음 사용 중인 TEMPORARY 테이블스페이스의 튜닝상태를 분석하십시오.

이 분석결과는 현재 분류작업이 진행 중인 정보 만 나타납니다. 즉, 방금 실행했던 SQL문이 완료되기 전에 자료사전을 참조하십시오.

 

SQL >

col USERNAME format a10

SQL >

col user format a10

SQL >

select USERNAME, USER, CONTENTS, SEGTYPE, EXTENTS, BLOCKS
from v$sort_usage;

 

USERNAME

USER

CONTENTS

SEGTYPE

EXTENTS

BLOCKS

SCOTT

SCOTT

TEMPORARY

SORT

4

1024

 

▲ SCOTT 사용자의 분류작업에 의해 4개의 익스텐트를 사용하고 있습니다.

 

이번에는 TEMPORARY 테이블스페이스를 여러 개 만들어 사용자별로 할당해 줄 수 있도록 재구성해 보겠습니다. 데이터베이스가 설치될 때 생성된 TEMPORARY 테이블스페이스는 데이터베이스를 사용하는 모든 사용자들이 공유하는 공간이므로 경합 현상이 발생하면 WAIT 현상이 발생하게 됩니다. 사용자마다 별도로 TEMPORARY 테이블스페이스를 생성하여 할당한다면 경합현상을 분산시킬 수 있습니다.

 

SQL >

CONNECT system/manager

SQL >

create tablespace temp10
datafile '$HOME/dbs/temp10.dbf' size 2m TEMPORARY;

  

SQL >

create tablespace temp11
datafile '$HOME/dbs/temp11.dbf' size 2m TEMPORARY;

 

SCOTT 사용자에게는 TEMP10을, HR 사용자에게는 TEMP11을 할당 하십시오.

 

SQL >

alter user scott temporary tablespace temp10;

SQL >

alter user hr temporary tablespace temp11;

  

SQL >

connect scott/tiger

SQL >

SELECT USERNAME, TEMPORARY_TABLESPACE
2 FROM DBA_USERS
3 WHERE USERNAME = 'SCOTT' OR USERNAME = 'HR';

 

USERNAME

TEMPORARY_TABLESPACE

HR

TEMP11

SCOTT

TEMP10

 
 

 

효과적인 SORT 영역의 활용

 
 

인덱스 생성시 NOSORT 옵션을 사용하라.

 
 

인덱스를 생성하면 기본적으로 분류작업이 발생합니다. 즉, 인덱스는 분류작업을 실행한 후 컬럼 값을 기준으로 인덱싱을 하기 때문입니다. 만약, 테이블의 해당 컬럼이 이미 분류가 되어 있다면 인덱스 생성시 분류작업을 할 필요가 없을 것 입니다. 이언 경우에는 다음과 같이 NOSORT 옵션을 사용하면 분류작업을 하지 않습니다. 하지만, 반드시 해당 컬럼이 분류되어 있어야 합니다.

 

SQL >

CREATE INDEX I_emp_empno ON emp (empno) NOSORT;

 
 

UNION 연산자보다 UNION ALL 연산자을 사용하라.

 
 

UNION 연산자는 여러 개의 연결된 SQL문에서 중복된 행들은 하나 만 출력되는 집합 연산자입니다. 실행될 때 내부적으로 분류작업이 발생합니다. 만약, 해당 컬럼에 중복된 행이 없거나 또는 중복된 행을 참조해도 무방한 경우에는 되도록 UNION ALL 연산자를 사용하는 것이 불필요한 TEMPORARY 테이블스페이스 공간을 사용하지 않는 방법입니다.

 
 
 

DISTINCT 키워드의 사용을 자제하라.

 
 

DISTINCT 키워드는 해당 컬럼을 참조할 때 중복된 값들은 하나 만 출력되는 키워드 입니다. 실행될 떄 내부적으로 분류작업이 발생합니다. 반드시, 사용해야 하는 경우이외에 사용을 자제하는 것이 불필요한 TEMPORARY 테이블스페이스 공간을 사용하지 않는 방법입니다.

 
 
 

ORDER BY절의 사용을 자제하라.

 
 

개발자들이 실행하는 대부분의 SQL문에는 ORDER BY 절이 항상 사용된다고 해도 무방할 정도로 자주 사용되는 문법절 입니다. 하지만, 빈번한 분류작업은 불필요한 분류공간을 사용하게 되기 떄문에 성능에 도움이 되지 않습니다. 되도록 적절한 인덱스를 사용한다면 원하는 형태의 결과를 참조할 수 있으므로 인덱스를 사용하십시오.

 
 

분류작업을 할 때 모든 컬럼의 사용을 자제하라.

 
 

분류작업을 할 때 SELECT절에 불필요한 컬럼들을 정의하면 분류공간이 낭비되므로 참조해야 만 하는 컬럼들 만 정의하십시오.(다음 페이지에서 자세히 소개됩니다.)

 

SQL >

SELECT ename FROM big_emp ORDER BY ename;

 

▲ ENAME 컬럼 만 분류작업 합니다.

 

SQL >

SELECT * FROM big_emp ORDER BY ename;

 

▲ 전체 컬럼을 분류작업 합니다.

  
 
 

 

분류영역의 크기 계산

 

개발자들이 실행하는 SQL문에서 SORTING이 발생하면 SORTING 작업을 하기 위해 임시공간이 필요한데 이 공간을 TEMPORARY 테이블스페이스라고 합니다. 오라클 데이터베이스를 설치하면 최초 하나의 임시공간이 생성되는데, 이 공간에 대한 경합현상을 피하기 위해서는 사용자별로 임시공간을 할당해 주는 것이 가장 좋습니다. 그렇다면, 추가적으로 TEMPORARY 테이블스페이스를 생성할 때 그 크기는 어느 정도가 되어야 할까요 ?
다르게 해석해 보면, 개발자들이 실행하는 SQL문에서 SORTING이 발생할 때 얼마나 많은 SORTING 정보가 생성되는지를 알 수 있다면 그 크기를 쉽게 산정 해 볼 수 있을 것 입니다.

위의 왼쪽그림은 SELECT문에 의해 발생되는 SORTING 정보의 크기를 산정하는 방법입니다.
먼저, 왼쪽그림은 SELECT절에 '*'를 정의한 후 ORDER BY절에 의해 SORTING하는 경우입니다. 만약, 테이블 하나의 컬럼 수가 10개이고, 행 길이는 100 BYTE 이며 3000 행이 저장되어 있다면 이 문장이 실행되면서 필요로 하는 SORTING 영역의 크기는 다음과 같습니다.

 

SORTING 영역의 크기

= 전체 행수 X (행의 길이 + (2 X 컬럼 수))
= 3000 X (100 + 2 X 10) = 420,000 BYTE

 

컬럼 수에 2를 곱하는 이유는 하나의 컬럼 당 2 BYTE의 오버헤드가 필요하기 때문 입니다.
계산결과 이 SELECT문은 실행 시 42,000 BYTE의 SORTING 공간을 사용하게 될 것입니다.

이번에는 오른쪽 그림의 경우입니다. 왼쪽그림과 같은 원리, 같은 공식에 의해 SORTING 영역을 계산해 보면 다음과 같습니다.

 

SORTING 영역의 크기

= 전체 행수 X (행의 길이 + (2 X 컬럼 수))
= 3000 X (8 + 2 X 2) = 36,000 BYTE

 

이번 경우에는 ENAME 컬럼 만을 참조하며, 36,000 BYTE의 SORTING 공간을 사용하게 될 것입니다.

두 개의 SQL문에서 계산된 SORTING 영역의 크기를 산정해 보면 개발자가 실행하는 SQL문이 어떻게 작성되었느냐에 따라 임시공간의 사용범위가 달라진다는 것을 확인해 보았습니다.
되도록 최적의 SQL문을 작성하는 것이 임시공간을 효과적으로 사용할 수 있을 뿐 아니라 SQL문의 성능을 향상시키는 방법이기도 합니다.

다음 예제를 따라 해 보십시오. (데이터베이스를 재 시작한 다음 실습을 하십시오.)

 

SQL >

SELECT ename FROM big_emp ORDER BY ename;

 

TABLESPACE
_NAME

CURRENT
_USERS

EXTENT
_HITS

MAX_USED
_BLOCKS

MAX_SORT
_BLOCKS

TEMP

1

1

256

256

 

▲ 특정 컬럼 만 참조하는 경우 분류작업을 위해 최대 256 블록공간이 사용되고 있습니다.

 

SQL >

SELECT * FROM big_emp ORDER BY ename;

 

TABLESPACE
_NAME

CURRENT
_USERS

EXTENT
_HITS

MAX_USED
_BLOCKS

MAX_SORT
_BLOCKS

TEMP

1

1

512

512

 

▲ 모든 컬럼을 참조하는 경우 분류작업을 위해 최대 512 블록공간이 사용되고 있습니다.

 

두 개의 SQL문은 거의 같은 결과가 리턴되지만 두 번째 문장은 분류영역을 최소한도로 사용하기 떄문에 더 빠른 성능을 보여줄 것입니다.

Posted by redkite
, |
 

 
  

UNDO 세그멘트

 

한 명의 사용자가 SQL*PLUS 툴 또는 애플리케이션을 통해 데이터베이스에 접속하면 오라클 서버는 UNDO 테이블스페이스에 미리 생성되어 있는 UNDO 세그멘트 중 가장 적게 사용중인 세그멘트를 사용자에게 할당해 줍니다. 언두 세그멘트를 할당 받은 사용자는 UPDATE, INSERT, DELETE 작업을 통해 만들어진 언두(ROLLBACL문의 실행을 위해 백업해두는 정보) 정보를 언두 세그멘트에 트랜잭션이 종료될 때까지 저장하게 됩니다. 하나의 언두 세그멘트는 여러 명의 사용자에 의해 공유되는 공간이며 데이터베이스 접속 시 무조건 하나의 언두 세그멘트를 할당 받습니다.

또한 , 오라클 이전버전에서 사용되던 롤백 테이블스페이스와 롤백 세그멘트는 데이터베이스 관리자에 의해 수동으로 공간관리가 되었지만 오라클 9i 버전의 언두 테이블스페이스와 언두 세그멘트는 오라클 서버가 자동으로 관리해 주기 때문에 훨씬 사용이 간편하고 쉬워졌습니다. 하지만, 하나의 언두 세그멘트는 한 명의 사용자가 독점적으로 사용하는 것이 아니라 여러 명의 사용자가 동시에 사용하는 공유 공간이기 때문에 항상 경합 현상이 발생할 수 있으며 결론적으로 이러한 문제들로 인해 성능이 저하되는 현상이 초래되기도 합니다.

 
 

  

언두 세그멘트의 경합현상

 

하나의 언두 세그멘트는 하나의 트랜잭션에 의해서만 사용되는 것이 아니라 여러 명의 사용자들이 실행한 여러 개의 트랜잭션이 저장되는 공유공간 입니다 .

동시에 많은 사용자들이 INSERT, UPDATE, DELETE문을 실행하게 되면 제한된 개수의 롤백 세그멘트를 나누어서 사용해야 하기 때문에 경합현상이 발생하게 됩니다.

즉 , 경합현상은 대기상태를 의미함으로 먼저 실행된 DML문이 처리될 때 까지 나중에 요구된 DML문은 일시적인 대기(WAIT)를 하게 됩니다.

다음 문장은 현재 활성화되어 있는 언두 세그멘트의 수와 현재 상태를 분석하는 방법입니다 .

 

SQL >

SELECT usn, extents, writes, xacts, waits, hwmsize, status
FROM v$rollstat;

 

USN

EXTENTS

WRITES

XACTS

WAITS

STATUS

0

7

5808

0

0

ONLINE

1

3

1149154

0

0

ONLINE

2

3

1566806

0

0

ONLINE

3

3

1284076

0

0

ONLINE

4

3

1174812

0

0

ONLINE

5

3

1803600

0

0

ONLINE

6

3

1076738

   

7

3

1573834

   

8

4

1504118

   
 

[USN] 컬럼은 언두 세그멘트 번호입니다. 0 번은 시스템 언두 세그멘트이고 1 번부터 8 번까지는 일반 사용자들의 트랜잭션 데이터가 저장되는 언두 세그멘트입니다.

[EXTENTS] 컬럼은 현재 각 언두 세그멘트가 활성화되어 있는 익스텐트 수입니다.

[WRITES] 컬럼은 언두 세그멘트에 저장된 언두 데이터의 크기입니다.

[XACTS] 컬럼은 하나의 언두 세그멘트에 현재 몇 개의 트랜잭션 데이터가 저장되어 있는지를 보여줍니다. 또한, [WAITS] 컬럼은 해당 언두 세그멘트에 발생한 대기상태의 회수를 나타냅니다

다음 문장은 언두 세그멘트에 경합현상이 발생하는지를 분석하는 방법입니다 .

 

SQL >

SELECT sum(waits) * 100 / sum(gets) "비율",
Sum(waits) "Waits", sum(gets) "Gets"
FROM V$ROLLSTAT;

 

비율

Waits

Gets

0

0

44879

 

GETS 컬럼은 언두 세그멘트를 사용했던 블록 수이며 WAITS 컬럼은 언두 세그멘트를 사용하려고 했을 때 경합현상이 발생하여 대기했던 블록수를 의미합니다.

WAITS / GETS 의 계산에 의해 얼마나 많은 대기상태가 발생하였는지를 백분율로 알아낼 수 있습니다. 이 비율의 값이 5% 이상이면 언두 세그멘트에 경합이 발생하고 있으므로 보다 충분한 언두 세그멘트를 생성해 주어야 경합현상을 피할 수 있습니다.

 
 

 

언두 세그멘트의 관리

 

오라클 9i 이전버전 까지는 모든 언두 세그멘트를 데이터베이스 관리자가 직접 생성하고 관리하였지만 9i 버전부터는 추가적으로 오라클 서버가 직접 생성하고 관리해주는 AUM(Auto matic Undo Management) 기능이 제공되고 있습니다.

다음은 수동관리와 자동관리 방법의 장단점에 대해 좀 더 자세히 알아 봅시다 .

1) 수동관리

- 모든 언두 세그멘트를 데이터베이스 관리자가 직접 생성하고 관리하기 때문에 관리가 어렵고, 문제가 발생하는 경우 직접 유지보수를 해야 합니다

- 세션별로 발생하는 데이터의 유형에 따라 적적한 크기의 언두 세그맨트를 할당해 줄 수 있기 때문에 각 기업에 맞는 최적의 언두 세그멘트 환경을 구현할 수 있습니다.

2) 자동관리

- 데이터베이스에서 기본적으로 사용하게 될 언두 세그멘트 공간은 오라클 서버에 의해 데이터베이스가 설치될 때 생성됩니다. 또한, 사용자 수에 따라 적절한 개수의 언두 세그멘트가 생성되기 때문에 경합현상을 최소화할 수 있습니다.

- 모든 언두 세그멘트의 관리가 서버에 의해 자동관리 되기 때문에 데이터의 양과 유형에 맞는 최적의 언두 세그멘트를 세션별로 할당해 주지 못하는 단점을 가지고 있습니다.

먼저 , 자동관리에 의한 언두 세그멘트를 적용해 보시고 지속적인 경합현상이 계속 발생하면 데이터의 양과 유형에 맞는 수동관리 기법을 적용하는 것이 튜닝의 순서입니다.

 
 

 

Snapshot Too Old 에러의 원인과 해결

 

오라클 데이터베이스를 사용하다 보면 개발자들이 자주 보게 되는 에러유형 중에 Snapshot Too Old 에러는 언두 세그멘트와 관련된 경우입니다.

이런 경우의 에러가 발생하는 근본적인 원인은 충분한 크기의 언두 세그멘트가 확보되지 못했기 때문입니다 . 자~ 위 그림을 통해 이 에러가 발생하는 원인을 알아 봅시다.

위 그림을 분석해 보면 , 현재 EMP 테이블에 10,000개의 행이 저장되어 있고, 해당 세션에 RBS01 이라는 언두 세그멘트가 할당되어 있습니다.

1) A 사용자는 EMP 테이블에 대해 UPDATE문을 실행하여 10,000 행을 변경하려고 합니다. 이 문장의 실행에 의해 변경 전 데이터가 RBS01 언두 세그멘트에 저장되었습니다.

2) B 사용자는 A 사용자가 현재 변경하고 있는 EMP 테이블로부터 모든 행을 검색하는 SELECT문을 실행하고 있습니다. 이런 경우, 오라클 서버는 EMP 테이블이 A 사용자에 의해 변경되고 있는 중이기 때문에 A 사용자가 사용하고 있는 RBS01 언두 세그멘트로 부터 해당 행들을 조회하게 됩니다.

3) 이때, A 사용자는 1) 단계에서 처리했던 UPDATE문에 대해 COMMIT문을 실행하게 됩니다.

트랜잭션의 종료에 의해 RBS01 언두 세그멘트의 모든 행들은 더 이상 필요 없는 변경 전 데이터들 이지만, 즉시 제거되지는 않습니다. 그 이유는 RBS01 언두 세그멘트의 내용을 삭제하는 오퍼레이션을 실행하게 되면 불필요한 오퍼레이션으로 인해 SQL문의 성능이 저하될 수 있기 때문입니다. A 사용자의 COMMIT문에 관계없이 B 사용자는 계속해서 RBS01에 저장되어 있는 변경 전 데이터를 읽게 됩니다.

4) A 사용자는 새로운 UPDATE문을 실행하게 됩니다.

5) 새로운 UPDATE문에 의해 발생한 변경 전 데이터들은 RBS01 언두 세그멘트의 빈 공간에 저장되게 됩니다. 하지만, 곧 RBS01의 빈공간은 모두 채워지게 되고 더 이상 여유공간을 발견하지 못하면서 6) 이전에 저장되었던 언두 데이터의 공간에 새로운 변경 데이터를 저장하게 됩니다. 즉, 이전 데이터 위에 재작성 하게 됩니다. 그리고, 이때까지도 B 사용자는 계속 이전 정보를 읽게 됩니다.

7) B 사용자는 A 사용자의 작업에 관계없이 계속 RBS01 언두 세그멘트에서 해당 테이블 정보를 참조하게 되는데, 문제는 6) 단계에서 새로운 트랜잭션이 발생하면서 재 작성된 부분의 데이터를 읽으려고 할 때 입니다. 검색하려는 행이 다른 트랜잭션에 의해 삭제되었기 때문에 읽을 행이 더 이상 존재하지 않는다는 것 입니다.

이런 경우 , 발생하는 것이 SnapShot Too Old 에러 입니다. 즉, 언두 세그멘트의 크기가 너무 작아 여러 개의 트랜잭션이 연속적으로 실행되는 경우 이전 변경정보가 새로운 변경정보에 의해 삭제되는 경우가 발생하는 것 입니다. 보다 충분한 언두 세그멘트를 확보하기 위해서는 충분한 크기의 언두 테이블스페이스를 생성해야 합니다

이러한 언두 세그멘트가 부족한 현상은 궁극적으로 SQL문의 성능을 저하시키게 되고 데이터베이스 전체의 성능을 저하시킬 수도 있는 원인을 제공하기 때문에 반드시 올바른 튜닝으로 원인을 해결해야 합니다.

  
 
 

 

언두 세그멘트의 할당

 

동시에 많은 사용자들이 데이터베이스를 사용하다 보면 , 어떤 사용자는 소량의 데이터를 입력, 수정, 삭제할 수 도 있고 또는 대용량의 데이터를 입력, 수정, 삭제할 수 도 있습니다. 하지만, 이러한 사용자들이 동시에 데이터베이스를 사용하다 보면 하나의 언두 세그멘트를 같이 사용하는 경우가 빈번하게 발생합니다. 이런 경우, 언두 세그멘트의 대부분의 공간은 대용량 데이터를 처리하는 사용자가 사용하게 되는데, 이런 현상이 발생하게 되면 소량의 데이터를 처리하는 사용자는 언두 세그멘트를 할당 받지 못해 계속적으로 대기하는 문제가 발생하게 됩니다. 소량의 데이터를 처리하는 사용자의 성능뿐 만 아니라, 대용량 데이터를 처리하는 사용자의 작업도 성능이 저하되게 됩니다.

이런 경우 , 서로 다른 양과 다른 유형의 데이터를 처리하는 사용자에게 각각 언두 세그멘트를 따로 설정해 줄 수 있다면, 서로의 성능에 영향을 미치지 않는 범위에서 변경작업을 진행할 수 있을 것 입니다.

 
 

 

특정 언두 세그멘트의 할당

 

대용량의 데이터를 변경하는 사용자에게 별도로 하나의 언두 세그멘트를 할당해 줄 수 있다면 OLTP 성 업무를 진행하는 일반 사용자의 성능은 보다 향상될 것이며 또한 대용량 데이터를 처리하는 사용자의 성능도 보다 향상시킬 수 있을 것 입니다.

다음 절차는 특정 사용자의 세션에서 전용으로 사용할 언두 세그멘트를 생성하고 할당하는 방법입니다 .

 
  
 
 

먼저 , ALTER ROLLBACK 명령어를 실행할 수 있는 시스템 권한을 특정 사용자에게 부여하십시오.

 
 

SQL >

CONNECT /AS SYSDBA

SQL >

GRANT alter rollback segment TO scott;

 
 
 

특정 사용자에게 할당할 아주 큰 언두 세그멘트를 생성하십시오 .

 
 

SQL >

CREATE ROLLBACK SEGMENT BIG_ROLL
STORAGE (INITIAL 5M NEXT 5M);

 
 
 

만약 , 개발된 애플리케이션 프로그램 중에 월마감 작업과 같이 대용량 데이터를 처리해야 하는 경우가 있다면 해당 프로그램 내에 다음 문장을 실행한 후 SQL문이 실행될 수 있도록 변경하십시오.

 
 

SQL >

ALTER ROLLBACK SEGMENT BIG_ROLL ONLINE;

SQL >

SET TRANSACTION USE ROLLBACK SEGMENT RBS01;

먼저 , BIG_ROLL 언두 세그멘트를 온라인 시킨 후 해당 세션에서 전용으로 사용하기 위해서 SET TRANSACTION 명령문을 실행하십시로.

 
 
 

전용 언두 세그멘트가 활성화되었으면 처리하려는 SQL문을 실행하십시오.

 
 

SQL >

UPDATE big_emp
SET loc = 'seoul';

SQL >

COMMIT;

 
 

변경 작업이 완료되고 나면 사용되었던 언두 세그멘트를 오프라인 시키십시오. 만약 , 작업 완료 후 오프라인 하지 않으면 다른 사용자들에 의해 사용될 수도 있기 때문에 다음에 재사용하려고 했을 때 사용하지 못할 수도 있습니다.

 
 

SQL >

ALTER ROLLBACK SEGMENT BIG_ROLL OFFLINE;

 
 

 

Blocking Session

 

누구나 한번쯤 다음과 같은 경험을 해 보았을 것 입니다 . 운영체계 상에서 프로그램을 실행하다가 CTRL+C 와 같은 인터럽트(Interrupt)를 실행하게 되면 애플리케이션이 강제로 종료되는 경우입니다. 문제는 애플리케이션 만 종료되고 프로세스는 운영체계 상에 계속 남아있는 경우가 발생할 수 있는데, 이런 경우 불필요한 프로세스들로 인해 시스템의 메모리가 낭비되게 됩니다. 즉, 메모리의 낭비는 시스템 전체의 성능저하를 초래할 수 있게 됩니다.

오라클 데이터베이스를 사용하다 보면 SQL*PLUS 툴 또는 애플리케이션 프로그램을 통해 데이터베이스에 접속하여 일련의 작업을 진행하게 됩니다. 이런 경우, UPDATE, INSERT, DELETE문에 의해 트랜잭션이 발생하게 되고 언두 세그멘트에는 변경 전 데이터가 저장되게 됩니다. 문제는 트랜잭션이 정상적으로 종료되지 않고 인터럽트와 같은 비정상적인 취소로 인해 종료되는 경우입니다. 이런 경우, 언두 세그멘트에 언두 데이터가 남아 있게 되는데, 이러한 비정상적인 언두 데이터들로 인해 실제 저장되어야 할 언두 데이터가 저장되지 못해 경합현상이 발생하게 됩니다. 이러한 경우가 빈번하게 발생하면 언두 세그멘트는 부족하게 되고 데이터베이스의 전체 성능은 저하되게 됩니다. 이러한 비정상적인 세션을 블록킹 세션(BLOCKING SESSION)이라고 합니다.

다음은 데이터베이스 내에 존재하는 비정상적인 블록킹 세션을 분석하는 방법입니다 .

 

SQL >

Select s.sid, s.serial#, t.start_time, t.xidusn, s.username
From v$session s, v$transaction t, v$rollstat r
Where s.saddr = t.ses_addr
AND t.xidsun=r.usn
AND ((r.curext = t.start_uext-1)
OR ((r.curext=r.extents-1) AND t.start_uext=0));

 

SID

SERIAL#

START_TIME

XIDUSM

USERNAME

9

27

2003/2/12 13:25:23

2

SCOTT

 

이 문장의 실행에 의해 분석된 결과를 참조해 보면 , 2003년 2월 12일 SCOTT 사용자로 접속한 9번 세션이 2번째 언두 세그멘트를 사용하면서 블록킹 세션으로 남아 있는 것을 알 수 있습니다.

분석된 블록킹 세션의 접속상태를 완전히 제거하기 위해서는 ALTER SYSTEM KILL SESSION 명령어를 사용해야 합니다.

 

SQL >

ALTER SYSTEM KILL SESSION '9,27';

Posted by redkite
, |
 

 
  

리두로그 파일과 아카이브 파일의 I/O 튜닝

 

사용자들이 UPDATE, INSERT, DELETE문을 실행하면 모든 변경 데이터는 리두로그 버퍼에 저장된 후 일정시점이 되면 LGWR 백그라운드 프로세스에 의해 리두로그 파일에 저장됩니다.

그리고, 아카이브 모드에서는 리두로그 시스템에 로그 스위치가 발생하면 ARCH 백그라운드 프로세스에 의해 리두로그 파일을 미리 지정된 경로로 복사 합니다.

이 메커니즘이 바로 오라클 데이터베이스의 백업 메커니즘입니다. 하지만, 이 메커니즘이 정상적으로 운영 되었을 경우에는 별 문제가 발생하지 않겠지만, 서로 연속적으로 발생하는 읽기,쓰기 작업이 순차적으로 진행되지 않으면 성능저하 현상이 발생하게 됩니다.

자, 그럼 보다 자세히 성능문제를 알아 봅시다.

 
  
 
 

리두로그 파일과 성능문제

 
 

만약, 여러 개의 리두로그 그룹들이 같은 디스크에 생성되어 있다면, 많은 변경 데이터를 저장할 때 연속적으로 여러 개의 리두로그 파일에 저장할 수 없는 문제가 발생합니다. 이유는 물리적 디스크 장치는 한번에 하나의 I/O 만을 유발시킬 수 있기 때문에 하나의 리두로그 파일에 변경 데이터를 저장한 후 다음 리두로그 파일에 저장하려고 할 때 다른 사용자들의 읽기,쓰기 작업으로 인해 연속적으로 쓰기 작업을 진행하지 못하고 일시적으로 대기해야 하는 문제가 발생하게 됩니다. 이러한 대기상태가 빈번하게 발생하면 데이터베이스 전체적인 성능저하 현상이 나타나게 됩니다.

< 해결방법-1 >
여러 개의 리두로그 파일들을 물리적으로 다른 여러 개의 디스크로 나누어 배치하게 되면 하나의 디스크에서 발생하는 집중현상을 여러 개의 디스크로 분산할 수 있습니다.

< 해결방법-2 >
리두로그 파일의 크기를 보다 크게 늘려 줍니다. 리두로그 파일의 크기가 너무 작으면 많은 변경 데이터를 여러 리두로그 파일에 나누어 저장해야 하기 때문에 불필요한 로그 스위치가 발생하기 때문입니다.

< 해결방법-3 >
리두로그 그룹의 수를 늘려 줍니다. 하나의 리두로그 파일의 크기를 너무 크게 설정해 두면 로그 스위치가 발생할 때 ARCH 프로세스에 의해 아카이브 되는데 소요되는 시간이 너무 길어질 수도 있습니다. 즉, 아카이브가 완료되지 않으면 로그 스위치에 의해 다음 리두로그 파일에 변경 데이터가 저장되지 못하고 일시적으로 대기해야 하는 문제가 발생하게 됩니다.
이런 경우를 최소화 하기 위해서는 리두로그 그룹 수를 적절히 늘려 주어야 합니다.
$HOME/ADMIN/BDUMP/alert_<SID>.ora 파일에 다음과 같은 메시지가 나타나면 LGWR 프로세스에 대기상태가 발생한 것 입니다.

 

"checkpoint not complete ; unable to allocate file"

 
 

아카이브 파일과 성능문제

 
 

메모리 영역인 리두로그 버퍼의 정보는 LGWR 프로세스에 의해 리두로그 파일에 저장됩니다. 하나의 리두로그 파일이 모두 쓰여지면 ARCH 프로세스에 의해 오프라인 또는 온라인 저장구조에 저장됩니다. 문제점은, ARCH 프로세스가 다른 저장구조에 저장하는데 소요되는 시간보다 LGWR 프로세스가 메모리 영역으로부터 리두로그 파일에 저장하는데 소요되는 시간이 훨씬 빠르다는 점입니다. 즉, 많은 변경 데이터가 보다 빠르게 리두로그 파일에 저장되려면 아카이브 작업이 빠르게 완료되어야 하는데, 시간이 많이 소요되게 되면 연속적인 쓰기 작업을 하지 못한다는 점입니다.

< 해결방법-1 >
log_archive_max_processes 파라메터의 값을 보다 높게 설정해 줍니다. ARCHIVE 모드로 데이터베이스 환경을 설정하게 되면 기본적으로 하나의 ARCH 프로세스가 활성화 됩니다. 동시에 많은 변경작업이 발생하는 경우 하나의 ARCH 프로세스로 아카이브를 하는 것 보다 여러 개의 ARCH 프로세스를 활성화하게 되면 보다 빠르게 아카이브 작업을 완료할 수 있습니다.

< 해결방법-2 >
아카이브 파일은 결국 리두로그 파일의 백업 정보이므로 리두로그 파일의 크기와 개수를 적절하게 조정하는 것이 아카이브 파일의 개수와 크기를 조절할 수 있는 방법입니다.

< 해결방법-3 >
아카이브 파일들이 저장되는 저장구조를 충분하게 확보하십시오. 아카이브 모드에서 파일들이 저장되는 저장구조에 여유공간이 없으면 아카이브 작업은 더 이상 진행되지 않습니다. 즉, LGWR 프로세스는 리두로그 영역의 데이터를 리두로그 파일에 저장하지 못하게 되고 궁극적으로 데이터베이스 전체적인 대기상태가 발생하게 됩니다.

Posted by redkite
, |
 

 
 

디스크의 I/O 경합

 
 

지금까지 디스크의 물리적 특성으로 인해 성능이 저하되는 현상이 왜 발생하는지 알아보았습니다. 그럼, 오라클 데이터베이스에서 디스크 상에 존재하는 파일들이 언제 읽기/쓰기 작업을 하게 되는지 알아봅시다. 3장에서 "SELECT문의 처리과정"과 "DML문의 처리과정" 그리고 "COMMIT"문의 처리과정을 통해 데이터베이스의 각 구조가 데이터 파일과 어떤 상관관계를 가지고 있는지 알아보았습니다. 즉, 사용자가 실행하는 모든 SELECT, UPDATE, INSERT, DELETE문이 실행될 때마다 디스크에 존재하는 파일로부터 I/O가 발생하며 이러한 I/O의 발생을 최소화 시켜주고 분산시켜줌으로서 데이터베이스의 성능을 향상시킬 수 있는 것입니다.

위 그림은 오라클 데이터베이스의 백그라운드 프로세스와 DISK I/O 와의 상관관계를 그림으로 표현한 것입니다. 한 예로, 개발자가 SELECT문을 실행하면 서버 프로세스는 데이터 파일(READ)로부터 테이블을 읽은 다음 데이터버퍼 캐시영역에 로더하게 됩니다. 또한, COMMIT문을 실행하면 DBWR 프로세스는 데이터버퍼 캐시의 내용을 데이터 파일(WRITE)에 저장하게 됩니다. 이와 같이, 모든 SQL문이 실행될 때 마다 관련 파일로부터 디스크 I/O가 발생하게 됩니다. 그리고, 수십 명, 수백 명의 사용자들이 동시에 SQL문을 수행한다면 디스크 I/O 문제로 인해 경합이 발생하며 성능이 저하되게 됩니다.

 

 

디스크 I/O 튜닝

 

디스크 I/O에 대한 튜닝 방법에 대해서 알아보겠습니다. 데이터베이스가 생성되면 기본적인 데이터 파일이 생성되며 사용자는 추가적인 데이터 파일을 생성하게 되는데 물리적 구조의 특성을 충분히 고려하지 않는다면 디스크의 경합 및 동시성으로 인한 문제로 데이터베이스의 성능이 저하될 수도 있습니다.

다음은 V$FILESTAT 자료사전을 통해 디스크 I/O 상태를 분석하는 방법입니다.

 

SQL >

SELECT tablespace_name, name, phyrds, phtwrts

 

FROM v$datafile df, v$filestat fs

 

WHERE df.file# = fs.file#;

 

[PHYRDS] 컬럼은 디스크로부터 읽기 작업을 한 횟수를 의미하며 [PHYWRTS] 컬럼은 디스크에 쓰기 작업을 한 횟수입니다. 만약, 어떤 데이터 파일에 대해 읽기/쓰기 작업이 과다하게 발생한다면 해당 데이터 파일에 저장되어 있는 테이블 검색시 성능이 저하되게 될 것 입니다.

위 그림을 보면 DISK-1에 SYSTEM.DBF 파일과 DATA1.DBF 파일이 저장되어 있고, DISK-2에는 DATA2.DBF 파일과 INDEX1.DBF, UNDO.DBF 파일이 저장되어 있으며 DISK-3에는 TEMP.DBF 파일이 저장되어 있습니다. V$FILESTAT 자료사전의 분석결과를 참조해 보면 SYSTEM 테이블스페이스의 SYSTEM.DBF와 UNDO 테이블스페이스의 UNDO.DBF 파일이 저장되어 있는 DISK-1과 DISK-2에 매우 많은 읽기/쓰기 작업이 진행되고 있음을 확인할 수 있습니다.

 

Tablespace_Name

Name

PHY_READS

PHY_WRTS

UNDO

/disk2/rbs.dbf

1200

5259

TEMP

/disk3/temp.dbf

650

634

DATA1

/disk1/data1.db

202

94

DATA2

/disk2/data2.db

62

50

INDEX1

/disk2/index1.dbf

42

3

SYSTEM

/disk1/system.dbf

740

44

 

첫 번째 문제점은 SYSTEM 테이블스페이스는 데이터 딕션너리 테이블 정보가 저장되어 있는 공간이기 때문에 계속적인 I/O가 증가할 수 밖에 없는 구조적 특징을 가지고 있습니다. 그런데, 사용자의 테이블이 저장되어 있는 DATA1.DBF 파일이 같은 디스크-1에 저장되어 있기 때문에 검색시 성능이 저하될 수 밖에 없는 상황입니다.

두 번째 문제점은 UNDO 테이블스페이스는 사용자들이 변경작업(UPDATE, INSERT, DELETE)을 실행할 때 ROLLBACK을 위해 변경 전 데이터를 잠시 저장하는 공간입니다. 데이터베이스를 사용하는 한 지속적인 변경작업이 진행될 수 밖에 없기 때문에 디스크 I/O가 증가할 수 밖에 없는 구조적 특징을 가지고 있습니다. 그런데, 사용자의 테이블이 저장되어 있는 DATA2.DBF 파일과 INDEX1.DBF 파일이 같은 디스크-2에 저장되어 있기 때문에 검색시 성능이 저하될 수 밖에 없는 상황입니다.

자, 이번에는 SCOTT 사용자로 접속하여 관련 테이블에 대한 SELECT문을 수행한 후, 파일 I/O가 어떻게 변경되었는지 다시 분석해 봅시다.

 

$ sqlplus scott/tiger

SQL >

select * from big_dept;

SQL >

select * from big_emp;

SQL >

col name format a52

SQL >

select f.phyrds, f.phywrts, d.name
from v$datafile d, v$filestat f
where d.file# = f.file#;

 

Tablespace_Name

Name

PHY_READS

PHY_WRTS

UNDO

/disk2/rbs.dbf

1210

5259

TEMP

/disk3/temp.dbf

650

634

DATA1

/disk1/data1.db

202

94

DATA2

/disk2/data2.db

62

50

INDEX1

/disk2/index1.dbf

42

3

SYSTEM

/disk1/system.dbf

755

44

 

어떤 테이블스페이스의 물리적 읽기/쓰기회수가 증가되었는지 확인해 보십시오.

 

 

데이터 파일의 설계

 

디스크의 I/O 문제로 인한 경합현상과 동시성 문제로 인해 성능이 저하되는 문제를 해결하기 위해서는 데이터베이스 설치단계에서 충분한 고려를 하여 물리적 설계를 하는 것이 문제를 최소화할 수 있는 방법입니다.

 
  
 
 

데이터 테이블스페이스와 SYSTEM 테이블스페이스는 분리하라.

 
 

SYSTEM 테이블스페이스는 자료사전 테이블, 뷰 등이 있는 저장공간입니다. 사용자가 생성한 테이블들이 새로운 익스텍트를 할당하거나 또는 삭제할 때 , 객체가 생성되거나 삭제될 때 등 모든 변화에 대한 상태정보를 자료사전 테이블에 저장합니다. 그런데, 사용자가 생성한 테이터 파일이 SYSTEM 테이블스페이스의 데이터 파일과 같은 디스크에 존재한다면 디스크 경합현상으로 인해 데이터베이스의 전체 성능이 저하될 수 있습니다.

 
 
 

데이터 테이블스페이스와 인덱스 테이블스페이스는 분리하라.

 
 

"디스크의 동시성"을 통해 알아보았듯이 인덱스를 가진 테이블에 대한 검색 시 발생하는 문제를 해결하기 위해 테이블을 가진 데이터 파일과 인덱스를 가진 데이터 파일을 물리적으로 다른 디스크에 생성해야 합니다.

 
 
 

각 데이터 테이블스페이스는 I/O 경합을 줄이기 위해 분리하라.

 
 

가능하다면 각각의 데이터 파일들을 물리적으로 다른 디스크에 저장하는 것이 I/O를 분산시킬 수 있는 가장 최적의 방법입니다. 자주 사용되는 데이터 파일들은 반드시 같은 디스크에 생성하지 마십시오.

 
 
 

데이터 테이블스페이스와 UNDO 테이블스페이스는 분리하라.

 
 

데이터베이스에 접속하는 사용자들은 무조건 하나의 언두 세그멘트를 할당 받습니다. 언두 세그멘트는 많은 사용자들이 공유하는 저장공간이기 때문에 데이터가 저장되어 있는 디스크와 같이 사용하게 되면 디스크 경합현상이 발생합니다. 때문에 테이블에 대한 데이터 파일과 물리적으로 다른 디스크에 생성해야 경합을 줄 일수 있습니다.

 
 

데이터 테이블스페이스와 TEMP 테이블스페이스는 분리하라.

 
 

말그대로 데이터 테이블스페이스와 TEMP 테이블스페이스는 분리하십시오.

 
 

대용량의 분류작업을 위해 TEMP 테이블스페이스를 각 사용자별로 할당하라.

 
 

언두 세그멘트와 마찬가지로 하나의 TEMPORARY 테이블스페이스는 많은 사용자들이 동시에 사용하는 공유영역이기 때문에 디스크 경합현상이 발생합니다. 때문에 테이블에 대한 데이터 파일과 물리적으로 다른 디스크에 생성해야 경합을 줄 일수 있습니다. 또한, 여러 개의 TEMPORARY 테이블스페이스 생성하여 데이터베이스 사용자별로 할당하는 것이 DISK-I/O 문제로 인한 경합현상을 피할 수 있습니다.

  
 

SQL >

create tablespace temp10
datafile '$HOME/dbs/temp10.dbf' size 2m TEMPORARY;

SQL >

create tablespace temp11
datafile '$HOME/dbs/temp11.dbf' size 2m TEMPORARY;

 

SCOTT 사용자에게는 TEMP10을, HR 사용자에게는 TEMP11을 사용하게 하십시오.

SQL >

alter user scott temporary tablespace temp10;

SQL >

alter user hr temporary tablespace temp11;

 
 

테이블스페이스 생성시 전체 경로를 지정하여 기본경로에 생성되는 것을 피하라.

 
 

테이블스페이스를 생성할 때 데이터 파일의 절대경로를 지정하지 않으면 오라클 서버가 지정하는 기본경로에(SYSTEM 테이블스페이스) 데이터 파일을 생성해 줍니다. 절대경로를 지정하지 않고 테이블스페이스를 생성했다면 모든 데이터 파일들은 같은 디스크, 같은 절대경로에 생성되기 때문에 디스크 경합현상이 발생할 것입니다.

  
 

다음 문장은 개발자가 실수로 SYSTEM 테이블스페이스에 테이블을 생성한 경우 분석하는 방법입니다.

  
 

SQL >

SET LINESIZE 500

SQL >

col owner format a10

SQL >

col segment_name format a25

SQL >

col tablespace_name format a15

  

SQL >

select owner, segment_name, segment_type, tablespace_name
from dba_segments
where tablespace_name = 'SYSTEM'and owner = 'SCOTT;

  
 

OWNER

SEGMENT_NAME

SEGMENT_TYPE

TABLESPACE_NAME

SCOTT

BIG_DEPT

TABLE

SYSTEM

SCOTT

ACCOUNT

TABLE

SYSTEM

SCOTT

EMP

TABLE

SYSTEM

SCOTT

DEPT

TABLE

SYSTEM

SCOTT

SALGRADE

TABLE

SYSTEM

SCOTT

ACCOUNT1

TABLE

SYSTEM

 
 
 

LOCALLY 테이블스페이스를 사용하라.

 
 

오라클 8i버전부터 추가된 로컬리매니저 테이블스페이스를 사용하면 SYSTEM 테이블스페이스에 저장되는 자료사전 정보를 각 테이블이 생성되는 로컬리 테이블스페이스에 저장함으로서 디스크 경합현상을 최소화 시킬 수 있습니다.

  

 

물리적 설계의 튜닝

 

디스크 I/O 와 경합문제로 인해 성능이 저하되면 먼저, 어떤 디스크에 있는, 어떤 데이터 파일에 I/O가 얼마나 발생하는지 분석해야 합니다. 이러한 문제는 데이터베이스의 설치와 추가적인 데이터 파일의 생성시 정확한 이해와 분석 없이 데이터베이스를 구축하게 되면 발생하는 문제점들 입니다. 사실 이런 문제가 발생하는 보다 근본적인 원인은 데이터베이스의 물리적 설계 단계에서 성능에 대한 충분한 고려를 하지 못한 경우입니다. (3장 "시스템 개발절차와 성능", "물리적 설계"를 참조하십시오.)

이러한 경우에는 두 가지 방법으로 튜닝할 수 있는데, 첫 번째 방법을 사전튜닝이라고 합니다. 즉, 물리적 분석/설계 단계에서 디스크 I/O와 경합을 최소화 시킬 수 있는 방법으로 설계하는 것 입니다. 데이터베이스가 구축되기 전 상태에서 성능을 고려하는 방법을 사전튜닝이라고 합니다. (3장 "시스템 개발절차와 성능", "물리적 설계"를 참조하십시오.)
가장 이상적인 형태의 튜닝방법 입니다.

두 번째 방법은 사후 튜닝입니다. 데이터베이스 분석/설계 단계에서 충분한 고려를 하지 못하였거나 잘못된 분석으로 인해 데이터베이스 구축 후 성능이 저하되는 문제가 발생하여 튜닝하는 방법을 사후 튜닝이라고 합니다. 여기서는 사후튜닝 방법을 자세히 소개하도록 하겠습니다.

위 그림을 참조해 보면, DISK-1에 SYSTEM.DBF 파일과 DATA1.DBF 파일이 저장되어 있고, DISK-2에는 DATA2.DBF 파일과 INDEX1.DBF, UNDO.DBF 파일이 저장되어 있으며 DISK-3에는 TEMP.DBF 파일이 저장되어 있습니다. V$FILESTAT 자료사전의 분석결과를 참조해 보면 SYSTEM 테이블스페이스의 SYSTEM.DBF와 UNDO 테이블스페이스의 UNDO.DBF 파일이 저장되어 있는 DISK-1과 DISK-2에 매우 많은 읽기/쓰기 작업이 진행되고 있음을 확인할 수 있습니다.
그리고, 시스템에는 DISK-4가 사용 가능한 상태로 남아 있습니다.

먼저, DISK-2에 있는 UNDO.DBF 파일은 디스크 I/O가 많이 발생하는 파일 중에 하나이기 때문에 별도의 디스크에 배치하는 것이 데이터베이스 전체 성능향상을 위해 좋습니다.
그리고, DISK-1에 있는 SYSTEM.DBF 파일도 가장 많은 디스크-I/O를 유발시키기 때문에 DATA1.DBF 파일을 DISK-4로 이동시켜 전체 4개의 디스크에 평균적으로 I/O가 발생할 수 있도록 이동시키는 것이 좋습니다.

 
  
 
 

먼저, 데이터베이스를 종료(SHUTDOWN) 합니다.

 
 

SQL >

SHUTDOWN TRANSACTIONAL

SQL >

EXIT

 
 

운영체계 상에서 UNDO 테이블스페이스의 데이터 파일을 DISK-4에 복사하고, DATA1.DBF 파일도 DISK4로 복사 하십시오.

 
 
 

$ cp $HOME/disk2/undo.dbf $HOME/disk4/undo.dbf

 

$ cp $HOME/disk1/data1.dbf $HOME/disk4/data1.dbf

 
 
 

데이터베이스를 MOUNT 단계로 시작합니다.

 
 
 

$ sqlplus "/as sysdba"

SQL >

STARTUP MOUNT

 
 
 

UNDO 데이터 파일은 DISK-2에서 DISK-4로, DATA1.DBF 파일은 DISK-1에서 DISK-4로 RENAME 합니다.

 
 

SQL >

ALTER DATABASE RENAME '$HOME/disk2/undo.dbf' TO '$HOME/disk4/undo.dbf'

SQL >

ALTER DATABASE RENAME '$HOME/disk1/data1.dbf' TO '$HOME/disk4/data1.dbf'

 
 

데이터베이스를 사용 가능한 상태로 오픈한 후 이전의 데이터 파일은 삭제합니다.

 
 

SQL >

ALTER DATABASE OPEN;

SQL >

EXIT

 
 

데이터 파일의 튜닝이 완료되고 나면 다시 V$FILESTAT 자료사전을 통해 전체 디스크에서 발생하는 DISK-I/O 상태를 모니터링 해 보십시오.

 
 

언두 세그멘트와 마찬가지로 하나의 TEMPORARY 테이블스페이스는 많은 사용자들이 동시에 사용하는 공유영역이기 때문에 디스크 경합현상이 발생합니다. 때문에 테이블에 대한 데이터 파일과 물리적으로 다른 디스크에 생성해야 경합을 줄 일수 있습니다. 또한, 여러 개의 TEMPORARY 테이블스페이스 생성하여 데이터베이스 사용자별로 할당하는 것이 DISK-I/O 문제로 인한 경합현상을 피할 수 있습니다.

 
 

$ sqlplus scott/tiger

SQL >

select * from big_dept;

SQL >

select * from big_emp;

SQL >

col name format a52

SQL >

select f.phyrds, f.phywrts, d.name
from v$datafile d, v$filestat f
where d.file# = f.file#;

 

Tablespace_Name

Name

PHY_READS

PHY_WRTS

UNDO

/disk4/rbs.dbf

1210

5259

TEMP

/disk3/temp.dbf

1650

4634

DATA1

/disk4/data1.db

1202

3594

DATA2

/disk2/data2.db

1262

230

INDEX1

/disk2/index1.dbf

1042

453

SYSTEM

/disk1/system.dbf

1555

1244

 

각 디스크에서 발생하는 물리적 읽기/쓰기 회수가 균등하게 발생하고 있는지 분석해 보십시오. 이 경우는 SYSTEM 테이블스페이스 또는 NON-SYSTEM 테이블스페이스의 데이터 파일에 모두 적용할 수 있는 절차입니다. 만약, 재 배치하려는 파일이 NON-SYSTEM 데이터 파일이라면 반드시 데이터베이스를 종료한 후 작업할 필요 없이 오픈 상태에서도 RENAME이 가능합니다.

 

다음 예제를 따라 해 보십시오.

이번에는 NON-SYSTEM 데이터 파일을 오픈 상태에서 재배치하는 절차입니다.

 
  
 
 

먼저, 위치 이동을 하고자 하는 데이터 파일의 테이블스페이스를 OFFLINE 하십시오.

 
 

SQL >

CONNECT system/manager

SQL >

ALTER TABLESPACE sample OFFLINE;

 
 

이동하고자 하는 sample 테이터 파일을 이동하려는 경로로 복사합니다.(SAMPLE 데이터파일은 오라클9i를 설치하면 기본적으로 설치됩니다. 해당 경로에서 다음 작업을 하십시오)

 
 
 

$ cp $HOME/dbs/sample01.dbf $HOME/disk4/sample01.dbf

 
 
 

다음과 같이 SAMPLE 데이터 파일의 경로를 RENAME 하십시오.

 
 

SQL >

ALTER TABLESPACE sample RENAME DATAFILE

 

'$HOME/dbs/sample01.dbf' TO '$HOME/disk4/sample01.dbf';

 
 
 

RENAME 작업이 완료되었으면 엑세스가 가능하도록 해당 TABLESPACE를 ONLINE해 주십시오

 
 

SQL >

ALTER TABLESPACE sample ONLINE;

 

정상적으로 자료입력이 되는지 확인 한 다음 O/S상의 OLD 파일은 삭제하십시오.

 
 

SQL >

! rm /oracle/app/product/910/oradata/disk2/sample01.dbf

SQL >

EXIT

 

데이터 파일을 재배치 하는 방법은 두 가지가 있습니다. 첫 번째 방법은 데이터베이스를 종료한 후 마운트 상태로 구동하여 RENAME 하는 방법이며, 주로 SYSTEM 데이터 파일을 RENAME할 때 사용됩니다. 이유는, 오라클 데이터베이스의 필수 데이터 파일이기 때문입니다. 즉, 데이터베이스가 구동되려면 반드시 SYSTEM 데이터 파일은 사용가능 상태이어야 하기 때문에 마운트 상태에서 만 RENAME할 수 있습니다. 두 번째 방법은 데이터베이스를 오픈 상태에서 사용하고 있는 동안 특정 데이블스페이스를 RENAME하는 방법입니다. 주로 NON-SYSTEM 데이터 파일을 RENAME할 때 사용합니다.

Posted by redkite
, |

 

 
   

LRU 알고리즘

 

먼저, 데이터베이스의 구조에는 데이터베이스 만을 위한 메모리 영역으로 SGA 영역(데이터버퍼 캐시, 로그버퍼, 공유 풀, 라지 풀 영역)이 있으며 또한 2개의 리스트(LRU-LIST, DIRTY-LIST)가 있습니다. LRU-LIST에는 데이터버퍼 캐시영역의 각 블록구조에 대한 사용 상태정보가 저장되어 있고 DIRTY-LIST에는 사용자에 의해 이미 트랜잭션이 완료된 블록에 대한 상태정보가 저장되어 있습니다.

먼저, LRU 알고리즘이 필요한 이유는 공유 풀 영역은 메모리 영역이기 때문에 그 크기가 항상 제한되어 있으며 항상 여유롭게 사용되지 못하는 물리적 한계를 가지고 있기 때문입니다.

자~ 그럼 LRU 알고리즘의 구체적 실행방법을 알아봅시다.

하나의 사용자가 데이터베이스에 접속하면 사용자 프로세스와 서버 프로세스가 할당됩니다. 사용자가 SQL문을 실행하면 서버 프로세스는 구문분석을 끝낸 후 실행(EXECUTE)작업을 하게되는데 먼저, 데이터버퍼 캐시영역을 검색해서 현재 실행된 SQL문에 있는 테이블이 다른 사용자에 의해 이미 읽혀져서 데이터버퍼 캐시영역에 로더되어 있는지 확인해 봅니다.
읽혀진 테이블 데이터가 없다면 데이터 파일로부터 테이블을 읽어서 데이터버퍼 캐시영역에 로더하게 될 것입니다

이때, 읽혀진 데이터를 데이터버퍼 캐시영역에 저장해야 하는데 데이터버퍼 캐시영역은 수많은 블록들로 구성되어 있기 때문에 매번 이 영역에서 빈 블록을 찾아 읽혀진 테이블 데이터를 저장하는 일은 너무 많은 시간이 필요하게 될 것이고 결국 성능을 저하시키게 될 것입니다.

예를 들어, 도서관의 선반에는 수많은 책들이 비치되어 있을 것입니다. 새로운 도서가 반입되어 책을 비치하려고 하는데 도서관 사서는 어떤 선반이 비어있는지를 책이 반입될 때마다 확인한 후 비치한다면 매번 반입작업을 처리하는데 많은 시간이 소요되어 일의 능률이 오르지 않을 것입니다. 이러한 문제를 극복하기 위해서 선반관리 리스트를 작성하며 책의 이동, 배치현황, 선반관리 등 정보를 미리 만들어 놓고 반입이 될 때마다 이 리스트를 통해 비어있는 선반을 쉽게 찾아낸다면 좋은 성능을 기대할 수 있을 것입니다.

데이터베이스에서도 마찬가지로 서버 프로세스가 매번 빈 블록을 데이터버퍼 캐시영역으로부터 직접 찾아낸다면 너무 많은 시간이 소요될 수 있기 때문에 미리 만들어져 있는 LRU-LIST를 통해 빈 블록을 쉽게 찾아냅니다.

이번에는 LRU-LIST를 통해 빈 블록을 찾아내는 방법입니다.
먼저, 데이터버퍼 캐시영역에는 3가지 종류의 버퍼타입이 있습니다

첫 번째는 [FREE BUFFER]이며 비어있는 버퍼공간으로 새로운 데이터를 저장할 수 있는 공간입니다. 최초 사용자에 의해 읽혀진 테이블 데이터가 저장될 수 있는 블록입니다.

두 번째로는 [PINED BUFFER]인데 [FREE BUFFER]이었다가 사용자에 의해 데이터가 저장된 후 트랜잭션이 진행 중인 데이터를 저장하고 있는 버퍼공간입니다.

마지막으로, [DIRTY-BUFFER]는 사용자에 의해 진행 중인 트랜잭션이 완료된 버퍼공간입니다. 또한, LRU-LIST의 가장 오른쪽을 [LRU-END]라고 하며 가장 왼쪽은 [MRU(MOST RECENTLY USED)-END]라고 합니다. [LRU-END] 부분에는 데이터버퍼 캐시영역의 블록들 중에 가장 오래 전에 사용한 블록 정보들이 배치되고 [MUR-END] 부분에는 가장 최근에 사용된 블록 정보들이 배치됩니다.

 

   
 
 
  

자~ 서버 프로세스에 의해 읽혀진 테이블 데이터를 데이터버퍼 캐시영역의 빈 블록에 저장하기 위해 서버 프로세스는 LRU-LIST의 LRU-END 부분 블록들 부터 MRU-END 부분으로 검색하면서 [FREE BUFFER] 블록을 찾습니다. 먼저, [D5] 블록을 읽었는데 이 블록은 [PINED BUFFER]이군요. 다른 사용자에 의해 사용되고 있는 버퍼공간이므로 이공간을 사용해서는 안될 것입니다.

다음은 [G9] 블록인데 [FREE-BUFFER] 블록이군요. 서버 프로세스는 데이터버퍼 캐시영역으로 가서 이 블록에 테이블 데이터를 저장하게 됩니다. 이제 [G9] 블록은 [FREE-BUFER]이었다가 [PINED-BUFFER]로 속성이 바뀌게 되었고 가장 최근에 사용한 블록이 되었기 때문에 MRU-END 부분으로 블록정보가 이동하게 됩니다. 즉, [A1] 블록정보 왼쪽에 [G9] 블록정보가 배치됩니다. 하나의 블록으로 읽혀진 데이터가 모두 저장되었다면 작업은 끝나게 될 것입니다. 만약, 하나의 블록으로 데이터를 모두 저장하지 못했다면 [FREE BUFFER]를 찾기 위한 노력은 계속 진행됩니다.

다음은 [S3] 블록인데 [PINED BUFFER]이므로 스킵, 다음은 [F2] 블록인데 [DIRTY BUFFER] 블록이군요. 이 블록은 다른 사용자에 의해 저장된 데이터가 COMMIT 또는 ROLLBACK 문장을 만나 트랜잭션이 완전히 끝난 블록이므로 빨리 테이블에 변경된 정보를 저장해야할 블록입니다. [FREE-BUFFER]를 찾기 위해 검색을 하다가 [DIRTY-BUFFER]를 만나게 되면 이 블록의 정보들은 DIRTY-LIST로 이동되어 별도로 관리됩니다. 그리고, 다시 다음 블록을 검색합니다.

이번에는 [K7] 블록, [FREE-BUFFER]이군요. 서버 프로세스는 읽혀진 데이터를 저장합니다. 이런 방법으로 계속해서 읽혀진 데이터를 저장하기 위해 [FREE-BUFFER]를 찾게되는데 너무 많은 사용자들이 이러한 작업을 하다보면 LRU-LSIT를 아무리 검색해도 [FREE-BUFFER]를 찾지 못하는 경우가 발생할 수도 있습니다. 이런 경우에는 DIRTY LIST에 저장된 블록 정보들을 DBWR 백그라운드 프로세스에 의해 테이블에 저장하고 그 블록들을 [FREE-BUFFER]로 만들어 새로운 데이터를 저장하게 됩니다.

서버 프로세스는 이러한 방법으로 데이터 파일로부터 읽은 데이터를 데이터버퍼 캐시영역에 저장하기 위해 [FREE-BUFFER]를 찾게되는데 이런 메커니즘을 LRU 알고리즘이라고 합니다.

  

  

LRU 래치

  

"LRU 알고리즘"에서 사용자가 실행한 SQL문에 의해 테이블 데이터가 데이터버퍼 캐시 영역에 저장되기 위해서는 LRU LIST로부터 FREE BUFFER를 찾아야하는데, 만약 서버 프로세스가 하나의 FREE BUFFER를 찾았다면 이것을 "래치를 얻었다"라고 합니다. 하나의 서버 프로세스가 얻은 하나의 래치는 약 50개의 FREE BUFFER를 동시에 처리할 수 있습니다.

하나의 서버 프로세스는 지금 이 순간에도 하나의 래치를 얻기 위해 다른 서버 프로세스와 경합을 벌여 FREE BUFFER를 찾게 됩니다. 오라클 데이터베이스를 설치하면 LRU 래치의 기본 값은 1입니다. 사용자 수가 많은 데이터베이스 환경이라면 래치를 얻기 위한 경합(CONTENTION)으로 인해 성능이 저하될 수도 있습니다.

래치의 경합현상은 V$LATCH 자료사전을 통해 알 수 있는데 GETS 컬럼은 LATCH를 얻은 수이며 SLEEPS 컬럼은 LATCH를 얻지 못하고 대기(WAITING)했던 수를 의미합니다.
GETS 컬럼에 대해 SLEEPS 컬럼이 99% 이상의 백분율을 보일 때 경합이 발생하지 않기 때문에 좋은 성능을 기대할 수 있습니다. 만약, 백분율이 좋지 못하다면 다음 파라메터를 통해 래치 수를 더 높게 설정해 주어야 합니다.

  
 

DB_BLOCK_LRU_LATCHES = [n]

   

오라클 데이터베이스를 설치하면 LRU 래치의 기본 값은 1입니다. 이 값은 시스템의 CPU 수와 균형을 맞추어서 설정해야 하는데 최대 값은 (CPU 수 X 2 X 3 또는 데이터버퍼 수 / 50)계산에 의해 산출할 수 있으며 CPU수가 하나인 시스템 환경에서 래치수의 증가는 성능에 별로 도움이 안됩니다.

참 !!! 이 기능은 오라클 9i 버전에서 추가된 동적인 SGA 관리 기법을 사용할 때는 설정해 줄 필요가 없습니다. 래치 수를 오라클 서버가 자동으로 관리해 주기 때문입니다.

 
   

 
   

리두로그 버퍼 래치

 
   

오라클 데이터베이스 구조에서 발생하는 두 번째 래치는 리두로그 버퍼영역에서 발생합니다.
많은 사용자들이 동시에 UPDATE, INSERT, DELETE문을 실행하면 변경 행의 이전 데이터와 이후 데이터가 모두 리두로그 버퍼영역에 저장되어야 합니다. 하지만 하나의 구조로 활성화되어 있는 메모리 공간에 많은 사용자들의 변경 데이터가 저장되려면 서로 리두로그 버퍼 영역을 사용하기 위해 경합을 벌여야 합니다. 결국 경합현상으로 인해 대기상태가 발생하게 되고 성능이 저하되게 됩니다.

서버 프로세스가 변경 데이터를 저장하기 위해 먼저 리두로그 버퍼의 공간을 확보하게 되는데 이것을 리두할당 래치(Redo Allocation Latch)라고 합니다. 리두할당 래치를 입수한 서버 프로세스는 변경 데이터를 리두로그 버퍼에 저장해야 하는데 이것을 리두카피 래치(Redo Copy Latch)라고 합니다.

 
   
 

래치의 유형

 

오라클 데이터베이스에서 제공하는 래치는 다음과 같이 크게 2가지 유형이 있습니다.

 
   
 

WILLING TO WAIT 타입

   
  

이 타입은 서버 프로세스가 래치를 이용할 수 없을 때 잠시 기다렸다가 다시 래치를 요청하고 또 이용할 수 없으면 기다렸다가 다시 래치를 요청하게 됩니다. 일정한 회수를 반복적으로 요청하다 계속 래치를 얻지 못하면 수면상태(SLEEP)에 빠졌다가 일정시간이 지나면 다시 래치를 요청하는 방법으로 운영됩니다. 리두할당 래치가 대표적인 WILLING-TO-WAIT 타입입니다.

   
  

SQL>

SELECT b.name, a.misses / a.gets "MISSES/GETS"

 

FROM V$LATCH A, V$LATCHNAME B

 

WHERE b.name = ('redo allocation')

 

And b.latch# = a.latch#;

  

NAME

MISSES/GETS

 

redo allocation

.000023226

redo copy

0

 
   
  

<- WILLING-TO-WAIT 타입의 래치 경합이 발생하면 MISSES/GETS의 결과 값이 1% 이상의 값을 보이게 됩니다. MISSES의 경우는 래치를 얻지 못한 경우이며, GETS는 래치를 얻은 경우를 나타냅니다. 이런 경우에는 리두로그 버퍼가 너무 작아 경합이 발생하는 경우이므로 리두로그 버퍼의 크기를 보다 크게 늘려줘야 합니다.

   
 

IMMEDIATE 타입

   
  

서버 프로세스가 WILLING-TO-WAIT 래치와 같이 요청된 래치를 이용할 수 없을 경우 기다리지 않고 바로 래치를 얻는 타입입니다. 리두카피 래치가 대표적인 IMMEDIATE 타입입니다.

   
  

SQL>

SELECT

b.name,

  

a.immediate_misses / decode((a.immediate_gets + immediate_misses), 0, 1,

  

(a.immediate_gets + a.immediate_misses)) "MISSES/GETS"

 

FROM

V$LATCH A, V$LATCHNAME B

 

WHERE

b.name = ('redo copy')

  

And b.latch# = a.latch#;

   

NAME

 

MISSES/GETS

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

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

redo allocation

 

0

redo copy

 

.000118391

 
   
  

<- IMMEDIATE 타입의 래치 경합이 발생하면 MISSES/GETS의 결과 값이 1% 이상의 값을 보이게 됩니다. MISSES의 경우는 래치를 얻지 못한 경우이며, GETS는 래치를 얻은 경우를 나타냅니다. 이런 경우에는 리두로그 버퍼가 너무 작아 경합이 발생하는 경우이므로 리두로그 버퍼의 크기를 보다 크게 늘려줘야 합니다.

   
 

리두로그 래치의 튜닝

   
 

리두할당 래치에서 MISSES/GET의 비율이 1% 이상의 경합이 발생한다면

 
   
  

LOG_SMALL_ENTRY_MAX_SIZE 파라메터의 값을 감소시켜야 합니다. 만약, 변경 데이터의 양이 이 파라메터의 값보다 작으면 리두할당 래치를 얻은 상태에서 변경 데이터를 복사하는 작업까지 완료할 수 있고, 만약, 크다면 리두할당 래치를 할당 받은 후 래치를 해제하게 됩니다.

   
 

리두카피 래치에서 MISSES/GET의 비율이 1% 이상의 경합이 발생한다면

 
   
  

LOG_SMALL_ENTRY_MAX_SIZE 파라메터의 값이 작아서 리두할당 래치가 너무 빨리 해제되어 리두카피 래치가 시작되는 문제 때문에 발생한 것입니다.
이런 경우에는 리두카피 래치 수를 증가시켜 경합이 발생하지 않도록 해야 합니다.

리두카피 래치의 기본 수는 해당 시스템의 CPU 수이며 최대 CPU수의 2배까지 정의할 수 있습니다. LOG_SIMULTANEOUS_COPIES 파라메터에 의해 리두카피 래치수가 결정됩니다.

Posted by redkite
, |

1-4 단계 : 자파풀 영역

   

자바 풀 영역의 튜닝

 

오라클 8 버전부터 제공되는 Jserver 옵션은 데이터베이스 내에 자바 컴파일러와 자바 실행코드를 내장하고 있어 데이터베이스 내에서 자바 애플리케이션을 작성할 수도 있고 또한 실행할 수도 있습니다. 이와 같은 Jserver 옵션을 설치하여 개발하는 곳은 반드시 SGA 영역 내에 JAVA POOL 영역을 활성화 시켜야 합니다.
자바 풀 영역을 활성화하기 위해서는 다음과 같이 환경설정을 하셔야 합니다.

   
 
  
  

$ vi init<SID>.ora

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

 

JAVA_POOL_SIZE = 20M

ß JAVA POOL 영역의 크기를 결정합니다.

SHARED_POOL_SIZE = 50M

ß SHARED POOL 영역의 크기를 결정합니다.

  
  
 

JAVA_POOL_SIZE 파라메터

   
  

- 이 파라메터의 기본값은 20MB 입니다.
- JAVA 애플리케이션으로 개발되는 환경에서는 50MB 이상의 크기가 요구됩니다.

   
 

SHARED_POOL_SIZE 파라메터

   
  

- 하나의 클래스가 실행될 때 마다 8 KB의 공간이 공유 풀 영역에서 요구됩니다.
- JAR 파일을 로더할 때 50 MB의 공간이 요구됩니다.
- 반드시 공유 서버 프로세스 환경이어야 사용가능 합니다

다음은 데이터베이스의 현재 공유 풀 영역의 크기를 분석하는 방법입니다.

   
  

SQL>

SELECT * FROM v$sgastat

 

WHERE pool = 'java pool';

  

POOL

NAME

BYTEX

 

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

java pool

free memory

3000234

 

java pool

memory in use

1974720

 

<- 현재 사용중인 자바 풀 영역(Memory in Use)의 크기와 사용 가능한 크기(Free Memory)를 알 수 있습니다.

 
   

기타 자바 풀 관련 파라메터

 
   
 

JAVA_SOFT_SESSIONSPACE_LIMIT

  

세션에서 자바 풀 영역을 사용할 수 있는 평균크기를 제한하며 이 값을 초과하면 TRACE 파일에 경고 메시지가 저장됩니다. 기본값은 1M 입니다.

   
 

JAVA_MAX_SESSIONSPACE_SIZE

  

세션에서 사용할 수 있는 자바 풀 영역의 최대크기를 제한하며 이 값을 초과하면 다음과 같이 에러가 발생하고 해당 세션은 종료됩니다. 기본값은 4GB 입니다.

   
 

ORA-29554: unhandled Java out of memory condition

 
Posted by redkite
, |

1-3 단계 : 로그버퍼영역

 

 
   

로그 버퍼 영역의 튜닝 분석

 

사용자들이 데이터베이스에 접속 한 후 INSERT, UPDATE, DELETE 작업을 실행하면 트랜잭션에 의해 만들어진 모든 변경정보(UPDATE문을 실행했다면 변경 전 데이터와 변경 후 데이터)가 로그버퍼 영역에 저장됩니다. 그러나, 로그버퍼 영역에는 사용자의 모든 변경된 데이터를 저장해 둘 수 없기 때문에 이 데이터들을 영구히 저장하기 위해 로그기록기(LGWR)에 의해 로그파일에 기록하게 됩니다.

리두로그 버퍼영역은 여려 명의 사용자가 같이 사용하는 공간이기 때문에 너무 많은 서버 프로세스가 동시에 많은 변경된 데이터를 저장하려고 한다면 서버 프로세스간에 경합(CONTENTION)이 발생할 것입니다. 또한, 사용자의 변경된 정보를 저장할 충분한 리두로그 버퍼공간이 없다면 미처 변경된 정보를 저장하지 못하고 대기(WAITING)해야 하는 경우도 발생할 것입니다.

이러한 문제가 발생하면 결국 UPDATE, INSERT, DELETE 시에 좋은 성능을 기대할 수 없습니다. 로그버퍼 영역의 크기는 INIT<DB명>.ORA 파일에 있는 LOG_BUFFER 파라메터에 의해 결정됩니다. 이 파라메터를 설정하지 않으면 오라클 서버는 블록크기(DB_BLOCK_SIZE 파라메터의 크기)의 4배 값을 기본 로그버퍼 영역의 크기로 할당합니다.

 

로그 버퍼 캐시 영역을 보다 크게 할당한다.

   
 
  

로그버퍼 영역에 대한 튜닝은 여러 사용자가 동시에 변경 작업을 했을 때 변경된 데이터를 저장할 만한 충분한 메모리 공간이 할당되어야 한다는 것입니다. 이 공간이 너무 작으면 서로 변경 데이터를 저장하기 위해 경합(CONTENTION)이 발생하고 결론적으로 어떤 서버 프로세스는 대기(WAITING) 상태가 발생할 것입니다.

다음은 자료사전을 통해 로그버퍼 영역을 분석하는 방법입니다.

  
 

V$SESSION_WAIT 자료사전을 참조하면 이 영역에 대한 튜닝 여부를 확인할 수 있습니다

SECOND_IN_WAITS 컬럼의 값이 계속 증가되는 값을 나타내고 STATE 컬럼이 "WAITING"을 나타내면 로그버퍼 영역이 작아서 서로 프로세스간에 경합이 발생하고 있음을 의미합니다.

   
  

SQL>

SELECT sid, event, second_in_wait, state

 

FROM v$session_wait

 

WHERE event = 'log buffer space%';

  
 

SID

EVENT

SECOND_IN_WAIT

STATE

 

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

 

5

log buffer space

110

WAIT

 
   
 

V$SYSSTAT 자료사전에서 서버 프로세스가 로그정보를 저장했던 로그버퍼의 블록 수(REDO ENTRIES)와 로그버퍼의 경합으로 인해 발생한 대기상태에서 공간이 생겨 다시 로그버퍼 공간을 할당 받았던 블록 수(REDO BUFFER ALLOCATION RETRIES)를 확인할 수 있습니다.

REDO BUFFER ALLOCATION RETRIES의 값은 0 이어야 하고 REDO ENTRIES 값의 1% 미만일 때 로그버퍼 영역에 대한 좋은 성능을 기대할 수 있습니다.

   
  

SQL>

SELECT name, value

 

FROM v$sysstat

 

WHERE name IN ('redo buffer allocation retries', 'redo entries');

  
 

NAME

VALUE

 

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

 

redo entries

2015

 

redo buffer allocation retries

12

 
   
  

만약, 기준치에 적합하지 않은 결과가 나오면 리두로그 영역이 작아서 발생한 문제임으로 LOG_BUFFER 파라메터 값을 더 높게 설정해야 합니다.

   
  

$ cd $HOME/dbs

$ vi init<SID>.ora

 

LOG_BUFFER = 8192 ß 이전 값 보다 더 크게 설정해야 합니다.

 

:wq!

 
   

 
   

인덱스 생성시 NOLOGGING절을 적용한다.

 
   

사용자가 테이블을 생성하고 데이터를 입력, 수정, 삭제하면 트랜잭션에 의해 발생한 모든 변경 전, 변경 후 정보가 로그버퍼 영역에 저장되는데 이러한 방법을 로깅모드(LOGGING)라고 합니다. 테이블, 인덱스를 생성할 때 NOLOGGING 키워드를 사용하면 생성 후 발생하는 모든 트랜잭션의 로그정보가 로그버퍼 영역에 저장되지 않습니다. 만약, 해당 테이블의 데이터가 다른 시스템 또는 다른 데이터베이스로부터 이동된 테이블이거나 언제든지 복구가 가능한 테이블이라면 LOGGING 모드로 데이터베이스를 사용하는 것보다 NOLOIGGING 모드로 사용하는 것이 로그버퍼 영역을 적게 사용하기 때문에 로그버퍼 영역에 대한 경합 현상을 최소화시킬 수 있습니다. 인덱스는 테이블을 통해 언제든지 만들 수 있기 때문에 NOLOGGING모드로 만드는 것이 좋은 성능에 도움이 될 수 있습니다.

다음 예제를 따라 해 보세요.
EMP10 테이블을 NOLOGGING 키워드로 생성하고 다시 LOGGING 모드로 바꿔보십시오. 그리고, I_EMP10 인덱스를 NOLOGGING 모드로도 생성해 보십시오.

 
   
  

SQL>

CONNECT scott/tiger

SQL>

CREATE TABLE emp10 (no NUMBER, name VARCAHR2(10))

 

NOLOGGING;

  
 
    
  

SQL>

ALTER TABLE emp10 LOGGING;

SQL>

CREATE INDEX I_emp10 ON emp10 NOLOGGING;

 
  

앞에서도 언급되었듯이 테이블을 NOLOGGING 모드로 설정할 때는 데이터의 유형과 복구 전략에 대한 충분한 고려를 한 후 결정해야 하며, 인덱스에 대해선 자주 변경이 발생하는 테이블에 대해 적용한다면 INSERT, UPDATE, DELETE 작업 시 보다 빠르게 변경작업을 수행할 수 있을 것 입니다. 만약, 데이터베이스에 문제가 발생하여 복구작업을 수행해야 한다면 인덱스의 경우에는 재 생성하는 것이 복구이기 때문에 반드시 LOGGING 모드일 필요가 없습니다.

 

Posted by redkite
, |

1-2 단계 : 버퍼캐쉬 영역

 

 
   

데이터 버퍼 캐시 영역의 튜닝 분석

 

"SQL문의 처리과정"을 통해 알아보았듯이 데이터 파일로부터 읽혀진 데이터가 저장되는 메모리 영역을 데이터버퍼 캐시영역이라고 하며 이 영역의 크기는 INIT<DB명>.ORA 파일에 정의되는 DB_CACHE_SIZE 파라메터에 의해 결정됩니다. 예를 들어, 윈도우 시스템에서 어떤 텍스트 파일의 내용을 편집하기 위해 더블-클릭을 하면 잠시 후 편집기 프로그램이 실행되고 파일의 내용이 화면에 출력됩니다.

이와 같이 실행되는 과정을 살펴보면 선택된 파일은 디스크로부터 읽혀지고 시스템의 메모리 버퍼영역에 로더된 다음 편집기를 통해 사용자의 화면에 출력되는 것 입니다. 만약, DB_CACHE_SIZE 파라메터의 값이 10MB일 때 읽혀지는 테이블의 크기는 20MB이라면 어떻게 될까요 ? 읽혀지는 테이블이 메모리 크기보다 크기 때문에 한번에 테이블 데이터 모두를 메모리에 저장하지 못하고 2번에 나누어서 저장해야 할 것입니다.

그렇다면, DB_CACHE_SZIE가 20MB이라면 어떻게 될까요 ? 당연히 읽혀진 테이블 데이터 20MB를 한번에 데이터버터 캐시영역에 저장하게 될 것입니다. 2번 이상, 여러 번 나누어서 데이터를 처리하는 것보다는 한번에 데이터를 처리하는 것이 좋은 성능을 기대할 수 있는 방법이 될 것입니다. 결론적으로, 데이터버퍼 캐시영역의 크기는 처리하려는 테이블 데이터를 충분히 로더할 수 있을 만큼 설정되어 있는 경우와 그렇지 못한 경우에 의해 성능차이가 발생할 수 있다는 것입니다.

 

데이터 버퍼 캐시 영역의 튜닝 방법

   
 
 

데이터 버퍼 캐시 영역을 보다 크게 할당한다.

  

그럼, 데이터버퍼 캐시영역에 대한 성능을 향상시킬 수 있는 첫 번째 방법을 알아 보겠습니다. 사용자가 질의한 데이터를 데이터버퍼 캐시영역에 로더하려면 충분한 메모리 공간이 필요합니다. 공간이 부족하면 한번에 데이터를 로더하지 못하고 여러 번 나누어서 로더해야 하기 때문에 성능이 저하될 수 있습니다.

다음은 자료사전을 통해 데이터 버퍼 캐시영역의 튜닝 상태를 분석하는 방법입니다.
V$SYSSTAT 자료사전을 참조하면 이 영역에 대한 튜닝 여부를 확인할 수 있습니다.

  
 
 

$ sqlplus scott/tiger

SQL> select * from big_dept;

SQL> select * from big_emp;

SQL> connect system/manager

SQL> select 1 - ((phy.value)/(cur.value + con.value)) "CACHE HIT RATIO"

from v$sysstat cur, v$sysstat con, v$sysstat phy

where cur.name = 'db block gets'

and con.name = 'consistent gets'

and phy.name = 'physical reads';

 

CACHE HIT RATIO

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

.973257182

 

"PHYSICAL READS"는 사용자가 SQL문을 실행했을 때 데이터 파일로부터 테이블을 읽은 블록 수를 의미하며

"DB BLOCK GETS"는 DML문의 처리과정에서 처럼 데이터버퍼 캐시영역에 있는 변경 후 데이터를 참조할 때 읽은 블록 수를 의미합니다.

"CONSISTENT GETS"은 변경작업 후 ROLLBACK문을 실행했을 때 변경 전 데이터로 복구하기 위해 이전 데이터를 저장해 두는 메모리 영역으로부터 읽은 블록 수입니다.

즉, "PHYSICAL READS"는 물리적 구조에서 읽은 블록 수이며 "DB BLOCK GETS"와 "CONSISTE NT GETS"는 논리적 구조(메모리 영역)에서 읽은 블록 수를 의미합니다. 되도록, 하나의 테이블을 질의할 때 물리적 구조로부터 데이터를 읽는 것보다는 논리적 구조로부터 읽은 블록 수가 더 많아야 성능에 도움이 될 것입니다. 만약, 충분한 데이터 버퍼 공간이 확보되었다면 "DB BLOCK GETS"와 "CONSISTENT GETS"가 더 높은 값이 출력될 것입니다.

그리고, 다음과 같은 공식에 의해 캐시 히트율을 계산할 수 있는데 이것은 사용자가 실행한 SQL문의 테이블이 이미 다른 사용자에 의해 읽혀져서 데이터버퍼 영역에서 발견되었던 블록
수를 백분율로 계산한 것입니다.

히트율 = 1 - (PHYSICAL READS / (DB BLOCK GETS + CONSISTENT GETS))

캐시 히트율이 90% 이상일 때 충분한 데이터버퍼 캐시영역이 설정되어 있고 좋은 성능을 기대할 수 있습니다. 만약, 캐시 히트율이 좋지 못하다면 다음 파라메터를 통해 데이터버퍼 캐시영역의 크기를 크게 설정해 주어야 합니다.

  
 

$ cd $HOME/dbs

$ vi init.ora

………………………

DB_CACHE_SIZE = [크기]

:wq!

  
 

<잠깐>

 

오라클 9i 이전 버전에서는 DB_CACHE_SIZE 파라메터가 DB_BLOCK_SIZE와 DB_BLOCK_BUFFERS 파라메터로 나누어져 사용되었습니다. 2개 파라메터 값을 곱한 결과가 DB_CACHE_SIZE의 값과 같습니다.

  

  

멀티 버퍼 캐시영역을 할당한다.

  

일반적으로 오라클 데이터베이스의 SGA 영역에는 데이터 버퍼 캐시영역이 하나의 구조로 생성되는데 이 영역을 데이터의 성격과 크기에 따라 여러 개의 영역으로 나누어 생성할 수 있는 기법을 멀티 버퍼캐시 영역이라고 합니다.

위 그림에서 보시는 것처럼 데이터버퍼 캐시영역을 3가지 영역으로 나누어 생성할 수 있습니다. 첫 번째 영역은 KEEP 데이터버퍼 캐시 영역입니다. 이 영역은 운영되는 데이터베이스 환경에서 빈번하게 사용되는 테이블 데이터를 계속 데이터버퍼 캐시영역에 상주시키기 위한 공간입니다. 이 공간에 저장될 수 있는 테이블은 그 크기가 너무 크면 안되고 사용자에 의해 빈번하게 사용되는 테이블이어야 합니다.

예를 들어, 부서 테이블, 사원 테이블, 코드 테이블 등은 모든 애플리케이션 프로그램에서 항상 참조되며 또한 테이블의 크기가 크지 않기 때문에 가장 적합한 테이블입니다.

두 번째 영역은 RECYCLE 데이터 버퍼 캐시영역입니다, 이 영역에는 자주 사용되지 않고 가끔 사용되는 테이블이면서 테이블의 크기가 아주 큰 테이블을 검색할 때 사용됩니다. 사용자에 의해 검색된 테이블은 이 영역에 데이터를 로더한 후 작업이 완료되면 모두 클리어(CLEAR) 됩니다.
마지막으로, DEFAULT 데이터 버퍼 캐시영역은 하나의 데이터버퍼 캐시영역을 가진 환경에서 사용되던 구조와 동일한 의미를 가집니다. 즉 여러 사용자에 의해 공용으로 사용되는 메모리 공간입니다. 다음은 다중 풀 영역을 설정하는 파라메트들 입니다.

  
 

DB_CACHE_SIZE = [크기]
DB_KEEP_CACHE_SIZE = [크기]
DB_KEEP_RECYCLE_SIZE = [크기]

  

DB_CACHE_SIZE 파라메터는 DEFAULT 데이터버퍼 캐시영역의 크기를 의미합니다.
DB_KEEP_CACHE_SIZE 파라메터는 KEEP 데이터버퍼 캐시영역의 크기를 의미합니다.
DB_KEEP_RECYCLE_SIZE 파라메터는 RECYCLE 데이터버퍼 캐시영역의 크기를 의미합니다.

  

다음은 다중 풀 영역에 로더될 테이블을 다음과 생성해야 합니다.

  
 

< 문법 >

CREATE TABLE [테이블명]

([컬럼-1] [데이터 타입],

…………………………………….

[컬럼-N] [데이터 타입])

STORAGE ( BUFFER_POOL [KEEP | RECYCLE] );

 

ALTER TABLE [테이블명]

STORAGE ( BUFFER_POOL [KEEP | RECYCLE] );

  
 
 
 

KEEP 데이터버퍼 캐시영역

  
 

자~ 그렇다면 KEEP 데이터버퍼 캐시 영역를 설정하고 싶은데 그 크기는 얼마나 할당해야 할까요 ? 크기를 설정하는 방법은 다음과 같습니다.
먼저, ANALYZE 명령어를 실행하여 캐싱하려는 테이블이 몇 개의 블록으로 구성되어 있는지를 분석해야 합니다. 테이블의 모든 데이터를 KEEP 데이터버퍼 영역에 저장하기 위해서 테이블의 실제 블록 수를 분석하는 것입니다.

  
 
  

$ sqlplus scott/tiger

SQL>

analyze table big_emp compute statistics;

SQL>

analyze table big_dept compute statistics;

SQL>

select table_name, blocks

 

from user_tables

 

where table_name in ( 'BIG_EMP' , 'BIG_DEPT' );

 

TABLE_NAME BLOCK

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

BIG_DEPT 5 <- 14 행을 저장하고 있슴

BIG_EMP 370 <- 28955 행을 저장하고 있슴

<-sum(blocks + 1) 을 모두 포함할 만큼의 충분한 크기로 keep buffer pool을 설정해야 합니다.

 
 
 
  

ANALYZE 명령어를 실행하면 분석결과가 DBA_TABLES, USER_TABLES 자료사전에 저장 됩니다.
만약, KEEP 데이터버퍼 캐시영역에 5개의 테이블을 저장하고 싶다면, 이러한 방법으로 각 테이블의 블록 수를 분석한 다음 전체 블록 수를 집계하여 그 값을 DB_KEEP_CACHE_SIZE 파라메터에 할당해 주면 됩니다.

   
  

$ cd $ORACLE_HOME/dbs

$ vi init<DB명>.ora

DB_KEEP_CACHE_SIZE = 4M ß BIG_DEPT, BIG_EMP 가 상주될 메모리 공간

DB_RECYCLE_CACHE_SIZE=4M ß RECYCLE 영역

:wq!

 

$ sqlplus "/as sysdba"

SQL > startup force ß Shutdown 후 다시 Startup

SQL > show parameter db_keep_cache_size

 

NAME TYPE VALUE

db_keep_cache_size big integer 8388608

 
  
  
  
 

RECYCLE 데이터버퍼 캐시영역

 
   
 

이번에는 RECYCLE 데이터버퍼 캐시 영역를 설정하고 싶은데 그 크기는 얼마나 할당해야 할까요 ? 크기를 설정하는 방법은 다음과 같습니다.
먼저, V$CACHE 자료사전을 참조하기 위해서 $HOME/rdbms/admin 경로에 있는 CATPARR.SQL 스크립트를 실행하십시오.

   
  

SQL>

CONNECT SYS/CHANGE_ON_INSTALL AS SYSDBA

SQL>

show parameter db_recycle_cache_size

    

NAME

TYPE

VALUE

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

db_recycle_cache_size

big integer

8388608

  

SQL>

START $HOME/rdbms/admin /catparr.sql

SQL>

SELECT owner#, name, count(*) blocks

 

FROM v$cache

 

WHERE name = 'EMP'

 

GROUP BY owner#, name;

  

OWNER

NAME

 

BLOCK

5

EMP

 

200

 
  
  

실행된 결과가 V$CACHE 자료사전에 저장됩니다. 이 결과에서 해당 테이블이 데이터버퍼 캐시영역에서 필요로 하는 블록 수가 어느 정도인지 알 수 있습니다. 이 블록 수의 1/4 값을 DB_RECYCLE_CACHE_SIZE 파라메터에 설정하십시오. RECYCLE 영역에는 가끔 질의되는 테이블 데이터가 저장될 메모리 공간이기 때문에 결과의 전체 블록 수를 설정할 필요가 없습니다.
현재 데이터베이스에 설정된 다중 풀 영역에 대한 정보를 참조할 때는 V$BUFFER_POOL_STA TISTICS와 V$BUFFER_POOL 자료사전을 참조하십시오.

 
    
  

SQL > select name, buffers from v$buffer_pool;

NAME

BUFFERS

 

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

-----------

 

KEEP

1934

<- KEEP 영역의 크기

RECYCLE

1934

<- RECYCLE 영역의 크기

DEFAULT

967

<- DEFAULT 영역 크기

 
  
  

다중 버퍼 캐시영역이 활성화 되었으면 개발자가 실행할 테이블 중에 KEEP 영역과 RECYCL
영역에 로더될 테이블을 결정합니다.

 
    
  

$ sqlplus scott/tiger

SQL>

alter table big_emp storage ( buffer_pool keep);

SQL>

alter table big_dept storage ( buffer_pool keep);

SQL>

alter table account storage ( buffer_pool recycle);

  
 
  
  

각 테이블이 어떤 버퍼 캐시영역에 로더 되는지를 확인하는 방법입니다.

 
    
  

SQL>

select table_name, buffer_pool

 

from user_tables

 

where table_name in ( 'BIG_EMP' , 'BIG_DEPT' );

   

TABLE_NAME

BUFFER_

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

-------

BIG_EMP

KEEP <-KEEP 될 테이블 정보를 의미합니다.

   

SQL>

SELECT * FROM big_dept;

SQL>

SELECT * FROM big_emp;

 

SELECT * FROM account;

 
    
  

다음 문장은 다중 버퍼 캐시영역의 현재 튜닝상태를 제공해 줍니다,

 
    
  

SQL>

select name,

 

1-(physical_reads/(db_block_gets+consistent_gets)) "Multiple RATIO"

 

from

v$buffer_pool_statistics

 

where

db_block_gets + consistent_gets > 0 ;

   

NAMA

Multiple RATIO

-----------

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

KEEP

.839300938 <-100 %에 가까울수록 최적

RECYCLE

.819349315 <-50 %가 최적

DEFAULT

.752205989 <-90 % 이상이 가장 최적

  
 
    
  

KEEP 영역은 모든 테이블을 캐싱하기 위한 공간이므로 100% 히트율을 유지하는 것이 좋은 성능을 제공하고, RECYCLE 영역은 잠깐 사용되는 공간이므로 50% 히트율로도 충분하며, DEFAULT 영역은 일반적으로 사용되는 공간이므로 90% 히트율을 유지하는 것이 좋습니다.

 
    
  

<잠깐>오라클 8i 버전에서 멀티 버퍼 캐시 영역의 활성화 방법

  

오라클 9i 이전 버전과 이후 버전에서 멀티 버퍼캐시 영역을 활성화하는 방법의 차이는 문법적인 측면입니다. 즉, 9i 버전에서는 SGA 영역을 동적으로 관리하는 기능이 추가되었기 때문에 이와 관련된 문법이 변경되었습니다

 
    
  

$ vi init<SID>.ora

 

DB_BLOCK_BUFFERS = 3147

DB_BLOCK_LRU_LATCHES = 6

BUFFER_POOL_KEEP = (BUFFERS:1400, LRU_LATCHES:1)

BUFFER_POOL_RECYCLE = (BUFFERS:200, LRU_LATCHES:3)

 
    
  

<잠깐>오라클 8i 버전에서 멀티 버퍼 캐시 영역의 활성화 방법

    
 

ID NAME LO_SETID HI_SETID SET_COUNT BUFFERS LO_BNUM JI_BNUM

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

0 0 0 0 0 0 0

1 KEEP 3 3 1 1400 0 0

2 RECYCLE 4 6 3 200 0 0

3 DEFAULT 1 2 2 1547 1600 0

 
    
  

DB_BLOCK_BUFFERS와 DB_BLOCK_SIZE 파라메터가 오라클 9i에서 DB_CACHE_SIZE 파라메터로 바뀌었고, DB_BLOCK_LRU_LATCHES와 같은 래치 파라메터는 오라클 서버가 자동 관리해 주는 방법으로 변경되어 9i에서는 정의하지 않습니다.

 
    

 
    

자주 사용되는 테이블을 캐싱한다.

 
    

다중 멀티 풀 영역을 이용한 성능향상 기법이 오라클 8 버전부터 추가된 기능이라면 캐싱 테이블 기법은 오라클 이전 버전부터 사용되던 기법입니다. 사용자가 실행한 SQL문에 의해 테이블 전체 스캔 방법으로 테이블이 검색된 후 계속해서 데이터 버퍼 캐시영역에 상주하게 됩니다.

정상적인 데이터버퍼 캐시 메커니즘이라면 다른 서버 프로세스에 의해 데이터버퍼 캐시영역은 재 사용되겠지만 캐싱 테이블로 만들어진 블록들은 다른 서버 프로세스에 의해 재 사용되지 않고 계속해서 상주할 수 있게 되는 기능입니다. 다중 멀티 풀 영역에서 KEEP 데이터버퍼 캐시영역과 유사한 기능을 가지고 있습니다. 이 기법을 통해 빈번하게 사용되는 테이블을 데이터버퍼 캐시영역에 상주시키면 검색 시 성능을 향상시킬 수 있습니다.

다음 문법은 캐시 테이블을 생성하는 방법입니다.

 
    

문법

CREATE

TABLE

[테이블명]

([컬럼] [데이터타입])

[CACHE / NOCACHE];

ALTER

TABLE

[테이블명]

[CACHE] /NOCACHE];

 
 
    

테이블을 생성할 때 [CACHE] 키워드를 사용하십시오. 또는, 이미 생성되어 있는 테이블을 [CACHE] 키워드로 변경할 수도 있습니다. 또한, [CACHE]된 테이블을 [NOCACHE]로 변경할 수도 있습니다. 만약, CACHE된 테이블 데이터가 [NOCACHE]로 바뀌면 데이터베이스가 종료되고 다시 시작될 때 적용됩니다.

다음 예제를 따라 해 보세요. DBA_TABLES, USER_TABLES 자료사전을 검색해 보면 어떤 테이블이 캐시 테이블로 생성되어 있는지를 알 수 있습니다.

 
    
    
  

SQL>

alter table big_dept cache;

SQL>

select table_name, cache

 

from user_tables

 

where table_name in ('BIG_EMP' , 'BIG_DEPT');

  
 

TABLE_NAME

CACHE

 

BIG_DEPT

Y

 

BIG_EMP

Y <-데이터 버퍼캐시 영역에 상주되는 테이블을

  

SQL>

SELECT * FROM big_dept;

SQL>

SELECT * FROM big_emp;

 
    
  

다음은 캐시 테이블로 생성하지는 않았지만 힌트절을 통해 테이블을 캐싱하는 방법입니다.

 
    
  

SQL> SELECT /*+CACHE*/
deptno, dname, loc
FROM big_dept;

SQL> EXIT

 
    

 
    

Multiple Block 구조

 
    

오라클 데이터베이스에서 테이블에 대해 읽기/쓰기 작업을 수행할 때 한번에 읽고 쓸 수 있는 데이터의 크기를 블록(Block)이라고 합니다.

하나의 블록크기는 오라클 데이터베이스가 설치될 때 init<SID>.ora 파일에 정의되는 DB_ BLOCK_SIZE 파라메터에 의해 결정됩니다. 그리고, 이 값은 더 이상 변경할 수 없습니다.

만약, 대용량의 데이터를 저장하고 있는 테이블에 대해 읽기작업을 실행했을 때 DB_BLOCK_SIZE가 4K(4096 Byte)인 경우보다 8K(8192 Byte)인 경우가 한번에 읽을 수 있는 데이터가 크기 때문에 약 2배 이상 빠르게 검색할 수 있을 것 입니다.

오라클 8i 버전까지는 설치할 때 결정된 한 개 블록의 크기를 더 이상 변경하지 못하여 대용량 데이터를 처리해야 하는 경우 근본적인 구조변경을 하지 못하였습니다.
오라클 9i 버전에서는 이러한 문제점을 해결하여 테이블스페이스를 생성할 때 특별한 블록크기를 지정하지 않으면 설치할 때 정의된 DB_BLOCK_SIZE 파라메터 값으로 결정하고, 대용량의 데이터를 저장하고 검색하는 테이블이 존재하는 테이블스페이스를 생성할 때는 별도로 블록크기를 보다 크게 설정할 수 있습니다. 이러한 기능을 다중 블록(Multiple Block)구조라고 합니다.

다음 내용은 블록의 크기가 작은 경우와 큰 경우에 대한 비교 분석된 내용입니다.

 
    

 
    

먼저, 다중 블록크기를 설정하기 위해서는 데이터 버퍼 캐시영역에 각 블록크기에 대한 전용 데이터 버퍼 캐시영역을 활성화해야 하고 테이블스페이스 생성시 적절한 블록크기를 정의하게 되면 사용가능 합니다.

 
    
 

init<SID>.ora 파일에 관련 파라메터 들을 설정하십시오.

 
    
  

$ vi init<SID>.ora

DB_BLOCK_SIZE = 2048

기본 블록크기

DB_4K_CACHE-SIZE = 8M

4K 블록크기를 설정한 테이블스페이스를 위한 메모리 공간

DB_8K_CACHE-SIZE = 16M

8K 블록크기를 설정한 테이블스페이스를 위한 메모리 공간

DB_16K_CACHE-SIZE = 32M

16K 블록크기를 설정한 테이블스페이스를 위한 메모리 공간

DB_32K_CACHE-SIZE = 64M

32K 블록크기를 설정한 테이블스페이스를 위한 메모리 공간

 
  

<- 이 파라메터들에 의해 SGA 영역에는 2K, 4K, 8K, 16K, 32K 블록크기를 가진 데이터가 읽혀질 때 저장될 전용 메모리 공간이 각각 활성화됩니다.

 
    
 

대용량 데이터를 저장해야 할 데이터가 저장될 테이블스페이스를 생성하십시오.

 
    
  

SQL>

CREATE TABLESPACE INSA

 

DATAFILE '$HOME/dbs/insa01.dbf' size 10m blocksize 4k;

SQL>

create table emp_test

 

(A number)

SQL>

select * from user_tables

 

where table_name = 'EMP_TEXT';

 
    

 
    

DB 캐시 영역의 시뮬레이션

 
    

이 기능은 오라클 데이터베이스의 SGA 영역에 활성화 되어 있는 데이터 버퍼 캐시영역에 대한 시물레이션 기능에 대해서 소개하고자 합니다.

오라클 9i 버전에서는 DB_CACHE_SIZE 파라메터에 의해 데이터 버퍼캐시 영역의 크기가 결정되는데 이 공간의 크기가 너무 작게 설정되어 있으면 데이터를 검색할 때 성능이 저하되고 또한 너무 크게 설정되어 있으면 메모리의 낭비가 초래될 수 있는 단점을 가지고 있습니다.
그렇다면 모든 사용자가 실행하는 SQL문을 보다 빠르게 처리할 수 있을 만큼 적절한 메모리 공간이 확보되어 있는지를 확인할 수 있어야 할 것 입니다.

DB_CACHE_ADVICE 파라메터를 ON으로 활성화하게 되면 현재 데이터 버퍼 캐시영역의 각 블록구조의 활용상태를 시뮬레이션 할 수 있습니다.

[OFF]로 설정되면 데이터 버퍼 캐시영역에 대한 시뮬레이션을 하지 않습니다.
[READY]는 향후 시뮬레이션을 위한 통계정보 만 수집하고 실제적인 시뮬레이션은 하지 않습니다. 이 값에 의해 수집된 통계정보는 공유 풀 영역에 약 700 바이트 만큼의 공간을 사용합니다.
[ON]은 지금 즉시 통계정보 수집 후 데이터 버퍼 캐시영역에 대한 시뮬레이션을 시작합니다.

위 그림에서 V$DB_CACHE_ADVICE 자료사전을 분석하여 데이터 버퍼 캐시영역을 분석하고 있습니다. SIZE_FOR_ESTIMATE 컬럼은 데이터 버퍼캐시 영역을 3M 단위로 시뮬레이션하는 것을 나타내는 것이며 BUFFERS_FOR_ESTIMATE 컬럼은 3M 단위의 버퍼크기에 대한 블록 수를 나타내며 ESTD_PHYSICAL_READ_FACTOR와 ESTD_PHYSICAL_READS 컬럼은 각 버퍼크기에 대한 사용된 회수를 보여주고 있습니다.

만약, 특정 메모리 영역에서 주로 작업이 발생하고 어떤 메모리 영역에서는 작업이 거의 일어나지 않는다면 불필요한 메모리 공간이 낭비되고 있음을 의미하는 것이며, 만약, 모든 메모리 영역에서 빈번한 읽기 작업이 발생한다면 오히려 데이터 버퍼 캐시영역의 크기가 너무 작아 성능저하가 발생하고 있음을 나타냅니다

 
 

<잠깐> 다음은 오라클 이전 버전까지 제공되던 데이터 버퍼 캐시 영역의
시뮬레이션 방법입니다.

 
    

 
    
 

이 기능을 사용하기 위해서는 DB_BLOCK_LRU_EXTENDED_STATISTICS 파라메터를 설정해야 합니다. 위 그림에서 데이터 버퍼캐시 영역의 블록 개수를 200개로 설정했을 때 각 블록구조의 사용된 회수를 V$CURRENT_BUCKET 자료사전을 통해 분석할 수 있습니다. 이 결과를 통해 현재 데이터 버퍼 캐시영역의 크기가 적절한지를 알 수 있습니다.

 
    

DB 캐시 영역의 시뮬레이션

 
    

사용자가 실행하는 SQL문에 의해 테이블이 디스크로부터 읽혀져서 메모리에 로더된 다음 서버 프로세스에 의해 다시 읽혀져 사용자에게 리턴되는 과정에서 데이터 버퍼 캐시영역의 히트율이 얼마나 되는지를 분석해 봅시다.

사용자의 요청에 의해 읽혀지는 테이블들이 디스크보다는 메모리로부터 읽혀진다면 히트율은 보다 높아질 것이며, 그렇지 않다면 히트율은 낮아질 것 입니다. 결론적으로 히트율이 높아진다면 보다 성능은 향상될 수 있을 것 입니다.

 
    
  

SQL>

SELECT 1 - (phy.value - lob.value - dir.value) / ses.value "Hit Ratio"

 

FROM

v$sysstat ses, v$sysstat lob,

  

v$sysstat dir, v$sysstat phy

 

WHERE

ses.name = 'session logical reads'

  

AND dir.name = 'physical reads direct'

  

AND lob.name = 'physical reads direct (lob)'

  

AND phy.name = 'physical reads';

   

Hit Ratio

 

---------

 

.849176501

 

<- 히트율이 90% 이상일 때 좋은 성능이 기대될 수 있습니다.

 
    
  

[physical reads]는 디스크로부터 읽은 블록 수이며

[physical reads direct]는 메모리로부터 읽은 블록 수이고,

[physical reads direct (lob)]는 Large Object Binary를 읽은 블록 수이고,

[session logical reads]는 해당 세션에서 논리적으로 읽은 블록 수입니다.

 

Posted by redkite
, |

1-1 단계 : 공유풀 영역

 

   
 

공유 풀 영역을 보다 크게 할당한다

   
 

공유 풀 영역을 보다 크게 할당한다.

   
 

INIT<SID>.ORA 파일의 SHARED_POOL_SIZE 파라메터에 의해 공유 풀 영역의 크기는 결정됩니다. 하지만 너무 많은 사용자들이 너무 많은 SQL문을 실행하면 구문 분석된 정보가 모두 라이브러리 캐시영역에 저장됩니다. 새로운 사용자가 데이터베이스에 접속하고 새로운 SQL문을 실행했다면 그 문장은 서버 프로세스에 의해 구문 분석되어 라이브러리 캐시영역에 저장될 것입니다.

그런데, 불행하게도 라이브러리 캐시영역은 이미 빈 공간이 모두 사용되어 풀(FULL)된 상태입니다. 이런 경우가 발생하면 오라클 서버 프로세스는 로더를 하지 않고 잠시 대기(WAITING)한 후 로더되어 있는 SQL문 중에 가장 오래된 SQL문을 찾아내어 그 정보를 라이브러리 캐시영역으로부터 제거하게 됩니다. 이런 과정을 통해 확보된 빈 공간에 사용자의 새로운 SQL문 정보를 저장하게 됩니다. 이러한 방법을 AGING 메커니즘이라고 합니다.

하지만, 이러한 공간관리로 인해 생기는 문제점은 라이브러리 캐시영역에서 빈 공간을 확보하기 위해 가장 오래된 SQL문을 제거한 후 동일한 문장이 다른 사용자에 의해 재 실행될 때 구문분석을 다시 한다는 점입니다. 즉, 다시 재 실행될 SQL문의 구문분석 정보가 불필요하게 메모리로부터 제거되기 때문에 재 파싱이 발생하여 SQL문의 성능저하 현상이 초래될 수 있다는 점입니다. 이러한 문제점은 결국 라이브러리 캐시영역의 크기가 너무 작아서 발생한 문제이므로 공유 풀 영역의 크기를 더 크게 할당해 주어야 만 성능을 향상시킬 수 있습니다.

   

라이브러리 캐시영역의 튜닝분석

지금까지, 라이브러리 캐시영역에 대한 튜닝개념에 대해서 알아보았습니다. 그렇다면, 데이터베이스의 라이브러리 캐시 영역의 크기가 너무 작아 성능이 저하되고 있는지 또는 너무 크게 설정되어 있어 메모리가 낭비되고 있는지를 데이터베이스 관리자가 분석할 수 있어야 만 튜닝 계획을 수립할 수 있을 것입니다. 다음은 자료사전을 통해 라이브러리 캐시영역을 분석하는 방법입니다.

 
  
  
  
  

먼저, V$LIBRARYCACHE 자료사전을 통해 히트율(GETHITRATIO)을 분석할 수 있습니다. 히트율이란 V$LIBRARYCACHE 테이블의 GET 컬럼과 GETHITS 컬럼을 백분율로 계산한 값으로 GET은 사용자가 실행한 SQL문이 구문 분석되어 라이브러리 캐시영역에 로더되려고 했던 수이며 GETHITS는 그 중에 로더 되었던 수를 의미합니다. 즉, 라이브러리 캐시영역이 충분한 공간을 할당하고 있어서 사용자의 모든 구문분석 정보를 로더하기에 충분한가를 백분율로 계산해 본 것입니다. 히트율이 90% 이상일 때 좋은 성능을 기대할 수 있습니다.

다음은 히트율을 확인하는 방법입니다.

  
   
 

SQL>

SELECT namespace, gets, gethits, gethitratio

 

FROM

v$libraycache

  
 

WHERE

namespace = 'SQL AREA';

  

NAMESPACE

GETS

GETHITS

GETHITRATIO

SQL AREA

8950

8411

.936779536

  
 
 

GETHITRATIO 컬럼의 값이 90% 이상이면 라이브러리 캐시영역이 개발자들의 SQL 파싱정보를 저장하기에 충분한 메모리 공간을 확보하고 있음을 의미하며, 만약 90% 이하라면 성능이 저하될 수 도 있다는 것을 의미합니다. 물론, 90% 이하라도 사용자들이 성능에 만족한다면 튜닝대상이 안될 수도 있으며 반드시 튜닝을 해야 할 필요가 없다는 것 입니다.

 
  
  
 

<잠깐>

 
  
 

이러한 히트율의 분석은 기업에서 가장 바쁘게 일을 진행하고 있는 시간대에 분석결과를 기준으로 설명되는 것입니다. 아무도 일을 하고 있지 않는 야간에 결과를 분석한다면 아무런 의미가 없겠죠 ??

 
   
 

조치사항

 
   
 

히트율이 90% 이하이면 다음과 같이 SHARED_POOL_SIZE 파라메터의 값을 높게 설정해야 합니다.

 
  
  
  
 

$ cd $HOME/dbs
$ vi init<SID>.ora
…..
SHARED_POOL_SIZE = 32000000 ß 이전 값보다 더 큰 값으로 변경

:wq!

 
  
   

두 번째로는, V$LIBRARY 자료사전을 통해 RELOAD 비율을 분석할 수 있습니다. RELOADS 비율이란 PINS 컬럼에 대한 RLOADS 컬럼의 백분율을 RELOADS 비율이라고 합니다. 이것은 라이브러리 캐시영역의 크기가 너무 작을 때 사용자의 SQL 구문분석 정보가 로더되지 못하고 가장 오래된 SQL문 정보를 라이브러리 캐시영역에서 삭제한 후 그 문장이 다시 실행될 때 RELOADS 컬럼 값이 증가하게 됩니다. 또는 구문 분석된 SQL문에서 사용된 객체가 다른 사용자에 의해 삭제된 상태에서 다시 SQL문이 재실행되면 발생합니다. PINS는 구문 분석되어 라이브러리 캐시영역에 저장될 수 있었던 SQL 정보를 의미합니다.
PINS 컬럼에 대한 RELOADS 컬럼의 백분율이 1% 미만일 때 좋은 성능을 기대할 수 있습니다.

다음은 PINS에 대한 RELOADS의 비율을 확인하는 방법입니다.

 
  
   
 

SQL>

SELECT sum(pins), sum(reloads), sum(reloads) / sum(pins)

 

FROM

v$libraycache

 
 

WHERE

namespace = 'SQL AREA';

 
    

SUM(PINS)

SUM(RELOADS)

SUM(RELOADS)/SUM(PINS)

36299

45

0.001239704

 

PINS에 대한 RELOADS의 비율이 1% 미만일 때 라이브러리 캐시영역의 크기가 SQL 파싱 정보를 저장하기에 충분하다는 것을 의미합니다. 1% 이상이라면 성능이 저하될 수 도 있다는 것을 의미합니다.

 
   

마지막으로, V$LIBRARYCACHE 자료사전을 통해 SQL문에서 사용된 객체가 다른 사용자들에 의해 얼마나 자주 삭제되거나 변경되었는지를 분석하는 방법입니다. 주로, ANALYZE 명령어, ALTER, DROP 명령어에 의해 테이블 구조가 변경되는 경우에 발생합니다

 
  
   
 

SQL>

SELECT namespace, invalidations

 

FROM

v$libraycache

 

WHERE

namespace = 'SQL AREA';

  

NAMESPACE

INVALIDATION

SQL AREA

5

  
 

INVALIDATION 컬럼의 값이 높게 출력되거나 계속적으로 증가 값을 보인다면 공유 풀 영역이 작아서 성능이 저하되고 있음을 의미합니다. 즉, 불필요한 재파싱, 재로딩 작업이 발생할 가능성이 높아지는 것 입니다. 이러한 결과에 의해 튜닝이 필요하다면 다음과 같은 파라메터 값을 높게 설정하셔야 합니다.

 
   
   
 
 

SHARED_POOL_SIZE = [크기]

문법

SHARED_POOL_RESERVED_SIZE = [크기]

 

SHARED_POOL_RESERVED_MIN_ALLOC = [크기]

 
   
 

[SHARED_POOL_SIZE] 파라메트는 공유 풀 영역의 전체크기를 결정하는 파라메터입니다. 만약, V$LIBRARY 자료사전을 통해 분석된 결과값이 튜닝 기준값에 적합하지 않다면 라이브러리 캐시영역의 크기를 크게 할당해 주어야 하는데 오라클 데이터베이스에서는 이 영역만 크게 할당해 주는 파라메터는 없으며 공유 풀 영역의 크기를 크게 할당해주면 오라클 서버가 적절한 크기로 라이브러리 영역의 크기를 할당해 줍니다.

[SHARED_POOL_RESERVED_SIZE] 파라메트는 PL/SQL 블록으로 실행된 SQL문의 구문분석 정보를 저장할 때 사용되는 공유 풀 영역입니다. 일반적인 SQL문과는 달리 PL/SQL 블록들은 문장의 크기가 매우 크기 때문에 구문 분석된 결과 또한 매우 크기 마련입니다. 이러한 결과를 라이브러리 캐시영역에 저장하다 보면 연속된 메모리 공간이 없는 경우(단편화-FRAGMENTATION 현상) 분석결과가 여러 개의 조각으로 쪼개어져 저장됨으로서 단편화 현상을 더 유발시킬 수 있습니다.

결국, 이러한 단편화 현상은 데이터 검색 시 성능을 저하시키기 때문에 미리 아주 큰 PL/SQL 믈록의 구문분석 정보를 저장할 수 있는 충분한 메모리 공간을 확보한다면 이런 문제를 해결할 수 있을 것입니다. 이때 사용하는 파라메터이며 기본값은 SHARED_POOL_SIZE의 10%니며 최대값은 SHARED_POOL _SIZE 값의 1/2 입니다. 만약, 사용자의 개발환경이 PL/SQL 블록으로 개발된 애플리케이션 프로그램이 많다면 이 파라메터 값을 충분하게 설정해 주는 것이 좋습니다.

이 파라메터는 V$SHARED_POOL_RESERVED 자료사전의 REQUEST_FAILURES 컬럼의 값이 0의값이 아니거나 계속 증가값을 보일 때 높은 값을 설정해 주어야 좋은 성능을 기대할 수 있습니다. 반대로, REQUEST_FAILURES 컬럼의 값이 계속 0이면 메모리의 낭비가 초래될 수 있으므로 파라메터의 값을 낮게 설정해 주어야 합니다.

[SHARED_POOL_RECERVED_MIN_ALLOC] 파라메터는 PL/SQL 블록의 크기가 이 파라메터에 지정된 크기 이상되어야 RESERVED 영역에 저장될 수 있습니다.

 
   
 

<잠깐>공유 풀 영역의 단편화(Fragmentation) 현상을 제거하는 방법

 
   
 

시스템을 사용하다 보면 메모리 영역과 디스크 영역에서 많은 읽기/쓰기 작업이 발생합니다. 제한된 공간을 계속적으로 사용하다 보면 공간에 대한 단편화(FRAGMENTATION) 현상이 발생할 수 밖에 없습니다. 문제는 단편화 현상으로 인해 효율적인 공간관리가 되지 않고 성능까지 저하되는 현상이 유발되는 것 입니다. 오라클 데이터베이스의 SGA 영역 또한 단편화 현상으로 인해 메모리 공간이 낭비되거나 SQL문의 성능저하를 유발시킬 수 도 있습니다. 문제를 해결할 수 있는 가장 효과적인 방법은 가끔 데이터베이스를 재 시작하는 방법이지만 실제 기업환경에서는 거의 불가능한 일 입니다. 다음과 같이 ALTER SYSTEM 명령어를 사용하면 데이터베이스의 재 시작없이 공유 풀 영역을 클리어(Clear) 시킬 수 있습니다.

SQL>

ALTER

SYSTEM

SET

FLUSH

SHARED_POOL;

 

 
   
 

데이터 딕셔너리 캐시 영역의 튜닝 분석

 
   
 

오라클 데이터베이스를 설치하면 기본적으로 생성되는 테이블, 뷰, 패키지, 프로시저, 함수 등을 데이터 딕션어리(DATAT DICTIONARY)라고 합니다. 그리고, 데이터베이스를 시작(STA RTUP)하거나, SQL문을 실행하면 일부 데이터 딕션어리(V$~로 시작되는 뷰) 정보가 오라클 서버에 의해 공유 풀 영역의 데이터 딕션너리 캐시영역에 로드 됩니다.

또한, 개발자 또는 데이터베이스 관리자가 튜닝 및 관리의 목적으로 자료사전을 참조하기도 합니다. 이러한 자료사전이 읽혀질 때마다 공유 풀 영역의 데이터 딕션너리 영역에 관련정보를 로더하게 됩니다. V$~로 시작되는 일부 자료사전은 데이터베이스의 마운트 단계에서 만 질의할 수 있지만 대부분의 자료사전(DBA_, USER_, X$~)들은 데이터베이스를 오픈 단계까지 시작해야 만 질의할 수 있습니다.

그럼, 데이터 딕션너리 영역에 대한 튜닝방법에 대해서 알아보겠습니다. 사용자가 참조하는 자료사전이 질의될 때마다 그 정보를 데이터 딕션너리 영역에서 참조할 수 있다면 자료사전을 보다 빠르게 조회할 수 있을 것이고 그렇지 못하다면 자료사전을 데이터 파일로부터 읽어서 데이터 딕션너리 영역으로 로더해야 할 것입니다. 결론적으로, 디스크로부터 자료사전이 읽혀지는 것보다는 데이터 딕션너리 영역으로부터 읽혀지는 것이 성능에 유리하다는 의미입니다

다음은 자료사전을 통해 라이브러리 영역을 분석하는 방법입니다. V$ROWCACHE 자료사전을 참조하면 이 영역에 대한 튜닝 결과를 확인할 수 있습니다.

 
   
   
   
 

SQL>

SELECT sum(gets), sum(getmisses), sum(gets)/sum(getmisses)

 

FROM

v$rowcache;

 
  

SUM(GETS)

SUM(GETMISSES)

SUM(GETS)/SUM(GETMISSES)

2126

902

2.3569

 

[GETS] 컬럼은 사용자가 자료사전을 질의했을 때 데이터 딕션너리 영역으로 자료를 요청했던 수를 의미합니다.
[GETMISSES] 컬럼은 자료요청을 했지만 데이터 딕션너리 영역으로부터 자료를 얻지 못했던 수를 의미합니다.

 
   
 

대부분의 자료사전을 빠르게 검색하기 위해서는 [GETS] 컬럼에 대한 [GETMISSES] 컬럼의 백분율이 2% 미만이어야 하고 아주 큰 자료사전 테이블에 대한 검색을 빠르게 하기 위해서는 15% 미만이어야 이 영역에 대한 성능을 기대할 수 있습니다.

만약, 이러한 조건을 만족하지 못한다면 공유 풀 영역의 크기를 더 크게 설정해야 합니다.

라이브러리 영역과 같이 이 영역 만 크게 할당해 주는 파라메터는 없으며 공유 풀 영역의 크기를 크게 할당해주면 오라클 서버가 적절한 크기로 데이터 딕션너리 영역의 크기를 할당해 줍니다.

 
   
 

<조치사항>

 
   
 

$ cd $HOME/dbs
$ vi init<SID>.ora
…..
SHARED_POOL_SIZE = 32000000 ß 이전 값보다 더 큰 값으로 변경

:wq!

 
   
   

 
   

2)

동일한 SQL문 작성을 위한 표준화 작업을 한다.

 
   
 

V$LIBRARY 자료사전을 통해 개발자가 실행한 SQL문이 LIBRARY CACHE 공간에서 어떻게 저장되고 관리되는지를 자세히 알아 보았습니다. 그렇다면, 개발자가 실행하는 SQL문의 성능을 극대화 시키고 메모리 공간도 효과적으로 사용할 수 있는 방법은 없을까요 ??

첫 번째 방법이 공유 풀 영역의 크기를 충분히 할당 해 주는 것이라면 두 번째 방법은 모든 SQL문이 동일하게 작성되어 실행되게 함으로서 불필요한 PARSING과 불필요한 RE-LOADING을 피하게 하는 것입니다.

자~ 그렇다면 동일한 SQL문을 실행할 수 있는 가장 최선의 방법은 무엇일까요 ?

프로젝트를 하다 보면 다양한 개성과 창조적인 감성을 가진 인간이 만들어 내는 SQL문을 동일하게 만든다는 것은 말처럼 그리 쉬운 일은 아닐 것 입니다.

가장 최선의 방법은 모든 개발자들이 정해진 규칙에 의해 SQL문을 작성할 수 있도록 표준화를 하는 것 입니다. 일반적으로 정보 시스템을 개발하기 전에 애플리케이션 프로그래밍에 대한 "코딩 표준화", 사용자와의 인터페이스를 효과적으로 하기 위한 "화면 디자인 표준화" 등이 같은 맥락에서 이해할 수 있는 내용입니다.

우리가 이러한 표준화를 하는 궁극적인 목적은 크게 2가지로 요약할 수 있습니다.

첫 번째 목적은 약속된 프로그래밍 규칙을 통해 향후 유지 보수적인 측면을 고려한 것 입니다. 만약, 아무런 규칙없이 프로그래밍을 한다면 너무나도 다양한 유형의 프로그램들이 생성되어 자신이 개발하지 않은 프로그램 소스는 제대로 이해하지 못하는 문제로 인해 유지보수에 어려움을 겪게될 것 입니다.

두 번째 문제는 성능 문제입니다. 다양한 소스코드를 만들게 되면 프로그래밍이 어려워질 뿐 아니라 불필요한 메모리와 디스크 공간의 낭비를 초래할 수 있기 때문입니다.

다음 예제는 시스템 개발초기에 프로젝트 관리자 또는 데이터베이스 분석/설계자에 의해 동일한 SQL문을 만들어 내기 위한 표준화 작업의 일부입니다. 이러한 표준화작업은 사용자의 개발환경에 맞게 적절하게 변형하셔야 하며 새로운 내용의 추가 및 삭제가 추가적으로 필요합니다. 그럼, 지금부터 저를 따라 오세요.

 
   

SQL문의 대문자 또는 소문자로 통일하십시오.

 
   
 

사용자의 애플리케이션 프로그램 속에는 수많은 SQL문이 저장되어 있습니다. 시스템 개발은 여러 명이 한 팀이 되어 진행되는데 프로그래머들은 각각 개성과 사고를 가진 인격체이기 때문에 어떤 약속을 해두지 않는다면 어떤 사람은 대문자로, 어떤 사람은 소문자로 SQL문을 작성할 것이고 결국 동일한 SQL문을 만들어 내지 못합니다.

 
   
 

SQL>

SELECT * FROM emp;

SQL>

UPDATE emp SET sal = sal 8 1.1 WHERE deptno = 10;

 
   

변수명은 SQL문, 객체명, 변수명과 구분하기 위해 소문자로 작성하십시오.

 
   
 

변수명은 SQL문, 객체명, 컬럼명과 구분하기 위해 소문자로 작성하십시오. SQL문의 빠른 이해와 컬럼 간의 구분을 쉽게 하기 위해 필요합니다.

 
   
 

SQL>

SELECT RESNO, HNAME INTO :ls_ResNo, :ls_HName

 

FROM EMPTBL

 

WHERE HNAME LIKE '홍길동';

 
   

다른 스키마의 테이블을 호출할 때는 SCHEMA명.테이블명 으로 작성하십시오.

 
   
 

다른 스키마의 테이블을 호출할 때는 "Schema명.테이블명"으로 작성하십시오. 만약, 시노늄을 작성하여 사용할 때에는 시노늄 명을 결정하는 네이밍 룰(NAMING RULE)을 잘 작성하여 이름 만 봐도 이해할 수 있도록 해 주십시오.

 
   
 

SQL>

SELECT RESNO, HNAME

 

FROM SCOTT.EMP

 

WHERE RESNO = 1234;

 
   

SQL문의 각 단어의 여백은 한 칸으로 하십시오.

 
   
 

SQL문의 각 단어와 단어의 여백은 1칸으로 작성하십시오. 콤마(comma)는 앞 문자와는 간격을 두지 말고 뒷 문자와는 간격을 1칸으로 작성하십시오. 또한, 컬럼 리스트는 되도록 모두 한 줄에 기술하십시오. (2줄 이상일 경우는 첫줄, 첫 컬럼에 맞추어 작성하십시오.)

 
   
 

SQL>

SELECT RESNO, HNAME INTO :ls_ResNo, :ls_HName

 

FROM EMPTBL

 

WHERE HNAME LIKE '홍길동';

 
   

SQL문 내의 변수명은 변수 선언 기준 안에 따르며 해당 컬럼명을 접두어와 경합하여 사용하십시오.

 
   
 

SQL문 내의 변수 명은 변수선언 기준 안에 따르며 해당 컬럼명을 접두어와 결합하여 사용하십시오. 또한, WHERE절의 비교 처리에는 변수를 사용하십시오.

 
   
 

string gi_resno

// 사원번호(G는 글로벌 변수, L은 로컬변수,

 

// i는 정수타입, s는 문자타입)

sql>

SELECT RESNO, HNAME

 

FROM EMPTBL

 

WHERE RESNO = :ii_resno;

 
   

SQL문의 SELECT, FROM, WHERE절은 각 라인의 선두에 사용하십시오.

 
   
 

SQL>

SELECT RESNO, HNAME

 

FROM EMPTBL

 

WHERE RESNO = :ii_resno;

 
   

 
   
 

자주 사용하는 SQL문을 캐싱한다.

 
   
 

라이브러리 캐시영역을 통해 성능을 향상시킬 수 있는 세 번째 방법은 자주 실행되는 SQL문의 구문 분석정보를 라이브러리 영역에 캐싱 해 두었다가 문장이 재실행될 때 마다 재사용하게 하는 방법입니다. 불필요한 구문 분석단계를 피할 수 있기 때문에 SQL문의 성능을 향상을 시킬 수 있습니다.

다음 내용은 라이브러리 캐시영역의 성능과 관련된 파라메터들 입니다.

 
   

CURSOR_SPACE_FOR_TIME = FALSE

 
   
 

만약, 2명의 사용자에 의해 동일한 SQL문이 실행된다면 공유 풀 영역에 있는 구문 분석된 SQL TEXT 정보를 공유하게 될 것입니다. 그러나, PRIVATE SQL AREA 라는 영역에는 2명의 사용자가 실행한 구문 분석된 정보가 각각 저장되게 됩니다. 이 파라메터 값을 TRUE로 설정하면 PRIVATE SQL AREA에 공유 풀 영역의 구문 분석정보를 참조하는 커서가 하나라도 있으면 AGING 알고리즘에 의해 공유 풀 영역으로부터 제거되지 않고 계속 상주할 수 있도록 해 줍니다. 이 파라메터의 기본값은 FALSE 입니다. 만약, 개발자들이 실행하는 SQL문에서 같은 문장이 반복적으로 실행되는 경우가 많다면 이 파라메터를 TRUE 설정하는 것이 성능향상에 유리합니다.

 
   

SESSION_CACHED_CURSORS = 0

 
   
 

오라클사에서 제공하는 FORMS 개발 툴 또는 C, COBOL, FORTRAN과 같은 3GL 언어로 시스템을 개발하는 경우, 하나의 조회화면에서 또 다른 조회화면으로 전환하는 프로그램을 구현하다 보면 메인 화면에서 다음 화면으로 이동할 때 메인 프로그램에서 실행되었던 SQL문의 구문 분석정보가 라이브러리 캐시영역으로부터 완전히 제거될 수가 있습니다. 서버 프로그램을 조회한 후 다시 메인 프로그램으로 돌아갔을 때 이미 실행했던 SQL문을 다시 구문 분석하게 된다면 불필요한 작업으로 인해 SQL문의 성능은 저하될 수 있을 것 입니다. 이 파라메터는 하나의 세션에서 캐싱할 수 있는 커서의 수를 지정할 수 있습니다. 이 파라메터의 기본값은 0 입니다

 
   

CLOSE_CACHED_OPEN_CURSORS = FALSE

 
   
 

개발자가 실행한 SQL문의 트랜잭션이 COMMIT문을 만나는 순간 관련된 구문 분석정보도 라이브러리 캐시영역으로부터 함께 제거하는 파라메터입니다. 이 파라메터의 기본값은 FALSE 입니다

 
   

OPEN_CURSORS = 300

 
   
 

하나의 세션에서 동시에 오픈할 수 있는 커서의 수를 지정할 때 사용합니다. 커서를 너무 많이 오픈하게 되면 그 만큼 많은 커서 정보가 라이브러리 캐시영역에 로더되어야 하기 때문에 성능에 도움이 되지 않습니다. 이 파라메터의 기본값은 오라클 데이터베이스 버전에 따라 틀리며 9i인 경우 300 입니다.

다음은 INIT<SID>.ORA 파일에 파라메터들을 등록하는 방법입니다.

 
   
 

$ cd $HOME/dbs

$ vi init<SID>.ora

…..

CURSOR_SPACE_FOR_TIME = TRUE

SESSION_CACHED_CURSORS = 10

CLOSE_CACHED_OPEN_CURSORS = TRUE

OPEN_CURSORS = 500

:wq!

 
   
   

 
   
 

자주 사용하는 PL/SQL/ 블록을 캐싱한다.

 
   
 

프로젝트를 수행하다 보면 아주 복잡한 스토어드 프로시저, 함수, 패키지, 트리거 등을 생성하게 됩니다. 하지만, 이런 PL/SQL 블록들은 너무 커서 실행할 때 마다 라이브러리 캐시영역에 로더 되었다가 다시 제거되는 현상이 반복적으로 발생할 수 밖에 없습니다. 이런 문제가 자주 발생하면 라이브러리 캐시영역에는 단편화 현상이 발생하게 되고 데이터베이스를 사용하는 전체 사용자들의 성능이 저하되게 됩니다.

오라클 사에서는 자주 실행되는 PL/SQL 블록, 시퀀스 등을 라이브러리 영역에 캐싱해 둘 수 있도록 DBMS_SHARED_POOL 패키지를 제공합니다. 다음 예제를 따라 해 보십시오.

먼저, 라이브러리 영역에 캐싱되어 있는 PL/SQL 블록의 정보를 참조하는 자료사전은 V$DB_OBJECT_CACHE 입니다.
v$db_object_cache 자료사전을 조회한 후, 그 중 하나를 공유 풀 영역에 상주 시켜봅시다.

 
   
 

SQL>

> select name, type, kept

 

from v$db_object_cache

 

where type in ( 'PACKAGE','PROCEDURE', 'TRIGGER', 'PACKAGEBODY');

 

NAME

TYPE

KEPT

 

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

<- 캐싱된 PL/SQL 블록 정보가 없습니다.

 
   
 

일단, 애플리케이션에서 자주 실행되는 스토어드 프로시저를 생성한 후 공유 풀 영역에 로더 시켜 보겠습니다. 라이브러리 캐시영역에 로더시킬 때는 DBMS_SHARED_KEEP 패키지를 사용하고 제거할 때는 DBMS_SHARED_UNKEEP 패키지를 사용합니다.

 
   
 

SQL>

CONNECT scott/tiger

SQL>

CREATE OR REPLACE PROCEDURE check_sawon

 

(v_emp_on)

IN

emp.empno%TYPE

 

IS

 

BEGIN

  

DELETE FROM emp WHERE empno = v_emp_no;

 

END check_sawon;

 

/

SQL>

execute DBMS_SHARED_POOL.KEEP('SCOTT.CHECK_SAWON');

 

<- CHECK_SAWON 프로시저를 공유 풀 영역에 상주합니다.

  

SQL>

select type, name

 

from v$db_object_cache

 

where type in ('PACKAGE','PROCEDURE','TRIGGER', 'PACKAGE BODY')

  

END check_sawon;

  

NAME

TYPE

KEPT

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

CHECK_SAWON

PROCEDURE

Y

 

<-KEPT = Y의 의미는 CHECK_SAWON 프로시저가 캐싱되어 있음을 의미합니다

  

SQL>

exit

 
   
 

STATS.SQL 스크립트를 실행하면 위와 같이 ANALYZE 명령어에 의해 분석된 테이블의 구조를 확인할 수 있습니다. 앞에서도 언급했듯이 튜닝을 잘 하기 위해서는 데이터베이스의 구조를 잘 알아야 합니다.

 

Posted by redkite
, |

4 단계 : 시스템의 과부하

 

 
   

로드밸런싱 (Load Balancing)

 
 

하나의 시스템을 운영하다 보면 다양한 종류의 애플리케이션들이 실행됩니다. 대용량의 데이터가 변경되는 오퍼레이션, 한달간 발생한 매출정보를 집계하는 마감작업, 소량의 데이터를 검색하는 오퍼레이션 등, 그 종류는 매우 다양할 것 입니다.

하지만, 처리하려고 하는 데이터의 양과 작업시간에 따라 각각의 오퍼레이션에 소요되는 시스템의 메모리와 CPU 사용시간 등은 제각각 틀릴 것입니다. 문제점은 이러한 오퍼레이션들이 다른 시간대에 따로 수행된다면 문제가 없겠지만, 같은 시간대에 동시에 발생한다면 시스템의 자원(메모리, CPU, 디스크 등) 문제로 인해 원할한 작업이 이루어지지 않을 것 입니다. 많은 데이터를 처리해야 하고 많은 시간이 소요되는 작업은 보다 많은 시스템 자원을 사용하려 할 것이기 때문입니다. 이런 경우, 대부분의 해결방법은 시스템에서 발생하는 작업들을 분석하여 시스템의 자원을 골고루 사용할 수 있도록 분배해 주는 방법일 것 입니다. 이러한 기능을 해주는 대표적인 소프트웨어를 미들웨어라고 말 합니다.

오라클 데이터베이스에서는 시스템이 여러 대인 경우 하나의 시스템이 아닌 여러 대의 시스템으로 작업을 분산시킬 수 있는 로드밸런싱 기능을 다음과 같이 제공해 줍니다.

    
  
   
  

$ cd $TNS_ADMIN

$ vi tnsnames.ora

 

ORA90 =

(DESCRIPTION =

(FAILOVER=on)

(Load_Balance=on)

(ADDRESS_LIST =

(ADDRESS=(PROTOCOL=tcp)(HOST=recruit4you)(PORT=1521))

(ADDRESS=(PROTOCOL=tcp)(HOST=recruit5you)(PORT=1522))

)

(CONNECT_DATA = (SID = ORA90))

)

 

:wq!

   
 

TNSNAMES,ORA 파일에 [LOAD_BALANCE = ON] 절을 정의하면, 첫 번째 리스너 프로세스에게 접속을 요청할 때 만약 BUSY 한 상태라면 접속을 거부하고 두 번째 정의된 리스너 프로세스에게 작업을 넘기게 됩니다. 즉, 정의된 리스너 프로세스 중 여유 있는 프로세스에게 작업을 넘겨서 전체적인 로드밸런싱을 유지하게 되는 것 입니다.

[FAILOVER] 절은 여러 대의 시스템을 사용하다 보면 시스템이 사용 가능한 상태가 아닌 경우도 발생합니다. 이런 경우가 발생하면 FAILOVER 기능에 의해 두 번째 정의된 리스너 프로세스에게 작업을 넘기게 됩니다. 메인 서버와 백업서버가 존재하는 네트워크 환경에서 메 인 서버에 문제가 발생하는 경우 사용자들도 모르게 자동으로 백업서버로 연결해야 하는 경우에도 적용할 수 있습니다.

[ADDRESS_LIST] 절에 정의되는 첫 번째 ADDRESS 조건은 처음에 요청할 리스너 프로세스에 대한 정보이고, 두 번째 ADDRESS 절은 로드밸런싱 또는 FAILOVER 기능에 의해 요청되는 리스
너 프로세스에 대한 정보를 정의합니다.

물론, 이 기능은 하나의 시스템에 여러 개의 데이터베이스가 설치되어 있는 환경에서도 적용할 수 있습니다.

    

  
    

자원 관리자 (Resource Manager)

  
    
 

데이터베이스 사용자는 자신이 처리하는 어떤 작업이 시스템의 자원(CPU, 메모리, 디스크 크기, 네트워크)을 얼마만큼 사용하고 있는지를 알지 못하며 또한 어떤 작업을 하기 위해 데이터베이스에 접속할 때 자신이 할당 받게 될 시스템의 자원이 얼마나 될 것인지를 아는 사용자는 거의 없을 것입니다. 데이터베이스 자원 관리자는 사용자가 처리하는 업무의 성격에 따라 CPU와 시스템의 자원을 차별적으로 사용할 수 있는 방법을 제공 해 줍니다.

예를 들어, ABC 회사에서는 고객에게 정보를 제공해주는 업무가 가장 중요한 일이라면OLTP(Online-Transaction Processing) 업무를 처리하는 사용자에게는 더 많은 시스템 자원을 할당해 주고 DSS(Discussion Support System) 업무와 유지보수(MAINTENANCE) 업무를 처리하는 사용자에게는 보다 작은 시스템 자원을 할당해야 할 것입니다. 즉, 보다 중요한 일을 처리하는 사용자에게 원할한 작업을 위해 시스템 자원을 충분하게 할당해 주고 그렇지 못한 사용자에게는 최소한의 자원을 할당해 주는 방법입니다.

    
 

구성요소

 
    
 

데이터베이스 자원 관리자는 다음과 같이 3가지 구성요소로 만들어집니다. 자원 소비자 그룹(RESOURCE CONSUMER GROUP)은 유사한 업무를 처리하는 데이터베이스 사용자(마감작업 또는 통계 분석작업 등)나 요구하는 시스템의 자원이 비슷한 업무(온라인 업무 또는 배치 업무)를 처리하는 사용자들의 그룹을 의미합니다.

자원사용 계획(RESOURCE PLANS)은 자원 소비자 그룹에게 할당해줄 시스템의 자원사용에 대한 계획명을 의미합니다.
마지막으로, 자원사용계획 다이렉티브(RESOURCE PLANS DIRECTIVES)는 자원사용 계획에 대한 구체적인 계획을 의미합니다.

    

  
    

생성 방법

  
    
 

다음 예제는 BATCH 업무 사용자를 위한 자원사용 계획을 생성하는 절차입니다.
자원사용자 그룹명은 "BATCH"이고 자원사원 계획은 "NIGHT"이며 CPU를 100% 사용할 수 있도록 하고 병렬처리를 위해 20개의 프로세스를 사용할 수 있는 자원사용 계획을 세우는 절차입니다. 자원 관리자는 다음의 5단계 과정으로 설정할 수 있습니다.

    
 

자원계획을 실행하는 사용자는 데이터베이스 관리자로 부터 자원계획을 생성할 수 있는 권한(ADMINISTER_RESOURCE_MANAGER 권한)을 부여받아야 만 사용할 수 있습니다. SCOTT 사용자에게 자원계획을 생성할 수 있는 권한을 부여하되 SCOTT 사용자가 다시 이 권한을 다른 사용자에게는 부여할 수는 없습니다.

 
    
 

먼저, 자원계획을 저장할 데이터베이스 공간을 할당해야 합니다.(이 작업은 한번만 실행하면 됩니다.) 공간할당이 끝나면 자원소비자 그룹 "BATCH"를 생성하십시오. COMMENT라는 인수를 통해 해당 자원 소비자그룹에 대한 주석을 작성하십시오.

 
    
 

다음은 자원 사용계획을 생성합니다. 새롭게 생성되는 자원계획은 ①에서 생성한 저장공간에 저장됩니다.

 
    
 

자원사용 다이렉티브를 생성 해 보십시오. CPU를 100% 사용할 수 있도록 하고 병렬처리를 위해 20개의 프로세스를 사용할 수 있는 자원사용 계획을 작성하십시오.

 
    
 

지금까지 작성한 자원계획이 제대로 작성되었는지 검증을 해 보십시오. 패키지를 실행하면 자원 소비자 그룹과 자원계획이 제대로 작성되었는지 검증해 줍니다.
또한, 작성된 자원사용계획을 저장공간에 저장하십시오.

 
    
 

자원 소비자 그룹을 사용자에게 부여합니다.
SCOTT은 자원 소비자 그룹의 사용자를 의미하며 'BATCH'는 자원 소비자 그룹을 의미합니다. 그리고, FALSE는 SCOTT 사용자가 자신이 받은 자원계획의 사용권한을 다른 사용자에게 부여하지 못함을 의미합니다.

 
    
 

자 ~ 지금까지 데이터베이스 자원사용계획과 그룹을 생성해 보았습니다. 이제부터는 자원 사용계획을 활성화시켜 특정 사용자가 처리하는 트랜잭션이 설정된 자원사용 계획에 의해 처리될 수 있도록 해 줍니다. 데이터베이스 전체 환경에서 설정할 수도 있고 특정 세션에서만 적용할 수도 있습니다. 다음은 데이터베이스 전체 환경에서 설정하는 방법입니다.
INIT<DB명>.ORA 파일에 자원사용 계획명을 정의하십시오.

 
    
 

RESOURCE_MANAGER_PLAN = NIGHT (NIGHT는 자원계획명 입니다.)

 
    
 

다음은 특정 세션에서만 적용하는 방법입니다.

 
    
 

SQL> ALTER SESSION SET RESOURCE_MANAGER_PLAN = NIGHT; (NIGHT는 자원계획명 입니다.)

 
    

  
    

기타 자원 사용 계획

  
    
 

자원 관리자(RESOURCE MANAGER) 기능은 오라클 이전 버전부터 제공되던 프로파일(시스템의 Resource를 제한하는 기능) 기능에 대한 보다 향상된 기능이며 오라클 8i 버전부터 소개되었지만 CPU 사용과 Parallel Query Process에 대한 시스템 자원만 사용할 수 있었습니다.
오라클 9i 버전에서는 이 기능 이외에 Active Session Pool 과 Undo(Rollback) Segment에 대한 자원계획을 추가하게 되었습니다. CPU와 병렬처리에 대한 자원 사용계획은 이미 소개되었으므로 오라클 9i에서 추가된 자원계획을 알아보도록 하겠습니다.

 
    
 

Active Session Pool

자원 소비자 그룹마다 동시에 접속할 수 있는 세션 수를 제한할 때 사용합니다.
기본적으로 ACTIVE_SESS_POOL_P1의 값은 1000000 이며 너무 많은 세션이 발생하면 큐(Queue)에서 접속을 위해서 대기하게 되는데 대기시간을 지정하는 파라메터는 Queueing이며
기본 값은 1000000입니다.

 
    
 

UNDO Segment

특정 사용자가 데이터베이스 내에서 사용하게 될 Undo(Rollback) Space에 대한 자원사용을 제한할 때 사용합니다. 기본 UNDO_POOL의 값은 1000000이며 Kilobytes 단위로 표시합니다.

 
    
 

AUTOMATIC CONSUMER GROUPING SWITCHING

정의된 시간이 지나면 특정 사용자의 자원소비자 그룹을 다른 소비자 그룹으로 자동으로 스위칭 해 줍니다.

 
    
 

MAXIMUM ESTIMATED EXECUTION TIME

실행될 SQL문을 실행하기 전에 실행시간을 분석한 다음 너무 오래 걸릴 것 같으면 SQL문을 실행시키지 않습니다.

 
    
 

자 ~ 다음 예제를 따라 해 보십시오

먼저, 데이터베이스 사용자는 Resource Manager를 사용할 수 있는 권한이 있어야 한다.

 
    
 

$ sqlplus "/as sysdba"

SQL> EXEC DBMS_RESOURCE_MANAGER_PRIVS.GRANT_SYSTEM_PRIVILEGE -

(grantee_name=>'scott',Admin_option=>false);

  
    
 

System Rersource에 대한 Plan을 저장할 영역을 할당하십시오.

 
    
 

SQL> EXEC DBMS_RESOURCE_MANAGER.CREATE_PENDING_AREA();

 
    
 

Consumer Group을 생성하십시오.

 
   
 

SQL> EXEC DBMS_RESOURCE_MANAGER.CREATE_CONSUMER_GROUP -

(consumer_group=>'DSS',Comment=>'Online 사용자를 위한 자원계획');

 
   
 

Consumer Group에 지정할 자원사용 계획명을 생성하십시오.

 
   
 

SQL> EXEC DBMS_RESOURCE_MANAGER.CREATE_PLAN -

(plan=>'NIGHT_PLAN',Comment=>'DSS 및 Batch 업무를 위한 자원계획');

 
   
 

생성된 자원사용 계획명에 실제 계획을 작성하십시오.

 
   
 

SQL> EXEC DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE -

(plan=>'NIGHT_PLAN',group_or_subplan=>'DSS', -

comment=>'Night Plan',cpu_p1=>70, -

Parallel_degree_limit_p1=>20, -

active_sess_pool_p1=>5,queueing_p1=>600);

 

SQL> EXEC DBMS_RESOURCE_MANAGER.CREATE_PLAN_DIRECTIVE -

( plan=>'NIGHT_PLAN',group_or_subplan=>'OTHER_GROUPS', -

comment=>'Extra Plan',cpu_p1=>30, Parallel_degree_limit_p1=>2);

 
   
 

할당된 Pending 영역에 작성된 자원사용 계획이 유효한지 검증해 보십시오.

 
   
 

SQL> EXEC DBMS_RESOURCE_MANAGER.VALIDATE_PENDING_AREA();

 
   
 

자원사용 계획을 데이터베이스에 저장하십시오

 
   
 

SQL> EXEC DBMS_RESOURCE_MANAGER.SUBMIT_PENDING_AREA();

  

Posted by redkite
, |

3 단계 : 세션수의 제한

   

접속 풀링(Connection Pooling) 기능

 
 

공유서버 프로세스 환경에서 하나의 디스패쳐 프로세스가 처리할 수 있는 세션 수에는 한계가 있습니다. 그리고, 디스패쳐 프로세스의 최대 개수도 제한되어 있습니다. 만약, 많은 사용자가 동시에 데이터베이스에 접속을 요구하고 SQL문의 실행을 요구한다면 제한된 디스패쳐 프로세스의 개수로는 모든 세션을 처리할 수 없을 것입니다.

오라클 8i 버전부터 제공되는 접속풀링 기능은 공유서버 프로세스 환경에서 만 적용할 수 있으며 하나의 디스패쳐 프로세스가 처리할 수 있는 세션 수보다 더 많은 세션을 처리할 수 있도록 이미 연결된 세션들을 풀링(POOLING)하는 기능입니다.

예를 들어, 하나의 디스패쳐 프로세스가 처리할 수 있는 가장 적절한 접속 수가 3개이고 최대 세션 수는 5개라면, 위 그림과 같이 MTS_DISPATCHERS 파라메터에 다음과 같이 환경설정을 하면 됩니다.

    
  
   
 

MTS_DISPATCHERS = "(PRO=TCP)(CON=3)(DIS=2)(POO=ON)(TIC=4)(SESS=T)"

   
 

[PRO]는 PROTOCOL을 의미하고, [CON]은 CONNECTION 수, [DIS]는 DISPATCHER 프로세스의 수,
[POO]는 POOLING 여부, [TIC]는 TIMOOUT 시간(1 TICK =10초), [SESS]는 SESSION 수를 의미하는 키워드입니다

TICK 키워드는 사용자 프로세스가 디스패쳐에게 요청하는 TIMEOUT 시간입니다. 무작정 요청을 하고 대기할 수는 없기 때문에 TIMEOUT 시간 동안 만 재 요청을 하게되고 TIMEOUT 시간이 지나면 다른 디스패쳐에게 요청하게 됩니다. 단위는 TICK이며 1 TICK은 10초를 의미합니다.

이렇게 환경설정을 한 다음 데이터베이스를 재 시작하면 접속풀링 기능이 활성화됩니다.
하나의 디스패쳐는 최대 5개의 세션을 관리하게 됩니다. 하지만, 가장 유효한 접속 수는 3개이고 2개는 유효한 3개의 세션 중에 유휴상태의 세션이 발견되면 접속상태를 보류시킨 후 새로운 세션의 작업으로 처리하게 됩니다. 결론적으로 2개는 기존의 세션상태에 따라 연결될 수도 있고 안될 수도 있게 됩니다.

접속풀링 기능은 제한된 디스패쳐 프로세스 환경에서 보다 많은 사용자들에게 데이터베이스에 접속할 수 있도록 허용하며 디스패쳐 프로세스에서 발생하는 경합문제로 인해 성능이 저하되는 문제를 해결해 주는 기능입니다.

   
 
Posted by redkite
, |

2 단계 : 프로세스의 경합

 

 
   

공유 서버 프로세스

 
 

하나의 사용자 프로세스마다 하나씩 할당되는 서버 프로세스를 전용서버 프로세스라고 한다면 여러 개의 사용자 프로세스에게 하나의 서버 프로세스가 할당되는 환경을 공유서버 프로세스라고 합니다.

일반적으로, 전용서버 프로세스는 마감작업, 통계 분석작업, 전산관리 업무 등과 같은 배치성 작업이나 데이터베이스의 유지보수 작업(STARTUP, SHUTDOWN, BACKUP & RECOVERY 작업)을 수행하는 사용자에게 적합하고 공유서버 프로세스는 간단한 입력, 수정, 삭제, 조회 등의 일상적인 작업처리 업무를 수행하는 사용자들에게 적합합니다. 만약, 마감작업을 처리하는 사용자가 공유서버 프로세스로 접속하여 SQL문을 처리한다면 작업 성능은 매우 늦어질 것입니다.

반대로, 한 건의 행을 입력하는 사용자가 전용서버로 접속하여 작업을 처리한다면 성능은 빨라지겠지만 메모리의 낭비가 될 것입니다. 오라클 데이터베이스를 설치하면 기본적인 모드는 전용서버 프로세스 환경입니다. 공유서버 프로세스를 사용하기 위해서는 별도의 환경설정이 필요하며, 환경설정 후에는 공유서버 프로세스와 전용서버 프로세스를 동시에 사용할 수 있습니다. 그럼, 공유서버 프로세스의 환경 설정하는 방법과 처리과정에 대해서 자세히 알아봅시다.

먼저, 공유서버 프로세스를 사용하기 위해서 필요한 기타 프로세스와 구조체를 알아봅시다. 위 그림에서 보시는 것처럼, 공유서버 프로세스(SHARED SERVER), 리스너 프로세스(LISTEN ER1), 디스패쳐 프로세스(DISPATCHER1, DISPATCHER2, DISPATCHER3)가 필요하고 SGA 영역에는 요청 큐(REQUEST QUEUE), 응답 큐(RESPONSE QUEUE)가 필요합니다. 그럼, 각 프로세스와 구조체가 어떤 기능을 하는지 처리과정을 통해 알아보겠습니다.

    
  
   
 

사용자가 리스너 프로세스에게 데이터베이스 접속을 요청합니다.

   
 

리스너 프로세스는 현재 활동 중인 디스패쳐 프로세스를 검사합니다.

   
 

분석된 디스패쳐 프로세스의 상태정보를 저장한 다음 그 중에 가장 적게 작업을 처리하는
디스패쳐 프로세스의 주소를 사용자에게 리턴해 줍니다.

   
 

사용자는 리스너 프로세스로부터 받은 디스패쳐 프로세스의 주소를 이용하여 해당 디스패쳐 프로세스에 접속한 다음 SQL문의 실행을 요구합니다.

   
 

디스패쳐 프로세스는 사용자의 요구사항(데이터베이스 접속, SQL문의 실행)을 요청 큐에 저장합니다.

   
 

공유서버 프로세스 중에 가장 적게 작업을 처리하고 있는 프로세스가 요청 큐로부터 사용자의 요구사항을 읽어옵니다.

   
 

공유서버 프로세스는 작업을 처리한 후 디스패쳐 프로세스가 활성화될 때 각각 만들진 해당 응답 큐에 작업결과를 저장합니다.

   
 

해당 디스패쳐 프로세스는 자신의 응답 큐로부터 결과를 읽어와서 ⑩ 사용자에게 리턴해 주면 모든 작업은 완료됩니다.

   
 

지금까지, 공유서버 프로세스를 통해 사용자의 작업이 어떻게 처리되는지 알아보았습니다.
이제부터, 각 프로세스를 활성화하기 위한 환경설정 방법을 알아보도록 하겠습니다.

이 프로세스들을 활성화하기 위해서는 INIT<DB명>.ORA 파일에 다음과 같은 파라메터를 추가하셔야 합니다.

    
 

$ cd $HOME/dbs

$ vi init.ora

 

DISPATCHERS = "(PROTOCOL = TCP)(DISPATCHER = 3)"

MAX_DISPATCHERS = 5

SHARED_SERVERS = 3

MAX_SHARED_SERVERS = 5

SHARED_SERVER_SESSIONS = 100

 

:wq!

  
    
 

[DISPATCHERS]에는 데이터베이스를 시작(STARTUP)했을 때 최초 할당될 디스패쳐 프로세스의 수와 프로토콜을 정의하십시오.(기본값은 5개입니다)

[MAX_DISPATCHER]는 [MTS_DISPATCHERS] 파라메터에 의해 사용 중인 디스패쳐 프로세스들이 현재 접속한 모든 사용자들의 작업을 원활하게 처리해 줄 수 없을 때 지정한 개수만큼 추가적으로 디스패쳐 프로세스를 활성화 해줍니다.

[SHARED_SERVERS]는 데이터베이스를 시작(STARTUP)했을 때 최초 할당될 공유서버 프로세스의 수를 정의하십시오.

[MAX_SHARED_SERVERS]는 [MTS_SERVERS] 파라메터에 의해 사용 중인 공유서버 프로세스들이 현재 접속한 모든 사용자들의 작업을 원활하게 처리해 줄 수 없을 때 지정한 개수만큼 추가적으로 공유서버 프로세스를 활성화 해줍니다.(기본값은 20입니다.)

[SHARED_SERVER_SESSIONS]는 할당된 전체 공유서버가 동시에 오픈할 수 있는 세션 수를 의미합니다.

공유서버 프로세스와 디스패쳐 프로세스에 대한 환경설정이 완료되었으면 데이터베이스를 재 시작해야 합니다. 그리고, 각 프로세스와 큐 구조들이 생성되었는지 확인해 보십시오.
먼저, V$SHARED_SERVERS 자료사전을 참조해 보면 MTS_SERVERS 파라메터에 의해 활성화 되어 있는 공유서버 프로세스를 확인할 수 있을 것입니다. 공유서버 프로세스의 이름은 S000부터 Snnn 형태로 생성됩니다.

  
    
    
  

SQL>

CONNECT system/manager

SQL>

SELECT name, status FROM v$shared_server;

  

NAME

STATUS

 

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

 

SOOO

WAIT(COMMON)

<- 첫번째 공유 서버 프로세스

SOO1

WAIT(COMMON)

<- 두번째 공유 서버 프로세스

S002

WAIT(COMMON)

<- 세번째 공유 서버 프로세스

  
    
  

V$DISPATCHER 자료사전을 참조해 보면 MTS_DISPATCHERS 파라메터에 의해 활성화 되어있는 디스패쳐 프로세스들을 확인할 수 있을 것입니다. 디스패쳐 프로세스의 이름은 D000부터 Dnnn 형태로 생성됩니다.

  
     
     
  

SQL>

SELECT name, status FROM v$dispatcher;

  

NAME

STATUS

 

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

 

SOOO

WAIT(COMMON)

<- 첫번째 디스패쳐 프로세스

SOO1

WAIT(COMMON)

<- 두번째 디스패쳐 프로세스

S002

WAIT(COMMON)

<- 세번째 디스패쳐 프로세스

  
     
  

마지막으로, V$QUEUE 자료사전을 참조해 보면 요청 큐와 각 디스패쳐 프로세스의 응답 큐를 확인할 수 있습니다. PADDR 컬럼의 값이 0은 REQUEST 큐에 대한 정보이고 TYPE 컬럼의 값이 DISPATCHER는 RESPONSE 큐에 대한 정보입니다. 공유서버 환경에서 REQUEST 큐의 개수는 한 개이고 RESPONSE 큐의 개수는 디스패쳐 프로세스 부 만큼 생성됩니다.

  
     
     
  

SQL>

SELECT * FROM v$queue;

  

PADDR

TYPE

QUEUED

 

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

00

COMMON

0

 

79D92A30

DISPATCHER

0

 

79D92A84

DISPATCHER

0

 

79D92AD8

DISPATCHER

0

 
    
  
     

  
     

공유 서버 프로세스 환경의 튜닝

  
     
 

공유서버 프로세스가 주로 사용될 수 있는 환경은 많은 사용자가 동시에 데이터베이스에 접속하며 소량의 데이터를 처리하는 OLTP(Online Transaction Process) 업무에 가장 적합합니다. 공유서버 프로세스 환경에는 디스패쳐 프로세스, 공유서버 프로세스, RESPONSE 큐와 같은 프로세스들이 필요한데 시스템에서 시용할 수 있는 개수는 항상 제한적입니다.

만약, 동시에 많은 사용자들이 데이터베이스에 접속하게 되면 제한된 프로세스를 서로 나누어서 사용해야 하기 때문에 하나의 프로세스에 대해 경합현상이 발생하게 됩니다.
경합현상이 발생하게 되면 대기상태가 일어나게 되고 결국 성능이 저하되는 문제가 발생하게 되는 것 입니다. 공유서버 환경에서 경합이 발생할 수 있는 부분은 3가지 포인트가 있습니다.

 
     
 

첫 번째 부분은, 사용자가 접속을 요구했을 때 디스패쳐 프로세스가 부족하여 발생하는 경합문제 입니다. 하나의 디스패쳐가 처리할 수 있는 작업의 범위는 제한되어 있습니다. 그리고, 디스패쳐 프로세스의 최대 개수도 제한되어 있습니다. 동시에 많은 사용자가 데이터베이스에 접속을 요구하고 SQL문의 실행을 요구한다면 서로 디스패쳐 프로세스를 사용하기 위해 경합을 벌이게 될 것 입니다. 즉, 성능이 저하될 수 있다는 의미입니다.
이런 경우에는 디스패쳐 프로세스에 경합이 발생하는지를 분석한 다음 보다 많은 디스패쳐 프로세스 수를 늘려주어야 경합을 최소화할 수 있고 궁극적으로 성능을 향상 시킬 수 있습니다. 다음은 디스패쳐 프로세스에 경합이 발생하는지를 분석하는 방법입니다.

  
     
     
  

SQL>

SELECT network, sum(busy) / (sum(busy + sum(idle)) "Total Busy Rate"

 

FROM v$dispatcher

 

GROUP BY network;

    

NETWORK

RATE

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

(ADDRESS=(PROTOCOL=tcp)(HOST=192.9.200.1)(PORT=1521))

4.2345E-07

(ADDRESS=(PROTOCOL=tcp)(HOST=192.9.200.1)(PORT=1521))

0.34651

(ADDRESS=(PROTOCOL=tcp)(HOST=192.9.200.1)(PORT=1521))

0.58722

  
     
  

<- 만약, , sum(busy) / (sum(busy + sum(idle) 의 비율이 0.5 를 초과한다면 디스패쳐 프로세스에 경합이 발생한 것이며 성능이 저하될 수 있음을 의미합니다.

  
     
  

이런 경우에는 다음과 같이 디스패쳐 프로세스의 수를 보다 많이 할당해 주어야 합니다.

  
     
  

SQL> ALTER SYSTEM SET mts_dispatchers = 'tcp, 10';

<- 디스패쳐 프로세스를 기본적으로 10개를 활성화 합니다.

 

SQL> ALTER SYSTEM SET mts_max_dispatchers = 'tcp, 20';

<- 디스패쳐 프로세스를 최대 20개까지 활성화 합니다.

  
     
 

두 번째 부분은 사용자의 요구를 받은 디스패쳐 프로세스가 REQUEST 큐에 요청된 내용을 저장한 후 공유서버 프로세스에 의해 처리되면 RESPONSE 큐에서 결과를 가져오게 됩니다.

그런데, 디스패쳐 프로세스가 너무 많은 일들을 처리하다 보면 이미 처리가 되어 RESPONSE 큐에 저장되어 있는 결과를 읽어서 사용자 프로세스에게 돌려주지 못하여 RESPONSE 큐에 결과가 쌓이는 현상이 발생하게 됩니다. 즉, 디스패쳐 프로세스 수가 부족하여 대기상태가 발생하는 경우입니다,

이런 경우에는 디스패쳐 프로세스와 RESPONSE 큐 간의 대기상태를 분석한 다음 보다 많은 디스패쳐 프로세스 수를 늘려주어야 성능을 향상 시킬 수 있습니다. 다음은 디스패쳐 프로세스와 RESPONSE 큐 간의 대기상태를 분석하는 방법입니다.

  
     
  

SQL> SELECT sum(wait) / sum(totalq)

FROM v$queue q, v$dispatcher d

WHERE q.paddr = d.paddr AND lower(q.type) = 'dispatcher';

 

SUN(WAIT)/SUM(TOTALQ)

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

0

  
     
  

<- 만약, sum(wait) / sum(totalq) 결과가 0 값이면 RESPONSE 큐에 대기중인 처리결과가 없다는 의미이며 이 결과값이 계속적으로 증가한다면 처리결과가 계속 쌓이고 있다는 의미입니다. 즉, 디스패쳐 프로세스의 수가 부족하여 성능이 저하될 수 있습니다.

  
     
  

이런 경우에도 첫 번째 경우의 조치방법과 같이 디스패쳐 프로세스의 수를 보다 많이 할당해 주어야 합니다.

  
     
 

세 번째 부분은 디스패쳐 프로세스들에 의해 요청된 내용이 REQUEST 큐에 저장되면 작업이 가능한 공유서버 프로세스가 요청된 내용을 처리하게 됩니다. 만약, 활성화된 공유서버 프로세스들 중에 여유가 있는 프로세스가 없다면 REQUEST 큐에는 요청된 작업 내용들이 쌓이게 될 것이고 SQL문의 성능은 저하될 것입니다.

이런 경우에는 공유서버 프로세스의 경합상태를 분석한 다음 보다 많은 공유서버 프로세스 수를 늘려주어야 성능을 향상 시킬 수 있습니다. 다음은 공유서버 프로세스의 경합상태를 분석하는 방법입니다.

  
     
  

SQL> SELECT count(*)

FROM v$shared_server

WHERE status NOT LIKE '%QUIT%';

 

COUNT(*)

----------

6

  
     
  

<- 만약, count(*)의 결과가 mts_servers 파라메터에 설정된 값보다 커지 않다면 공유서버 프로세스에는 경합이 발생하고 있지 않은 것을 의미하며, mts_max_servers 파라메터에 설정된 값과 같은 결과가 나타나면 경합이 발생하고 있는 것 입니다. 즉, 공유서버 프로세스의 수가 부족하여 성능이 저하될 수 있습니다.

  
     
  

이런 경우에는 다음과 같이 공유서버 프로세스의 수를 보다 많이 할당해 주어야 합니다.

  
     
  

SQL> ALTER SYSTEM SET mts_servers = 10;

ß 공유서버 프로세스를 기본적으로 10개를 활성화 합니다.

 

SQL> ALTER SYSTEM SET mts_max_servers = 20;

ß 공유서버 프로세스를 최대 20개까지 활성화 합니다.

  
     
  

다음 예제는 REQUEST 큐에 얼마나 많은 요청된 내용이 쌓여 있는지를 분석하는 방법입니다.

  
     
  

SQL> SELECT wait / totalq

FROM v$queue

WHERE lower(type) = 'common';

 

WAIT/TOTALQ

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

0

  
     
  

<- 만약, wait / totalq 결과가 0 값이면 REQUEST 큐에 대기중인 요청내용이 없다는 의미이며 이 결과값이 계속적으로 증가한다면 요청내용이 계속 쌓이고 있다는 의미입니다. 즉, 공유서버 프로세스의 수가 부족하여 성능이 저하될 수 있습니다.

  

Posted by redkite
, |

1 단계 : 서버프로세스의 활성화 지연

 

 
   

Pre-Spawn 서버 프로세스

 
 

전용서버 프로세스는 개발자가 클라이언트로부터 데이터베이스에 접속하는 순간 리스너 프로세스에 의해 할당됩니다. 이렇게 개발자의 요구가 있을 때 마다 리스너 프로세스가 하나씩 활성화되게 되는데 이런 유형을 스판 전용서버 프로세스(SPAWN DEDICATED SERVER PROCESS)라고 합니다. 문제점은 대용량 데이터베이스 환경에서 많은 사용자들이 동시에 데이터베이스에 접속을 요구하게 되면 하나의 리스너 프로세스가 필요할 때 마다 서버 프로세스를 활성화시켜야 하기 때문에 접속할 때 일시적으로 대기상태가 발생할 수도 있습니다.
즉, 데이터베이스에 접속할 때 성능이 저하될 수 있다는 의미입니다.

이런 문제를 해결하기 위해서는 여러가지 네트워크 솔루션들이 사용될 수 있지만 가장 기본적인 방법은 프리스판 전용서버 프로세스(PRE-SPAWN DEDICATED SERVER PROCESS)를 활성화하는 방법입니다. 스판 전용서버 프로세스가 필요할 때 마다 프로세스를 활성화하는 방법이라면 프리스판 전용서버 프로세스는 미리 여러 개의 프로세스를 활성화시킨 다음 새로운 접속이 요구될 때마다 미리 활성화 해둔 프리스판 전용서버 프로세스를 할당해 주는 방법입니다.
즉, 데이터베이스에 접속을 요구하면 미리 준비된 프로세스를 연결 만 시켜주면 되기 때문에 데이터베이스 접속이 빨라집니다.( 이 기능은 오라클 8i 버전까지 사용가능 합니다.)

위 그림의 오퍼레이션 절차를 보다 자세히 알아 봅시다.

    
  
   
 

LISTENER.ORA 파일에 프리스판 전용서버 프로세스를 활성화하기 위한 환경설정을 한 다음 리스너 프로세스를 시작합니다. 만약, 3개의 프리스판 프로세스를 활성화 시켰다면
운영체계 상에 프로세스들을 확인할 수 있을 것 입니다

다음 예제는 LISTENER.ORA 파일에 프리스판 전용서버 프로세스를 위한 환경설정 방법입니다.(DB명은 ORA90이라고 가정합니다)

   
   
  

$ cd $TNS_ADMIN

$ vi listener.ora

 

LISTENER=

(ADDRESS_LIST =

(ADDRESS = (PROTOCOL = TCP)(Host = 192.9.200.1)(Port = 1521))

)

SID_LIST_LISTENER =

(SID_LIST =

(SID_DESC =

(ORACLE_HOME=/export/home/dbaxx)

(SID_NAME = ORA90)

######### 프리스판 전용서버 프로세스 환경설정 ###########

(PRESPAWN_MAX=99) ß 프리스판 전용서버 프로세스의 최대개수

(PRESPAWN_LIST=

(PRESPAWN_DESC=

(PROTOCOL=TCP) ß 사용될 프로토콜

(POOL_SIZE=3) ß 항상 활성화될 프리스판 전용서버 프로세스의 수

)

)

########################################################

)

)

 

:wq!

  

리스너를 다시 재 시작하여 프리스판 프로세스를 활성화 하십시오.

$ cd $TNS_ADMIN

$ lsnrct| stop LISTENER

 

$ lsnrct| start LISTENER

   
  

LSNRCTL for Solaris : Version 9.0.1.0.0 - Production on 27-JUL-99 17:37:53

(c) Copyright 1997 Oracle Corporation. All rights reserved.

(ADDRESS=(PROTOCOL=tcp)(DEV=12)(HOST=XXX.XXX.XXX)(PORT=80xx))

Connected to (ADDRESS=(PROTOCOL=TCP)(Host=192.9.200.1)(Port 1521))

STATUS of the LISTENER

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

Alias LISTENER

……………………

Services Summary…

LISTENER has 1 service handler(s)

The command completed successfully

   
   
  

자~ 정말 프리스판 전용서버 프로세스가 활성화되었는지 확인해 봅시다.

   
  

$ ps -ef| grep ORA90 | sort

 

ora90 6280 1 0 18:06:12 ? 0:00 oracleLISTENER

(DESCRIPTION=(COMMAND=prespawn)(PROTOCOL=TCP)(SERVICE_ID=2)(HANDLER_I

 

ora90 6282 1 0 18:06:12 ? 0:00 oracleLISTENER

(DESCRIPTION=(COMMAND=prespawn)(PROTOCOL=TCP)(SERVICE_ID=3)(HANDLER_I

 

ora90 6284 1 0 18:06:12 ? 0:00 oracleLISTENER

(DESCRIPTION=(COMMAND=prespawn)(PROTOCOL=TCP)(SERVICE_ID=4)(HANDLER_I

   
  

<- 각 프로세스 상태를 확인해 보면 "COMMAND=prespawn" 이라는 상태값을 확인할 수 있을 것 입니다.

   
 

프리스판 전용서버 프로세스가 활성화 되었다면 이제 데이터베이스에 접속해 보도록 하겠습니다.

   
  

$ sqlplus scott/tiger@ora816

   
   
  

SQL*PLUS 툴을 이용해 데이터베이스에 접속을 요구하면, 먼저 사용자 프로세스가 활성화됩니다. 그리고, 사용자 프로세스는 호스트 스트링이 의미하는 네트워크 환경정보를 통해 데이터베이스가 있는 서버의 리스너에게 접속을 요구하게 됩니다.

   
 

리스너 프로세스는 현재 활성화되어 있는 프리스판 전용서버 프로세스 중에서 하나의 프로세스 주소를 읽어 옵니다.

   
 

프리스판 전용서버 프로세스의 주소를 사용자 프로세스에게 알려 줍니다.

   
 

접속할 프로세스의 주소를 넘겨받은 사용자 프로세스는 할당 받은 전용서버 프로세스에게 개발자가 실행한 SQL문의 실행을 요청합니다.

   
 

프리스판 전용서버 프로세스는 사용자 프로세스로부터 요청 받은 SQL문을 분석한 다음 실행합니다.

   
 

리스너 프로세스는 방금 하나의 프리스판 전용서버 프로세스가 사용되었으므로 필요한 개수만큼 활성화시켜 놓습니다.

Posted by redkite
, |
  1. PRECISE 모니터링 결과 High Redo Log Buffer Wait 발생함.
  2. 통계 정보를 확인한 결과 Log File Sync Wait 비중이 높음
  3. 오렌지를 통해 Redo Log Time Interval 확인한 결과
    인터벌 간격이 분당 평균 5회 정도로 확인됨.(10회 미만일 경우 파일크기 조정)
  4. Redo Log file 확인
    select a.group#, a.member, b.bytes/1024/1024 mb, b.sequence#, b.archived, b.status
    from v$logfile a, v$log b
    where a.group# = b.group#
    order by 1,2;

select a.group#, a.member, b.bytes/1024/1024 mb, b.sequence#, b.archived, b.status

from v$logfile a, v$log b

where a.group# = b.group#

order by 1,2;


SQL> /


GROUP# MEMBERMB SEQUENCE# ARC STATUS

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

1 /oracle/product/10.2.0/db_1/oradata/EMSDB/redo01.log 100 184106 NO INACTIVE

2 /oracle/product/10.2.0/db_1/oradata/EMSDB/redo02.log 100 184107 NO ACTIVE

3 /oracle/product/10.2.0/db_1/oradata/EMSDB/redo03.log 100 184108 NO CURRENT

4 /oracle/product/10.2.0/db_1/oradata/EMSDB/redo04.log 100 184104 NO INACTIVE

5 /oracle/product/10.2.0/db_1/oradata/EMSDB/redo05.log 100 184105 NO INACTIVE

-- 반드시 INACTIVE 로그 삭제

SQL> alter database drop logfile group 1;

SQL> alter database drop logfile group 2;

SQL> alter database drop logfile group 3;

SQL> alter database drop logfile group 4;

SQL> alter database drop logfile group 5;


-- 파일 및 그룹 추가

SQL> ALTER DATABASE ADD LOGFILE GROUP 1 ('/oracle/product/10.2.0/db_1/oradata/EMSDB/redo01.log') SIZE 200M;

SQL> ALTER DATABASE ADD LOGFILE GROUP 2 ('/oracle/product/10.2.0/db_1/oradata/EMSDB/redo02.log') SIZE 200M;

SQL> ALTER DATABASE ADD LOGFILE GROUP 3 ('/oracle/product/10.2.0/db_1/oradata/EMSDB/redo03.log') SIZE 200M;

SQL> ALTER DATABASE ADD LOGFILE GROUP 4 ('/oracle/product/10.2.0/db_1/oradata/EMSDB/redo04.log') SIZE 200M;

SQL> ALTER DATABASE ADD LOGFILE GROUP 5 ('/oracle/product/10.2.0/db_1/oradata/EMSDB/redo05.log') SIZE 200M;



-- 로그 파일 스위치

SQL> alter system switch logfile;

Description - 최근 3일간에 대하여 Log Switch가 발생한 간격을 시간당 평균수치로 보여준다.

Criteria - 10.0 min Minimum

Guide - Redo Log Switch의 Time Interval이 10분이하로 계속된다면, redo log file의 size를 증가시켜서
redo log switch가 너무 자주 발생하지 않도록 하여 checkpoint에 의한 I/O를 예방할 수 있다,
또한 생략할 수 있는 데이터베이스내의 Trasaction을 없애는 방식으로 작업을 하여 성능의
개선효과를 볼수 있다
예: CREATE TABLE AS SELECT … NOLOGGING, CREATE INDEX ..... NOLOGGING등.
#

-- 리두로그 파일 변경 주기 확인
select /*+ use_hash(a b) */ a.thread#, to_char(a.first_time,'YYYY/MM/DD HH24') "Time",
round(avg(trunc((a.first_time - b.first_time)*24*60,3)),2) "Interval(Min)"
from
(select thread#, sequence#, first_time from v$loghist where first_time > sysdate -3) a,
(select thread#, sequence#, first_time from v$loghist where first_time > sysdate -3) b
where a.thread# = b.thread#
and a.sequence# = b.sequence# + 1
group by a.thread#, to_char(a.first_time,'YYYY/MM/DD HH24')

order by 2,3;

REDO LOG FILE SIZE의 변경 또는 추가, 삭제
======================================

Oracle에서 사용 중인 REDO Log file은 Default로 Size가 512K인 3개의 REDO Log file이 생깁니다.
DB를 이용하는 Application의 특성에 따라 REDO Log file의 수를 늘리거나 REDO Log file의 Size를 크게 할 필요가 있는데,
REDO Log file의 Size를 변경하거나 수를 늘리는 방법을 다음과 같이 소개합니다.
(참고로 리두로그 사이즈는 Resize 안됩니다... drop 하고 원하는 size 를 주고 add 하셔야 합니다.)


1. REDO Log file의 Size 변경
****************************


1) V$Log와 V$Logfile을 이용해 어느 것이 Current Log인지 또한 각 Log file의 Data file 명인지를 확인합니다.

SVRMGR> select * from v$log;

GROUP# THREAD# SEQUENCE# BYTES MEMBERS ARC STATUS FIRST_CHAN
------ ------- --------- ------ -------- --- -------- ----------
FIRST_TIME
------------------
1 1 55 512000 1 NO CURRENT 65081
2/06/95 19:32:57
2 1 53 512000 1 NO INACTIVE 64111
1/21/95 11:06:14
3 1 54 512000 1 NO INACTIVE 64801
1/21/95 11:41:44
3 rows selected.


SVRMGR> select * from v$logfile order by GROUP#, MEMBER ;

GROUP# STATUS MEMBER
-------- ------- ------------------------------------------------
1 ONLINE /oracle/app/oracle/redo/redo01b.log NO
1 ONLINE /oradata/CHUN/redo01a.log NO
2 ONLINE /oracle/app/oracle/redo/redo02b.log NO
2 ONLINE /oradata/CHUN/redo02a.log NO
3 ONLINE /oracle/app/oracle/redo/redo03b.log NO
3 ONLINE /oradata/CHUN/redo03a.log NO

6 rows selected.


2) V$Logfile에서 확인된 Inactive Log file을 먼저 아래의 Command를 이용해 Log file을 Drop합니다. (반드시 Inactive Log file 이어야 합니다.)

정상적으로 Drop되면 Host Command (OS 명령어)를 이용해 Datafile을 삭제 합니다.
(그룹으로 drop 할때)
ALTER DATABASE DROP LOGFILE GROUP 3 ;
또는
(logfile 을 찾아서 drop 할때)

SQLDBA> alter database drop logfile ('/oradata/CHUN/redo03.log') ;
Statement processed.


Logfile 삭제

SQLDBA> !rm /oradata/CHUN/redo03.log


참고로 Oracle 에서는 Default로 3개의 REDO Log file이 생기는데 최소한 2개 이상의 REDO Log file이 필요합니다.

(Drop하면 Ora-1567 & Ora-1532 Error가 발생합니다.)



3) 2)에서 Drop한 GROUP 1의 Logfile을 Add합니다. (참고로 /oradata 에 /oracle 에 두개의 Logfile 을 생성시켰습니다. - 같은 그룹에 member 추가)

SVRMGR> ALTER DATABASE ADD LOGFILE GROUP 3 ('/oradata/CHUN/redo03a.log', '/oracle/app/oracle/redo/redo03b.log') size 50m ;
Statement processed.



4) V$Log에서 Current Logfile은 바로 Drop할 수 없다. 이를 Drop하기 위해서는 현재의 Current Logfile을 Inactive로 하여야 합니다.

Inactive로 하려면 'Alter System Switch Logfile' Command를 이용하여 Logfile 을 변경합니다.

SVRMGR> alter system switch logfile ;
Statement processed.

(확인)

select * from v$log ;

1 1 104 52428800 2 NO CURRENT 1561464201 2007/01/22 10:13:04
2 1 105 52428800 2 YES INACTIVE 1561464775 2007/01/22 10:22:40
3 1 103 52428800 2 YES INACTIVE 1561463530 2007/01/22 10:02:36



5) 작업하려는 LOGFILE GROUP의 Status가 INAVTIVE 되었으면 위에서 했던것 처럼 Logfile을 Drop하고 Datafile의 Remove후 Logfile의 Size를 (변경하여) Add한다.

(그룹으로 drop 할때)

ALTER DATABASE DROP LOGFILE GROUP 2 ;

또는

(logfile 을 찾아서 drop 할때)

SVRMGR> alter database drop logfile ('/oradata/CHUN/redo02.log') ;
Statement processed.

Logfile 삭제

SVRMGR> !rm /oradata/CHUN/redo03.log


Logfile 추가 (group 2)

SVRMGR> ALTER DATABASE ADD LOGFILE GROUP 2 ('/oradata/CHUN/redo02a.log', '/oracle/app/oracle/redo/redo02b.log') size 50m ;
Statement processed.




2. redo log file 추가
*********************

설치시 기본으로 생기는 3개의 Log file 외 Log file 을 추가할 경우에는 아래와 같이 Add 하시면 됩니다. (같은 그룹의 member 를 두개 둘때....)
SVRMGR> ALTER DATABASE ADD LOGFILE GROUP 4 ('/oradata/CHUN/redo04a.log', '/oracle/app/oracle/redo/redo04b.log') size 50m ;
Statement processed.


예) 50M 와 52428800 (Byte) 는 같은 사이즈를 갖는다.

alter database add logfile '/oradata/TEST/redo04.log' size 52428800 ;
alter database add logfile '/oradata/TEST/redo05.log' size 50M ;
alter database add logfile
group 6 ('/oradata/TEST/redo06.log') size 50M ;


[dell:/oradata/TEST] # ls -l
총 2279060
drwxr-x--- 2 oracle dba 512 10월 2일 11:08 ./
drwxr-xr-x 9 oracle dba 512 10월 2일 09:37 ../
-rw-r----- 1 oracle dba 7061504 10월 2일 11:09 control01.ctl
-rw-r----- 1 oracle dba 7061504 10월 2일 11:09 control02.ctl
-rw-r----- 1 oracle dba 7061504 10월 2일 11:09 control03.ctl
-rw-r----- 1 oracle dba 52429312 10월 1일 18:12 redo01.log
-rw-r----- 1 oracle dba 52429312 10월 2일 09:16 redo02.log
-rw-r----- 1 oracle dba 52429312 10월 2일 11:09 redo03.log
-rw-r----- 1 oracle dba 52429312 10월 2일 11:07 redo04.log
-rw-r----- 1 oracle dba 52429312 10월 2일 11:08 redo05.log
-rw-r----- 1 oracle dba 293609472 10월 2일 11:07 sysaux01.dbf
-rw-r----- 1 oracle dba 513810432 10월 2일 11:07 system01.dbf
-rw-r----- 1 oracle dba 20979712 9월 30일 06:00 temp01.dbf
-rw-r----- 1 oracle dba 68165632 10월 2일 11:08 undotbs01.dbf
-rw-r----- 1 oracle dba 5251072 10월 2일 09:21 users01.dbf


[dell:/oradata/TEST] # du -sk *log
51248 redo01.log
51248 redo02.log
51248 redo03.log
51248 redo04.log
51248 redo05.log


3. redo log file 삭제
*********************

ALTER DATABASE DROP LOGFILE GROUP 6 ;

 

 

Posted by redkite
, |

<원인 1>
Row-Chaining 및 Row-Migration 현상이 발생하면 불필요한 블록에 대한 읽기 작업이 발생하기 때문에 성능저하 현상이 발생합니다. (과다한 VARCHAR2 타입의 사용문제)

조치 1

ANALYZE 작업을 수행한 후 전체공간의 30%이상에서 발생하면 테이블을 재구성한다. SQL> EXECUTE dbms_redefinition.start_redef_table( ~~~~~ )

조치 2

EXPORT로 해당 테이블을 백업한 후 삭제하고 다시 IMPORT 한다. (Export시 extents compress 옵션을 반드시 yes로 설정해야함) EXPORT scott/tiger COMPRESS=yes

조치 3

테이블을 생성할때 충분한 PCTFREE와 PCTUSED를 할당한다. CREATE TABLE emp (no number(4), ename varchar2(10)) PCTFREE 30 PCTUSED 60;

<원인 2>
테이블의 구조적 설계에 문제가 발생하면 성능이 저하될 수 있습니다

조치 1

해당 테이블이 SYSTEM 테이블스페이스에 저장되어 있는지 확인하십시오. SYSTEM 테이블스페이스는 자료사전 테이블이 저장되는 공간이므로 집중적인 Disk I-O가 발생하는 공간입니다.

SELECT owner, segment_name, tablespace_name FROM dba_segments WHERE tablespace_name = ‘SYSTEM’ and owner = ‘SCOTT’; ALTER TABLE emp MOVE TABLESPACE users;

조치 2

해당 테이블이 저장되는 테이블스페이스를 Locally Management 테이블스페이스로 생성하십시오. Data-Dictionary 타입의 테이블스페이스는 테이블의 모든 익스텐트 정보를 SYSTEM 테이블스페이스에 저장하기 때문에 성능이 저하될 수 있습니다.

CREATE TABLESPSCE sample DATAFILE ‘/disk1/sample1.dbf’ SIZE 500m EXTENT MANAGEMENT LOCAL UNIFORM SIZE 10m;

조치 3

대용량 데이터가 저장되는 컬럼(VARCHAR2, LONG, LONG RAW)이 있는 테이블을 수직 파티션 또는 수평 파티션 테이블로 분리하십시오. VARCHAR2와 LONG/LONG RAW 컬럼은 검색할 때 불필요한 메모리와 디스크 I-O를 유발시키기 때문에 성능저하 현상을 유발하게 됩니다.

CREATE TABLE emp (empno number, ename varchar2(15)) ; CREATE TABLE emp_pic (empno number, pic LONG RAW); CREATE TABLE jeon(idate date, no char(2),name v2(20), qty number) Partition By Range(idate) (Partition t1 values less than(to_date(‘2000’)) Tablespace chul1999, Partition t2 values less than(to_date(‘2001’)) Tablespace chul2000, Partition t3 values less than(MAXVALUE) Tablespace chul2001);

 

Posted by redkite
, |

원인 : CREATE INDEX, ORDER BY, GROUP BY, DISTINCT, DECODE, UNION, INTERSECT, MINUS와 같은 문장을 통해 대용량 데이터를 분류(Sorting)할 때 Temporary 테이블스페이스가 부족할 때 발생하는 에러입니다.

조치 1

사용자의 SQL문에서 필요한 컬럼 만을  분류대상으로 설정한다. 
    SELECT  *   FROM  emp ORDER BY ename;
->  SELECT  empno, ename  FROM emp ORDER BY ename;

조치 2

사용자의 SQL문에서 ORDER BY 절에 사용되는 컬럼에 대해 INDEX를 설정한다.
      SELECT  *   FROM  emp ORDER BY ename;
 ->   CREATE INDEX  I_emp_ename ON emp(ename);
      SELECT  empno, ename  FROM emp;
 
조치 3
Temporary 테이블스페이스의 공간을 추가로 늘려주어야 합니다.
SQL> ALTER TABLESPACE  temp  ADD DATAFILE ‘temp02.dbf’ SIZE 500M;   
또는
SQL> ALTER DATABASE DATAFILE  ‘temp01.dbf’  RESIZE  800M; 

조치 4

SORT_AREA_SIZE의 값을 추가로 늘려주어야 합니다.
SORT_AREA_SIZE = 1000000;

조치 5

각 사용자별로 Temporary 테이블스페이스를 생성하여 Contention 현상을 
방지한다.
SQL> CREATE TABLESPACE  temp1  DATAFILE ‘temp1_01.dbf’ SIZE 500M;   
SQL> ALTER USER  scott   Temporary TABLESPACE  temp1;

 

Posted by redkite
, |

 

DB에서 INDEX 제대로 사용하기

작성자 : 김문규
최초 작성일 : 2009.5.18

데브피아에서 기가 막히게 좋은 전문가 글을 찾았습니다. 간단하게 정리해 봅니다.

인덱스를 사용하기를 기대하지만 그렇지 않은 기본적이고 대표적인 예입니다.

1. 인덱스 컬럼을 변형하여 비교할 때
BAD
WHERE TO_CHAR(HIREDATE,'YYYYMMDD') = '19980518';
GOOD
WHERE HIREDATE = TO_DATE('19980518')


BAD
WHERE SALARY + 1000 > 100000;
GOOD
WHERE SALARY > 100000 - 1000;


비교하는 인덱스 컬럼의 형이나 값을 변경하면 발생합니다.
이 경우에는 비교값을 변경해 주어야 인덱스를 사용하게 됩니다.

2. 비교 대상의 형이 달라서 내부적으로 형변환을 하는 경우

BAD
WHERE EMP_ID = 200383;
GOOD
WHERE EMP_ID = ‘200383’;


EMP_ID가varchar라고 할 경우에 비교값이 숫자인 경우에 DB에서 자동으로 이를 숫자로 변경하고 비교하게 됩니다. 이 경우에 인덱스 컬럼에 변형이 일어났기 때문에 인덱스를 사용하지 못하게 됩니다.

3. NULL을 비교하는 경우

BAD
WHERE JOB IS NULL;


일반적으로 Oracle을 기준으로 NULL은 인덱스 대상이 아니라고 합니다. 따라서, 이를 해결하기 위해서는 NULL을 쓰지 말고 다른 정해진 값을 이용해서 비교해야 합니다. (흠..이건 좀...)

4. 부정형 조건인 경우

BAD
WHERE JOB NOT IN ( 'INSTRUCTOR','STAFF');


부정형 역시 인덱스를 사용하지 못하는 대표적인 조건 쿼리 입니다. 아닌 놈을 찾으려면 전체를 뒤지는 수 밖에요. 이를 피하기 위한 근본적인 DB 모델링이 중요합니다.


이하 원문을 그대로 가져다 붙입니다. 추가 설명이 필요하면 읽어 보세요.
http://www.devpia.com/DevStudy/Lecture/OffLineDetail.aspx?nSemiID=1429&lectype=evt

필자가 처음에 SQL을 배울 때 SQL이 상당히 이상했다. 원하는 것만 요구할 뿐 어떻게 가져오라는 정보가 SQL에는 없었기 때문이다. FILE레벨의 I/O까지 코딩에 익숙한 필자에게 절차가 없다는 것이 오희려 더 이상했던 것이다.
물론 상세한 과정이 필요하지 않으므로 편리하고 좋았다 그러나 어떻게 가져오는지는 알지못하고 단지 사용할 뿐이었다.
그러나 SQL이 PLAN이라는 실행 계획을 만들고 그에 따라 가져오게 된다는 사실은 안것은 한참 뒤에 일이었다.
결국은 내가 하지않은 일을 Optimizer라는 프로그램이 대신 해주고 있는 것이 아닌가? 그래서 정말 고마운 놈이라고 생각했었다. 그러나 밑는 도끼에 발등을 찍힌다는 말이 있지 않은가?
Plan에 index를 달아주어도 Index를 사용하지 않고 full table scan만 하고 있으니 당체 속도가 나지를 않았다.
이래저래 해서 나중에 알게되었지만 결국 컬럼의 변형을 가하면 index를 사용하지 못한다는 것이다. 우리가 직접 사용하지는 않지만 결국 우리가 SQL을 사용한다는 것은 Optimizer라는 놈에게 SQL의 수행을 부탁하는 것이다. 따라서 우리가 Optimizer에 대해서 잘 안다면 SQL을 좀더 효율적으로 수행하도록 할 수 있지 않은가!
그러면 인덱스를 달았을 때 Optimizer가 index를 사용하지 못하는 경우를 통해서 우리가 애써(?)생성한 인덱시가 무용지물이 되지 않도록 해보자.
아래예제에 사용할 TABLE LAYOUT이다.
EMPLOYEES
---------
Rows=15,132
Empty Blocks=7
Chain Count=0
Avg Space Freelist Blocks=0
Sample Size=15,132
Partitioned=NO

Blocks=121
Avg Space=885
Avg Row Length=51
Freelist Blocks=0
Last Analyze=2009/05/04
Column Name
---------------
EMP_ID
MGR_ID
LAST_NAME
FIRST_NAME
HIREDATE
JOB
SALARY

Nullable
-----------------


NOT NULL
Column Type
-----------------
VARCHAR2(40)
VARCHAR2(40)
VARCHAR2(24)
VARCHAR2(14)
DATE
VARCHAR2(24)
NUMBER(7,2)
Distinct
-----------------
15,132
679
9,443
3,579
3,903
53
3,267
Buckets
------------------
75
75
75
75
75
53
75
INDEX
--------------------------------------------------------------------------------------
IDX_GENDER : GENDER
Type=NORMAL, Uniq=No, Distinct=2, Rows=15,132, Last Analyze=2009/05/04
IDX_HIREDAT : HIREDATE
Type=NORMAL, Uniq=No, Distinct=3,903, Rows=15,132, Last Analyze=2009/05/04
IDX_JOB : JOB
Type=NORMAL, Uniq=No, Distinct=53, Rows=15,129, Last Analyze=2009/05/04
IDX_SALARY : SALARY
Type=NORMAL, Uniq=No, Distinct=3,267, Rows=15,132, Last Analyze=2009/05/04
IDX_TYPE2 : TYPE
Type=NORMAL, Uniq=No, Distinct=6, Rows=15,132, Last Analyze=2009/05/04
PK_EMP_ID : EMP_ID
Type=NORMAL, Uniq=No, Distinct=15,132, Rows=15,132, Last Analyze=2009/05/04
필자가 여러군데 튜닝을 하면서 가장 많이 본것중에 하나는 INDEX를 달았으나 쓰지 못하게 되는 경우이다. 대표적인 경우가 아래와 같이 날짜타입(HIREDATE)에 TO_CHAR를 씌운 경우이다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE TO_CHAR(HIREDATE,'YYYYMMDD') = '19980518';
물론 INDEX는 아래와 같이 생성되어있다.
CREATE INDEX IDX_HIREDATE ON EMPLOYEES(HIREDATE);
우리가 원하는 것은 INDEX를 타고 테이블을 가져오기를 바란것이었다.

그러나 실제 PLAN은 아래와 같이 나온다.
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=28 Card=151 Bytes=3K)
1 0 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=28 Card=151 Bytes=3K)
TABLE ACCESS (FULL) 이란 뜻은 INDEX를 타지 않고 테이블을 처음부터 끝까지 읽어서 찾는다는 뜻이다. 한마디로 10건이며 10건읽고 100만건이면 100만건을 다 읽어야 결과가 나온다는 말이다.

OPEN시에는 빠르던 시스템이 시간이 지날수록 느려지는 결정적인 역할을 하는 것이 바로 위와 같은 경우이다. 그럼 어떻게 해야 제대로 인덱스를 사용할 수 있을가?
일단 간단히 SQL의 수정으로 해결할수 있다. HIREDATE는 날짜 타입이다.
따라서 인덱스를 HIREDATE로 했을 때 인덱스를 타기위해서는 INDEX를 생성한것에 변형을 주어서는 안된다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE HIREDATE = TO_DATE('19980518')
따라서 간단하게 위와 같이 고치면 INDEX를 사용하게된다.
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=4 Bytes=92)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (TABLE) (Cost=3 Card=4 Bytes=92)
2 1 INDEX (RANGE SCAN) OF 'IDX_HIREDATE' (INDEX) (Cost=1 Card=4)
물론 결과도 빠르게 나온다 그러나 중요한 점이 있다 결과가 같을까?
운이 좋으면 결과가 같을 것이고 대부분의 경우는 결과가 틀리다.
왜 그럴까?
날짜 타입은 날짜와 시분초의 정보도 가지고 있다. 따라서 TO_DATE(‘19980518’)라는 말은 정확히 1998년5월18일 0시0분0초라는 뜻이다. 그래서 우리가 원하는 1998년5월18일자와는 차이가 있다.
따라서 1998년5월18일 0시0분1초 ~ 23시59분59초까지의 데이터는 나오지 않게되는것이다.
이것은 튜닝할 때 유의할 점이다. 결과를 같게 유지해야하는것이다. 이 상황을 알고있다면 방법은 간단하다.
아래아 같이 고치면 빠른시간에 원하는 결과를 얻을 수 있을 것이다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE HIREDATE BETWEEN TO_DATE('19980518'||'00:00:00','YYYYMMDD HH24:MI:SS')
AND TO_DATE('19980518'||'23:59:59','YYYYMMDD HH24:MI:SS')
비슷하지만 함수의한 변형이 아닌 간단한 연산에의한 변형의 경우도 마찬가지이다.
$1000의 인센티브를 더주면 $10000이 넘는 사람을 찾는 SQL을 만들어보자.
아마 아래와 같을 것이다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE SALARY + 1000 > 100000;
물론 INDEX는 아래와 같이 만들었다.
CREATE INDEX IDX_SALARY ON EMPLOYEES(SALARY);
그러나 PLAN을 보자
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=29 Card=757 Bytes=13K)
1 0 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=29 Card=757 Bytes=13K)
인데스를 타지 못한다. 왜일까. 간단한 연산이지만 SALARY컬럼에 가공을 했기 때문에 OPTIMIZER는 인덱스를 타는 것을 포기해버린다.
따라서 우리가 기초적인 수학 실력을 발휘해서 이항을 해준다면 아래와 같은 조건이 될것이다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE SALARY > 100000 - 1000;
이경우에 PLAN을 보자.
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=1 Bytes=17)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (TABLE) (Cost=3 Card=1 Bytes=17)
2 1 INDEX (RANGE SCAN) OF 'IDX_SALARY' (INDEX) (Cost=2 Card=1)
재미 있게도 이번에 제대로 된 인덱스를 탄다. Optimizer가 바보 같다는 생각이 들지 않는가?
물론 바보같다. 그러나 OPTIMIZER나름대로 깊은 고민이 있다. 아주 잛은 시간내에 OPTIMIZER는 많은 경우의 수를 타진해야한다. 따라서 이항연산과 같은 것 까지 검토하면 너무 많은 시간을 소모하게 된다 따라서 그런부분은 포기한것이다.

또다른 경우중에 하나가 DB의 내부적인 변형이다. 이는 개발자가 의도하지 않게 문제를 야기하는 경우이다.
여기 PK 조건으로 검색하는 SQL이 있다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE EMP_ID = 200383;
그러나 PLAN은 아래와 같이 나왔다.
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=29 Card=1 Bytes=19)
1 0 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=29 Card=1 Bytes=19)
분명히 아래와 같은 INDEX를 생성하였다.
CREATE INDEX PK_EMP_ID ON EMPLOYEES(EMP_ID);
왜 인덱스를 안타는 것일까?
그 이유은 OPTIMIZER의 내부 변형 규칙에 있다.
일반적으로 비교를 하려면 두개의 데이터 형이 같아야 한다.
그런데 EMP_ID는 VARCHAR2(40)이다 그리고 비교하려는 것은 200383이라는 숫자이다.
따라서 숫자와 문자는 비교할수 없기 때문에 내부적으로 변형이 이루어진다.
문자보다 숫자가 우선순위가 높아서 문자와 숫자를 비교하게되면 문자쪽이 숫자로 변형되어 비교하게 되는 것이다.
따라서 위의 SQL은 OPTIMIZER는 아래와 같은 SQL로 수행하게된다.
EMP_ID를 TO_NUMBER(EMP_ID) = 2000393과 같이 처리하게 된다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE TO_NUMBER(EMP_ID) = 200383;
이는 처음 예제에서 날짜 컬럼에 TO_CHAR를 씌원것과 같은 효과이다. 따라서 이문제를 해결하기위해서는 반대쪽, 즉 2000293을 문자로 변환해주면 문자대 문자의 비교이므로 내부적 변형이 발생하지 않게된다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE EMP_ID = ‘200383’;
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=1 Bytes=19)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (TABLE) (Cost=2 Card=1 Bytes=19)
2 1 INDEX (RANGE SCAN) OF 'PK_EMP_ID' (INDEX) (Cost=1 Card=1)
아래 SQL을 보자 JOB에 NULL인 조건을 검색하는 것이다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE JOB IS NULL
아래 SQL을 보자 JOB이 NULL인 조건을 검색하는 것이다.
물론 아래와 같은 JOB INDEX를 생성하였다.
CREATE INDEX IDX_JOB ON EMPLOYEES (JOB);
아래 PLAN을 보자 왜 IDX_JOB INDEX를 타지 못하는가?
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=29 Card=3 Bytes=63)
1 0 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=29 Card=3 Bytes=63)
이경우에는 Oracle의 경우 일반적으로 index를 생성할 때 null값은 index항목에 넣지 않는다. 따라서 null은 index에 없기 때문에 null조건을 준다면 그것은 index를 탈수 없다.
따라서 위와 같은 경우 반드시 index를 타려거든 job컬럼을 NOT NULL로 설정하고 NUL대신 특정값 (예를 들면 : ‘NOT ASSIGN’ ) 으로 설정하고 QUERY를 아래와 같이 수정한다면 인덱스를 탈수 있을 것이다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE JOB = ‘NOT ASSIGN’;
아래 SQL를 하나 더 보자
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE JOB NOT IN ( 'INSTRUCTOR','STAFF');
이번의 NULL을 비교한것도 아닌데 INDEX를 사용하지 못한다. 이것은 일반적인 INDEX가 =이나 <, > , BETWEEN조건에 만 인덱스를 탈수 있고 부정형으로 비교했을때는 인덱스를 탈수 없기때문이다.
생각해보자 어떤 것을 순서대로 정리해 놓았는데 그것이 아닌 것을 찾으려고 한다면 전체를 다 읽어봐야지만 아니것을 알수 있지 않은가?
따라서 가급적 프로그램 구성에서 부정형 조건이 들어가게 한다는 것은 성능을 저하시킬 가능성이 매우 높기 때문에 이런 조건이 되지 않도록 설계단설계부터 고려해야한다.

이상은 간단하게 INDEX를 주었을 때 일반적으로 INDEX를 타지 못하는 경우를 든것이다. 사실 위예 예처럼 실제 프로젝트에서 많은 부분이 INDEX를 생성하고도 OPTIMIZER의 특성을 몰라서 INDEX를 쓰지 못한채 APPLICATION이 돌고 있다. 이는 곧바로 자원의 과도 사용으로 나타나고 느린 응답시간으로 나타나게 된다. 항상 시스템을 OPEN하고 마음을 조리지 않으려면 내가 생성된 INDEX를 잘 탈수 있게 내가 SQL을 잘 작성했는지 검토해 보기 바란다.
아래 4개의 항목은 반드시 기억해 두기 바란다.
인덱스를 사용하지 못하는 경우는 아래와 같다.
  • 인덱스 컬럼에 변형이 일어난 경우
    • WHERE TO_CHAR(HIREDATE,'YYYYMMDD') = '19980518';
    • WHERE SALARY + 1000 > 100000;
  • 내부적인 변형이 일어난 경우
    • WHERE EMP_ID = 200383;
  • NULL을 비교하였을 경우
    • WHERE JOB IS NULL;
  • 부정형으로 조건을 기술한 경우
    • WHERE JOB NOT IN ( 'INSTRUCTOR','STAFF');

물론 이 경우 이외에 Optimizer의 판단에 따라서 인덱스를 사용하지 못하는 경우도 있다. 그러나 대부분의 경우에는 위에 항목을 만족한다면 원하는 index를 타는 효율적인 sql작성에 좋은 기준이 될것이다. 마지막으로 sql을 작성한후 반드시 plan을 확인해 보기 바란다.
실제 plan이 어떻게 되는냐를 확인해보지 않으면 무심코 과거의 실수를 답습할 수 있기때문이다.

테이블 생성시 데이터의 예상치를 산출하여 초기SIZE 및 NEXT EXTENTION SIZE를 확정지어야 함.

테이블 생성시 사용용도 및 특성을 감안하여 PCTFREE 및 PCTUSED를 결정한다.

참고
$1. PCTFREE
小 :전체 TABLE SCAN하는 성능이 향상.
따라서 질의가 대부분이고 수정이 없는 테이블에 대해서 값을 작게 잡아준다.
大 :MIGRATION이 감소하므로 NULL이나 가변길이를 갖는 필드를 갖고 있는 경우는 값을 크게 잡는다.
$2. PCTUSED
小 :MIGRATION 감소
大 :많은 DATA를 저장할수 있으나 MIGRATION이 증가.

따라서
#1. ROW SIZE가 증가되는 UPDATE가 많은 경우는
PCTFREE=20
PCTUSED=40
#2. INSERT,DELETE 가 많은 경우는
PCTFREE=5
PCTUSED=60
#3. TABLESPACE가 매우 크고 작업의 대부분이 READ ONLY일 경우는
PCTFREE=5
PCTUSED=90
으로 조정하는 것이 일반적이다.

DELETE가 자주 일어나는 테이블은 주기적으로 해당 INDEX를 재생성시켜준다.

(ALTER INDEX idx_name REBUILD;)

테이블의 재정의

테이블의 중복화 : 관련있는 몇개의 테이블의 중요한 COLUMN을 같이 집계한 TABLE을 작성하여 각각의 여러테이블에서 집계하여 가져오는 것보다 한개의 집계한 테이블에서 데이터를 가져옴으로써 유리.(예:집계용 테이블,진행관리용 테이블...)
테이블의 분할 : COLUMN길이가 길고 사용빈도가 낮은 COLUMN을 모아 테이블을 분할한다. 타 테이블과의 RELATION을 가지고 있는 COLUMN이나 조회의 조건이 되는 COLUMN을 분할하지 않는것이 좋다.
테이블 제거 : 테이블의 중복화나 테이블의 분할에 의해 ACCESS되지않는 불필요한 테이블이 발생하게 된다. 이때는 과감히 삭제한다.

INDEX 선정

테이블의 크기가 적은 것은 (보통 5~6 BLOCK) INDEX를 만들지 않고 FULL TABLE SCAN이 유리하다.
INDEX 추가에 따른 OVERHEAD에 부담이 없다고 판단될때.
다른 TABLE과 JOIN이 빈번히 일어나서 불필요한 SCAN을 최소화하고자 할때.
BATCH 처리로 생성되는 테이블은 INDEX생성과 삭제를 적용시기에 따라 적절히 조절함으로써 효율을 향상함.

INDEX COLUMN 선정기준

INDEX COLUMN선정은 COLUMN분포도가 10~15%를 초과하지 않아야 함.(분포도=((COLUMN값별 평균 ROW수)/전체 ROW 수) * 100)
타 테이블과 JOIN시 사용되는 외부식별자(FOREIGN KEY)는 가능한 INDEX를 생성해야 함.
INDEX COLUMN운 SQL SELECT문의 WHERE절에서 자주 조건으로 사용되거나 ORDER BY, GROUP BY에 자주 등장하는 것을 대상으로 검토.

테이블의 EXTENTION이 자주 일어나면 PERFORMANCE에 좋지않다.

참고 VIEW: USER_EXTENTS, DBA_FREE_SPACE
위의 참고 VIEW를 이용하여 검토한 후 INITIAL SIZE 및 EXTENT SIZE 등을 재조정하여 재생성한다.

 

Posted by redkite
, |

06/12 공인인증 RA DBMS 제주계정 튜닝

-- 설정 후 플랜정보 확인

SQL> set autot traceonly explain -- explain 설정

SQL> create index raclient_certi_idx on cjb.raclient(certi_number) nosegment;

SQL> exec dbms_stats.gather_table_stats(ownname => 'CJB', tabname => 'RACLIENT', estimate_percent=>10)

SQL> alter session set "_use_nosegment_indexes" = true;

-- 설정 해제

SQL> drop index raclient_certi_idx;

SQL> alter session set "_use_nosegment_indexes" = false;

-- 운영서버 반영

SQL> create index raclient_certi_idx on raclient(certi_number);

Posted by redkite
, |

Overview

오래전 메가존(현 혜택존) 서비스를 담당하던 시기, 포인트 관련된 부분에서 심각한 성능 저하 및 유효성 취약 문제가 발생하였습니다. 장비 고도화를 통해 관련 문제를 해결하기에 앞서, 서비스 로직 및 테이블 재구성을 통한 최적화 작업을 통해 문제를 해결하였습니다.

이에 관해 간단하게 소개하도록 하겠습니다.

Problems

다음과 같이 크게 두 가지 문제가 있었습니다.

  1. 성능 이슈
    1. 로그 테이블은 거대한 한 개의 테이블로 구성
    2. 데이터 누적에 따라 성능이 급격하게 저하
  2. 유효성 이슈
    1. 자바 어플리케이션에서만 유효성 체크 – 비 정상적인 사용 존재
    2. 포인트가 현금처럼 사용될 수 있으므로 반드시 필요함

Solutions

1) 파티셔닝을 통한 성능 최적화

오라클 엔터프라이즈 버전에서는 테이블 파티셔닝을 제공하지만, 아쉽게도 오라클 스탠다드에서는 관련 기능이 없습니다. 즉, 어플리케이션 레벨에서 적당히 데이터 분산을 유도하는 방법 밖에는 없습니다. 데이터를 분산하는 방법으로는 여러가지가 존재하겠지만, 저는 월별로 테이블을 분산 저장하는 방식을 사용하였습니다.

매월 하단과 같은 스키마의 테이블을 MCHIP”YYYYMM” 형식으로 생성을 합니다.

물론 해당 월 다음 달로 생성을 해야겠죠?

1
2
3
4
5
6
 Name            Type
 --------------- ------------
 SEQ             NUMBER
 USER_NO         VARCHAR2(12)
 POINT           NUMBER
 ISSUE_DATE      DATE
 Name            Type
 --------------- ------------
 SEQ             NUMBER
 USER_NO         VARCHAR2(12)
 POINT           NUMBER
 ISSUE_DATE      DATE

그리고 인덱스 필요 시 데이터 테이블스페이스와는 “물리적으로 분리”된 전용의 테이블스페이스에 생성하여 DISK I/O 비효율도 최소화 유도하였습니다.

데이터 조회는 기본적으로 월별로만 가능하며, 최근 3개월 동안만 조회할 수 있도록 하였고, 필요 시 과거 테이블을 DROP하는 방식으로 변경하였습니다. 만약 최근 3개월 데이터가 필요할 시에는 ”UNION ALL” 구문으로 데이터를 가져왔습니다.

다음은 쿼리를 월별로 생성하여 데이터를 저장하는 간단한 자바 프로그램입니다. 저는 SpringFramework 환경에서 구현했었는데, 그것보다는 알아보기 쉬운 단순 자바 프로그램으로 보여드리는 것이 좋을 것 같습니다. (설마 아래 있는 것을 그대로 쓰시는 것은 아니겠죠? ㅎㅎ)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
    public static void main(String[] args) {
        DateFormat df = new SimpleDateFormat("yyyyMM");
        Date date = new Date();
 
        // 현재 기준 년월 
        String yyyymm = df.format(date).toString();
 
        // 쿼리 생성
        String sql = "INSERT INTO MCHIP"+yyyymm+" \n" +
                "(SEQ, USER_NO, POINT, ISSUE_DATE) \n" +
                "VALUES \n" +
                "(SEQ_MCHIP.NEXTVAL, ?, ?, SYSDATE)";
 
        System.out.println(sql);
    }
}
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
    public static void main(String[] args) {
        DateFormat df = new SimpleDateFormat("yyyyMM");
        Date date = new Date();

        // 현재 기준 년월 
        String yyyymm = df.format(date).toString();

        // 쿼리 생성
        String sql = "INSERT INTO MCHIP"+yyyymm+" \n" +
                "(SEQ, USER_NO, POINT, ISSUE_DATE) \n" +
                "VALUES \n" +
                "(SEQ_MCHIP.NEXTVAL, ?, ?, SYSDATE)";

        System.out.println(sql);
    }
}

위와 같이 쿼리를 생성하고, 원하는 테이블에 선별적으로 데이터를 넣습니다. 조회 시에도 위와 같은 방식으로 쿼리를 생성합니다. 만약 한달 이상의 데이터가 필요하다면 UNION ALL로 쿼리를 붙여서 데이터를 조회하면 되겠죠. ^^

2) 트리거를 사용한 유효성 체크

포인트 내역을 저장하는 테이블이 있었다면, 포인트 현황을 조회하기 위한 전용 테이블 또한 존재했습니다. 포인트 현황에 저장된 점수가 사용자가 현재 소지하고 있는 포인트이며, 이를 차감하여 서비스에서 사용하는 방식으로 사용하고 있었습니다.

개인 당 한 건의 데이터만 있기 때문에, 사용자 데이터를 조회하는 것에는 큰 무리가 없었지만, 유효성을 자바 어플리케이션에서만 체크했었기 때문에 여기저기 헛점이 많은 상태였습니다. 물론 내구성이 뛰어나게 개발되었다면 큰 문제는 없었겠지만, 협력사가 시간에 쫓겨서 급하게 개발한 산출물인지라.. 아무래도.. ^^;;

자바 소스 전체를 뒤져서 모든 유효성을 체크하는 것은 거의 무리에 가까웠고, 그래서 제가 채택한 방식은 트리거였습니다. 트리거를 사용하여 사용자 포인트 유효성 여부를 DB레벨에서 체크해서 어플리케이션과 분리하자는 의도였죠.

MCHIP201209 테이블에 포인트가 내역이 들어가는 동시에 유효성 체크 후 정상적이면 포인트 현황 테이블에 반영하는 트리거입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CREATE OR REPLACE TRIGGER TRG01_MCHIP201209
BEFORE INSERT
ON MCHIP201209
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
  POINT NUMBER;
BEGIN
  /*** 현재 사용자 포인트 조회 ***/
  SELECT POINT INTO POINT FROM MCHIP_INFO
  WHERE USER_NO = :NEW.USER_NO;
  … 중략…
 
  /*** 포인트 유효성 체크  ***/
  IF POINT - :NEW.POINT < 0 THEN
    RAISE_APPLICATION_ERROR(-20001,'Not enough point!!');
  END IF;
 
  /*** 포인트 현황 업데이트 ***/
  UPDATE MCHIP_INFO SET POINT = POINT + POINT_NEW
  WHERE USER_NO = :NEW.USER_NO;
END;
/
CREATE OR REPLACE TRIGGER TRG01_MCHIP201209
BEFORE INSERT
ON MCHIP201209
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
  POINT NUMBER;
BEGIN
  /*** 현재 사용자 포인트 조회 ***/
  SELECT POINT INTO POINT FROM MCHIP_INFO
  WHERE USER_NO = :NEW.USER_NO;
  … 중략…

  /*** 포인트 유효성 체크  ***/
  IF POINT - :NEW.POINT < 0 THEN
    RAISE_APPLICATION_ERROR(-20001,'Not enough point!!');
  END IF;

  /*** 포인트 현황 업데이트 ***/
  UPDATE MCHIP_INFO SET POINT = POINT + POINT_NEW
  WHERE USER_NO = :NEW.USER_NO;
END;
/

Conclusion

오래전에 성능을 최적화한 내용을 정리하였습니다.

당시 서버에 과부하로 인하여 서버 고도화 및 장비 도입을 위한 투자를 검토 중이었으나, 서비스 로직 재구성 이후 서버가 안정적이었기 때문에 기존 장비로 서비스를 진행하였습니다. 게다가 현재 기존 장비 사용률이 CPU 기준 80~90%였던 상황이 10~20%로 유지되는 쾌거를 거두었죠.

데이터 처리량을 최소로 유도하는 것이 성능 최적화의 첫걸음이라는 것을 느낀 경험이었습니다.

좋은 포스팅으로 다시 찾아뵐께요^^;

Tags: ,

DB Link와 Export/Import를 활용한 데이터 이관 성능 리포트

2012-04-13 by gywndi | No Comments | Filed in Oracle, Research

안녕하세요. 한동안 MySQL에 빠져 있다가, 최근 Oracle 데이터 이관 작업 도중 재미난 사례 공유 합니다. DB Link를 통한 CTAS(Create Table As Select)와 Export/Import를 통한 데이터 이관 비교입니다.

서비스 요구 사항

서비스 DBMS 버전 : Oracle 9i
전체 데이터 파일 사이즈 : 120G (인덱스 포함)
타겟 테이블 데이터 사이즈 : 26G (인덱스 제외)
네트워크 속도 : 100Mbps (max: 12.5MB/s)
일 1회 현재 서비스 데이터 동기화 수행
모든 작업은 “자동화”하여 운영 이슈 최소화

위 환경을 고려하였을 때, 전체 데이터 파일 Copy는 동기화 시간 및 스토리지 낭비 요소가, Archive Log 활용하기에는 운영 이슈가 존재했습니다.

그래서 결정한 것이 필요한 테이블만 이관하자는 것이고, 가장 효율적인 방안을 모색하기 위해 DB Link와 Import/Export 두 가지 성능을 비교해보았습니다.

DB 환경 구축

Oracle DBMS 환경은 장비 효율을 높이기 위해서 Oracle VM 상에 가상 머신으로 구성을 하였습니다. Oracle VM에 대한 소개는 추후 천천히 소개를 드릴께요^^

서버 구성

서버 구성

Export/Import 결과

아래 그림과 같이 nfs Server/Client 구성을 하였는데, 타겟 테이블을 별도로 Export 후, 해당 파일을 nfs로 직접 끌어와서 데이터를 가져오는 방식입니다.

Export/Import를 위한 nfs 서버 구성

Export/Import를 위한 nfs 서버 구성

Export/Import 결과

Export 시간은 약 20분, Import시간은 약 60분 그리고 사전 작업 시간을 고려해봤을 때, 데이터만 이관할 시 90분이라는 시간이 소요되었습니다. 하루에 1시간 30분 정도 동기화 시간을 제공하면 되니 크게 나쁜 결과는 아니었습니다.

그러나 Import 수행되는 동안 alert.log를 확인을 해보니 Log Switch가 너무 빈도있게 발생(1분 단위)하였으며, 아래와 같이 Log가 데이터 파일로 Flush되지 않아서 대기하는 상황이 발생하였습니다.

Import 시 Alert 로그 현황

DB Link 결과

Export/Import 시간 체크 후 Redo Log 사용 없이 직접적으로 데이터를 Write할 수 있다면 더욱 빠른 데이터 이관이 가능할 것이라고 판단이 되어 이번에는 DB Link를 통한 CTAS(Create Table As Select) 방법으로 접근을 해보았습니다. 물론 CTAS에서 반드시 NOSLOGGING 옵션을 줘야겠죠 ^^

Oracle VM 서버에서 환경에 맞게 계정 별로 DB Link를 생성을 하고 하단과 같이 테이블 Drop 후 Create Table As Select 구문을 실행합니다.

1
2
3
4
SQL> DROP TABLE TABLE01;
SQL> CREATE TABLE TABLE01 NOLOGGING
   > AS
   > SELECT * FROM TABLE01@DB_LINK;
SQL> DROP TABLE TABLE01;
SQL> CREATE TABLE TABLE01 NOLOGGING
   > AS
   > SELECT * FROM TABLE01@DB_LINK;

Oracle VM에서는 물리적으로 다른 장비 세 대로부터 동시에 데이터를 받으며, 물리적인 장비 안에는 각각 두 개의 서비스 계정이 포함됩니다.

결과는 다음과 같습니다.

DB Link Result

위와 마찬가지로 동시에 실행을 했기 때문에 가장 오래 걸린 서비스가 최종 소요된 시간이며, 00:00~ 00.19:13 에 종료되었으니 대략 20분 소요되었습니다. 앞선 결과에서 90분 소요되던 것을 20분으로 줄였으니, 상당히 괜찮은 결과이네요^^

물론 Alert Log에 Log Switch 관련 메시지는 나오지 않았습니다.

Conclusion

DB Link를 활용하여 데이터를 이관하는 것이 압도적으로 빠른 결과였습니다.

그러나 항상 DB Link가 좋은 것은 아닙니다. 다행히 대상 테이블에는 LOB 관련 필드는 없었지만, 만약 LOB 필드가 있었다면 어쩔 수 없이 Export/Import 방식을 써야만 했겠죠.

데이터 이관을 위한 여러가지 방안이 있겠지만, 한번 쯤은 두 가지 방법의 장단점을 따져보고 싶었는데 좋은 기회가 되어서 내용 공유 드립니다

Posted by redkite
, |

최근에 달린 댓글

최근에 받은 트랙백

글 보관함