OS 카피를 이용한 아카이브 로그 모드 백업도 기본적으로 노 아카이브 모드에서의 뱍업과 같이 OS의 cp명령어를 이용한다는 점은 같다.
 하지만 아카이브 모드를 사용할 때는 서비스 중지를 하지 않고 On-line에서의 백업이 가능하다.


 아카이브 모드에서의 OPEN 백업 및 복구에 대해 알아보도록 한다.

1. 백업 디렉토리 생성

 먼저 아래와 같이 백업을 할 디렉토리를 생성한다.

 mkdir -p /u02/bkg/open/



2. 백업 진행을 위한 스크립트 생성

 아래의 SQL을 이용해 백업을 진행 할 스크립트를 생성한다.

 SQL> set pagesize 900

select     'conn / as sysdba'  AS "command" from dual
 union all
select     'set echo on'                    from dual 
 union all
select 'ALTER TABLESPACE '||tablespace_name||' BEGIN BACKUP ;' 
        AS "command"
from   dba_tablespaces
where  contents in ('PERMANENT' , 'UNDO')
 union all
select 'host cp '||file_name||' /u02/bkg/open/.'
from   dba_data_files 
 union all
select 'ALTER TABLESPACE '||tablespace_name||' END BACKUP ;'
from   dba_tablespaces
where  contents in ('PERMANENT' , 'UNDO') 
 union all
select     'ALTER SYSTEM CHECKPOINT ;'      from dual
 union all
select     'exit'                           from dual ;


command
--------------------------------------------------------------------------------
conn / as sysdba
set echo on
ALTER TABLESPACE SYSTEM BEGIN BACKUP ;
ALTER TABLESPACE UNDOTBS1 BEGIN BACKUP ;
ALTER TABLESPACE SYSAUX BEGIN BACKUP ;
ALTER TABLESPACE USERS BEGIN BACKUP ;
ALTER TABLESPACE EXAMPLE BEGIN BACKUP ;
host cp /u01/app/oracle/oradata/orcl/users01.dbf /u02/bkg/open/.
host cp /u01/app/oracle/oradata/orcl/sysaux01.dbf /u02/bkg/open/.
host cp /u01/app/oracle/oradata/orcl/undotbs01.dbf /u02/bkg/open/.
host cp /u01/app/oracle/oradata/orcl/system01.dbf /u02/bkg/open/.
host cp /u01/app/oracle/oradata/orcl/example01.dbf /u02/bkg/open/.
ALTER TABLESPACE SYSTEM END BACKUP ;
ALTER TABLESPACE UNDOTBS1 END BACKUP ;
ALTER TABLESPACE SYSAUX END BACKUP ;
ALTER TABLESPACE USERS END BACKUP ;
ALTER TABLESPACE EXAMPLE END BACKUP ;
ALTER SYSTEM CHECKPOINT ;
exit

SQL > exit

 SQL문에서 나온 결과 값을 이용해 아래와 같이 스크립트 파일을 생성한다.

 주의 깊게 살펴 보았다면 눈치 챘겠지만, 이번에는 저번 노 아카이브 모드와 다르게 데이터파일들에 대한 백업 스크립트를 생성하였다.
 사실 데이터파일에 비해 SPFILE은 그렇게 빈번히 발생되지 않고, 컨트롤 파일의 경우 사용자들의 명령어 및 현재 데이터베이스 상태에 대해 기록되는 것 이기 때문에   데이터파일 만큼의 빈번한 백업 보다는 시스템 변경 사항과 같은 일이 있을 시에 필요에 의해 백업 받는 것을 권장한다.

 스크립트를 설명하자면 

 먼저 오프라인에서 백업을 진행하기 위해서는 ALTER 명령어를 위해 해당 데이터파일의 백업을 진행한다는 것을 명시하여야 하고, 

 종료 시 종료 되었다고 명시해 주어야 한다.
 작업이 종료 된 후에는 SCN값이 변경되기 때문에 강제로 CHECKPOINT를 진행하여 SCN 싱크 정보를 맞춰 주어야 한다.



3. 백업 진행

 이제 생성된 스크립트를 이용해 실제 백업을 진행해 보도록 한다.

sqlplus /nolog @/u02/bkg/backup_orcl2.sql

SQL*Plus: Release 10.2.0.1.0 - Production on Wed Feb 8 01:28:26 2012

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Connected.
SQL> ALTER TABLESPACE SYSTEM BEGIN BACKUP ;

Tablespace altered.

SQL> ALTER TABLESPACE UNDOTBS1 BEGIN BACKUP ;

Tablespace altered.

SQL> ALTER TABLESPACE SYSAUX BEGIN BACKUP ;

Tablespace altered.

SQL> ALTER TABLESPACE USERS BEGIN BACKUP ;

Tablespace altered.

SQL> ALTER TABLESPACE EXAMPLE BEGIN BACKUP ;

Tablespace altered.

SQL> host cp /u01/app/oracle/oradata/orcl/users01.dbf /u02/bkg/open/.

SQL> host cp /u01/app/oracle/oradata/orcl/sysaux01.dbf /u02/bkg/open/.

SQL> host cp /u01/app/oracle/oradata/orcl/undotbs01.dbf /u02/bkg/open/.

SQL> host cp /u01/app/oracle/oradata/orcl/system01.dbf /u02/bkg/open/.

SQL> host cp /u01/app/oracle/oradata/orcl/example01.dbf /u02/bkg/open/.

SQL> ALTER TABLESPACE SYSTEM END BACKUP ;

Tablespace altered.

SQL> ALTER TABLESPACE UNDOTBS1 END BACKUP ;

Tablespace altered.

SQL> ALTER TABLESPACE SYSAUX END BACKUP ;

Tablespace altered.

SQL> ALTER TABLESPACE USERS END BACKUP ;

Tablespace altered.

SQL> ALTER TABLESPACE EXAMPLE END BACKUP ;

Tablespace altered.

SQL> ALTER SYSTEM CHECKPOINT ;

System altered.

SQL> exit
Disconnected from Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options

복사가 되었으면 아래와 같이 복사 된 내용을 확인한다.

 ls -l /u02/bkg/open
합계 891816
-rw-r-----  1 oracle oinstall 104865792  2월  8 01:31 example01.dbf
-rw-r-----  1 oracle oinstall 262152192  2월  8 01:29 sysaux01.dbf
-rw-r-----  1 oracle oinstall 503324672  2월  8 01:31 system01.dbf
-rw-r-----  1 oracle oinstall  36708352  2월  8 01:29 undotbs01.dbf
-rw-r-----  1 oracle oinstall   5251072  2월  8 01:28 users01.dbf

 정상적으로 데이터 파일이 복사 되었다.
 
 여기까지 아카이브 로그 모드에서의 온라인 백업이 완료 되었다.
 이제 이 백업 데이터를 이용해 장애 복구 하는 과정을 알아보도록한다.



4. 테스트 테이블 및 데이터 입력

 아래와 같이 HR계정에 테스트 테이블 및 데이터를 입력해 보도록 한다.

conn hr/hr

CREATE TABLE RECO_TEST01(A VARCHAR2(1));
INSERT INTO HR.RECO_TEST01 VALUES ('a');
INSERT INTO HR.RECO_TEST01 VALUES ('b');

COMMIT;

스키마 생성 후 sys계정으로 강제로 로그 스위치와 체크포인트를 변경한다.

alter system switch logfile ;
alter system checkpoint ;
alter system switch logfile ;
alter system checkpoint ;
alter system switch logfile ;
alter system checkpoint ;
alter system switch logfile ;
alter system checkpoint ;
alter system switch logfile ;
alter system checkpoint ;
alter system switch logfile ;
alter system checkpoint ;
alter system switch logfile ;
alter system checkpoint ;
alter system switch logfile ;
alter system checkpoint ;



5. 장애 발생

아래와 같이 OS에서 데이터 파일을 삭제한다.

rm -f /u01/app/oracle/oradata/orcl/example01.dbf
ls -l /u01/app/oracle/oradata/orcl/example01.dbf
ls: /u01/app/oracle/oradata/orcl/example01.dbf: --그런 파일이나 디렉토리가 없음

sqlplus로 접속해 유실된 테이블스페이스의 스키마인 HR계정의 데이터의 조회를 시도한다.

sqlplus / as sysdba

SQL> SELECT * FROM HR.RECO_TEST01;
SELECT * FROM HR.RECO_TEST01
                 *
ERROR at line 1:
ORA-01116: error in opening database file 5
ORA-01110: data file 5: '/u01/app/oracle/oradata/orcl/example01.dbf'
ORA-27041: unable to open file
Linux Error: 2: No such file or directory
Additional information: 3

 데이터 파일이 깨졌기 때문에 데이터 조회가 불가능하다.



6. 복구

 먼저 유실 된 데이터파일이 속한 테이블스페이스를 조회하고 오프라인 상태로 변경한다.

col TABLESPACE_NAME format a50
set linesize 200

SQL> SELECT TABLESPACE_NAME FROM DBA_DATA_FILES
WHERE FILE_NAME LIKE '%example01.dbf%';
 
TABLESPACE_NAME
--------------------------------------------------
EXAMPLE

ALTER TABLESPACE EXAMPLE OFFLINE IMMEDIATE;

 별도의 터미널을 열고 백업된 데이터를 데이터 파일의 경로로 이동 시킨다.

 cp /u02/bkg/open/example01.dbf /u01/app/oracle/oradata/orcl/example01.dbf

 다시 sqlplus에서 아래와 같이 데이터 파일 복구를 시도한다.

SQL> RECOVER TABLESPACE EXAMPLE;
ORA-00279: change 590107 generated at 02/08/2012 01:28:27 needed for thread 1
ORA-00289: suggestion : /u02/arch/1_10_774589334.arc
ORA-00280: change 590107 for thread 1 is in sequence #10


Specify log: {<RET>=suggested | filename | AUTO | CANCEL}
-- 여기서 명령 프롬프트가 깜빡 거림. 이 때 아래와 같이 AUTO를 입력
AUTO
Log applied.
Media recovery complete.

SQL> ALTER TABLESPACE EXAMPLE ONLINE;

 복구가 완료 되었으면 hr계정으로 접속해 데이터를 조회해 본다.

SQL> conn hr/hr
Connected.
SQL> SELECT * FROM RECO_TEST01;

A
---
a
b

 데이터가 정상적으로 조회된다.


 
 복구 과정을 살펴보면, 백업본 이후로 새로운 데이터가 입력되었고, 

 수차례 로그 스위치를 하면서 데이터 변경 이후의 온라인 리두 로그가 없음에도 불구하고 데이터가 정상적으로 복구 되었음을 알 수 있다.

 또, 가장 중요한 점은 아카이브 모드에서의 백업 및 복구는 모두 데이터베이스 오픈 상태에서 이루어 졌다.

 노 아카이브 모드에서 백업 및 복구 모두 데이터베이스 셧다운 이후 이루어졌다는 점에서 이는 아주 큰 차이가 아닐 수 없다.

 데이터가 특정 사용자에 의해 입력되거나 배치성 데이터 입력이 아닌 이상 이런 온라인 백업/복구는 아주 유용한 기능이라 할 수 있다.






출처: http://larcyuki83.tistory.com/entry/BR-4-Archive-log-mode에서의-OS-카피-백업-및-복구?category=417422 [나름 공부하는 블로그]

모든 데이터베이스의 변경 사항은 온라인 리두 로그 파일에 기록되고, 리두 로그 파일이 로그 스위칭 될 때 이 전의 가장 마지막으로 사용된 로그 파일을 아카이브 로그 파일로 백업 시키고 로그 스위칭을 진행 하게 된다.
그렇기 때문에 아카이브 로그 모드에서는 아카이브 모드를 유실하지 않는 다면 완전 복구가 가능하다.
이는 다르게 말한다면 아카이브 로그 모드 이더라도 최종 백업본으로 아카이브 로그가 유실 된다면 어쩔 수 없이 불안전 복구를 해야 하는 상황이 될 수도 있으므로, 항상 아카이브 로그 파일의 관리를 철저히 해야 한다는 것을 명심해야 할 것이다. 물론 아카이브가 되지 않은 온라인 리두 로그 파일도 유실된다면 완전 복구가 불가능한 것은 당연한 얘기이다.

이번 장에서는 아카이브 모드 설정 및 데이터 파일 유실 시 노 아카이브 모드와는 어떻게 다른 방식으로 백업 및 복구를 하는 지에 대해 알아보도록 한다.

1. 아카이브 로그 파일이 생성된 디렉토리 생성

먼저 OS에서 아래와 같이 아카이브 로그가 생성될 디렉토리를 생성한다.

 mkdir -p /u02/arch


2. 아카이브 로그 모드와 관련된 초기화 파라미터 설정

sys계정으로 접속하여 아래와 같이 아카이브 로그 파일의 생성 형식을 지정한다.

sqlplus / as sysdba

SQL>  alter system set log_archive_format='%t_%s_%r.arc' scope=spfile ;
SQL>  alter system set log_archive_dest_1='location=/u02/arch/';

System altered.

-------
========================================
 파일 이름에 사용되는 형식
========================================
      %d: 데이터베이스 이름
      %t: 인스턴스 번호
      %s: Log Sequence 번호
      %r: Resetlogs ID로, 
     리두 로그가 초기화 될 때 지정되는 ID 
 ========================================

(주) 운영 중 특별 세션, 혹은 시스템 상의 상태를 변경할 수 있는 경우에는 scope=memory로 메모리 상태에서 변경이 가능하지만 서버 재시작 시에는 변경 사항이 적용되지 않고, scope=both일 경우 spfile에도 내용이 저장되고 메모리 상태 변경도 가능하다. 
하지만 scope=spfile일 경우 변경 사항을 spfile에만 기록 하고 현재 상태에서 시스템 변경이 일어나지 않는다. 때문에 scope=spfile로 상태를 변경 할 경우는 서버를 재시작하여야 한다.

 SQL> shutdown immediate  -- shutdown abort는 사용못함.
Database closed.
Database dismounted.
ORACLE instance shut down.


3. 아카이브 로그 모드 변경

 이제 아카이브 모드를 설정할 차례다. 아카이브 모드는 데이터베이스의 상태가 mount 상태에만 가능하다.

 먼저 데이터베이스를 mount 단꼐로 startup한다.

 SQL> startup mount
ORACLE instance started.

Total System Global Area  608174080 bytes
Fixed Size                  1220820 bytes
Variable Size             197136172 bytes
Database Buffers          402653184 bytes
Redo Buffers                7163904 bytes
Database mounted.

 아카이브 로그 로그 상태를 확인하면 아래와 같이 No Archive Mode 상태이다.

 SQL> archive log list
Database log mode              No Archive Mode
Automatic archival             Disabled
Archive destination            /u02/arch/
Oldest online log sequence     1
Current log sequence           2

 아카이브 로그 모드를 활성화 시킨 후 아카이브 로그 상태를 확인한다.

SQL> alter database archivelog ;

Database altered.

SQL> archive log list
Database log mode              Archive Mode
Automatic archival             Enabled
Archive destination            /u02/arch/
Oldest online log sequence     1
Next log sequence to archive   2
Current log sequence           2

 데이터베이스가 아카이브 로그로 문제 없이 변경되었음을 확인하였다.
 이제 데이터베이스의 상태를 open 상태로 변경한다.

 SQL> alter database open;

Database altered.

 아래와 같이 강제로 로그 스위칭과 체크포인트를 수차례 시도한다.

SQL> alter system switch logfile ;
SQL> alter system checkpoint ;
SQL> alter system switch logfile ;
SQL> alter system checkpoint ;
SQL> alter system switch logfile ;
SQL> alter system checkpoint ;
SQL> alter system switch logfile ;
SQL> alter system checkpoint ;
SQL> alter system switch logfile ;
SQL> alter system checkpoint ;
SQL> alter system switch logfile ;
SQL> alter system checkpoint ;
SQL> alter system switch logfile ;
SQL> alter system checkpoint ;
SQL> alter system switch logfile ;
SQL> alter system checkpoint ;

 OS터미널에서 아래와 같이 /u02/arch 디렉토리를 조회한다.

ls -l /u02/arch/
합계 14464
-rw-r-----  1 oracle oinstall 14760960  2월  8 01:08 1_2_774589334.arc
-rw-r-----  1 oracle oinstall     2048  2월  8 01:08 1_3_774589334.arc
-rw-r-----  1 oracle oinstall     1024  2월  8 01:08 1_4_774589334.arc
-rw-r-----  1 oracle oinstall     1024  2월  8 01:08 1_5_774589334.arc
-rw-r-----  1 oracle oinstall     1024  2월  8 01:08 1_6_774589334.arc
-rw-r-----  1 oracle oinstall     1024  2월  8 01:08 1_7_774589334.arc
-rw-r-----  1 oracle oinstall     1024  2월  8 01:08 1_8_774589334.arc
-rw-r-----  1 oracle oinstall     1024  2월  8 01:08 1_9_774589334.arc

 위와 같이 .arc 파일들이 생성된다면 성공적으로 아카이브 모드가 활성화 된 것이다.




출처: http://larcyuki83.tistory.com/entry/BR-3-Achive-Mode-설정-및-OS카피를-이용한-데이터-파일-유실-복구?category=417422 [나름 공부하는 블로그]

 컨트롤 파일은 오라클의 인스턴스를 제어하며 오라클의 모든 정보의 위치를 가지고 있는 파일로 데이터 파일과 더불어 가장 중요한 역할을 하는 파일이다.

 이번 장에서는 컨트롤 파일의 백업 및 복구에 대해 알아보도록 한다.

1. 컨트롤 파일 백업


 컨트롤 파일은 Trace형식의 TEXT파일과 BINARY파일을 직접 받는 두가지 형태로 백업을 받을 수 있다.
 두가지 방식의 백업에 대해 알아보도록 한다.



(1) Trace형식 백업


 Trace형식의 백업은 아래와 같이 받을 수 있다.


$ sqlplus / as sysdba

 SQL> ALTER DATABASE BACKUP CONTROLFILE TO TRACE 
   AS '/home/oracle/myctlbkg.trc';

 호스트에서 확인 하면 아래와 같이 Text 형식의 파일이 생성 되었음을 확인 할 수 있다.

 $ cat /home/oracle/myctlbkg.trc
-- The following are current System-scope REDO Log Archival related
-- parameters and can be included in the database initialization file.
--
-- LOG_ARCHIVE_DEST=''
-- LOG_ARCHIVE_DUPLEX_DEST=''
--
-- LOG_ARCHIVE_FORMAT=%t_%s_%r.arc
--
-- DB_UNIQUE_NAME="orcl"
--
-- LOG_ARCHIVE_CONFIG='SEND, RECEIVE, NODG_CONFIG'
-- LOG_ARCHIVE_MAX_PROCESSES=2
-- STANDBY_FILE_MANAGEMENT=MANUAL
-- STANDBY_ARCHIVE_DEST=?/dbs/arch
-- FAL_CLIENT=''
-- FAL_SERVER=''
--
-- LOG_ARCHIVE_DEST_1='LOCATION=/u02/arch/'
-- LOG_ARCHIVE_DEST_1='OPTIONAL REOPEN=300 NODELAY'
-- LOG_ARCHIVE_DEST_1='ARCH NOAFFIRM NOEXPEDITE NOVERIFY SYNC'
-- LOG_ARCHIVE_DEST_1='REGISTER NOALTERNATE NODEPENDENCY'
-- LOG_ARCHIVE_DEST_1='NOMAX_FAILURE NOQUOTA_SIZE NOQUOTA_USED NODB_UNIQUE_NAME'-- LOG_ARCHIVE_DEST_1='VALID_FOR=(PRIMARY_ROLE,ONLINE_LOGFILES)'
-- LOG_ARCHIVE_DEST_STATE_1=ENABLE

--
-- Below are two sets of SQL statements, each of which creates a new
-- control file and uses it to open the database. The first set opens
-- the database with the NORESETLOGS option and should be used only if
-- the current versions of all online logs are available. The second
-- set opens the database with the RESETLOGS option and should be used
-- if online logs are unavailable.
-- The appropriate set of statements can be copied from the trace into
-- a script file, edited as necessary, and executed when there is a
-- need to re-create the control file.
--
--     Set #1. NORESETLOGS case
--
-- The following commands will create a new control file and use it
-- to open the database.
-- Data used by Recovery Manager will be lost.
-- Additional logs may be required for media recovery of offline
-- Use this only if the current versions of all online logs are
-- available.

-- After mounting the created controlfile, the following SQL
-- statement will place the database in the appropriate
-- protection mode:
--  ALTER DATABASE SET STANDBY DATABASE TO MAXIMIZE PERFORMANCE

STARTUP NOMOUNT
CREATE CONTROLFILE REUSE DATABASE "ORCL" NORESETLOGS  ARCHIVELOG
    MAXLOGFILES 16
    MAXLOGMEMBERS 3
    MAXDATAFILES 100
    MAXINSTANCES 8
    MAXLOGHISTORY 292
LOGFILE
  GROUP 1 '/u01/app/oracle/oradata/orcl/redo01.log'  SIZE 50M,
  GROUP 2 '/u01/app/oracle/oradata/orcl/redo02.log'  SIZE 50M,
  GROUP 3 '/u01/app/oracle/oradata/orcl/redo03.log'  SIZE 50M
-- STANDBY LOGFILE

DATAFILE
  '/u01/app/oracle/oradata/orcl/system01.dbf',
  '/u01/app/oracle/oradata/orcl/undotbs01.dbf',
  '/u01/app/oracle/oradata/orcl/sysaux01.dbf',
  '/u01/app/oracle/oradata/orcl/users01.dbf',
  '/u01/app/oracle/oradata/orcl/example01.dbf'
CHARACTER SET KO16KSC5601
;

-- Commands to re-create incarnation table
-- Below log names MUST be changed to existing filenames on
-- disk. Any one log file from each branch can be used to
-- re-create incarnation records.
-- ALTER DATABASE REGISTER LOGFILE '/u02/arch/1_1_562360180.arc';
-- ALTER DATABASE REGISTER LOGFILE '/u02/arch/1_1_773778338.arc';
-- ALTER DATABASE REGISTER LOGFILE '/u02/arch/1_1_774589334.arc';
-- Recovery is required if any of the datafiles are restored backups,
-- or if the last shutdown was not normal or immediate.
RECOVER DATABASE

-- All logs need archiving and a log switch is needed.
ALTER SYSTEM ARCHIVE LOG ALL;

-- Database can now be opened normally.
ALTER DATABASE OPEN;

-- Commands to add tempfiles to temporary tablespaces.
-- Online tempfiles have complete space information.
-- Other tempfiles may require adjustment.
ALTER TABLESPACE TEMP ADD TEMPFILE '/u01/app/oracle/oradata/orcl/temp01.dbf'
     SIZE 20971520  REUSE AUTOEXTEND ON NEXT 655360  MAXSIZE 32767M;
-- End of tempfile additions.
--
--     Set #2. RESETLOGS case
--
-- The following commands will create a new control file and use it
-- to open the database.
-- Data used by Recovery Manager will be lost.
-- The contents of online logs will be lost and all backups will
-- be invalidated. Use this only if online logs are damaged.

-- After mounting the created controlfile, the following SQL
-- statement will place the database in the appropriate
-- protection mode:
--  ALTER DATABASE SET STANDBY DATABASE TO MAXIMIZE PERFORMANCE

STARTUP NOMOUNT
CREATE CONTROLFILE REUSE DATABASE "ORCL" RESETLOGS  ARCHIVELOG
    MAXLOGFILES 16
    MAXLOGMEMBERS 3
    MAXDATAFILES 100
    MAXINSTANCES 8
    MAXLOGHISTORY 292
LOGFILE
  GROUP 1 '/u01/app/oracle/oradata/orcl/redo01.log'  SIZE 50M,
  GROUP 2 '/u01/app/oracle/oradata/orcl/redo02.log'  SIZE 50M,
  GROUP 3 '/u01/app/oracle/oradata/orcl/redo03.log'  SIZE 50M
-- STANDBY LOGFILE

DATAFILE
  '/u01/app/oracle/oradata/orcl/system01.dbf',
  '/u01/app/oracle/oradata/orcl/undotbs01.dbf',
  '/u01/app/oracle/oradata/orcl/sysaux01.dbf',
  '/u01/app/oracle/oradata/orcl/users01.dbf',
  '/u01/app/oracle/oradata/orcl/example01.dbf'
CHARACTER SET KO16KSC5601
;

-- Commands to re-create incarnation table
-- Below log names MUST be changed to existing filenames on
-- disk. Any one log file from each branch can be used to
-- re-create incarnation records.
-- ALTER DATABASE REGISTER LOGFILE '/u02/arch/1_1_562360180.arc';
-- ALTER DATABASE REGISTER LOGFILE '/u02/arch/1_1_773778338.arc';
-- ALTER DATABASE REGISTER LOGFILE '/u02/arch/1_1_774589334.arc';
-- Recovery is required if any of the datafiles are restored backups,
-- or if the last shutdown was not normal or immediate.
RECOVER DATABASE USING BACKUP CONTROLFILE

-- Database can now be opened zeroing the online logs.
ALTER DATABASE OPEN RESETLOGS;

-- Commands to add tempfiles to temporary tablespaces.
-- Online tempfiles have complete space information.
-- Other tempfiles may require adjustment.
ALTER TABLESPACE TEMP ADD TEMPFILE '/u01/app/oracle/oradata/orcl/temp01.dbf'
     SIZE 20971520  REUSE AUTOEXTEND ON NEXT 655360  MAXSIZE 32767M;
-- End of tempfile additions.
--

 정보를 자세히 살펴 보면 데이터 파일 정보, 온라인 리두 로그 정보, 아카이브 로그 정보 등의 아주 중요한 정보들이 설정되어 있음을 알 수 있다.
 이러한 점을 감안 하였을 때, 중요 파일의 변경 사항이 있을 때는 컨트롤 파일의 백업을 해주는 것이 좋다.



(2) BINARY형식 백업

BINARY형식의 백업은 아래와 같이 받을 수 있다.

$ sqlplus / as sysdba

 SQL> ALTER DATABASE BACKUP CONTROLFILE  TO '/home/oracle/control.bkg.20120219' ;

 호스트에서 확인 하면 아래와 같이 파일이 생성되었음을 알 수 있다.

 ls -l /home/oracle/control.bkg.20120219
-rw-r-----  1 oracle oinstall 7061504  2월 19 11:40 /home/oracle/control.bkg.20120219

 BINARY형식의 파일 역시 중요한 파일 정보 변경 시 백업을 받아야 하는 것은 당연한 일이라 할 수 있다.

 컨트롤 파일의 백업은 아래와 같은 변경 시 에는 반드시 해주는 것 이 좋다.
* Tablespace 추가/삭제(단, 삭제전과 삭제후에 각각 백업권장)
* Tablespace 에 Datafile 추가
* Tablespace 모드를 read only , read write로 변경
* Redo log group 추가/삭제
* Redo log member 추가/삭제
* Datafile이나 Redo logfile의 위치를 변경
* 운영환경(예, archvelog mode의 변경처럼)이 변경




2. 컨트롤 파일 복구

컨트롤 파일은 기본적으로 멀티 플랙싱으로 구성되어 있다.
멀티 플랙싱은 기본적으로 동일한 파일을 여러개로 만들어 하나가 유실되더라도 다른 파일을 바로 이용하거나, 복구를 하는데 사용할 수 있도록 용도로 사용된다.
오라클에서는 컨트롤 파일과 백업이 되지 않는 온라인 리두 로그 파일은 멀티플랙싱 구성을 할 것을 적극적으로 권장한다. 

이러한 구조이기 때문에 컨트롤 파일의 유실은 기본적으로 두가지로 볼 수 있다.
 첫번째로 컨트롤 파일 그룹 중 일부 파일 유실이고, 두번째로는 전체 컨트롤 파일의 유실이다.
 두가지 유실에 대해 복구하는 방법에 대해 알아보도록 한다.

(1) 일부 파일 유실

일부 파일의 유실은 전체 파일에 비해 치명적인 장애는 아니라고 할 수 있다. 먼저 컨트롤 파일을 확인 하고 일부 파일을 지워보도록 한다.


 $ sqlplus / as sysdba

SQL> select name from v$controlfile ;
SQL> col name format a100
SQL> select name from v$controlfile ;

NAME
----------------------------------------------------
/u01/app/oracle/oradata/orcl/control01.ctl
/u01/app/oracle/oradata/orcl/control02.ctl
/u01/app/oracle/oradata/orcl/control03.ctl

 위의 파일 중 control03.ctl 파일을 삭제 해 보도록 한다.

$ rm -f /u01/app/oracle/oradata/orcl/control03.ctl
$ ls -l /u01/app/oracle/oradata/orcl/c*
-rw-r-----  1 oracle oinstall 7061504  2월 19 11:57 /u01/app/oracle/oradata/orcl/control01.ctl
-rw-r-----  1 oracle oinstall 7061504  2월 19 11:57 /u01/app/oracle/oradata/orcl/control02.ctl

control03.ctl 파일이 삭제 되었음을 알 수 있다.

 컨트롤 파일의 일부를 유실 하였지만, 아래와 같이 HR계정으로 조회 및 DML작업을 한다 해도 큰 이슈가 없이 작동이 됨을 확인 할 수 있다.

 SQL> conn hr/hr
Connected.
SQL> select last_name from employees where rownum = 1;

LAST_NAME
----------------------
Abel

SQL> insert into regions values (1000, 'TEST_REGION');
1 row created.

  한개의 컨트롤파일이 유실 되었지만, 나머지 컨트롤 파일이 있기 때문에 데이터베이스가 작업을 하는 데 있어 문제 없이 동작을 할 수 는 있다.
 하지만 그렇다고 장애 상황이 아닌 것 은 아니다.
 아래와 같이 데이터베이스를 정상 종료하려고 하면 종료가 되지 않는 다.

 SQL> shutdown immediate;
ORA-00210: cannot open the specified control file
ORA-00202: control file: '/u01/app/oracle/oradata/orcl/control03.ctl'
ORA-27041: unable to open file
Linux Error: 2: No such file or directory
Additional information: 3


 이러한 상황에서 비 정상 종료가 발생하면 아래와 같이 데이터베이스 기동을 할 수 없다.

 SQL> shutdown immediate;
ORA-00210: cannot open the specified control file
ORA-00202: control file: '/u01/app/oracle/oradata/orcl/control03.ctl'
ORA-27041: unable to open file
Linux Error: 2: No such file or directory
Additional information: 3


 이러한 상황에서 데이터베이스를 기동하려고 하면 기동이 되지 않는 다.

 SQL> shutdown abort
ORACLE instance shut down.
SQL> startup
ORACLE instance started.

Total System Global Area  608174080 bytes
Fixed Size                  1220820 bytes
Variable Size             230690604 bytes
Database Buffers          369098752 bytes
Redo Buffers                7163904 bytes
ORA-00205: error in identifying control file, check alert log for more info

 기본적으로 3개의 컨트롤 파일을 멀티플랙싱으로 구성이 되어 있는 데, 이 중 일부가 없기 때문에 설정과 맞지 않아 오픈이 불가능한 상황이다.
 하지만 복구의 큰 무리는 없다.
 기존 컨트롤 파일 그룹은 운영 되면서 지속적으로 데이터파일의 정보를 기록했기 때문에 이 파일을 이용해 유실된 파일로 그대로 복사하고 오픈 시키기만 하면 된다.

 cp /u01/app/oracle/oradata/orcl/control01.ctl /u01/app/oracle/oradata/orcl/control03.ctl


SQL> alter database mount;

Database altered.

SQL> alter database open;

Database altered.

데이터베이스의 오픈 과정은 아래와 같다.

NOMOUNT : SPFILE의 파라미터 파일을 읽어 인스턴스 기동
MOUNT :  컨트롤 파일 들의 정보를 일거 데이터베이스의 SYNC확인.
OPEN :  SYNC가 정상적일 경우 OPEN. 정상적이지 않을 경우 SMON에 의해 REDO로그를 이용해 SYNC일치 후 데이터 베이스 OPEN

 위와 같은 순서로 데이터베이스를 OPEN하기 때문에 컨트롤 파일이 유실된다면 MOUNT상태로 상태를 변경 할 수 없다. 즉, 이 상태로 startup을 했을 시에는 NOMOUNT 상태 였다는 것 이다.
 그렇기 때문에 데이터베이스의 상태를 단계적으로 MOUNT > OPEN 순으로 변경하게 된 것이다.

 이렇듯 멀티플랙싱 되어 있을 때 일부 파일의 유실은 운영에도 문제가 없고, 비정상 종료가 되었다 하더라도 복구도 어렵지 않은 치명적이지 않은 복구라 할 수 있다.



(2) 전체 컨트롤 파일 유실에 대한 복구

컨트롤 파일 전체 복구는 위의 백업을 이용해 다시 두가지 방법으로 나눠 복구 할 수 있다.


(2-1) Trace백업을 이용한 복구

 먼저 Trace백업을 이용해 복구를 시도해 보도록 한다.
 아까 생성한 trc 파일을 조회하여 컨트롤 파일에 복구에 필요한 정보를 복사한다.

$ vi ctlrecreate.sql

 CREATE CONTROLFILE REUSE DATABASE "ORCL" RESETLOGS  ARCHIVELOG
    MAXLOGFILES 16
    MAXLOGMEMBERS 3
    MAXDATAFILES 100
    MAXINSTANCES 8
    MAXLOGHISTORY 292
LOGFILE
  GROUP 1 '/u01/app/oracle/oradata/orcl/redo01.log'  SIZE 50M,
  GROUP 2 '/u01/app/oracle/oradata/orcl/redo02.log'  SIZE 50M,
  GROUP 3 '/u01/app/oracle/oradata/orcl/redo03.log'  SIZE 50M
DATAFILE
  '/u01/app/oracle/oradata/orcl/system01.dbf',
  '/u01/app/oracle/oradata/orcl/undotbs01.dbf',
  '/u01/app/oracle/oradata/orcl/sysaux01.dbf',
  '/u01/app/oracle/oradata/orcl/users01.dbf',
  '/u01/app/oracle/oradata/orcl/example01.dbf',
  '/u01/app/oracle/oradata/orcl/examspace.dbf'
CHARACTER SET KO16KSC5601
;

아래와 같이 전체 컨트롤 파일을 삭제한다.

$ rm -f /u01/app/oracle/oradata/orcl/*.ctl
$ ls -l /u01/app/oracle/oradata/orcl/*.ctl
ls: /u01/app/oracle/oradata/orcl/*.ctl: 그런 파일이나 디렉토리가 없습니다.

이 상황에서 장애가 발생하여 비정상 종료를 실행한다.

SQL> shutdown abort
ORACLE instance shut down.

당연히 startup을 실행하면 정상적으로 실행되지 않는 다.
컨트롤 파일이 전부 유실 되었기 때문이다.

 startup
ORACLE instance started.

Total System Global Area  608174080 bytes
Fixed Size                  1220820 bytes
Variable Size             234884908 bytes
Database Buffers          364904448 bytes
Redo Buffers                7163904 bytes
ORA-00205: error in identifying control file, check alert log for more info

데이터베이스의 상태를 mount 모드로 변경한다.
데이터베이스 복구를 시도하려고 하면 다음과 같이 에러가 나타난다.

 SQL> recover database;
ORA-00283: recovery session canceled due to errors
ORA-01610: recovery using the BACKUP CONTROLFILE option must be done

 아까 백업해 둔 컨트롤 파일 생성 스크립트를 실행한다.

 SQL> @/home/oracle/ctlrecreate.sql

Control file created.


 데이터베이스 복구를 시도하려고 하면 다음과 같이 에러가 나타난다.

 SQL> recover database;
ORA-00283: recovery session canceled due to errors
ORA-01610: recovery using the BACKUP CONTROLFILE option must be done


 아카이브된 로그 /u02/arch/1_6_773778338.arc을 사용하거나, 현재 온라인 리두 로그의 SEQUNCE #6번을 이용해 복구하라는 내용이다.
 먼저 다음 아카이브 로그 파일이 존재하는지 확인해 본다.

$ ls -l /u02/arch
합계 11936
-rw-r-----  1 oracle oinstall 12206080  2월 20 21:44 1_5_773778338.arc


컨트롤 파일 복구는 BACKUP CONTROLFILE 옵션을 사용해 복구해야 된다는 내용이다.
아래와 같이 BACKUP CONTROLFILE 옵션을 사용하려고 하면 다음과 같은 에러가 발생한다.

 SQL> recover database using backup controlfile ;
ORA-00279: change 559805 generated at 02/20/2012 21:44:21 needed for thread 1
ORA-00289: suggestion : /u02/arch/1_6_773778338.arc
ORA-00280: change 559805 for thread 1 is in sequence #1

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}


 아카이브 로그 파일에는 /u02/arch/1_6_773778338.arc 파일이 존재 하지 않는 다. 복구에 필요한 로그 파일이 아직 아카이브 되지 않았다는 의미이다.

 recover를 잠시 CANCEL하고 온라인 리두 로그의 SEQUNCE#가 6인 파일이 몇번 그룹에 있는 지 확인해 보도록 한다.

 SQL> select group#, SEQUENCE#, STATUS from v$log where SEQUENCE#=1;

    GROUP#  SEQUENCE#    STATUS
---------- --------------- ------------------------------------------------
         2               1               CURRENT

현재 6번 SEQUNCE에 해당하는 로그는 2번 그룹임을 확인 하였다.
이제 2번 그룹의 온라인 리두 로그가 어디에 있는 지 확인해 보도록 한다.

 select member from v$logfile where group#=2 ;
MEMBER
-----------------------------------------------------
/u01/app/oracle/oradata/orcl/redo02.log

 이제 확인한 온라인 리두 정보를 기준으로 다시 복구를 시도한다.
(Specify log: {<RET>=suggested | filename | AUTO | CANCEL} 가 뜨고 프롬프트가 활성화 되 었을 때 /u01/app/oracle/oradata/orcl/redo02.log 입력

 SQL> recover database using backup controlfile ;
ORA-00279: change 559805 generated at 02/20/2012 21:44:21 needed for thread 1
ORA-00289: suggestion : /u02/arch/1_6_773778338.arc
ORA-00280: change 559805 for thread 1 is in sequence #1

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}
/u01/app/oracle/oradata/orcl/redo02.log
Log applied.
Media recovery complete.

 오픈을 시도하면 아래와 같이 RESETLOGS 혹은 NORESETLOGS 옵션을 사용해 복구하라는 에러가 발생한다.

SQL> alter database open;
alter database open
*
ERROR at line 1:
ORA-01589: must use RESETLOGS or NORESETLOGS option for database open

 로그를 이용해 복구하였으므로 RESETLOGS 옵션으로 오픈을 시도해 보도록 한다.

 SQL> alter database open resetlogs;

Database altered.

 정상적으로 복구를 완료 하였다.



 (2-2)BINARY백업을 이용한 복구

 이번에는 BINARY백업을 이용한 복구를 시도해보도록 한다.
 아래와 같이 전체 컨트롤 파일을 삭제한다.

 rm -f /u01/app/oracle/oradata/orcl/*.ctl
[oracle@localhost:orcl:~]$ ls -l /u01/app/oracle/oradata/orcl/*.ctl
ls: /u01/app/oracle/oradata/orcl/*.ctl: 그런 파일이나 디렉토리가 없음


이 상황에서 장애가 발생하여 비정상 종료를 실행한다.

 SQL> shutdown abort
ORACLE instance shut down.

당연히 startup을 실행하면 정상적으로 실행되지 않는 다.
컨트롤 파일이 전부 유실 되었기 때문이다.

 startup
ORACLE instance started.

Total System Global Area  608174080 bytes
Fixed Size                  1220820 bytes
Variable Size             234884908 bytes
Database Buffers          364904448 bytes
Redo Buffers                7163904 bytes
ORA-00205: error in identifying control file, check alert log for more info

 이제 복구를 시작한다.
 먼저 아까 해둔 백업 파일을 이용해 컨트롤 파일에 경로에 맞게 복사한다.

$ cp /home/oracle/control.bkg.20120219 /u01/app/oracle/oradata/orcl/control01.ctl
$ cp /home/oracle/control.bkg.20120219 /u01/app/oracle/oradata/orcl/control02.ctl
$ cp /home/oracle/control.bkg.20120219 /u01/app/oracle/oradata/orcl/control03.ctl


 데이터베이스의 상태를 mount 모드로 변경한다.
 데이터베이스 복구를 시도하려고 하면 다음과 같이 에러가 나타난다.

 SQL> recover database;
ORA-00283: recovery session canceled due to errors
ORA-01610: recovery using the BACKUP CONTROLFILE option must be done

 컨트롤 파일 복구는 BACKUP CONTROLFILE 옵션을 사용해 복구해야 된다는 내용이다.
 아래와 같이 BACKUP CONTROLFILE 옵션을 사용하려고 하면 다음과 같은 에러가 발생한다.

 SQL> recover database using backup controlfile ;
ORA-00279: change 559805 generated at 02/20/2012 21:44:21 needed for thread 1
ORA-00289: suggestion : /u02/arch/1_6_773778338.arc
ORA-00280: change 559805 for thread 1 is in sequence #6

Specify log: {<RET>=suggested | filename | AUTO | CANCEL}

 아카이브된 로그 /u02/arch/1_6_773778338.arc을 사용하거나, 현재 온라인 리두 로그의 SEQUNCE #6번을 이용해 복구하라는 내용이다.
 먼저 다음 아카이브 로그 파일이 존재하는지 확인해 본다.

$ ls -l /u02/arch
합계 11936
-rw-r-----  1 oracle oinstall 12206080  2월 20 21:44 1_5_773778338.arc

 아카이브 로그 파일에는 /u02/arch/1_6_773778338.arc 파일이 존재 하지 않는 다. 복구에 필요한 로그 파일이 아직 아카이브 되지 않았다는 의미이다.
 recover를 잠시 CANCEL하고 온라인 리두 로그의 SEQUNCE#가 6인 파일이 몇번 그룹에 있는 지 확인해 보도록 한다.

 SQL> select group#, SEQUENCE#, STATUS from v$log where SEQUENCE#=6;

    GROUP#  SEQUENCE#    STATUS
---------- --------------- ------------------------------------------------
         2               6               CURRENT

현재 6번 SEQUNCE에 해당하는 로그는 2번 그룹임을 확인 하였다.
이제 2번 그룹의 온라인 리두 로그가 어디에 있는 지 확인해 보도록 한다.

 select member from v$logfile where group#=2 ;
MEMBER
-----------------------------------------------------
/u01/app/oracle/oradata/orcl/redo02.log

 이제 확인한 온라인 리두 정보를 기준으로 다시 복구를 시도한다.
(Specify log: {<RET>=suggested | filename | AUTO | CANCEL} 가 뜨고 프롬프트가 활성화 되 었을 때 /u01/app/oracle/oradata/orcl/redo02.log 입력

 SQL> recover database using backup controlfile ;
ORA-00279: change 559805 generated at 02/20/2012 21:44:21 needed for thread 1
ORA-00289: suggestion : /u02/arch/1_6_773778338.arc
ORA-00280: change 559805 for thread 1 is in sequence #6


Specify log: {<RET>=suggested | filename | AUTO | CANCEL}
/u01/app/oracle/oradata/orcl/redo02.log
Log applied.
Media recovery complete.

 오픈을 시도하면 아래와 같이 RESETLOGS 혹은 NORESETLOGS 옵션을 사용해 복구하라는 에러가 발생한다.

SQL> alter database open;
alter database open
*
ERROR at line 1:
ORA-01589: must use RESETLOGS or NORESETLOGS option for database open

 로그를 이용해 복구하였으므로 RESETLOGS 옵션으로 오픈을 시도해 보도록 한다.

 SQL> alter database open resetlogs;

Database altered.

 정상적으로 복구를 완료 하였다.

 


출처: http://larcyuki83.tistory.com/entry/BR6-Confrol-File-백업-및-복구 [나름 공부하는 블로그]

튜닝 전

  아래 SQL문장은 OLTP환경에서 부분범위를 처리하기 위한 예제이다.

  Trace를 확인한 결과 901건이 추출되었으며. 이 중 화면상에 설계된 15건만 부분범위 처리하여 보여주고자 한다.

 
SELECT D.DNAME,
       EMP_V.DEPTNO,
       EMP_V.HIREDATE,
       EMP_V.MIN_JOB       
FROM   (SELECT E.DEPTNO,
               TO_CHAR(E.HIREDATE, 'YYYYMMDD') HIREDATE,
               MIN(E.JOB) MIN_JOB
        FROM   EMP E
        GROUP BY E.DEPTNO,
                 TO_CHAR(E.HIREDATE, 'YYYYMMDD')) EMP_V,
       DEPT D
WHERE  EMP_V.DEPTNO = D.DEPTNO
AND    D.DNAME      = :B1;

Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.001          0          0          0          0
Execute      1    0.000        0.004          0          0          0          0
Fetch       92   43.910       45.473       6989      44244          0        901
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total       94   43.910       45.477       6989      44244          0        901

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  STATEMENT
    901   HASH JOIN  (cr=44244 pr=6989 pw=0 time=44110198 us)
      1    TABLE ACCESS FULL DEPT (cr=3 pr=0 pw=0 time=126 us)
1000007    VIEW  (cr=44241 pr=6989 pw=0 time=46107766 us)
1000007     HASH GROUP BY (cr=44241 pr=6989 pw=0 time=45107756 us)
10000000      TABLE ACCESS FULL EMP (cr=44241 pr=6989 pw=0 time=10000130 us)
    

1차 개선 - ROWNUM 사용

  1차로 위 SQL문에서 최종 SQL에 ROWNUM을 넣은 뒤 인라인 뷰로 만든 다음 밖에서 화면 설계된 로우수 만큼 추출하는 조건을 주었다. 하지만 이 조건만으로는 부분범위 처리가 되지 않는다.

  부분범위 처리 조건은 실행계획에서 모든 조인은 NL 조인이 되어야 하며, 만약 HASH JOIN이 하나라도 있으면 부분범위처리가 안 된다.

 
SELECT *
FROM   (SELECT D.DNAME,
               EMP_V.DEPTNO,
               EMP_V.HIREDATE,
               EMP_V.MIN_JOB,
               ROWNUM CNT      
        FROM   (SELECT E.DEPTNO,
                       TO_CHAR(E.HIREDATE, 'YYYYMMDD') HIREDATE,
                       MIN(E.JOB) MIN_JOB
                FROM   EMP E
                GROUP BY E.DEPTNO,
                         TO_CHAR(E.HIREDATE, 'YYYYMMDD')) EMP_V,
               DEPT D
        WHERE  EMP_V.DEPTNO = D.DEPTNO
        AND    D.DNAME      = :B1)
WHERE  CNT <= 15;

-- Trace 내용
Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.002          0          0          0          0
Execute      1    0.000        0.004          0          0          0          0
Fetch        3   43.170       42.211          0      44244          0         15
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total        5   43.170       42.218          0      44244          0         15

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  STATEMENT
     15   VIEW  (cr=44244 pr=0 pw=0 time=40881287 us)  -- ROWNUM 적용
    901    COUNT  (cr=44244 pr=0 pw=0 time=40883966 us)
-- HASH JOIN이 적용되어 부분범위 처리를 하지 못하고 EMP 테이블을 모두 액세스함    
    901     HASH JOIN  (cr=44244 pr=0 pw=0 time=40883056 us)
      1      TABLE ACCESS FULL DEPT (cr=3 pr=0 pw=0 time=131 us)
1000007      VIEW  (cr=44241 pr=0 pw=0 time=41880601 us)
1000007       HASH GROUP BY (cr=44241 pr=0 pw=0 time=41880596 us)
10000000       TABLE ACCESS FULL EMP (cr=44241 pr=0 pw=0 time=114 us)  

-- XPLAN 내용
---------------------------------------------------------------------------------------
| Id  | Operation              | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |      |   706K|    40M|       | 40769   (4)| 00:08:10 |
|*  1 |  VIEW                  |      |   706K|    40M|       | 40769   (4)| 00:08:10 |
|   2 |   COUNT                |      |       |       |       |            |          |
|*  3 |    HASH JOIN           |      |   706K|    15M|       | 40769   (4)| 00:08:10 |
|*  4 |     TABLE ACCESS FULL  | DEPT |     1 |    11 |       |     3   (0)| 00:00:01 |
|   5 |     VIEW               |      |  3534K|    40M|       | 40731   (4)| 00:08:09 |
|   6 |      HASH GROUP BY     |      |  3534K|    43M|   311M| 40731   (4)| 00:08:09 |
|   7 |       TABLE ACCESS FULL| EMP  |    10M|   124M|       | 12240   (3)| 00:02:27 |
---------------------------------------------------------------------------------------
                                                                                       
Predicate Information (identified by operation id):                                    
---------------------------------------------------                                    
                                                                                       
   1 - filter("CNT"<=15)                                                               
   3 - access("EMP_V"."DEPTNO"="D"."DEPTNO")                                           
   4 - filter("D"."DNAME"=:B1) 

-- 위의 내용을 보면 HASH JOIN 부분인 Id 3번에 조인키인 3 - access("EMP_V"."DEPTNO"="D"."DEPTNO") 
-- 내용이 있는것을 확인할 수 있으며, 이를 통해 조인키가 EMP_V 인라인 뷰 안으로 Merge가 
-- 되지 못하였다는 것을 알 수 있으므로 가급적 NL 조인이 되도록 만들어야 한다.
    

2차 개선 - NL 조인 유도하기 위해 힌트 사용

  1차 개선에서 HASH JOIN 때문에 조건이 EMP_V 인라인 뷰 안으로 Merge가 되지 않았으므로 USE_NL 힌트를 사용해 NL 조인이 되도록 유도한다.

 
SELECT *
FROM   (SELECT /*+ LEADING(D) USE_NL(D EMP_V) PUSH_PRED(EMP_V) INDEX(EMP_V.E EMP_N1) */
               D.DNAME,
               EMP_V.DEPTNO,
               EMP_V.HIREDATE,
               EMP_V.MIN_JOB,
               ROWNUM CNT      
        FROM   (SELECT E.DEPTNO,
                       TO_CHAR(E.HIREDATE, 'YYYYMMDD') HIREDATE,
                       MIN(E.JOB) MIN_JOB
                FROM   EMP E
                GROUP BY E.DEPTNO,
                         TO_CHAR(E.HIREDATE, 'YYYYMMDD')) EMP_V,
               DEPT D
        WHERE  EMP_V.DEPTNO = D.DEPTNO
        AND    D.DNAME      = :B1  
        )
WHERE  CNT <= 15;

-- Trace 내용
Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.002          0          0          0          0
Execute      1    0.000        0.004          0          0          0          0
Fetch        3   32.600       31.880          0      44245          0         15
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total        5   32.600       31.886          0      44245          0         15

Misses in library cache during parse: 1
Optimizer goal: ALL_ROWS
Parsing user: APPS (ID=44)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  STATEMENT
     15   VIEW  (cr=44245 pr=0 pw=0 time=30636573 us)
    901    COUNT  (cr=44245 pr=0 pw=0 time=30639256 us)
    901     NESTED LOOPS  (cr=44245 pr=0 pw=0 time=30638349 us)
      1      TABLE ACCESS FULL DEPT (cr=4 pr=0 pw=0 time=204 us)
    901      VIEW  (cr=44241 pr=0 pw=0 time=30637328 us)
1000007       SORT GROUP BY (cr=44241 pr=0 pw=0 time=31636297 us)
10000000        TABLE ACCESS FULL EMP (cr=44241 pr=0 pw=0 time=113 us)


-- 위에서 NESTED LOOPS이 되었음에도 불구하고 EMP_V 인라인 뷰 안의 EMP 테이블을 
-- 여전히 TABLE ACCESS FULL을 하고 있으며, 부분범위처리 또한 되지 않았다.
-- 그 이유를 확인하기 위해 아래에서 XPLAN의 Predicate Information 내용을 살펴보자.

-- XPLAN 내용
---------------------------------------------------------------------------------------
| Id  | Operation              | Name | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |      |   706K|    40M|       | 40734   (4)| 00:08:09 |
|*  1 |  VIEW                  |      |   706K|    40M|       | 40734   (4)| 00:08:09 |
|   2 |   COUNT                |      |       |       |       |            |          |
|   3 |    NESTED LOOPS        |      |   706K|    15M|       | 40734   (4)| 00:08:09 |
|*  4 |     TABLE ACCESS FULL  | DEPT |     1 |    11 |       |     3   (0)| 00:00:01 |
|*  5 |     VIEW               |      |   706K|  8284K|       | 40731   (4)| 00:08:09 |
|   6 |      SORT GROUP BY     |      |  3534K|    43M|   311M| 40731   (4)| 00:08:09 |
|   7 |       TABLE ACCESS FULL| EMP  |    10M|   124M|       | 12240   (3)| 00:02:27 |
---------------------------------------------------------------------------------------
                                                                                       
Predicate Information (identified by operation id):                                    
---------------------------------------------------                                    
                                                                                       
   1 - filter("CNT"<=15)                                                               
   4 - filter("D"."DNAME"=:B1)                                                         
   5 - filter("EMP_V"."DEPTNO"="D"."DEPTNO") 


-- Id 5번을 보면 HASH에서 NL로 변경되었음에도 불구하고 5 - filter("EMP_V"."DEPTNO"="D"."DEPTNO") 
-- 내용처럼 조인키가 Merge가 되지 않았다. 왜 NL 조인으로 변경하였음에도 불구하고 조인키가 
-- Merge가 되지 않았을까? 그 이유는 바로 EMP_V 인라인뷰에 그룹함수가 있기 때문이다.
-- 일반적으로 View Merging이 되기 위해서는 아래의 제약조건이 없어야 하는데 이를 일부 살펴보면 
-- 다음과 같다.

① 집합 연산자를 사용할 경우(union, intersect, minus)
② connect by 절을 사용할 경우
③ rownum을 사용할 경우
④ 그룹함수를 사용할 경우(avg, count, max, min, sum)
⑤ 분석함수를 사용할 경우(row_number, rank, ...)

-- 이 중 여기서의 문제는 4번째 Case인 그룹함수를 EMP_V에서 사용했기 때문에 Merge가 일어나지 못하고 
-- 있는데, 일반적으로 위의 구문을 사용할경우 옵티마이저는 가급적 해당 집합을 먼저 구성한 다음 
-- 조인을 해야 데이터 정합성을 해치지 않기 때문에 View Merging을 가급적 시도하지 않는다.

-- 하지만 이런 그룹함수들도 분석함수를 사용할 경우 Complex View Merging을 통해 옵티마이저가 
-- Merging을 시키도록 할 수 있다. 
    

3차 개선 - 그룹함수를 분석함수를 사용하여 변경

  여기서는 그룹함수의 MIN을 분석함수의 ROW_NUMBER를 통해 변경하는 방법을 살펴보려고 한다. 변경내용에만 집중하기 위해 EMP_V 인라인 뷰 내용만 가지고 설명을 하고자 한다.

 
-- 1. EMP_V 인라인 뷰 그룹함수 버전
SELECT E.DEPTNO,
       TO_CHAR(E.HIREDATE, 'YYYYMMDD') HIREDATE,
       MIN(E.JOB) MIN_JOB
FROM   EMP E
GROUP  BY E.DEPTNO,
          TO_CHAR(E.HIREDATE, 'YYYYMMDD');

-- 위의 내용을 보면 DEPTNO, HIREDATE 2개의 컬럼을 Grouping 지은 후, 
-- 그 중 JOB 컬럼 값 중 최소값(MIN)을 가져오는 내용이다.
-- 이를 분석함수로 대체할 경우 어떻게 하면 될까? 바로 ROW_NUMBER() 함수를 
-- 사용하여 JOB 컬럼값을 Asc로 ORDER BY 시킨 후 첫 번째 값만 가져오도록 변경해주면 된다.

-- 2. EMP_V 인라인 뷰 분석함수 버전
SELECT INLINE_EMP.DEPTNO,
       INLINE_EMP.HIREDATE,
       INLINE_EMP.MIN_JOB
FROM   (SELECT E.DEPTNO,
               TO_CHAR(E.HIREDATE, 'YYYYMMDD') HIREDATE,
               E.JOB MIN_JOB,
               ROW_NUMBER() OVER(PARTITION BY E.DEPTNO, TO_CHAR(E.HIREDATE, 'YYYYMMDD') ORDER BY E.JOB) GUBN
        FROM   EMP E) INLINE_EMP
WHERE  INLINE_EMP.GUBN = 1;


-- 즉, 위의 내용처럼 PARTITION BY를 통해 2개의 컬럼을 Grouping 시킨 후 ORDER BY 한 결과를 
-- 밖에서 1 값만 취득하면 그룹함수의 MIN()과 동일한 효과를 나타낼 수 있으며,
-- 이를 통해 EMP_V 인라인 뷰 안으로 조인키인 DEPTNO 값이 Merge 되도록 할 수 있다.  
    

4차 개선 - EMP_V 인라인 뷰의 그룹함수를 분석함수로 대체

  이제 위에서 EMP_V 인라인 뷰의 그룹함수를 분석함수로 대체하여 다시 테스트를 해보자.

 
SELECT *
FROM   (SELECT /*+ LEADING(D) 
                   USE_NL(D EMP_V) 
                   PUSH_PRED(EMP_V) 
                   INDEX(EMP_V.INLINE_EMP.E EMP_N1) */
               D.DNAME,
               EMP_V.DEPTNO,
               EMP_V.HIREDATE,
               EMP_V.MIN_JOB               
        FROM   (SELECT INLINE_EMP.DEPTNO,
                       INLINE_EMP.HIREDATE,
                       INLINE_EMP.MIN_JOB
                FROM   (SELECT E.DEPTNO,
                               TO_CHAR(E.HIREDATE, 'YYYYMMDD') HIREDATE,
                               E.JOB MIN_JOB,
                               ROW_NUMBER() OVER(PARTITION BY E.DEPTNO, 
                                                              TO_CHAR(E.HIREDATE, 'YYYYMMDD') 
                                                 ORDER BY E.JOB) GUBN
                        FROM   EMP E) INLINE_EMP
                WHERE  INLINE_EMP.GUBN = 1) EMP_V,
               DEPT D
        WHERE  EMP_V.DEPTNO = D.DEPTNO
        AND    D.DNAME      = :B1)
WHERE  ROWNUM <= 15;    

-- Trace 내용
Call     Count CPU Time Elapsed Time       Disk      Query    Current       Rows
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Parse        1    0.000        0.003          0          0          0          0
Execute      1    0.000        0.010          0          0          0          0
Fetch        3    0.060        0.055         18         59          0         15
------- ------ -------- ------------ ---------- ---------- ---------- ----------
Total        5    0.060        0.068         18         59          0         15

Misses in library cache during parse: 1
Optimizer goal: ALL_ROWS
Parsing user: APPS (ID=44)

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  STATEMENT
     15   COUNT STOPKEY (cr=59 pr=18 pw=0 time=53933 us)
     15    NESTED LOOPS  (cr=59 pr=18 pw=0 time=53924 us)
      1     TABLE ACCESS FULL DEPT (cr=3 pr=0 pw=0 time=85 us)
-- View Merging이 되어 PUSHED PREDICATE 실행계획이 나타남      
     15     VIEW PUSHED PREDICATE  (cr=56 pr=18 pw=0 time=53834 us)
    132      WINDOW SORT PUSHED RANK (cr=56 pr=18 pw=0 time=54082 us)
   9000       TABLE ACCESS BY INDEX ROWID EMP (cr=56 pr=18 pw=0 time=18068 us)
   9000        INDEX RANGE SCAN EMP_N1 (cr=21 pr=18 pw=0 time=9053 us)(Object ID 6720703)   


-- 이제 다시 Trace 내용을 살펴보면 좀 전까지 View로 막혔던 내용이 'VIEW PUSHED PREDICATE'라는 
-- 내용으로 변경되었으며, EMP_V 인라인 뷰 안의 EMP 테이블이 FULL SCAN이 아닌 
-- INDEX RANGE SCAN으로 변경되었음을 알 수 있다.
-- 또한, 우리가 원하는 부분범위처리가 되어 EMP 테이블을 천만건 액세스하는 것이 아닌 9,000건만 
-- 액세스하여 수행속도를 기존 45초에서 0.068초로 감소된것을 확인할 수 있다.

-- 우리가 원하는 실행계획을 통해 부분범위처리가 되었긴 하지만, 
-- 실제 조건이 잘 들어갔는지 확인하기 위해 XPLAN을 다시 살펴보자.

-- XPLAN 내용
--------------------------------------------------------------------------------------------------      
| Id  | Operation                       | Name   | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |      
--------------------------------------------------------------------------------------------------      
|   0 | SELECT STATEMENT                |        |    15 |   765 |       |     4   (0)| 00:00:01 |      
|*  1 |  COUNT STOPKEY                  |        |       |       |       |            |          |      
|   2 |   NESTED LOOPS                  |        |  1111K|    54M|       |     4   (0)| 00:00:01 |      
|*  3 |    TABLE ACCESS FULL            | DEPT   |     1 |    14 |       |     3   (0)| 00:00:01 |      
|*  4 |    VIEW PUSHED PREDICATE        |        |     3 |   111 |       |     1   (0)| 00:00:01 |      
|*  5 |     WINDOW SORT PUSHED RANK     |        |  2001K|    24M|    91M| 26239   (2)| 00:05:15 |      
|   6 |      TABLE ACCESS BY INDEX ROWID| EMP    |  2001K|    24M|       | 16592   (1)| 00:03:20 |      
|*  7 |       INDEX RANGE SCAN          | EMP_N1 |  2018K|       |       |  3986   (2)| 00:00:48 |      
--------------------------------------------------------------------------------------------------      

Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter(ROWNUM<=15)                                                                               
   3 - filter("D"."DNAME"=:B1)                                                                          
   4 - filter("INLINE_EMP"."GUBN"=1)                                                                    
   5 - filter(ROW_NUMBER() OVER ( PARTITION BY                                                          
              "E"."DEPTNO",TO_CHAR(INTERNAL_FUNCTION("E"."HIREDATE"),'YYYYMMDD') 
               ORDER BY "E"."JOB")<=1)
   7 - access("E"."DEPTNO"="D"."DEPTNO") -- 조인 조건이 뷰 안으로 침투가 된 것이 확인됨

-- 위의 내용에서 보듯이 Id 4번에 조인키가 위치해 있지 않고 Id 7번에 조인키가 위치해 
-- 있는것으로 보아 Merge가 잘 되었음을 확인할 수 있다.
-- 이처럼, 튜닝을 진행할 때는 우리가 원하는 방향을 설정한 후 분할&정복 방식으로 진행하는 
-- 것이 좋으며, 실행계획 유도가 안 될 경우 그 이유가 무엇인지 XPLAN 정보를 통해 보정할 
-- 경우 원하는 성능향상을 얻을 수 있을것이다.
    


윈도우 함수(WINDOW FUNCTION)

행과 행간의 관계를 쉽게 정의하기 위해 만든 함수가 바로 WINDOW FUNCTION이다.
윈도우 함수를 활용하면 복잡한 프로그램을 하나의 SQL 문장으로 쉽게 해결할 수 있다.
분석 함수(ANALYTIC FUNCTION)나 순위 함수(RANK FUNCTION)로도 알려져 있는 윈도우 함수 (ANSI/ISOSQL 표준은 WINDOW FUNCTION이란 용어를 사용함)는 데이터웨어하우스에서 발전한 기능이다.

WINDOW 함수는 다른 함수와는 달리 중첩(NEST)해서 사용하지는 못하지만, 서브쿼리에서는 사용할 수 있다.

  • WINDOW FUNCTION 종류
    구분종류종류
    순위(RANK) 관련RANK, DENSE_RANK, ROW_NUMBER대부분 지원
    집계(AGGREGATE) 관련SUM, MAX, MIN, AVG, COUNTSQL Server 경우 Over절 내 Orderby 지원 못함
    순서 관련 함수FIRST_VALUE, LAST_VALUE, LAG, LEADORACLE 만 지원
    그룹 내 비율 관련 함수CUME_DIST, PERCENT_RANK, NTILE, RATIO_TO_REPORTPERCENT_RANK 함수는 ANSI/ISO SQL 표준과 Oracle DBMS에서 지원하고 있으며, NTILE 함수는 ANSI/ISO SQL 표준에는 없지만, Oracle, SQL Server에서 지원하고 있다. RATIO_TO_REPORT 함수는 Oracle에서만 지원되는 함수(현업에서 유용).
    선형분석을 포함한 통계분석 함수CORR, COVAR_POP, COVAR_SAMP, STDDEV, STDDEV_POP, STDDEV_SAMP, VARIANCE, VAR_POP, VAR_SAMP, REGR_(LINEAR REGRESSION), REGR_SLOPE, REGR_INTERCEPT, REGR_COUNT, REGR_R2, REGR_AVGX, REGR_AVGY, REGR_SXX, REGR_SYY, REGR_SXY특화되어있으므로 생략
    WINDOW FUNCTION SYNTAX
    • WINDOW 함수에는 OVER 문구가 키워드로 필수 포함된다.
    SELECT WINDOW_FUNCTION (ARGUMENTS) OVER ( [PARTITION BY 칼럼]] [ORDER BY 절] [WINDOWING 절] )
     FROM 테이블 명;
    
    
    BETWEEN 사용 타입
     ROWS | RANGE BETWEEN UNBOUNDED PRECEDING | CURRENT ROW | VALUE_EXPR PRECEDING/FOLLOWING AND UNBOUNDED FOLLOWING | CURRENT ROW | VALUE_EXPR PRECEDING/FOLLOWING
    BETWEEN 미사용 타입
     ROWS | RANGE UNBOUNDED PRECEDING | CURRENT ROW | VALUE_EXPR PRECEDING
    
    • WINDOW_FUNCTION : 기존에 사용하던 함수도 있고, 새롭게 WINDOW 함수용으로 추가된 함수도 있다. - ARGUMENTS (인수) : 함수에 따라 0 ~ N개의 인수가 지정될 수 있다.
    • PARTITION BY 절 : 전체 집합을 기준에 의해 소그룹으로 나눌 수 있다.
    • ORDER BY 절 : 어떤 항목에 대해 순위를 지정할 지 ORDER BY 절을 기술한다.
    • WINDOWING 절 : WINDOWING 절은 함수의 대상이 되는 행 기준의 범위를 강력하게 지정할 수 있다. ROWS는 물리적인 결과 행의 수를, RANGE는 논리적인 값에 의한 범위를 나타내는데, 둘 중의 하나를 선택해서 사용할 수 있다. 다만, WINDOWING 절은 SQL Server에서는 지원하지 않는다.

그룹 내 순위함수.

3.1 RANK 함수
  • RANK 함수는 ORDER BY를 포함한 QUERY 문에서 특정 항목(칼럼)에 대한 순위를 구하는 함수이다.
  • 이때 특정 범위(PARTITION) 내에서 순위를 구할 수도 있고 전체 데이터에 대한 순위를 구할 수도 있다. 또한 동일한 값에 대해서는 동일한 순위를 부여하게 된다.
SELECT JOB, ENAME, SAL,
       RANK( ) OVER (ORDER BY SAL DESC) ALL_RANK, 
       RANK( ) OVER (PARTITION BY JOB ORDER BY SAL DESC) JOB_RANK
  FROM EMP;

JOB       ENAME             SAL   ALL_RANK   JOB_RANK
--------- ---------- ---------- ---------- ----------
PRESIDENT KING             5000          1          1
ANALYST   FORD             3000          2          1
ANALYST   SCOTT            3000          2          1
MANAGER   JONES            2975          4          1
MANAGER   BLAKE            2850          5          2
MANAGER   CLARK            2450          6          3
SALESMAN  ALLEN            1600          7          1
SALESMAN  TURNER           1500          8          2
CLERK     MILLER           1300          9          1
SALESMAN  WARD             1250         10          3
SALESMAN  MARTIN           1250         10          3
CLERK     ADAMS            1100         12          2
CLERK     JAMES             950         13          3
CLERK     SMITH             800         14          4

14 rows selected.


------------------------------------------------------------------------------------
| Id  | Operation                   | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |      |    14 |   364 |     5  (40)| 00:00:01 |
|   1 |  WINDOW SORT                |      |    14 |   364 |     5  (40)| 00:00:01 |
|   2 |   WINDOW SORT               |      |    14 |   364 |     5  (40)| 00:00:01 |
|   3 |    TABLE ACCESS STORAGE FULL| EMP  |    14 |   364 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------------


SELECT JOB, ENAME, SAL, 
       RANK() OVER (PARTITION BY JOB ORDER BY SAL DESC) JOB_RANK 
  FROM EMP;

JOB       ENAME             SAL   JOB_RANK
--------- ---------- ---------- ----------
ANALYST   FORD             3000          1
ANALYST   SCOTT            3000          1
CLERK     MILLER           1300          1
CLERK     ADAMS            1100          2
CLERK     JAMES             950          3
CLERK     SMITH             800          4
MANAGER   JONES            2975          1
MANAGER   BLAKE            2850          2
MANAGER   CLARK            2450          3
PRESIDENT KING             5000          1
SALESMAN  ALLEN            1600          1
SALESMAN  TURNER           1500          2
SALESMAN  MARTIN           1250          3
SALESMAN  WARD             1250          3

14 rows selected.
3.2 DENSE_RANK 함수
SELECT JOB, ENAME, SAL
     , RANK( ) OVER (ORDER BY SAL DESC) RANK
     , DENSE_RANK( ) OVER (ORDER BY SAL DESC) DENSE_RANK
  FROM EMP; 

JOB       ENAME             SAL       RANK DENSE_RANK
--------- ---------- ---------- ---------- ----------
PRESIDENT KING             5000          1          1
ANALYST   FORD             3000          2          2
ANALYST   SCOTT            3000          2          2
MANAGER   JONES            2975          4          3
MANAGER   BLAKE            2850          5          4
MANAGER   CLARK            2450          6          5
SALESMAN  ALLEN            1600          7          6
SALESMAN  TURNER           1500          8          7
CLERK     MILLER           1300          9          8
SALESMAN  WARD             1250         10          9
SALESMAN  MARTIN           1250         10          9
CLERK     ADAMS            1100         12         10
CLERK     JAMES             950         13         11
CLERK     SMITH             800         14         12

14 rows selected.

3.3 ROW_NUMBER 함수
  • ROW_NUMBER 함수는 RANK나 DENSE_RANK 함수가 동일한 값에 대해서는 동일한 순위를 부여하는데 반해, 동일한 값이라도 고유한 순위를 부여한다.
SELECT JOB, ENAME, SAL 
     , RANK( ) OVER (ORDER BY SAL DESC) RANK
     , ROW_NUMBER() OVER (ORDER BY SAL DESC) ROW_NUMBER
  FROM EMP; 

JOB       ENAME             SAL       RANK ROW_NUMBER
--------- ---------- ---------- ---------- ----------
PRESIDENT KING             5000          1          1
ANALYST   FORD             3000          2          2
ANALYST   SCOTT            3000          2          3
MANAGER   JONES            2975          4          4
MANAGER   BLAKE            2850          5          5
MANAGER   CLARK            2450          6          6
SALESMAN  ALLEN            1600          7          7
SALESMAN  TURNER           1500          8          8
CLERK     MILLER           1300          9          9
SALESMAN  WARD             1250         10         10
SALESMAN  MARTIN           1250         10         11
CLERK     ADAMS            1100         12         12
CLERK     JAMES             950         13         13
CLERK     SMITH             800         14         14

14 rows selected.

일반 집계 함수

3.4 SUM 함수
  • SUM 함수를 이용해 파티션별 윈도우의 합을 구할 수 있다.
SELECT MGR, ENAME, SAL
     , SUM(SAL) OVER (PARTITION BY MGR) MGR_SUM 
  FROM EMP;

       MGR ENAME             SAL    MGR_SUM
---------- ---------- ---------- ----------
      7566 FORD             3000       6000
      7566 SCOTT            3000       6000
      7698 JAMES             950       6550
      7698 ALLEN            1600       6550
      7698 WARD             1250       6550
      7698 TURNER           1500       6550
      7698 MARTIN           1250       6550
      7782 MILLER           1300       1300
      7788 ADAMS            1100       1100
      7839 BLAKE            2850       8275
      7839 JONES            2975       8275
      7839 CLARK            2450       8275
      7902 SMITH             800        800
           KING             5000       5000

14 rows selected.


SELECT MGR, ENAME, SAL
     , SUM(SAL) OVER (PARTITION BY MGR ORDER BY SAL RANGE UNBOUNDED PRECEDING) as MGR_SUM 
  FROM EMP;

       MGR ENAME             SAL    MGR_SUM
---------- ---------- ---------- ----------
      7566 SCOTT            3000       6000
      7566 FORD             3000       6000
      7698 JAMES             950        950
      7698 WARD             1250       3450
      7698 MARTIN           1250       3450
      7698 TURNER           1500       4950
      7698 ALLEN            1600       6550
      7782 MILLER           1300       1300
      7788 ADAMS            1100       1100
      7839 CLARK            2450       2450
      7839 BLAKE            2850       5300
      7839 JONES            2975       8275
      7902 SMITH             800        800
           KING             5000       5000

14 rows selected.

(SQL Server의 경우 집계 함수의 경우 OVER 절 내의 ORDER BY 절을 지원하지 않는다.) 
3.5 MAX 함수
SELECT MGR, ENAME, SAL
     , MAX(SAL) OVER (PARTITION BY MGR) as MGR_MAX 
  FROM EMP;

       MGR ENAME             SAL    MGR_MAX
---------- ---------- ---------- ----------
      7566 FORD             3000       3000
      7566 SCOTT            3000       3000
      7698 JAMES             950       1600
      7698 ALLEN            1600       1600
      7698 WARD             1250       1600
      7698 TURNER           1500       1600
      7698 MARTIN           1250       1600
      7782 MILLER           1300       1300
      7788 ADAMS            1100       1100
      7839 BLAKE            2850       2975
      7839 JONES            2975       2975
      7839 CLARK            2450       2975
      7902 SMITH             800        800
           KING             5000       5000

14 rows selected.

SELECT MGR, ENAME, SAL 
  FROM 
     ( SELECT MGR, ENAME, SAL
            , MAX(SAL) OVER (PARTITION BY MGR) as IV_MAX_SAL 
         FROM EMP
     ) 
 WHERE SAL = IV_MAX_SAL ; 

       MGR ENAME             SAL
---------- ---------- ----------
      7566 FORD             3000
      7566 SCOTT            3000
      7698 ALLEN            1600
      7782 MILLER           1300
      7788 ADAMS            1100
      7839 JONES            2975
      7902 SMITH             800
           KING             5000

8 rows selected.


3.6 MIN 함수
 SELECT MGR, ENAME, HIREDATE, SAL
      , MIN(SAL) OVER(PARTITION BY MGR ORDER BY HIREDATE) as MGR_MIN 
   FROM EMP;

       MGR ENAME      HIREDATE         SAL    MGR_MIN
---------- ---------- --------- ---------- ----------
      7566 FORD       03-DEC-81       3000       3000
      7566 SCOTT      09-DEC-82       3000       3000
      7698 ALLEN      20-FEB-81       1600       1600
      7698 WARD       22-FEB-81       1250       1250
      7698 TURNER     08-SEP-81       1500       1250
      7698 MARTIN     28-SEP-81       1250       1250
      7698 JAMES      03-DEC-81        950        950
      7782 MILLER     23-JAN-82       1300       1300
      7788 ADAMS      12-JAN-83       1100       1100
      7839 JONES      02-APR-81       2975       2975
      7839 BLAKE      01-MAY-81       2850       2850
      7839 CLARK      09-JUN-81       2450       2450
      7902 SMITH      17-DEC-80        800        800
           KING       17-NOV-81       5000       5000

14 rows selected.
3.7 AVG 함수
  • EMP 테이블에서 같은 매니저를 두고 있는 사원들의 평균 SALARY를 구하는데,
  • 조건은 같은 매니저 내에서 자기 바로 앞의 사번과 바로 뒤의 사번인 직원만을 대상으로 한다.
SELECT MGR, ENAME, HIREDATE, SAL
     , ROUND (AVG(SAL) OVER (PARTITION BY MGR ORDER BY HIREDATE ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)) as MGR_AVG 
  FROM EMP; 

       MGR ENAME      HIREDATE         SAL    MGR_AVG
---------- ---------- --------- ---------- ----------
      7566 FORD       03-DEC-81       3000       3000
      7566 SCOTT      09-DEC-82       3000       3000
      7698 ALLEN      20-FEB-81       1600       1425
      7698 WARD       22-FEB-81       1250       1450
      7698 TURNER     08-SEP-81       1500       1333
      7698 MARTIN     28-SEP-81       1250       1233
      7698 JAMES      03-DEC-81        950       1100
      7782 MILLER     23-JAN-82       1300       1300
      7788 ADAMS      12-JAN-83       1100       1100
      7839 JONES      02-APR-81       2975       2913
      7839 BLAKE      01-MAY-81       2850       2758
      7839 CLARK      09-JUN-81       2450       2650
      7902 SMITH      17-DEC-80        800        800
           KING       17-NOV-81       5000       5000

3.8 COUNT 함수
SELECT ENAME, SAL
     , COUNT(*) OVER (ORDER BY SAL RANGE BETWEEN 50 PRECEDING AND 150 FOLLOWING) as SIM_CNT
  FROM EMP; 

ENAME             SAL    SIM_CNT
---------- ---------- ----------
SMITH             800          2
JAMES             950          2
ADAMS            1100          3
WARD             1250          3
MARTIN           1250          3
MILLER           1300          3
TURNER           1500          2
ALLEN            1600          1
CLARK            2450          1
BLAKE            2850          4
JONES            2975          3
SCOTT            3000          3
FORD             3000          3
KING             5000          1

14 rows selected.

그룹 내 행 순서 함수

3.9 FIRST_VALUE 함수
  • FIRST_VALUE 함수를 이용해 파티션별 윈도우에서 가장 먼저 나온 값을 구한다.
  • SQL Server에서는 지원하지 않는 함수이다. MIN 함수를 활용하여 같은 결과를 얻을 수도 있다.
SELECT DEPTNO, ENAME, SAL
     , FIRST_VALUE(ENAME) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC ROWS UNBOUNDED PRECEDING) as DEPT_RICH 
  FROM EMP; 

    DEPTNO ENAME             SAL DEPT_RICH
---------- ---------- ---------- ----------
        10 KING             5000 KING
        10 CLARK            2450 KING
        10 MILLER           1300 KING
        20 SCOTT            3000 SCOTT
        20 FORD             3000 SCOTT
        20 JONES            2975 SCOTT
        20 ADAMS            1100 SCOTT
        20 SMITH             800 SCOTT
        30 BLAKE            2850 BLAKE
        30 ALLEN            1600 BLAKE
        30 TURNER           1500 BLAKE
        30 MARTIN           1250 BLAKE
        30 WARD             1250 BLAKE
        30 JAMES             950 BLAKE


14 rows selected.

SELECT DEPTNO, ENAME, SAL
     , FIRST_VALUE(ENAME) OVER (PARTITION BY DEPTNO ORDER BY SAL DESC, ENAME ASC ROWS UNBOUNDED PRECEDING) as RICH_EMP
  FROM EMP; 

    DEPTNO ENAME             SAL RICH_EMP
---------- ---------- ---------- ----------
        10 KING             5000 KING
        10 CLARK            2450 KING
        10 MILLER           1300 KING
        20 FORD             3000 FORD
        20 SCOTT            3000 FORD
        20 JONES            2975 FORD
        20 ADAMS            1100 FORD
        20 SMITH             800 FORD
        30 BLAKE            2850 BLAKE
        30 ALLEN            1600 BLAKE
        30 TURNER           1500 BLAKE
        30 MARTIN           1250 BLAKE
        30 WARD             1250 BLAKE
        30 JAMES             950 BLAKE

14 rows selected.

3.9 LAST_VALUE 함수
  • LAST_VALUE 함수를 이용해 파티션별 윈도우에서 가장 나중에 나온 값을 구한다.
  • SQL Server에서는 지원하지 않는 함수이다. MAX 함수를 활용하여 같은 결과를 얻을 수도 있다.
SELECT DEPTNO, ENAME, SAL
     , LAST_VALUE(ENAME) OVER ( PARTITION BY DEPTNO ORDER BY SAL DESC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as DEPT_POOR 
  FROM EMP;

    DEPTNO ENAME             SAL DEPT_POOR
---------- ---------- ---------- ----------
        10 KING             5000 MILLER
        10 CLARK            2450 MILLER
        10 MILLER           1300 MILLER
        20 SCOTT            3000 SMITH
        20 FORD             3000 SMITH
        20 JONES            2975 SMITH
        20 ADAMS            1100 SMITH
        20 SMITH             800 SMITH
        30 BLAKE            2850 JAMES
        30 ALLEN            1600 JAMES
        30 TURNER           1500 JAMES
        30 MARTIN           1250 JAMES
        30 WARD             1250 JAMES
        30 JAMES             950 JAMES

14 rows selected.



3.9 LAG 함수
  • LAG 함수를 이용해 파티션별 윈도우에서 이전 몇 번째 행의 값을 가져올 수 있다. SQL Server에서는 지원하지 않는 함수이다.
직원들을 입사일자가 빠른 기준으로 정렬을 하고, 본인보다 입사일자가 한 명 앞선 사원의 급여를 본인의 급여와 함께 출력한다. 


SELECT ENAME, HIREDATE, SAL
     , LAG(SAL) OVER (ORDER BY HIREDATE) as PREV_SAL 
  FROM EMP 
 WHERE JOB = 'SALESMAN';

ENAME      HIREDATE         SAL   PREV_SAL
---------- --------- ---------- ----------
ALLEN      20-FEB-81       1600
WARD       22-FEB-81       1250       1600
TURNER     08-SEP-81       1500       1250
MARTIN     28-SEP-81       1250       1500


SELECT ENAME, HIREDATE, SAL
     , LAG(SAL, 2, 0) OVER (ORDER BY HIREDATE) as PREV_SAL 
  FROM EMP 
 WHERE JOB = 'SALESMAN' ;

ENAME      HIREDATE         SAL   PREV_SAL
---------- --------- ---------- ----------
ALLEN      20-FEB-81       1600          0
WARD       22-FEB-81       1250          0
TURNER     08-SEP-81       1500       1600
MARTIN     28-SEP-81       1250       1250



  • LAG 함수는 3개의 ARGUMENTS 까지 사용할 수 있는데,
  • 두 번째 인자는 몇 번째 앞의 행을 가져올지 결정하는 것이고 (DEFAULT 1),
  • 세 번째 인자는 예를 들어 파티션의 첫 번째 행의 경우 가져올 데이터가 없어 NULL 값이 들어오는데
  • 이 경우 다른 값으로 바꾸어 줄 수 있다. 결과적으로 NVL이나 ISNULL 기능과 같다.
3.10 LEAD 함수
  • LEAD 함수를 이용해 파티션별 윈도우에서 이후 몇 번째 행의 값을 가져올 수 있다.
  • 참고로 SQL Server에서는 지원하지 않는 함수이다.
SELECT ENAME, HIREDATE
     , LEAD(HIREDATE, 1) OVER (ORDER BY HIREDATE) as "NEXTHIRED" 
  FROM EMP;

ENAME      HIREDATE  NEXTHIRED
---------- --------- ---------
SMITH      17-DEC-80 20-FEB-81
ALLEN      20-FEB-81 22-FEB-81
WARD       22-FEB-81 02-APR-81
JONES      02-APR-81 01-MAY-81
BLAKE      01-MAY-81 09-JUN-81
CLARK      09-JUN-81 08-SEP-81
TURNER     08-SEP-81 28-SEP-81
MARTIN     28-SEP-81 17-NOV-81
KING       17-NOV-81 03-DEC-81
JAMES      03-DEC-81 03-DEC-81
FORD       03-DEC-81 23-JAN-82
MILLER     23-JAN-82 09-DEC-82
SCOTT      09-DEC-82 12-JAN-83
ADAMS      12-JAN-83

14 rows selected.

  • LEAD 함수는 3개의 ARGUMENTS 까지 사용할 수 있는데,
  • 두 번째 인자는 몇 번째 후의 행을 가져올지 결정하는 것이고 (DEFAULT 1),
  • 세 번째 인자는 예를 들어 파티션의 마지막 행의 경우 가져올 데이터가 없어 NULL 값이 들어오는데
  • 이 경우 다른 값으로 바꾸어 줄 수 있다. 결과적으로 NVL이나 ISNULL 기능과 같다

그룹 내 비율 함수

3.11 RATIO_TO_REPORT 함수
  • RATIO_TO_REPORT 함수를 이용해 파티션 내 전체 SUM(칼럼)값에 대한 행별 칼럼 값의 백분율을 소수점으로 구할 수 있다.
  • 결과 값은 > 0 & <= 1 의 범위를 가진다.
  • 그리고 개별 RATIO의 합을 구하면 1이 된다. SQL Server에서는 지원하지 않는 함수이다.

예제) JOB이 SALESMAN인 사원들을 대상으로 전체 급여에서 본인이 차지하는 비율을 출력한다.

SELECT ENAME, SAL
     , ROUND(RATIO_TO_REPORT(SAL) OVER (), 2) as R_R 
  FROM EMP 
 WHERE JOB = 'SALESMAN'; 

ENAME             SAL        R_R
---------- ---------- ----------
ALLEN            1600        .29
WARD             1250        .22
MARTIN           1250        .22
TURNER           1500        .27



3.11 PERCENT_RANK 함수
  • PERCENT_RANK 함수를 이용해 파티션별 윈도우에서 제일 먼저 나오는 것을 0으로,
  • 제일 늦게 나오는 것을 1로 하여, 값이 아닌 행의 순서별 백분율을 구한다.
  • 결과 값은 >= 0 & <= 1 의 범위를 가진다. 참고로 SQL Server에서는 지원하지 않는 함수이다.
SELECT DEPTNO, ENAME, SAL
     , PERCENT_RANK() OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) as P_R 
  FROM EMP; 

    DEPTNO ENAME             SAL        P_R
---------- ---------- ---------- ----------
        10 KING             5000          0
        10 CLARK            2450         .5
        10 MILLER           1300          1
        20 SCOTT            3000          0
        20 FORD             3000          0
        20 JONES            2975         .5
        20 ADAMS            1100        .75
        20 SMITH             800          1
        30 BLAKE            2850          0
        30 ALLEN            1600         .2
        30 TURNER           1500         .4
        30 MARTIN           1250         .6
        30 WARD             1250         .6
        30 JAMES             950          1

14 rows selected.

3.11 CUME_DIST 함수
  • CUME_DIST 함수를 이용해 파티션별 윈도우의 전체건수에서 현재 행보다 작거나 같은 건수에 대한 누적백분율을 구한다.
  • 결과 값은 > 0 & <= 1 의 범위를 가진다. 참고로 SQL Server에서는 지원하지 않는 함수이다.
SELECT DEPTNO, ENAME, SAL
     , CUME_DIST() OVER (PARTITION BY DEPTNO ORDER BY SAL DESC) as CUME_DIST 
  FROM EMP; 

    DEPTNO ENAME             SAL  CUME_DIST
---------- ---------- ---------- ----------
        10 KING             5000 .333333333
        10 CLARK            2450 .666666667
        10 MILLER           1300          1
        20 SCOTT            3000         .4
        20 FORD             3000         .4
        20 JONES            2975         .6
        20 ADAMS            1100         .8
        20 SMITH             800          1
        30 BLAKE            2850 .166666667
        30 ALLEN            1600 .333333333
        30 TURNER           1500         .5
        30 MARTIN           1250 .833333333
        30 WARD             1250 .833333333
        30 JAMES             950          1

14 rows selected.



3.11 NTILE 함수
  • NTILE 함수를 이용해 파티션별 전체 건수를 ARGUMENT 값으로 N 등분한 결과를 구할 수 있다.
SELECT ENAME, SAL
     , NTILE(4) OVER (ORDER BY SAL DESC) as QUAR_TILE 
  FROM EMP ;

ENAME             SAL  QUAR_TILE
---------- ---------- ----------
KING             5000          1
FORD             3000          1
SCOTT            3000          1
JONES            2975          1
BLAKE            2850          2
CLARK            2450          2
ALLEN            1600          2
TURNER           1500          2
MILLER           1300          3
WARD             1250          3
MARTIN           1250          3
ADAMS            1100          4
JAMES             950          4
SMITH             800          4

14 rows selected.


'ORACLE > SQL' 카테고리의 다른 글

SQL trace _ 참조  (0) 2019.10.02
복수행 함수 (그룹 함수)  (0) 2018.12.13
PLSQL - 다차원 콜렉션  (0) 2018.01.24
피벗 (행->열)  (0) 2017.12.22
BULK COLLECT  (0) 2017.08.17




성능 튜닝의 3대 요소

  • Library cache 최적화
  • DB Call 최소화
  • I/O 효율화 및 버퍼 최적화 



1. Block 단위 I/O



  • 대부분의 DB 에서 I/O 단위는 블록(혹은 Page)다.
    --> 논리적 Row 단위가 아닌 I/O 를 위한 물리적 단위가 존재
  • Seqential read: 하나의 블록을 엑세스 해 모든 내용을 읽는것.
  • Random access: 하나의 레코드를 읽으려고 블록 통채로 읽는 것.
  • Random access 보다 Sequential accessl 가 효율이 높다.
  • 특정 컬럼만 조회하나 전체를 조회하나 일 량(Cost)는 같다.
    -->일부 컬럼 레벨 I/O 를 지원하는 DW 계열 DB 도 있다.
  • SQL 성능이나 Optimizer 의 판단을 좌우하는 지표는 I/O 에 소요 된 Block 이다.
  • Block I/O 는 아래와 같은 상황에서 발생한다.
        메모리 버퍼에서 블록을 읽고 쓸 때
        파일에 저장 된 블록을 읽고 쓸 때
        DB 에서 버퍼캐시로 적재 할 때
        버퍼에서 다시 DB 로 저장할 때
  • Block 의 단위는 2kb 부터 4kb, 8kb ... 64kb 까지 가능하다.
        다른 크기의 블록을 사용하려면 Tablespace, Buffer 를 개별 지정 해 주어야 한다.
  • Oracle Dicaionary cache 는 Block 이 아니라 Row 단위로 I/O 수행한다.
        Dictionary cache 를 Row cache 라고도 한다. 


    Sequentian read vs. Random access



Sequential read


  • 논리적/물리적 순서를 따라 레코드 스캔하는 방식
        인덱스 리프블록의 레코드는 포인터를 따라 연결되어 있다.
        이를 따라 스캔하는것은 시퀀셜 리드에 속한다.
        읽은 레코드 중 실제 결과로 선택되는 비중이 클 수록 효과적이다.
Random access


  • 논리/물리 순서와 상관 없이 한 건을 읽기 위해 한 블록 씩 접근한다.
        보통 인덱스-테이블 간 엑세스에서 많이 발생한다
        Inner table NL 조인을 위한 인덱스 엑세스일 경우 루트-리프 블록 간 엑세스도 성능에 많은 영향을 준다
        Random access 시 발생하는 성능 향상을 위해 버퍼 피닝이나 테이블 프리패치 등의 기능이 구현된다. 

    성능 향상을 위해선 random access 를 줄이고 sequential read 를 높여야 한다.




Sequential read 에서 결과집합 선택도 높이기


-테이블 풀 스캔 후 대부분이 필터링되고 일부만 선택된다면 인덱스를 이용하는 것이 효과적.
-참조 컬럼이 모두 인덱스 안에 있으면 인덱스 range scan 만으로 결과를 얻을 수 있다.
-인덱스의 컬럼 순서에 따라서도 range scan 의 선택도가 달라짐
    동일한 결과를 얻기 위해 더 많은 leaf block 을 읽어야 함 


Random access 줄이기


-인덱스가 속하지 않는 컬럼을 Select 참조 시 인덱스 -> 테이블 간 random access 발생

  • 인덱스 출력 건수에 비해 테이블 블록 엑세스 횟수가 적은것은 버퍼 피닝 효과 때문
        Table access 단계의 1016 - Index access 단계의 81 = 935 block
         37094 번 access 했으나 935 block 만 읽음
        클러스터링 펙터가 좋을 수록 버퍼 피닝에 의한 I/O 감소 효과는 커짐
    select /*+ index(t ind1) */ count(*) from test1 t ^M
    where owner like 'SYS%' and object_name='ALL_OBJECTS'
    
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        4      0.00       0.00          0          2          0           0
    Execute      4      0.00       0.00          0          0          0           0
    Fetch        8      0.06       0.14         80       4064          0           4
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total       16      0.06       0.14         80       4066          0           4
    
    Misses in library cache during parse: 2
    Optimizer mode: ALL_ROWS
    Parsing user id: 84
    Number of plan statistics captured: 4
    
    Rows (1st) Rows (avg) Rows (max)  Row Source Operation
    ---------- ---------- ----------  ---------------------------------------------------
             1          1          1  SORT AGGREGATE (cr=1016 pr=20 pw=0 time=36844 us)
             1          1          1   TABLE ACCESS BY INDEX ROWID TEST1 (cr=1016 pr=20 pw=0 time=36834 us cost=237 size=5478 card=66)
         37094      37094      37094    INDEX RANGE SCAN IND1 (cr=81 pr=20 pw=0 time=19669 us cost=23 size=0 card=47568)(object id 526155)
    
    
  • 읽고 필터링 되는 데이터가 많을 경우 필터조건을 인덱스 구성 컬럼 끝에 추가하여 엑세스 조건으로 변환. 


2. 메모리/디스크 I/O



  • 디스크가 메모리보다 느리므로 DB 는 버퍼캐시를 통해 I/O 수행한다.
    버퍼캐시를 먼저 찾고 없으면 디스크를 검색
        버퍼 효율을 높여서 대부분의 처리를 메모리에서 할 수 있도록 하는것이 성능을 좌우 함 

    버퍼 캐시 히트율


  • 전체 읽은 블록 중 얼마만큼을 버퍼에서 찾았는지를 나타내는 것
        캐시에서 직접 찾은 블록 / 전체 읽은 블록 * 100
  • Direct path read 를 제외하고 모든 읽기는 버퍼를 통해 이루어 진다.
        디스크에서 읽는다고 해도 버퍼에 적재 후 읽는다.
        논리적 읽기 에서 물리적 읽기를 해야 실제 캐시에서 직접 읽은 블록 수가 나온다.
        아래의 경우 4066 - 80 = 3986 이 실제 Buffer cache 에서 읽은 블록이다.
        cpu time 과 elapsed time 의 차이는 대부분 I/O 에 의한 소요 시간이다. 
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        4      0.00       0.00          0          2          0           0
    Execute      4      0.00       0.00          0          0          0           0
    Fetch        8      0.06       0.14         80       4064          0           4
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total       16      0.06       0.14         80       4066          0           4
    
    


버퍼 캐시 히트율의 한계


  • 같은 블록을 반복적 엑세스 하는 상황이면 논리적 I/O 가 비 효율적으로 많아도 BCHR 은 높다.
  • 이 경우 버퍼를 찾는 과정에서 래치를 얻어야 하므로 많은 비용이 소모된다. 


Network / Filesystem cache



  • DB 밖의 영역(Storage, network) 등으로 인해서도 성능의 차이가 많이 난다.
    ex> 동일한 Block 을 읽어도 스토리지 캐시 등으로 인해 성능차이가 날 수도 있다.
  • 각 벤더사들은 이를 효율화 하기 위해 노력을 기울이는 중이며, 성능에 미치는 요소들이 점점 다각화 되는 추세이다.
  • 그럼에도 불구하고 가장 확실한 해결책은 블록 요청 횟수를 최소화 하는 것. 


3. Single block I/O / Multi block I/O



블록을 Disk 를 통해 버퍼에 적재하는 방법은 크게 두 가지


Single block I/O

  • 인덱스를 통한 테이블 엑세스 시 인덱스/테이블 모두 이 방법으로 처리
    Multi block I/O
  • IO Call 시 인접한 블록을 같이 읽어 메모리에 적재하는 것
  • Full scan 처럼 물리적 순서에 따라 읽을 때는 인접한 블록을 같이 읽는것이 효율적
        OS 레벨의 I/O 단위가 DB Block size 와는 다르게 별도로 있기 때문에(보통 1M 라고 함)
  • 물리적으로 연속된 단위는 Extent 이므로 이 범위를 넘어 Multi block read 를 하지는 못한다. 
  • 멀티블록 리드의 단위는 db_multi_block_read_cont 파라메터로 정해진다
        OS 에서 설정하는 I/O 단위를 넘을수는 없다 

인덱스 스캔은 왜 single block I/O 를 하는가


  • 인덱스는 논리적 순서로 스캔하지만, 블록의 물리적 순서는 논리적 순서와 다르기 때문
  • Index range scan / Index full scan 모두 single block 읽기를 수행 함
  • multi block read 는 다음 상황에서 적용 된다.
        index fast full scan
        table full scan
        테이블 엑세스 없는 index range scan/index full scan 

  • 서버 프로세스는 읽는 시점마다 I/O 요청 후 대기한다.
        db file sequential read - Single block I/O 요청 시 발생
        db file scattered read - Multi block I/O 요청 시 발생
  • 대량 데이터를 읽을 때 multi block 방식이 유리 한 이유는 I/O call 을 줄이기 때문
  • Single block I/O 방식으로 읽은 블록은 LRU list 에서 MRU 쪽에 위치
  • Multi block I/O 방식으로 읽은 블록은 대략 중간 쯤 정도에 위치




4 Table/Index Prefetch



정의

  • 디스크를 읽을 때 다음에 읽을 가능성이 있는 블록을 같이 읽어오는 기능
        Multi block I/O 도 일종의 프리패치 기능이라 할 수 있다
  • 테이블/인덱스 프리패치는 한 번에 여러개 Single block I/O 를 동시에 수행하는 것
  • 물리적으로 인접하지 않은 블록을 배치 방식으로 미리 적재 하는 것
  • 블록을 읽는 도중 물리적 I/O 가 필요하면 call 을 발생시키고 잠시 대기하는데,
        다음 블록을 미리 적재하면 대기상태에 빠지는 횟수를 줄일 수 있음
  • 시스템 전반의 디스크 경합을 줄이기보다, 시스템 Call 을 줄이고 개별 쿼리 성능 향상 목적



  • 오라클이 내부적으로 판단해서 수행하지만, 프리패치 블록이 실제 엑세스로 이어지지 않는 비율이 높으면 기능 정지시킴
        CKPT 프로세스가 모니터링 함
        아래 쿼리로 조회 가능하다 
    SQL> select name, value from v$sysstat
    where name in ('physical reads cache prefetch','prefetched blocks aged out before use');
    
    NAME                                                             VALUE
    ---------------------------------------------------------------- -----------
    physical reads cache prefetch                                      214298948
    prefetched blocks aged out before use                               31027630
    
    2 rows selected.
    



  • 이벤트는 db file parallel read 이다(10046 으로 프리패치 확인 가능하다) 


인덱스 Prefetch


  • 브렌치 블록을 읽을 때 앞으로 읽을 주소를 미리 얻을 수 있으므로 미리 캐싱 가능 
  • 2번 브렌치 블록을 읽고 6번 리프블록을 읽는 시점에 6,7번까지 같이 적재 해 놓는 것


  • 가장 효과적일 수 있는 상황은 인덱스 풀 스캔 시
        부분범위 처리로 스캔 후 멈추지만 않으면 모든 리프블록을 다 읽기 때문
        프리패치 방법으로 스캔 시 리프블록 위의 브랜치블록을 읽어야 하므로 I/O 량은 약간 더 증가 
  • 연관 파라메터는 아래 두 개이다
    _index_prefetch_factor
        기본값은 100
        작게 설정할수록 인덱스 프리패치 더 선호

_db_file_noncontig_mblock_read_count
    한 번에 최대 몇 개 블록을 프리패치 할지 결정
    1 지정 시 기능 정지 

SQL> select ksppinm name,
  2        ksppstvl value,
  3        decode(bitand(ksppiflg/256,1),1,'true','false') ses_modifiable,
  4        decode(bitand(ksppiflg/65536,3),1,'immediate',2,'deferred',3,'immediate','false') sys_modifiable,
  5        ksppdesc description
  6  from sys.x$ksppi i, sys.x$ksppcv v
  7  where i.indx = v.indx
  8* and i.ksppinm in ('_index_prefetch_factor', '_db_file_noncontig_mblock_read_count')
SQL> /

NAME                                               VALUE      SES_M SYS_MODIF DESCRIPTION
-------------------------------------------------- ---------- ----- --------- --------------------------------------------------------------------------------
_db_file_noncontig_mblock_read_count               11         false false     number of noncontiguous db blocks to be prefetched
_index_prefetch_factor                             100        true  immediate index prefetching factor

SQL>




테이블 프리패치


  • 테이블 룩겁 프리패치, 혹은 데이터블록 프리패지라고도 함
  • 인덱스 경유해 레코드를 엑세스 할 때 리프블록에 연결된 다른 블록까지 미리 캐싱하는 기능
  • Disk I/O 대기 횟수 감소시켜 랜덤엑세스 성능 향상 가능하다
  • 인덱스 클러스터링 펙터가 나쁠 때 효과를 발휘한다
    --> 클러스터링 팩터가 나쁘면 Disk I/O 가 많기 때문 
     
  • 6번 리프블록을 읽어 12번 테이블 블록을 읽는 시점에 13, 15, 18 번 블록까지 미리 적재 

  • 연관 파라메터는 아래와 같다
    SQL> select ksppinm name,
      2        ksppstvl value,
      3        decode(bitand(ksppiflg/256,1),1,'true','false') ses_modifiable,
      4        decode(bitand(ksppiflg/65536,3),1,'immediate',2,'deferred',3,'immediate','false') sys_modifiable,
      5        ksppdesc description
      6  from sys.x$ksppi i, sys.x$ksppcv v
      7  where i.indx = v.indx
      8* and i.ksppinm in ('_table_lookup_prefetch_size','_table_lookup_prefetch_thresh','_multi_join_key_table_lookup')
      9
    
    NAME                                               VALUE                SES_M SYS_MODIF DESCRIPTION
    -------------------------------------------------- -------------------- ----- --------- ------------------------------------------------------------
    _table_lookup_prefetch_size                        40                   false false     table lookup prefetch vector size
    _multi_join_key_table_lookup                       TRUE                 false false     TRUE iff multi-join-key table lookup prefetch is enabled
    _table_lookup_prefetch_thresh                      2                    false false     table lookup prefetch threshold
    
    SQL>
    



  • DB2 의 경우 리프블록을 먼저 스캔 후 결과집합을 rowid 순으로 정렬 후 테이블 엑세스 하는 방식을 취한다. 



Direct parh I/O



  • 일반적 I/O 는 버퍼를 경유하며 DBWR 이 주기적으로 변경 블록을 파일에 기록
  • 버퍼 캐싱은 시스템 전반적 I/O 성능을 향상시키지만
        개별 프로세스 입장에서는 대용량 I/O 시 모두 버퍼에 적재 후 읽으면 성능이 오히려 나빠 짐
        재 사용 가능성이 없는 세그먼트의 경유는 버퍼에 적재하지 않는것이 시스템 전반적 성능에 유리하다 
  • Direct Path I/O 가 수행되는 경우는 아래와 같다
        temp segment 블록을 읽고 쓸 때
        병렬 full scan 을 수행
        nocache 옵션 지정 한 lob segment 를 읽을 때
        direct 옵션으로 export 수행할 때
        parallel dml 수행 할 때
        direct path insert 수행할 때 


대기 이벤트 별 direct I/O



direct path read/write temp


-데이터 정렬 시 pga 안의 sort area 를 이용한다
-sort 공간 부족 시 temp tablespace 이용하는데
    정렬 된 데이터를 temp tablespace 에 쓰고 읽을 때 direct path i/o 방식 사용한다 

direct path read


-병렬 쿼리로 full scan 수행 시 발생
-수행 전 연관 세그먼트에 대해 버퍼 - 파일 간 동기화 수행하여 dirty buffer 를 없엠
    버퍼의 내용이 파일에 기록되지 않은 상태에서 파일을 직접 읽으면 정합성에 문제 

direct path write


  • 병렬 DML 혹은 direct path insert 방식 사용 시 발생
    /*+ append */ 힌트 사용, 병렬 인서트, direct 옵션을 준 sql loader, CTAS 수행 시 발생 



RAC 캐시퓨전



  • 동시 사용자가 많을 때 부하 분산 목적으로 db 마다 분산전략을 사용하는데
    1. 서버 간 복제
        여러 서버를 두고 각 서버의 트렌젝션 데이터를 상호 복제
        실시간 동기화 필요 시 복제 부하때문에 분산 효과를 얻기 힘듬
    2. 업무 별 수직 분할
        업무 영역 별 db 를 따로 두고 각기 테이블 관리하며 분산쿼리 이용해 조회
        분산 영역으로 자주 조회되는 공통 영역의 범위에 따라 성패가 좌우 됨
    3. 데이터 구분에 따른 수평 분할
        스키마는 같지만 데이터 구분에 따라 db 를 따로 가져가는 방식
          ex>동일 구조의 테이블인데 지역별로 다른 db
        분할 데이터 간 의존성이 낮을 때 유리
        서버 간 데이터 이동이 발생할 시 어떻게 처리 할 지 고민 필요
  • 물리적으로 분산시킨 데이터를 논리적으로 통합하는 클러스터링 기법도 발전을 거듭 해 왔다
        RAC 모델은 공유 디스크 방식에 기반을 두면서 인스턴스 간 버퍼 공유 가능
        튜닝이 잘 되지 않아 많은 블록 I/O 를 일으킬 때인스턴스 간 경합이 심해 짐
        RAC 의 캐시퓨전 프로세싱 원리를 이해 할 필요 있음 

    RAC 의 글로벌 캐시



  • 클러스터링 된 모든 노드의 버퍼를 하나의 버퍼 캐시로 간주
  • 필요 데이터블록이 다른 노드에 있으면 직접 가져 옴
  • 모든 블록에 마스터 노드가 있고 이를 통해 캐싱 및 락 정보 관리 

    글로벌 캐시 원리는
    읽고자 하는 블록이 로컬 캐시에 없을 때 마스터 노드에 전송 요청
    마스터노드는 블록을 캐시한 노드에 전송 지시
    어느 노드에도 캐싱되어있지 않으면 직접 읽도록 권한 부여 

    Current 블록
    디스크로부터 읽은 후 갱신이 반영된 한개의 최종 원본 
    CR 블록
    커런트 블록의 복사본으로 여려 버전이 존재 가능함 
  • RAC 의 Curent 블록은 Shared 와 Exclusive 로 나뉜다
        Scur 상태일 때는 동시에 여러 노드에 캐싱 가능
        XCur 상태일 때는 하나의 노드에만 캐싱 가능
  • 자주 읽히는 데이터 블록을 각 노드가 Scur 로 캐싱하면 가장 효율적
  • 한 노드가 Scur 블록을 Xcur 로 변경 시 다른 노드는 Null 로 down 되어 사용 불가 함 


노드 간 전송 메커니즘 상세




1. 전송 없는 읽기



  • A 노드에서 K 블록 읽을 때 어떤 노드에도 캐싱된 상태가 아니며 k 블록 scn 은 123 

    A 노드는 마스터인 B 노드에 전송 요청 - gc cr request
    B 노드는 어떤 노드에도 K 를 캐싱하지 않음을 확인하고 A 노드에 직접 Scur 로 읽도록 권한 부여
    A 노드는 디스크에서 읽어 로컬캐싱 



2. 읽기/읽기



  • A 노드만 K 를 Scur 로 캐싱 한 상태에서 C 가 K 를 Scur 로 읽음 

    C 는 MASTER 인 B 에 K 전송요청 - gc cr request
    B 는 K 를 A 가 캐싱하므로 C 에 k 를 주도록 A 에 지시
    A 는 C 에 전송, C 는 받아서 Scur 캐싱하고 B 에 완료 메시지 보냄 



3. 읽기/쓰기


 

  • A, C 노드 모두 K 블록을 SCur 로 캐싱 중
  • C 가 K 를 Xcur 로 업그레이드 시도(블록 갱신 목적) 
        마스터 B에게 K 를 Xcur 로 업글 요청
        B 는 k 를 A 노드도 캐싱하는 중임을 확인하고 A 에 null 로 다운하라고 지시
        A 는 c에게 down 했음을 알려줌
        C 는 k 를 Xcur 로 업글하고 결과를 마스터 B 에게 알림, A 가 null 로 다운한것도 함께 알림
        C Xcur 로 변경하고 블록 변경하여 scn 증가 123 --> 154 



4. 쓰기/쓰기


 

  • A 는 k 를 null 로 소유 중이며 C는 Xcur 로 가지고 있음
  • C 의 Xcur scn 은 154 로 증가,
  • 데이터파일 블록은 아직 123 이므로 dirty buffer 상태
  • 이때 A 가 다시 k 를 Xcur 로 갱신 위해 소유하려고 함 

    A 가 마스터 B 에게 k 를 xcur 로 요청
    B 는 k 를 C 가 Xcur 로 캐싱 학인, A 에게 전송 지시
    C 는 A 에게 전송, 가진 블록은 null 로 다운(c 의 xcur 은 커밋되지 않아 rowlock 상태일 수도 있다)
    A 는 k 를 Xcur 로 갱신했음을 B 에게 알림 
--> 다른 인스턴스가 생신중인 블록을 읽을 때 row lock 이 해소되기 전에도 블록을 주고받는다
--> 쓰기 쓰기 상황이라도 Disk 에 기록 없이 바로 다른 Instance 에 전송한다(디스크 동기화 없이 진행) 
    A 가 xcur 로 다시 갱신했으므로 k scn 은 154 --> 168 



5. 쓰기/읽기


 

  • A 는 k 를 xCur 로 가지고 있고 C 는 null 로 가지고 있음
  • A 가 가진 Cur sCn 은 168
  • DB 블록 scn 은 123
  • C 가 k 를 Scur 모드로 읽기 시도 

C 는 B 에게 k 를 sCur 로 요청
B 는 k 를 A 가 Xcur 로 캐싱 확인 후 C 에게 전송 지시
A 는 C 에게 블록 전송, 자신의 블록은 Scur 로 다운

A 에서 Commit 되지 않았을 경우 Current 가 아닌 Cr Copy 를 만들어 전송
      --> C 는 읽기만 하므로 Current 를 보낼 필요까지는 없다
      --> Current 를 보내면 언젠가 다시 가져와야 하므로 부담스럽다
A 에서 Commit 되더라도 바로 Current 블록을 보내지 않는다
      --> 처음에는 Cr Copy 만 전송하다 일정 횟수 이상 반복 요청이 오면 Current 를 보냄
      --> Current 를 보내려면 자신의 xCur 을 sCur 로 다운해야 하는데 곧이어 갱신이 필요한 상황이면 다시 xCur 로 업그레이드 해야 하기 때문
      --> 그때는 sCur 을 가져간 다른 노드도 모두 null 로 다운해야 하므로 RAC 부하 증가
      --> Cr Copy 를 보내는 횟수는 _fairness_threshold 에 의해 결정(10g defAult = 4, 11g 는 2)
      --> 값에 도달하면 redo 버퍼를 비우고 Xcur 을 Scur 로 다운,
Scur 이므로 이후에 읽기 요청하는 노드는 Cr Copy 가 아니라 Scur 로 전송받음 

  • C 는 k 를 Scur 로 캐싱한 후 B 에게 알림, A 가 가진 블록이 sCur 로 다운된 것 도 같이 알림 



캐시퓨전 성능 향상을 위해선



  • 주로 읽기 위주라면 _fairness_count 를 낮게 설정하는것이 성능에 도움
        cr copy 전송 없이 빠르게 scur 로 다운하고 current 블록으로 전송하므로
        xcur 을 scur 로 다운했다 다시 xcur 로 업글하는 반복 할 가능성이 적다면
        읽기 요청이 반복되는 블록을 가급적 빨리 scur 로 보내주는것이 좋다 

아래 쿼리의 downgrade ratio 가 높다면 current 로 공유할 수 밖에 없음에도 cr 을 만들어 보내주느라 비효율적 작업을 함을 의미 

SQL> select
data_requests, fairness_down_converts
, round (fairness_down_converts / data_requests * 100) "DOWNGRADE RATIO(%)"
from v$cr_block_server;

DATA_REQUES FAIRNESS_DO DOWNGRADE R
----------- ----------- -----------
  136788509    83742131          61

1 rows selected.


  • rac 구성 시 데이터 가공 노드와 읽는 노드를 분리하는것은 성능에 좋지 않다
  • Dynamic remastering 이 가능 함
        A 가 master 인 리소스를 B 가 반복 요청한다면 어느순간부터 master 가 B 로 변경
        자주 사용하는 리소스의 상태정보를 직접 관리하므로 성능 향상에 도움이 됨 

RAC 캐시퓨전 부하도 I/O 부하와 같은 맥락이다
    블록의 읽기 요청이 많으면 I/O 이벤트 증가, RAC 관련 이벤트 증가
    SQL 튜닝을 통해 블록 읽기 요청 횟수를 줄여 인터커넥트 전송량을 감소시켜야 한다 



Result cache




  • Shared pool 에 위치
  • 버퍼캐시는 쿼리에서 자주 사용되는 블록을 캐싱하는 메모리 공간
    -> 이것도 비용이 수반되는 작업이므로 반복 엑세스가 많을 때 성능 저하 발생
    -> BCHR 이 낮을 수 밖에 없는 대용량 데이터라면 더 어려움

11g 에서 제공하는 result cache 기능은

  • 한 번 수행한 쿼리나 함수의 결과 값을 캐싱
  • DML 이 발생하지 않는 테이블이나 반복 수행이 많은 쿼리에 효과적
  • Result Cache: Latch, Result Cache: SO Latch 래치를 통해 관리한다
  • V$RESULT_CACHE_OBJECTS 뷰로 확인 가능하다
    SQL> select /*+ result_cache */ object_name from test1;
    
    
    SQL> select
    CREATION_TIMESTAMP
    ,ID
    ,TYPE
    ,STATUS
    ,NAME
    ,NAMESPACE
    ,BLOCK_COUNT
    ,CACHE_ID
    from V$RESULT_CACHE_OBJECTS
    ORDER BY CREATION_TIMESTAMP;
    
    CREATION_TIMESTAMP  ID          TYPE       STATUS    NAME                                                                                                                             NAMES BLOCK_COUNT CACHE_ID
    ------------------- ----------- ---------- --------- -------------------------------------------------------------------------------------------------------------------------------- ----- ----------- ---------------------------------------------------------------------------------------------
    2016/04/30 07:39:20           0 Dependency Published OPS$ORACLE.TEST1                                                                                                                                 1 OPS$ORACLE.TEST1
    2016/04/30 07:39:20           1 Result     Invalid   select /*+ RESULT_CACHE */ * from test1                                                                                          SQL             9 7dw9mfrbc4sckb8u8ubc9fswk1
    2016/04/30 07:39:46          10 Result     Invalid   select /*+ result_cache */ object_name from test1                                                                                SQL             2 3ndv1c9p6scrv33mxya0cyknzg
    2016/04/30 07:40:04          12 Result     Invalid   select /*+ result_cache */ object_name from test1                                                                                SQL             2 3ndv1c9p6scrv33mxya0cyknzg
    2016/04/30 07:40:10          14 Result     Invalid   select /*+ result_cache */ object_name from test1                                                                                SQL             2 3ndv1c9p6scrv33mxya0cyknzg
    2016/04/30 07:41:47          16 Result     Bypass    select /*+ result_cache */ object_name from test1                                                                                SQL             2 3ndv1c9p6scrv33mxya0cyknzg
    
    6 rows selected.
    



  • 메뉴얼 지정 방법일 때 아래와 같이 사용 가능하다 
    select /*+ result_cache */ col1, col2 from table;
    
  • 함수에서는 아래와 같이 사용 가능하다
    create or replace function test_fnc (in_num number)
    return varchar2
    RESUTL_CACHE RELIES_ON(cache_table)
    is
    ..
    
    


    -서버 프로세스는 result cache 메모리를 먼저 찾아보고 캐싱되지 않으면 쿼리 수행 후 캐시에 저장



두 가지 영역으로 구성 된다


-SQL Query result cache
-PL/SQL function result cache 

관련 파라메터는 아래와 같다


  • result_cache_mode
    manual: result_cache 힌트 명시한 sql 등록
        force: no_result_cache 힌트 명시하지 않은 모든 sql
        Default manual
  • result_cache_max_size
        sga 에서 bytes 로 result cache 사용량 지정
        Default N/A
  • result_cachem_max_result
        하나 sql 이 전체 result cache 에서 사용할 수 있는 최대 크기 % 지정
    Default 5
  • result_cache_remote_expiration
        remote 객체의 result cache 결과를 얼마나 보관할지 분 단위로 지정
        Default 0 


크기 지정은 메리 관리 방법에 따라


  • mamory_target 사용 시 0.25%
  • sga target 사용 시 0.5%
  • shared pool 지정 시 1%
  • 어떤 방법이든 shared pool 의 75% 이하 

사용 불가능한 경우


  • 일관성 없는 결과가 나오는 경우 사용이 불가하다
        DICTIONARY 오브젝트 참조
        TEMP TABLE 참조
        시퀀스 사용
        DATE 관련 함수 사용할 때
        SYS_CONTEXT, USERENV 등의 함수 사용 할 때 
  • 참조 테이블에 DML 발생 시 캐시 무효화 된다
        변경 사항에 따른 쿼리 영향에 관계 없이 무조건 무효화
        파티션으로 나뉘어져 있어도 동일하다
  • 함수 사용 시 RELIES_ON 에 명시한 테이블에 DML 발생 시 캐시 무효화 된다 


사용 유의사항 및 권고사항


  • 바인드 변수가 달라져도 개별적으로 캐싱한다
        변수 값이나 결과집합이 다양한 쿼리는 사용을 지양한다
  • 자주 DML 이 발생하는 테이블을 캐싱 시 비 효율적이다
        캐시 관리하는 과정에서 래치 경합 발생
        result cache 의 hit ratio 가 낮아진다면 쿼리 수행 비용 증가
  • Inline view, WITH, UNION 절 사용 시 힌트를 준 쿼리 블록만 독립적으로 캐싱된다


    Where 조건절의 서브쿼리 캐싱은 불가능하다 

  • 작은 결과집합을 얻기 위해 대용량 데이터 읽을 때 권장
  • 읽기 전용의 작은 테이블을 반복 엑세스 할 때 권장
  • 읽기 전용 코드 테이블을 읽어 코드명 반환 할 때 권장 
  • ient result cache 기능도 함께 제공하나 설명은 되어 있지 않음 



I/O 효율화 원리 요약




필요한 최소 블록만 읽도록 쿼리 제공


쿼리 튜닝 예시는 책 참조 

최적 옵티마이징 팩터 제공


1. 전략적 인덱스 구성 
2. DBMS 제공하는 기능 활용
    파티션, 클러스터, IOT 등... 
3. 옵티마이저 모드 설정
    all_rows, first_rows 
4. 통계정보 생성
아래 항목들이 통계정보로 수집 가능하다
    CPU
    평균 Single block, Multi block I/O 속도
    평균 Multi block I/O 수 
5. 필요 시 힌트를 적극적으로 사용한다 


'ORACLE > ADMIN' 카테고리의 다른 글

Oracle Events  (0) 2018.12.11
Achive Mode 설정 및 복구  (0) 2018.12.05
gc buffer busy 대기이벤트의 Parameter P3의 의미  (0) 2018.10.29
Performance Tuning Tools (성능 튜닝 툴)  (0) 2018.06.20
Oracle Wait Event 모니터링  (0) 2018.03.08

gc buffer busy 대기이벤트의 Parameter P3의 의미

오라클 2009.02.23 17:19

gc buffer busy와 gc current request 이벤트의 P1, P2, P3 값의 의미를 조회해 보자.

UKJA@ukja102> begin                                                        
  2    print_table('select name, parameter1, parameter2, parameter3        
  3             from v$event_name                                             
  4             where name in (''gc buffer busy'', ''gc current request'')'); 
  5  end;                                                                  
  6  /                                                                     
NAME                          : gc buffer busy                             
PARAMETER1                    : file#                                      
PARAMETER2                    : block#                                     
PARAMETER3                    : id#                                        
-----------------                                                          
NAME                          : gc current request                         
PARAMETER1                    : file#                                      
PARAMETER2                    : block#                                     
PARAMETER3                    : id#                                        
-----------------                         
                                 

Parameter1과 Parameter2의 의미는 명확하다. Parameter3(id#)의 의미는 무엇일까? 이것이 오랜 의문 중 하나였는데, 최근에 그 의미의 일부를 알게 되었다. Tanel Poder가 OTN Forum에 올린 답변을 통해서이다. 

gc cr request와 같은 대기이벤트의 경우에는 Parameter3 값을 통해 Block Class 정보를 제공하기 때문에 Block Dump를 수행하지 않고도 어떤 Block Class에서 문제가 발생하는 정확하게 알 수 있다. Block 레벨의 경합인 경우에는 Block Class 정보가 필수적이다. 

UKJA@ukja102> begin                                                 
  2    print_table('select name, parameter1, parameter2, parameter3 
  3             from v$event_name                                      
  4             where name in (''gc cr request'')');                   
  5  end;                                                           
  6  /                                                              
NAME                          : gc cr request                       
PARAMETER1                    : file#                               
PARAMETER2                    : block#                              
PARAMETER3                    : class#                              
-----------------                                    
               

다행스러운 것은 gc current request나 gc buffer busy 이벤트의 경우에도 Parameter3 값을 통해 Block Class 정보를 얻을 수 있다는 것이다. 더 정확하게 말하면 이들 Parameter3 값의 하위 2 Byte의 값이 Block Class이다.

가령 아래와 같이 대기 이벤트가 발생했다고 가정하면

gc current request  file#= 717   block#= 2  id#= 33554445 
gc buffer busy      file#= 1058  block#= 2  id#= 65549    

다음과 같이 하위 2 Byte의 값을 구할 수 있다(16진수로 변환했을 때 하위 2자리).

UKJA@ukja102> with                                     
  2       v1 as (select to_hex(33554445) as h from dual), 
  3       v2 as (select to_hex(65549) as h from dual)     
  4  select                                            
  5    to_dec(substr(v1.h, length(v1.h)-1, 2)) as v1,  
  6    to_dec(substr(v2.h, length(v2.h)-1, 2)) as v2   
  7  from v1, v2                                       
  8  ;                                                 
                                                       
        V1         V2                                  
---------- ----------                                  
        13         13                                  


13의 의미는 아래 뷰에서 찾을 수 있다.

UKJA@ukja102> select rownum, class from v$waitstat;

    ROWNUM CLASS
---------- ------------------
         1 data block
         2 sort block
         3 save undo block
         4 segment header
         5 save undo header
         6 free list
         7 extent map
         8 1st level bmb
         9 2nd level bmb
        10 3rd level bmb
        11 bitmap block
        12 bitmap index block
        13 file header block <-- Here!
        14 unused
        15 system undo header
        16 system undo block
        17 undo header
        18 undo block


즉, 위의 대기 이벤트는 File Header Block(LMT에서 Bitmap을 관리하는 Block)에서의 경합에 의해 발생했다는 것을 짐작할 수 있다. Block Class 정보만으로도 진단이 매우 손쉬워진 것이다. 이 정보가 없다면 Block Dump라는 귀찮은 작업이 뒤따른다.

몇 년간의 의문이 풀려서 기쁠 뿐이다(아마 Oracle Engineer에게 문의했으면 빨리 알 수 있었을텐데!)

Tanel Poder. 멋진 놈이다.

PS) to_hex, to_dec 함수는 여기서 구할 수 있다.



출처: http://ukja.tistory.com/209 [오라클 성능 문제에 대한 통찰 - 조동욱]

'ORACLE > ADMIN' 카테고리의 다른 글

Achive Mode 설정 및 복구  (0) 2018.12.05
I/O 효율화 원리  (0) 2018.10.29
Performance Tuning Tools (성능 튜닝 툴)  (0) 2018.06.20
Oracle Wait Event 모니터링  (0) 2018.03.08
LOB SEGMENT 생성 GUIDE(LOB column)  (0) 2018.03.07

Performance Tuning Tools (성능 튜닝 툴)


 

이번장에서는 튜닝을 위한 Performance Tuning Tools을 이용한 tuning 지점을 찾아내는 방법에 대해 알아보겠습니다. 

 

우리는 누적통계(v$), Metrics, sampled 통계 형태로 통계정보를 확인할 수 있습니다.

 

Tuning Tool에는 다음과 같은 것들이 있습니다.

기본 툴 : EM(enterprise manager page)
             Alert log
             Trace files
             Dynamic performance views and tables

Add-in : statspack
Option : Diagnostics Pack
            Tuning Pack

 

본 문서에서는 위에선 언급한 tool 중  기본툴을 중심으로 다루어 보겠습니다.

 

 

1. EM (Enterprise manager page)

 

이미 수많은 실습을 통해 확인했던 것처럼

EM을 통해 우리는 많은 통계 정보를 확인해 볼 수 있습니다.

 

- EM은 Web brower를 통해 작동되며 접속하는 방법은 http://host name:port number/em

   URL을 사용해 가능합니다.

- 해당 EM의 포트번호는 $ORACLE_HOME/install/portlist.ini file을 통해 확인해 볼수 있습니다.

 

2. Alert Log

 

- 모든 database는 alert_<sid>.log 파일을 가지고 있으며 alert_log에는 다음과 같은 사항이 기록됩니다.

 

   1) startup시 사용된 모든 nondefault 초기화 파라미터 정보

   2) ORA-600 에러를 비롯한 block curruption, deadlock 등 오류 메세지 관련정보

   3) 관리자의 동작 : SQL을 통한 테이블스페이스 생성, 변경, Alter 명령을 통한 시스템 변경 등.

   4) 데이터베이스의 시작과 종료

   5) 백그라운드 프로세스의 시작과 종료

   6) LGWR가 기록한 로그 순차번호 및 로그 스위치 관련 정보.

 

    ... 등등.

 

    여기서 nondefault parameter 는  오라클의 default parameter 인 내장된 hidden parameter를 제외한 파라미터들을

    말합니다.

 

- 우리는 alert.log의 다음과 같은 정보를 통해 tuning에 필요한 정보를 얻을 수 있습니다.

 

  1) 불완전 체크포인트

  2) archiving 동작 시점

  3) instance 복구의 시작과 완료 시점

  4) 데드락(Dead lock), time out 에러 정보

  5) checkpoint의 시작과 끝 시점

      : alert.log에서 RBA(Redo byte address)를 확인하기 위해서는 LOG_CHECKPOINTS_TO_ALERT 파라미터를

        True로 설정해야 합니다.

 

실습1  : alert.log를 통한 Deadlock 관련 정보 확인하기
           
Deadlock 이 발생하여, 서로 lock이 걸려 이에따른 wait가 일어나고 있으며 이는 응답시간(DB time)이 길어지고 있는상황입니다. 이때, tunning 포인트는 해당 Deadlock 지점을 확인해 해당 세션을 끈어주는 것이 방법이 되겠습니다. 


1) Deadlock 발생

====  세션 1

SCOTT>update emp set sal=sal*1.2
  2  where empno=7788;

1 row updated.

==== 세션 2

SCOTT>update emp set sal=sal*1.2
  2  where empno=7654;

1 row updated.

SCOTT>select sal from emp
  2  where empno=7788;

       SAL
----------
      3500

SCOTT>update emp set sal=sal*1.2
  2  where empno=7788;

 wait 발생 세션 1이 해당 열에 lock(TX)를 걸었기 때문입니다.

====  세션 1

SCOTT>
SCOTT>update emp set sal=sal*1.2
  2  where empno=7654;
update emp set sal=sal*1.2
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource

deadlock이  발생했습니다.


          
2) alter log에서 해당 deadlock 정보 확인

  ..
  ORA-00060: Deadlock detected. More info in file /u01/app/oracle/admin/orcl/udump /orcl_ora_18749.trc.


3) 해당 trace 파일을 통한  deadlock 정보 확인

DEADLOCK DETECTED
[Transaction Deadlock]
Current SQL statement for this session:
update emp set sal=sal*1.2
where empno=7654
The following deadlock is not an ORACLE error. It is a
deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following
information may aid in determining the deadlock:
Deadlock graph:
                       ---------Blocker(s)--------  ---------Waiter(s)---------
Resource Name          process session holds waits  process session holds waits
TX-00020015-000002d3        15     144     X             22     159           X
TX-0005002d-000002ea        22     159     X             15     144           X
session 144: DID 0001-000F-00000018     session 159: DID 0001-0016-00000074
session 159: DID 0001-0016-00000074     session 144: DID 0001-000F-00000018
Rows waited on:
Session 159: obj - rowid = 0000CDD7 - AAAM3XAAEAAAAOuAAH
  (dictionary objn - 52695, file - 4, block - 942, slot - 7)
Session 144: obj - rowid = 0000CDD7 - AAAM3XAAEAAAAOuAAE
  (dictionary objn - 52695, file - 4, block - 942, slot - 4)
Information on the OTHER waiting sessions:

SCOTT>
SCOTT>select dbms_rowid.rowid_object('AAAM3XAAEAAAAOuAAH') from dual;

DBMS_ROWID.ROWID_OBJECT('AAAM3XAAEAAAAOUAAH')
---------------------------------------------
                                        52695

SYS>select owner, object_name, object_type
 2  from dba_objects
 3  where object_id=52695

OWNER           OBJECT_NAME                    OBJECT_TYPE
--------------- ------------------------------ ---------------
SCOTT           EMP                            TABLE


alter log에서 확인한 trace 파일을 열어 문제의 rowid를 확인하고 해당 지점을
파악할 수 있습니다.

 

 

3. User Trace Files

 

 - 사용자 추적파일(user trace file)은 서버 프로세스에 의해 기록되며 주로 사용자들의 SQL 문장을 수집하여

    SQL문장을 튜닝하고 하는 경우 사용된다. 

 - Show parameter user_dump를 통해 user trace file의 위치를 확인할 수 있습니다.

 - trace file에 식별을 위한 식별자 달기

   SQL>alert session set tracefile_identifier="minho'

 - 이 파일을 분석하여 DBA는 튜닝을 위한 진단을 할 수 있다.

 

 

실습2  : User trace file을 통한 사용자가 사용한 SQL 문장 정보 확인하기 (tkprof 사용하기)
           
User trace file사용해 사용자가 적적한 SQL문장을 사용하고 있는 확인 및 SQL을 분석해
튜닝해야할 지점을 예측해 볼 수 있다.

1) 세션 조회

SYS>select username, sid, serial#, status from v$session;

USERNAME                              SID    SERIAL# STATUS
------------------------------ ---------- ---------- --------
SYS                                   144         77 ACTIVE
SYS                                   158         15 INACTIVE
HR                                    159         82 INACTIVE


2) 해당 세션에 trace 파일 생성 설정


SYS>exec dbms_monitor.session_trace_enable (159, 82, waits=>TRUE, binds=>TRUE);

PL/SQL procedure successfully completed.

SYS>

3) 해당 세션이 작업진행

SQL> select * from employees;
SQL> select * from employees where employee_id=100;
SQL> select * from employees order by 1,2;
SQL> select * from job_history;
SQL> exit

등등..


4) tkprof를 통한 trace 파일 분석

[ocp@orcl : /u01/app/oracle/admin/orcl/udump]$ tkprof orcl_ora_19547.trc output.txt explain=hr/hr sys=no

TKPROF: Release 10.2.0.1.0 - Production on Mon Sep 21 16:26:05 2009

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

[ocp@orcl : /u01/app/oracle/admin/orcl/udump]$ ls
orcl_ora_19547.trc  output.txt

output.txt 파일을 통해 사용자의 작업에 대한 추적이 가능하다.

 

 

4. Dynamic Performance Views

 

 - 인스턴스 내에서 이루어지는 변경사항을 실시간으로 조회 할 수 있는 View이다.

 - startup 이후로 부터 쌓아지 누적통계 정보이다.

 - 소유자는 sys임

 

 

5. 통계 levels

 

 - 통계 level에는 basic, typical, all 형태가 있다.

 

   1) basic 레벨 : 통계수집을 하지 않는다.

   2) typical 레벨 : default값으로 기본적인 사항의 통계수집이 이루어짐

   3) all 레벨 : typical 의 통계자료이외에 추가적인 통계수집이 이루어지며, SQL 진단기능을 제공한다.

 

 6. System Statistic Classes

 

 - 시스템 통계정보는 v$SESSTAT와 v$SYSSTAT 등의 뷰에서 확인할 수 있다

 - 이들 뷰에는 통계정보에 대한 class 구분이 되어져 있어서 문제가 발생하는 요소에 대한

    진단을 용이하게 하고 있다.

 - class 구분

    1, user

    2, redo

    4, enqueue

    8, cache

    16, os

    32, real application clusters

    64, sql

    128, debug

 

   만약 class 값이 72였다면 64+8을 의미하므로 sql과 cache에 관한 문제가 있음을 예측할 수 있다.

 

7. Wait event

 

 - wait 는 문제의 원인이 아닌 문제에 대한 징후, 현상이다.

 - 오라클 10g 에서는 약 870개의 wait 이벤트가 존재한다.

    : buffer busy wait, free buffer wait, library cache wait 등..

 - wait 또한 특성에 따라 class로 구분하여 볼수 있다.

    관련뷰 : V$SESSION_WAIT_CLASS, V$SYSTEM_WAIT_CLASS, V$SERVICE_WAIT_CLASS, V$EVENT_NAME

 

 - 세션 레벨 wait 이벤트는 다음 3가지로 view로 나누어 볼수 있다.

    1) V$SESSION_EVENT : 과거의 wait 정보를 확인

    2) V$SESSION_WAIT : 현재의 wait 정보를 확인

    3) V$SYSTEM_EVENT : 과거와 현재 모두(전체) wait 정보 확인

 

8. Time model

 

 - 튜닝의 목적은 DB time 사용자 관점으로 볼때 응답시간을 줄이는 것이 목표이다.

 - 튜닝지점은 가장 튜닝시 가장 impact가 큰 지점을 대상으로 한다.

 - SQL 튜닝이 보편적으로 가장 핵심적인 튜닝 요소이다.
















ORA-60 Dead Lock Detected 에러가 발생하면 다음과 같이 데드록 그래프와 관련된 프로세스 및 세션 그리고 트랜잭션 정보 및 수행중인 SQL 문을 담고있는 Trace 파일을 생성시켜 주고 있다. 일반적으로 발생하는 로우레벨 록(row-level lock)에 의한 데드록은 이정보가 문제상황을 어느정도 설명하는데 부족함이 없으나, 로우레벨록이 아닌 다른 모드의 록이 개입될 경우에 대한 분석을 사례를 통해 알아보자.
결론적으로 이 문제는 Exclusive 모드의 TX 록과 ITL의 부족으로 인한 공유모드의 TX 록에 의한 데드록 상황이었다. 분석 과정에서 사용된 툴은 아래의 On Error Event Trace를 사용하였으며, 이 정보들을 분석하여 좀 더 명확히 당시 상황을 확인할 수 있었다.

 

주의 : System State Dump는 오라클 인스턴스를 구성하고 있는 모든 프로세스에 대한 Process State Dump를 기록하기 때문에 경우에 따라서는 매우 많은 디스크 용량 및 IO 작업을 필요로 할 수 있다.

다음은 ORA-60 에러 발생시 발생되는 Trace 정보이다(Real Application Cluster 환경에서는 LMD 프로세스의 Trace 파일에 데드록 그
래프에 대한 정보를 Global WFG에서 제공한다.)

 

*** 2005-02-18 02:54:25.153
*** SESSION ID:(7.7) 2005-02-18 02:54:25.134
DEADLOCK DETECTED
Current SQL statement for this session:
update tctab set name = null where id = 5342
The following deadlock is not an ORACLE error. It is a deadlock due to user error in the design of an application
or from issuing incorrect ad-hoc SQL. The following information may aid in determining the deadlock:
Deadlock graph:
---------Blocker(s)-------- ---------Waiter(s)---------
Resource Name process session holds waits process session holds waits
TX-00020020-0000013f 8 7 X 11 11 X
TX-0008001c-00000126 11 11 X 8 7 S
session 7: DID 0001-0008-00000002 session 11: DID 0001-000B-00000002
session 11: DID 0001-000B-00000002 session 7: DID 0001-0008-00000002
Rows waited on:
Session 11: obj - rowid = 00007B9B - AAAHubAABAAARLiAF9
(dictionary objn - 31643, file - 1, block - 70370, slot - 381)
Session 7: obj - rowid = 00007B9B - AAAAAAAABAAARLjAAA
(dictionary objn - 31643, file - 1, block - 70371, slot - 0)
SQL statements executed by the waiting sessions:
Session 11:
update tctab set name = null where id = 4966

 

위의 내용으로 SID-7 세션과 SID-11 세션이 데드록 상태로 다음과 같은 상태였음을 알 수 있다.

• SID-11
- TX-0008001c-00000126를 X 모드에서 잡고 TX-00020020-0000013f를 X 모드에서 기다리고 있음.
- update tctab set name = null where id = 4966
- 위의 Update 문으로 변경되는 로우의 RowID가 ‘AAAHubAABAAARLiAF9’로 file /block/slot이 1/70370/381 로우를 업데이트하려고 기다리던 상태

• SID-7
- TX-00020020-0000013f를 X 모드로 잡고 있으며 TX-0008001c-00000126을 공유 모드에서 기다리고 있음.
- update tctab set name = null where id = 5342
- 위의 Update 문으로 변경되는 로우의 RowID가 ‘AAAAAAAABAAARLjAAA’로 file /block/slot이 1/70371/0 로우를 업데이트하려고 기다리던 상태

 

위 정보를 보면 SID-7이 공유모드로 TX 록을 기다리던 상태로 보아 일반적인 로우레벨록에 의한 데드록 문제가 아님을 알 수 있다.

TX 록을 공유모드로 기다리게 되는 경우는 다음과 같다.

• Unique or Primary Key Constraint
- Unique Constraint나 PK Constraint가 비활성화되어 있는 상태에서 동일한 키를 입력하는 세션들로 인하여 발생되는 경우.

 

Eg:
Ses#1: ALTER TABLE tx_eg ADD CONSTRAINT tx_eg_pk PRIMARY KEY( num );
Ses#1: insert into tx_eg values (10,'New','MALE');
Ses#2: insert into tx_eg values (10,'OtherNew',null);

 

• ITL 슬롯 부족
- DML 수행중 테이블 또는 인덱스 록에 처음 변경을 하기 위해서는 우선 블록헤더에 트랜잭션 정보를 저장시킬 트랜잭션 엔트리(ITL 슬롯)를 확보한 후에 로우에 대한 변경 작업이 가능하다. 이때 이 블록에서 사용 가능한 ITL 슬롯들을 다른 트랜잭션이 사용중인 경우 공유 모드로 특정 TX가 종료되기를 기다리게된다.
• 동일한 비트맵 인덱스의 프래그먼트에서 관리되는 로우들에 대한 업데이트 작업 비트맵 인덱스의 동일한 프래그먼트 내에 아직 종료되지 않는 TX에 의하여 변경된 키가 존재하는 경우 후속TX는 공유 모드로 선행TX가 종료되기를 기다리게된다.
• Prepared State TX
- Prepare 상태의 TX를 지닌 블록의 경우 액세스(SELECT/DML)시 이 Prepared State TX를 공유 모드로 기다리게 된다(실제 이 사례의 경우 공유 모드로 기다리던 프로세스가 ORA-60이 발생되어 Errorstack Dump의 Stack Trace의 기능을 확인하여ITL 할당중에 있었음을 확인하였음.)


위의4가지 경우에 해당하는지는 당시 업데이트되던 테이블의 조건 및 인덱스종류등을확인하여ITL 부족에의한문제이거나Prepared State TX 에 의한 문제로 압축되었다.

다시 위 2가지 경우에 대한 추가 분석을 위하여 Systemstate Dump에서 SID-7이 작업 중이던 블록(1/70371)에 대한 덤프를 추가로 분석하였다.

 

다음은 블록(1/70371)에 대한 현재 상태의 버퍼 핸들 및 블록 덤프 내용이다.

 

BH (0xc000000072fcab00) file#: 1 rdba: 0x004112e3 (1/70371) class 1 ba: 0xc0000000728a0000
... <생략> ...
st: XCURRENT, md: NULL, rsop: 0x0000000000000000, tch: 3
... <생략> ...
Block header dump: 0x004112e3
Object id on Block? Y
seg/obj: 0x7b9b csc: 0x00.6b504 itc: 2 flg: - typ: 1 - DATA
^^
총ITL 슬롯개수
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 xid: 0x0008.01c.00000126 uba: 0x0080007a.007b.01 ---- 1 fsc 0x0009.00000000
0x02 xid: 0x0006.019.000001be uba: 0x00800068.006f.03 ---- 1 fsc 0x0009.00000000
=> 2개의slot 모두active TX의의하여사용중.

 

data_block_dump
===============
tsiz: 0x1fa0
hsiz: 0x30e
pbl: 0xc0000000728a005c
bdba: 0x004112e3
flag=-------------
ntab=1
nrow=382
frre=-1
fsbo=0x30e
fseo=0x43d
avsp=0x1ba => 가용공간이442 byte
tosp=0x1cc
0xe:pti[0] nrow=382 offs=0
0x12:pri[0] offs=0x1f8e
0x14:pri[1] offs=0x1f7c

 

위 블록에서 사용 가능한 스페이스가442바이트로 비교적 여유가 있는 상태로 ITL 슬롯의 총 수를 제한하는 Maxtrans를 확인해 본 결과 Maxtrans 파라미터가 2로 설정되어있어 이에 의한 문제로 확인이 되었다. 사실이 문제는 ORA-60 에러에 대한 Errorstack Dump가 마침 공유모드로 TX를 기다리던 프로세스에 대하여 발생되어 이 Trace 파일의 Call Stack에서 분석 초기부터 ITL 슬롯의 할당 문제임이 확인되었었다.
그 이유는 이 프로세스가 ORA-60 에러를 만났을 때 수행 중이던 오라클 기능이 ITL 슬롯을 할당받는 과정이었던 상태임을 설명하고 있기 때문이다.

 

Call Stack Trace
• Ksqgel - get an enqueue
• ktcwit1 - transaction controlfile wait
• ktbgfi - find an itl entry





Reference by http://cafe.naver.com/metanetbts.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=491&

'ORACLE > ADMIN' 카테고리의 다른 글

I/O 효율화 원리  (0) 2018.10.29
gc buffer busy 대기이벤트의 Parameter P3의 의미  (0) 2018.10.29
Oracle Wait Event 모니터링  (0) 2018.03.08
LOB SEGMENT 생성 GUIDE(LOB column)  (0) 2018.03.07
Compound Triggers  (0) 2018.01.23


펌 :  https://m.blog.naver.com/PostView.nhn?blogId=mabina&logNo=100006040112&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F


[1] Oracle Wait Event 모니터링

흔히 DBA를 3D업종이라고 부르는 이유 가운데 하나는 몸은 고달픈데 반해 그 성과가 별로 티가 나지 않는다는 사실 때문일 것이다. 실제로, DBA가 수행해야 하는 일상적인 관리 업무들은 몸은 다소 피곤하게 만들지 몰라도 어느 정도 경험이 쌓이면 그리 부담을 주는 일은 아니다. 우리가 한단계 업그레이드된 전문가로서 인정 받는 DBA가 되기 위해서는 장애상황 혹은 유사 장애 상황에서 DB 모니터링 작업을 수행하고 분석할 수 있어야 한다. 시스템이 갑자기 느려지고 업무가 마비되는 상황에 맞닥뜨렸을 때 문제의 원인이 무엇인지를 집어낼 수 있는 능력이 있어야 하며 최소한 오라클의 문제인지 아닌지를 판단할 수는 있어야 몸으로 야간작업이나 때우는 DBA가 아니라 조직에 없어서는 안될 전문가로서의 나의 존재가치를 인정 받을 수 있을 것이다. 

이 글에서는 오라클 Wait Event에 대하여 간단히 알아보고 일시적인 성능저하 상황에서 Wait Event를 모니터링하고 그 원인을 찾아가는 방법에 대하여 다루어 보고자 한다. 짧은 지면 위에 다룰 수 있는 내용도 제한되어 있고 글쓴이의 지식 또한 일천하지만 오라클 전문가가 되기 위해 같은 길을 가고 있는 동료로서 가진 지식 몇 가지 공유한다는 취지로 이 글을 쓴다.

오라클의 Wait Event 정보는 V$SYSTEM_EVENT, V$SESSION_EVENT, V$SESSION_WAIT 등이 있는데, 이 가운데 V$SESSION_WAIT는 각 세션이 현재 Waiting 하고 있는 Event나 마지막으로 Wait한 Event 정보를 보관하고 있으며, V$SYSTEM_EVENT와 V$SESSION_EVENT는 시스템이 Startup된 이후 각각 시스템 전체, 혹은 세션별로 발생한 Wait Event 정보를 누적하여 기록하고 있다. 

오라클의 Wait Event는 성격에 따라 Network교신이나 IO를 위해 대기하는 일상적인 Wait와 특정 자원에 대해 여러 프로세스가 동시에 액세스하고자 할 때 발생하는 Wait, 별달리 할 일이 없어 대기하고 있는 Idle Wait 등 세가지 유형으로 구분할 수 있는데 그 유형에 따라 해석방법도 달라진다. 일단, Idle Wait는 일반적인 관심의 대상에서 제외되며 IO나 Network 관련 Wait는 작업량이 증가하면 같이 증가하는 Wait이므로 전체 서비스 시간(CPU time)과 비교하여 상대적으로 평가해야 하며 총 Wait time보다는 평균 Wait Time에 관심을 두고 분석을 해야 할 것이다. 시스템 자원에 대한 Wait는 데이터베이스 서버 튜닝시 가장 주된 관심 대상이 되며 이들 Wait에 대해서는 평균 Wait Time뿐만 아니라 총 Wait Time에도 관심을 가지고 분석해야 할 것이다. 유형별로 대표적인 Wait Event를 살펴본다면 아래와 같다. 

[주요 Wait Event]

구분
이벤트명
설 명
일상적인 Wait Eventdb file scattered readFull Scan시 OS에 I/O를 요청해놓고 대기
db file sequential readIndex Scan시 OS에 I/O를 요청해놓고 대기
(IO, Network)log file sync변경 log buffer를 log file에 반영하는 동안 대기
DFS lock handleOPS 환경에서 노드간 분산 Lock 교환에 따른 대기
global cache cr requestOPS 환경에서 노드간 Buffer Block 교환에 의한 대기
자원 경합에 따른 
Wait Event
enqueueType에 따라 세분화 (24개의 enqueue type (9i))
latch freeName에 따라 세분화 (239개의 latch가 존재 (9i))
buffer busy waits동일블록에 대한 동시 액세스에 따른 경합
free buffer waitsfree buffer를 할당위해 DBWR의 Write를 대기
Log buffer spaceLog buffer를 할당 받기 위해 LGWR의 write를 대기
library cache lockSGA내의 library cache를 참조하기 위한 대기(검색)
row cache lockSGA내의 dictionary cache를 참조하기 위한 대기
Idle EventSQL*Net message from clientClient로부터의 작업요청을 대기
Pmon timerPMON이 할일 없을 때 대기하는 Event


업무시간대에 시스템이 갑자기 느려졌다면서 오라클 서버에 문제가 없는지 문의가 들어오면 글쓴이는 우선 아래의 SQL을 수행시켜본다. 

select /*+ ordered / distinct /* 속도를 위해 v$sql을 조인할 경우 중복되는 레코드 제거 */
           s.sid SID, s.username, s.program, p.spid "OS-Pid",w.seconds_in_wait as "W_time(Sec)",
          decode(w.wait_time,0,'Wai-ting', 'Waited') Status, w.ename event,
--              p1text || ':' || decode(event,'latch free',p1raw, to_char(p1)) ||','||
--              p2text || ':' || to_char(p2) ||','|| p3text || ':' || to_char(p3) "Additional Info",
           q.sql_text
from ( select a.*, decode(a.event,'latch free', 'latch free (' ||b.name||')', 
                                         'row cache lock', 'row cache lock (' || c.parameter || ')',
                                         'enqueue', 'enqueue ('||chr(bitand(p1, -16777216)/16777215)||
                                                                             chr(bitand(p1,16711680)/65535)||':'||
                                                decode(bitand(p1,65535), 1, 'N', 2, 'SS',3,'SX',4,'S',5,'SSX',6,'X') ||')',
                              a.event ) ename
           from v$session_wait a, v$latchname b, v$rowcache c
         where a.p2 = b.latch#(+) and a.p1 = c.cache#(+) and c.type(+) = 'PARENT'
           and a.event not in ('rdbms ipc message','smon timer','pmon timer','slave wait','pipe get','null event',
                                     'SQL*Net message from client', 'SQL*Net message to client','PX Idle Wait', 
                                      'PX Deq: Execution Msg', 'KXFQ: kxfqdeq - normal deqeue',
                                         'ges remote message', 'wakeup time manager', /* idle event 적절히 수정 */
                                         'lock manager wait for remote message', 'single-task message')
        ) w, v$session s, v$process p, v$sql q
where w.sid = s.sid and s.paddr = p.addr
and s.sql_hash_value = q.hash_value(+) and s.sql_address = q.address(+)
order by w.ename;


SQL의 구체적인 내용이야 필요한 정보와 개인적 취향에 따라 달라지겠지만, 중요한 것은 일단 V$SESSION_WAIT 뷰로부터 실시간 Wait Event 정보를 얻어낸다는 것이다. 위 SQL을 수행했을 때 나타나는 결과가 없다면 일단 오라클 측면에서 업무성능을 심각하게 마비시키는 Waiting이 발생하고 있지 않다고 봐도 큰 무리가 없을 것이다. 

일반적인 상태에서는 주로 'db file sequential read'나 'db file scattered read' 가 나타날 텐데, 이러한 Wait Event는 보통 짧은 시간 동안 지속되며 대상 자원(블록)을 바꿔가며 Wait가 반복되는 형태로 나타날 것이다. 이는 작업 처리량이 많을 때 일상적으로 발생하는 IO관련 Wait Event이므로 해당 세션에서 IO를 제법 많이 유발하고 있다는 정도로 이해하고 넘어가면 될 것이다. 물론, Wait의 지속시간이 길거나 지나치게 빈번히 나타나는 SQL에 대해서는 비효율적인 실행계획을 수립하고 있지 않은지 검토해서 튜닝해 주어야 한다.

성능저하의 원인이 오라클 쪽에 있는 경우에는 특정 자원에 대한 Waiting이 상당히 오랫동안 지속되어 현재까지 Waiting이 진행 중인 세션들(STATUS가 'Wai-ting' (wait_time=0)이며 'W_time(sec)' (seconds_in_wait) 값이 상당히 큰 세션)이 존재할 가능성이 높다. 오라클의 내부적인 작업들은 매우 짧은 기간에 처리되어야 하므로, Idle event(where절에서 not in으로 처리한 부분, 버전에 따라 달라질 수 있다.) 이외의 특정 Wait Event가 눈에 띌 정도로 검출된다는 것은 오라클 내부적으로는 훨씬 더 많은 Waiting이 발생하고 있다고 생각해야 한다. 바로 이런 세션들이 문제의 범인들이며 이제부터 DBA는 이들 Wait Event에 대한 원인을 파악하여 조치하는 작업을 해주어야 한다. 각각의 Wait Event에 따라 원인을 추적하고 조치하는 방법은 달라질 것이다. 

다음 글에서는, 자주 경험하는 몇가지 대표적인 Wait Event들에 대하여 SGA 영역별로 구분하여 좀 더 자세히 살펴보고, 그에 앞서 Lock 또는 Latch Event의 이해를 위해 필요한 Enqueue와 Latch의 개념을 간단히 알아보도록 하겠다.

[2] Enqueue와 Latch 개념 이해하기 

DBMS의 가장 주된 기능 중에 하나는 동일 자원에 대한 동시 액세스를 관리하는 것이며, 이를 위해 오라클이 사용하는 대표적인 제어 구조가 Enqueue와 Latch이다. 
Enqueue와 Latch는 모두 특정 자원에 대한 접근을 serialize하는 것이 목적이라는 점에서는 같은 Lock의 일종이지만 관리방식이나 용도에서 차이가 있다. Enqueue는 이름에서 보듯 Queue를 통해 관리된다. 대상 자원에 대한 Owner, Waiter, Converter Queue를 관리하면서 먼저 요청한 순서대로 Lock을 획득하도록 하는 구조이며, Exclusive 모드 뿐 아니라 다양한 수준의 공유를 허용한다. 대표적인 것이 테이블 데이터를 Update할 때 사용되는 TM, TX enqueue이다. 

반면에, Latch는 Enqueue에 비해 훨씬 단순한 구조로서 매우 짧은 시간 내에 획득되고 해제된다. Queue를 통해 관리되지 않으므로 먼저 Request한 프로세스가 먼저 latch를 획득한다는 보장이 없으며, 대부분의 경우 Exclusive모드로만 획득된다. Latch는 주로 SGA의 특정 메모리 구조체에 대한 액세스(library cache latch, cache buffers chains latch) 혹은 메모리 할당 시 (shared pool latch) 사용되거나 오라클의 중요한 코드가 동시에 수행되지 않도록 하기 위한 용도로(redo writing latch) 사용된다. Latch는 Enqueue보다는 하위 level에서 Locking 자체의 부하를 최소화하며 작동하는 제어 메커니즘이라고 할 수 있으며, 실제로 Enqueue 역시 내부적으로는 Latch (enqueues, enqueue hash chains latch )에 의해 운영된다는 점을 생각하면 둘 사이의 차이를 쉽게 이해할 수 있을 것이다. 

■ Enqueue

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


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


■ Latch

오라클 운영 시에 하위레벨에서 내부적으로 처리되는 다양한 조작들이 latch의 관할 하에 수행되는데 V$LATCHNAME을 조회해보면 (9i 기준으로) 239 종류나 되는 Latch가 존재하는 것을 확인할 수 있다. 이 가운데 우리가 자주 접하게 되는 latch는 다음과 같은 정도이며 각 Latch의 기능은 관련 SGA별 Wait를 다룰 때 간단하게나마 소개하도록 하겠다. 

Shared pool
library cache latch, shared pool latch, row cache objects
Buffer Cache
cache buffers chains latch, cache buffers lru latch, cache buffer handle
Redo log
redo allocation latch, redo copy latch, redo writing latch
OPS
dlm resource hash list



▷ Willing to wait 모드와 No-wait 모드

Latch 획득 방식은 No-wait과 Willing to wait 의 두 가지 모드로 구분할 수 있다. Willing to wait 모드는 Latch의 획득에 실패하면 좀더 시간을 끌면서 해당 Latch를 잡을 때까지 재시도를 해보는 방식을 말한다. 일차적으로는 CPU를 놓지 않고 정해진 횟수만큼 Spinning을 한 후 재시도를 해보다가 그래도 실패하면 CPU를 놓고 Sleep하다가 timeout되어 재시도하는 작업을 반복하면서 Latch의 획득을 노력하게 된다. Latch가 sleep에 들어가게 되면 'latch free' wait event 대기가 시작된다. sleep의 지속시간은 sleep 횟수가 늘어갈수록 점점 길어지게 되는데, 따라서 V$LATCH의 Gets와 Sleeps의 비율과 함께 Sleep1~sleep4 항목에서 몇차 Sleep까지 발생했는지 여부도 각 Latch Wait의 심각성을 판단하는 요소 가운데 하나가 된다. 

No-wait 모드는 Willing to wait과는 달리 더 이상 미련을 두지 않고 해당 Latch에 대한 획득을 포기하는 것이다. No-wait 모드가 사용되는 경우는 두 가지가 있는데, 하나는 동일한 기능을 하는 Latch가 여러 개 존재하여 그 중에 하나만 획득하면 충분하여서 특정 Latch에 미련을 가질 필요가 없는 경우이다. 물론, 이 때에도 같은 기능의 모든 Latch에 대한 시도가 실패로 끝날 경우에는 Willing to wait 모드로 요청을 할 것이다. No-wait 모드가 사용되는 다른 한가지 경우는 dead lock을 피하기 위해서 이다. 오라클은 기본적으로 latch dead lock 상황을 피하기 위하여 모든 Latch에 level을 부여하여 정해진 순서를 따라서만 Latch를 획득하도록 하고 있는데, 필요에 의해 이 규칙을 어기고 Latch를 획득하고자 할 경우 일단 No-wait 모드로 시도를 해보는 것이다. 다행히 Latch를 잡으면 좋은 것이고 비록 latch를 잡을 수 없더라도 무한정 기다림으로써 dead lock 상태에 빠지는 일은 피할 수 있는 것이다. No-wait 모드의 Latch작업에서는 당연히 Latch 관련 wait이 발생하지 않으며, redo copy latch를 제외하고는 Willing to wait 모드로 Latch를 획득하는 경우가 훨씬 많다. 

 Parent latch와 Child latch

Latch 가운데에는 동일 기능을 하는 Child latch들의 set으로 운영되는 Latch도 있으며 하나의 Latch로만 운영되는 Latch도 있다. 전자의 대표적인 예로는 cache buffers chains (버퍼캐쉬 블록 들을 같은 이름의 다수의 Latch가 나누어 담당)가 있으며, 후자의 예로는 shared pool latch (shared pool내에서 메모리 할당을 위해 획득해야 하는 Latch로 시스템에 하나만 존재)가 있다. 이와 같은 Latch 관련 통계 정보는 Parent latch와 Child latch의 개념으로 관리가 되는데 Latch set에서 개별 Child latch에 대한 통계정보는 V$LATCH_CHILDREN 뷰를 통해 조회할 수 있으며, 단일 Latch 혹은 Latch set의 마스터 Latch (parent)에 대한 통계정보는 V$LATCH_PARENT 뷰를 통해 조회할 수 있다. 

지금까지 한 회 분량을 할애하여 Enqueue와 Latch에 대해 요약해본 이유는, 많은 Waiting이 SGA내의 공유자원 (Block, Cursor 등)에 대한 경합으로 인해 발생하며 이러한 경합은 다시 해당 자원에 대한 동시 액세스를 제어하는 Enqueue와 Latch에 대한 경합으로 흔히 드러나게 되므로 오라클의 Wait Event를 모니터링하기 위해서는 Enqueue와 Latch의 구조와 작동원리에 대해 이해하는 것이 필수적이기 때문이다.

 

[3] Shared Pool 관련 Wait 

■ Share pool과 성능문제

오라클이 공유 메모리(SGA)를 사용하는 가장 큰 이유는 기본적으로 메모리 사용을 최소화하면서 처리성능은 최대화하기 위한 것이다. 한번 액세스된 블록을 Database buffer cache에 캐쉬 함으로써 비용이 큰 Disk I/O를 최소화하는 것처럼, 한번 처리된 SQL의 실행 정보를 Shared Pool에 공유함으로써 파싱 작업을 위한 CPU, 메모리 자원의 사용을 최소화하고 SQL 수행속도를 증가시킬 수 있다. Shared Pool에는 SQL이나 PL/SQL을 수행하기 위한 각종 정보 - SQL구문 및 실행계획, PL/SQL 소스, 테이블, 뷰 등의 각종 오브젝트와 오브젝트 상호간의 의존관계, 권한관계 등 - 가 저장되어 있다. 지면 관계상 이 글에서 Shared Pool의 관리 메커니즘을 상세히 기술할 수는 없지만 몇 가지 내재적인 특징으로 인해 Shared Pool은 오라클의 메모리 영역 가운데에서도 가장 성능문제의 요소가 많은 곳이면서도 효과적인 튜닝이 수월치 않은 영역이기도 하다.

무엇보다, Shared Pool에서 가장 문제가 되는 것은 메모리의 조각화(Fragmentation)이다. Shared Pool에서 라이브러리 캐쉬 오브젝트를 위해 할당되는 메모리 단위를 chunk라고 부르는데 chunk의 크기는 수 바이트에서 수 K바이트에 이르기까지 필요에 의해 다양하게 할당된다. 새로운 chunk의 할당이 필요하게 되면, 프로세스는 이미 존재하는 chunk로부터 필요한 만큼의 크기만을 떼어내어 사용하므로 시간이 흐를수록 점차 메모리가 조각화 되는 것을 피할 수 없다. ( 이는, Pctincrease가 0가 아닌 테이블스페이스에서 익스텐트의 할당과 해제가 반복됨에 따라 공간의 조각화가 심해지는 것을 떠올리면 이해가 쉬울 것이다. ). 어느 정도 정형화된 패턴의 애플리케이션이 수행되는 환경이 아니라, 공유가 불가능한 다양한 형태의 SQL(대표적으로 Literal SQL)이 빈번히 요청되는 환경이라면 Shared Pool 메모리 조각화에 따른 문제는 더욱 심각해진다. 

또한, Shared Pool은 일반적인 메모리 캐쉬와는 달리 메모리에 저장되었던 정보를 잠시 기록해둘 대응되는 디스크 공간이 없으므로 한번 flush된 라이브러리 캐쉬 오브젝트를 reload하기 위해서는 해당 정보를 재생성 해야만 한다. 이 과정에서 관련 오브젝트 정보의 검색 및 참조, locking, 메모리 할당 등의 작업을 위해 많은 비용이 들기 때문에 결국 Shared Pool 관련 튜닝의 최대 과제는 SQL 공유를 최대화하여 새로운 파싱 요청과 메모리 요청을 최소화하는 것이라고 할 수 있다. 헌데, 이는 애플리케이션의 설계와 연계되는 영역으로서 이미 개발이 완료된 운영서버에서는 변경작업이 여의치 않은 것이 현실이다. 앞서, Shared Pool이 DBA로서 튜닝이 수월치 않은 영역이라고 표현한 이유 가운데 하나가 여기에 있다. 

■ 
Shared Pool 관련 오해 바로잡기

Shared Pool과 관련하여 판단이 쉽지 않은 부분 가운데 하나가 과연 shared_pool_size를 얼마나 할당할 것인가 하는 것이다. 오라클은 Shared Pool 메모리를 최대한 효율적으로 활용하기 위하여 다양한 기법을 동원하고 있는데, 이러한 메모리 관리 메커니즘에 대해 정확히 알지 못하여 Shared Pool 크기를 지나치게 크게 할당함으로써 오히려 문제를 악화시키는 경우도 드물지 않다. 이러한 오해를 바로잡기 위해 Shared Pool의 메모리 할당과정을 간단하게나마 살펴보도록 하겠다.

새로운 메모리 Chunk가 할당되는 과정을 살펴보면, 우선 프로세스는 Free List를 검색하여 자신이 필요로 하는 크기의 Free Chunk를 찾고, 그러한 Free Chunk가 없으면 원하는 크기보다 한단계 큰 Free Chunk를 찾아서 필요한 크기만큼 분할하여 사용하게 된다. 만약 Free List에서 충분한 크기의 Free Chunk를 찾을 수 없다면, 이미 사용되었으나 현재는 사용되고 있지 않는(unpinned) Chunk들의 LRU List를 검색하여 오래된 것부터 8개씩 flush시켜 Free Chunk로 만든 후 자신이 필요한 크기를 할당하여 사용하게 된다. 만약 이 과정에서 현재 사용중인(pinned) Chunk가 대부분이거나, 너무 메모리 조각화가 많이 일어나서 기존 Chunk를 Flush시킨 후 인접한 Free Chunk들을 병합해보아도 원하는 크기의 Free Chunk를 얻어낼 수 없다면 오라클은 ORA-4031 에러를 발생시키는데, 그 이전에 한가지 최후의 비밀무기가 더 숨어 있다. 바로 Spare Free 메모리라는 것인데 오라클은 인스턴스 기동 후 처음에는 전체 Shared Pool의 50% 가량은 Free List에 올려놓지 않고 아예 숨겨두었다가 앞서와 같이 도저히 피할 수 없는 순간이 되면 조금씩 해제 시켜 사용하도록 한다. 그야말로 메모리의 조각화를 최소화하기 위한 오라클의 눈물 나는 노력이라고 할 수 있을 것이다. 물론 이 영역까지 다 소모한 후에 flush를 통해서도 필요한 Chunk를 확보할 수 없는 상황이 되면 결국 ORA-4031 에러가 발생할 것이다. 

많은 이들이 Shared Pool의 남아있는 Free memory의 크기가 작으면 shared_pool_size를 증가시켜주어야 한다고 믿고 있는데 이는 잘못된 것이다. Shared Pool은 정보의 재사용을 위해 운영하는 것이므로 SQL 실행이 끝났다고 해서 해당 Chunk를 Free List로 반납하지 않는다. 즉, Free Memory가 남아있는 한 계속 소모 시키는 방식으로 사용되므로 오랜 시간동안 운영되어온 시스템에서 Shared Pool의 Free Memory가 매우 적게 남아 있는 것은 그 자체로는 문제가 되지 않으며, 오히려 피크타임이 지난 후에도 많은 양의 Free Memory가 남아있다면 이는 Spare Free 메모리도 다 소모하지 않은 상태로서 불필요하게 많은 메모리가 할당되어 낭비되고 있음을 의미한다. 더구나, Shared Pool 크기가 지나치게 크면 Free Memory를 다 사용할 때까지의 기간이 연장되는 효과는 얻을 수 있겠지만, 시간이 지날수록 Memory의 조각화가 더욱 심해지고 Free List의 길이가 길어져 Free Chunk의 검색과 할당에 걸리는 시간이 지연되므로 오히려 성능이 악화되는 결과를 초래할 것이다. 

또한, 메모리 조각화에 따른 영향을 줄이기 위해 오라클은 5000 bytes가 넘는 큰 사이즈의 Chunk만을 위해 전체 Shared Pool의 5% 정도를 따로 관리하는 방법을 사용하고 있는데, 경험적으로 보면 이 공간은 거의 사용되지 않고 버려지고 있는 경우가 많다. 이는 V$SHARED_POOL_RESERVED 뷰의 USED_SPACE 값을 확인해 보면 알 수 있으며, 5000 bytes 이상의 large chunk가 거의 요구되지 않는 환경에서는 오히려 이 크기를 줄여주는 것이 나을 것이다. 


■ Shared Pool 관련 wait

Shared Pool과 관련하여 흔히 발생하는 Wait은 라이브러리 캐쉬 오브젝트에 대한 동시 액세스와 메모리 할당에 따른 관련 Lock 또는 Latch에 대한 경합이 대부분이며, 구체적인 이름은 다음과 같다. (Latch free 이벤트시 괄호 안의 관련 latch 이름은 v$session_wait의 p2값과 v$latchname의 latch#를 조인하여 얻어낼 수 있다. 1회 SQL 참조)

Latch
Lock
latch free ( library cache )
latch free ( library cache load lock)
library cache lock, library cache pin 
library cache load lock
latch free ( row cache objects )
row cache lock
latch free ( shared pool )


Library cache lock, library cache pin, library load lock은 각각 특정 라이브러리 캐쉬 오브젝트에 대한 검색이나 변경 및 실행 또는 로드 시에 대상 오브젝트에 대해 할당되며, 이러한 Locking 작업은 library cache latch와 library cache load lock latch의 관할 하에 처리된다. Shared pool latch는 Free List나 LRU List를 검색하거나 메모리를 할당하는 작업에 사용되며, row cache lock과 row cache objects latch는 Data dictionary cache 오브젝트에 대한 동시 액세스를 제어하는데 사용된다. 

Latch의 개수는 시스템 전체적으로 하나 또는 제한된 개수가 존재하는 것이고 Lock은 대상 오브젝트 각각 대해 할당되는 것이므로, 엄밀하게 말해서 Lock에 대한 경합은 직접적으로는 특정 라이브러리 캐쉬 오브젝트에 대한 동시 액세스로 인해 유발되는 것인 반면에, Latch에 대한 경합은 시스템 전체적으로 관련 오퍼레이션(즉, SQL 파싱) 자체가 지나치게 많이 발생하거나, 짧은 시간 내에 처리되지 못함으로 인해 유발되는 것이라고 구분해볼 수 있다. 그러나, 결국 이 모든 경합은 근본적으로 Shared Pool의 조각화(Fragmentation)에 따른 문제가 주된 원인이며 다시 이러한 조각화는 요청되는 SQL들이 공유되지 못하고 지속적으로 새롭게 파싱되고 메모리가 할당됨으로 인해 발생하는 것이다. 따라서, 이러한 문제를 해결하는 가장 효과적인 방법은 Literal SQL을 바인드 변수를 사용하도록 수정하거나, SQL작성 표준을 마련하고, HOLD_CURSOR/ RELEASE_CURSOR, SESSION_CACHED_CURSORS, CURSOR_SPACE_FOR_TIME, CURSOR_SHARING 등의 파라미터를 활용하는 등의 방법을 통해 SQL의 공유도를 높여주는 것이며, 또한 자주 사용되는 PL/SQL에 대해서는 DBMS_SHARED_POOL 패키지를 사용하여 메모리에서 Flush되지 않도록 보존하는 등의 조치를 취해주면 도움이 될 것이다. SQL의 수정이 어려운 환경이거나 시스템에 요청되는 SQL의 절대량이 확보된 메모리 공간에 비해 많은 상황이라면 주기적으로 피크타임을 피해 Shared Pool을 직접 Flush(alter system flush shared_pool 명령을 사용한다.) 시켜주는 것도 권장할 만한 관리 방법이다. 많은 이들이 우려하는 바와는 달리 Shared Pool을 직접 flush 시키는 것이 심각한 성능상 문제를 야기하지는 않으며 특히 중요한 패키지나 SQL cursor, Sequence 등이 keep되어 있는 경우라면 더욱 그러하다. 

가끔 버그를 포함한 특수한 상황에서 특정 라이브러리 캐쉬 오브젝트에 대한 lock이 장시간 해제되지 못하고 있는 경우도 있는데 이때는 X$KGLLK 뷰를 조회하면 library cache lock에 대한 holder/waiter를 확인하여 조치할 수 있다. 또한, Row cache lock에 대한 경합은 Locally managed tablespace를 도입하거나, DML이 빈번한 테이블에 대한 인덱스의 개수를 줄여주는 등의 조치를 통해 완화될 수 있을 것이다. 

부연하자면, Shared Pool과 관련된 Wait는 특정 오브젝트 자원에 대한 경합에 의해 발생하기 보다는 애플리케이션의 설계, 보다 단순화시켜 표현하면 Literal SQL에 의한 메모리 조각화에 의해 발생하는 경우가 많다. 따라서, Shared Pool관련 Wait가 많이 발생하여 오라클이 그로 인한 성능상의 문제를 드러낼 때 눈에 띄는 하나의 주범을 찾아내려는 노력은 별 효과를 거두지 못하는 경우가 많으며, 그러한 시점에 DBA가 즉각적으로 취할 수 있는 조치로는 직접 Shared Pool을 Flush 시키는 정도가 있을 것이다. 결국, 평소에 꾸준한 모니터링을 통해 Shared Pool의 적절한 크기와 관련 파라미터 값을 찾아가는 것, 그리고 무엇보다 애플리케이션 측면에서 튜닝 및 수정 작업을 진행함으로써 성능문제를 사전에 예방하는 것이 최선이다.

 

[4] Buffer Cache 관련 Wait

■ Buffer Cache 구조

Buffer Cache의 기본적인 기능은 여러 프로세스에 의해 공통으로 자주 액세스 되는 데이터베이스 블록을 메모리에 캐쉬하여 물리적인 디스크 IO를 최소화함으로써 더 빠른 액세스 속도를 제공하기 위한 것이다. 복잡한 설명은 생략하고, Buffer Cache 의 기본구조를 이해하기 위한 몇 가지 핵심 용어들을 간단히 정리해 보도록 하겠다.

▷ Buffer header

모든 버퍼 블록들은 각자의 buffer header를 통해 액세스되고 관리된다. 즉, 메모리에 캐쉬된 특정 데이터 블록에 대한 액세스는 먼저 해쉬 알고리즘을 통해 cache chain 상의 buffer header를 찾고 해당 buffer header에 기록된 데이터 블록의 메모리상 주소를 찾아가 원하는 정보를 읽는 방식으로 이루어진다. Buffer header에 기록되는 주요정보는 다음과 같으며 Buffer header의 내용은 V$bh 뷰를 통하여 조회해볼 수 있다.

     - 메모리상에서의 해당 버퍼블록의 주소
     - 해당 버퍼 블록(실제로는 버퍼헤더)가 포함되어 있는 hash chain
     - LRU, LRUW, CKPTQ와 같은 리스트상에서의 해당 버퍼블록의 위치
     - 해당 버퍼블록에 대한 User, Waiter와 상태를 나타내는 각종 Flag 

▷ Hash Buckets/ Hash Chains

Buffer Cache의 모든 블록은 해쉬 알고리즘을 통해 관리된다. 곧, 데이터 블록의 DBA, Class 값으로 Hash Function을 적용하여 해당 블록이 속하는 hash buckets을 할당하며, 동일한 hash buckets에 할당되는 데이터 블록의 버퍼헤더들은 linked list형태로 hash chain을 이루게 된다. Hash buckets/hash chains는 특정 데이터 블록을 찾아가기 위한 수단을 제공한다. 각각의 hash buckets에는 자신에 속한 hash chain을 보호하기 위한 latch(cache buffers chains)가 할당된다. 

▷ LRU

LRU는 두개의 리스트, 즉 LRUW와 LRU 리스트의 쌍으로 구성된다. LRUW(LRU Write list)는 dirty list와 같은 말이며, 수정되어 디스크에 반영되어야 할 블록들의 리스트이다. LRU(Least recently used list)는 LRUW에 올라가지 않은 나머지 버퍼 블록들이 등록되어 있다. Buffer cache 상의 버퍼블록은 반드시 LRU나 LRUW 둘 중의 하나에 등록되며, 두 리스트에 동시에 포함되는 경우는 없다. LRU는 Free Buffer를 찾기 위한 수단을 제공한다. 경합을 피하기 위해 버퍼캐쉬 블록들을 여러 개의 LRU쌍으로 나누어 관리할 수 있으며, 각 LRU리스트를 보호하기 위해 Latch(Cache buffers lru chain)가 하나씩 할당된다.

■ 
Buffer Cache 운영규칙

▷ 메모리상의 특정 버퍼블록을 찾아가거나, 특정 블록이 메모리에 캐쉬 되어 있는지를 확인하기 위해서 오라클은 hash bucket/hash chain 구조를 사용한다. 

▷새로운 데이터블록을 디스크로부터 메모리로 읽어 들이기 위한 free buffer를 확보하기 위해 오라클은 LRU 리스트를 사용한다. 

▷ 버퍼블록은 LRU나 LRUW 둘 가운데 하나에 등록된다.

▷ 하나의 블록에 대해 시간대가 다른 여러 개의 복사본이 존재할 수 있으며, 그 가운데 오직 CURRENT 버퍼만이 변경될 수 있다. 

▷하나의 버퍼블록은 한번에 오직 하나의 프로세스에 의해서만 변경될 수 있다. 

■ Buffer Cache 관련 Waits

버퍼캐쉬와 관련되어 흔히 발생하는 대표적인 Wait 이벤트는 다음과 같다. 

▷ buffer busy waits

여러 세션이 동시에 같은 블록을 읽으려고 하거나 여러 세션이 같은 블록에 대한 변경작업이 완료되기를 기다리고 있는 경우에 발생하며, 특정 블록에 대한 경합을 해소하기 위한 조치는 블록의 유형에 따라 달라진다. Data block에 대한 경합이 많은 경우는 Pct free나 Pct used 값을 사용하여 블록 당 로우수를 줄이거나, 특정 블록에 로우 입력이 몰리는 구조의 인덱스(right-hand-index)일 경우는 reverse key index의 사용을 검토하는 등의 방법이 있으며, segment header의 경합이 많은 경우는 freelist 수를 늘리거나 Extent의 크기를 증가시키는 등의 방법이 있고, undo header나 undo block에 대한 경합은 롤백세그먼트의 개수나 크기를 증가시키는 것이 전형적인 조치 방법이다. v$waitstat과 x$kcbfwait을 이용하며 Class 또는 file별로 wait 발생상황을 판단할 수 있다. 

▷ free buffer waits/write complete waits

DBWR가 dirty buffer를 write하는 동안 서버 프로세스가 대기하고 있는 경우 발생한다. 곧, 너무나 많은 dirty buffer가 생겨나거나 DBWR의 쓰기 속도가 충분히 튜닝 되지 못한 경우에 발생한다. 점검 포인트는 물리적 디스크의 속성(stripe size, layour, cache size) 최적화, Raw device의 활용, Async IO나 multi-DBWR(db_writer_processes) 활용여부 등이다.

위와 같은 버퍼 블록에 대한 경합 역시 비효율적인 실행계획을 통해 수행되는 애플리케이션에 의하여 불필요하게 많은 블록이 메모리로 올라오는 것이 원인일 경우가 많으므로 경합이 빈번한 블록이 속하는 테이블/인덱스 명을 찾아낼 수 있다면 관련 SQL을 찾아내어 보다 효과적인 튜닝작업이 이루어질 수 있을 것이다. v$session_wait의 p1,p2 컬럼에 각각 file#, block#값을 표시하여 주므로 이 값을 이용하여 아래의 SQL문으로 현재 어떤 오브젝트에 대하여 해당 wait가 발생하고 있는지를 추적할 수 있다. ( 1회에 소개한 SQL문에서는 Additional Info 값을 참조. )

     select segment_name, segment_type
     from dba_extents
     where file_id = :file#
     and :block# between block_id and block_id + blocks -1

 cache buffers chains latch

SGA내에 캐쉬된 데이터블록을 검색할 때 사용된다. 버퍼캐쉬는 블록들의 chain을 이루고 있으므로 각각의 chain은 이 Latch의 child들에 의해 보호된다. 이 Latch에 대한 경합은 특정 블록에 대한 대량의 동시 액세스가 발생할 때 유발된다. 애플리케이션을 검토해 보아야 한다. 
Ø cache buffers lru chain latch
버퍼캐쉬의 버퍼를 LRU 정책에 따라 이동시켜야 할 필요가 있는 경우 프로세스는 이 Latch 획득하게 된다. 이 Latch에 대한 경합은 Multiple buffer pool을 사용하거나 DB_BLOCK_LRU_LATCHES 를 증가시켜 LRU Latch의 개수를 늘려서 해소할 수 있다. SQL문을 튜닝하면 해당 프로세스에 의해 액세스 될 블록의 수가 줄어들 것이므로 당연히 효과를 거둘 수 있다.

위와 같이 버퍼캐쉬를 관리하는 Latch에 대한 경합은 경합이 집중되는 특정 Child Latch에 의해 관리되는 버퍼블록을 찾아 해당 블록이 속한 세그먼트 정보를 알아낸다면 보다 효과적인 조치가 가능할 것인데, latch free wait일 경우 v$session_wait의 p1raw 값이 해당 Latch address를 의미한다. 이 값을 x$bh의 hladdr 값과 조인하면 관련 오브젝트 이름을 추적해볼 수 있다. 

     select file#, dbarfil, dbablk, obj, o.name
     from x$bh bh, obj$ o 
     where bh.hladdr = :latch_address
     and bh.obj = o.obj#;

[5] Redo buffer 관련 Wait

 Redo buffer 구조

오라클 리두 구조의 핵심은 모든 트랜잭션 정보를 OS 파일에 기록해 둠으로써 시스템 장애가 발생해도 트랜잭션 단위의 일관성을 잃지 않고 데이터베이스를 복구할 수 있도록 하겠다는 것이다. 리두버퍼(redo buffer)는 이처럼 데이터베이스에 가해진 모든 변경내역을 파일에 기록 하기 위해 잠시 사용되는 메모리 영역이며 리두버퍼에 기록된 리두 정보는 다시 리두로그 파일에 기록되어짐으로써 향후 시스템 복구 작업이 필요할 때에 사용하게 된다. 오라클의 리두 구조를 이해하기 위한 핵심적인 개념을 간단히 정리해보면 다음과 같다. 

데이터베이스에 대한 변경내역은 블록단위로 저장된다. 물론 변경되는 모든 블록의 복사본을 통째로 저장하는 것은 아니고 블록별로 어떠한 오퍼레이션을 수행하는가, 그리고 그러한 블록별 오퍼레이션을 어떠한 순서로 수행하는가를 기록한다. 이러한 블록별 단위액션을 change vector라고 부르며 change vector가 순차적으로 모여 하나의 의미 있는 redo record가 된다. 리두로그는 시스템내의 모든 프로세스들에 의해 생성되는 redo record를 SCN 순서대로 저장해놓은 것이다. 이때 리두로그에 기록되는 내용에는 테이블이나 인덱스 등의 데이터 블록 뿐만 아니라 UNDO 블록 또는 UNDO 세그먼트 헤더블록에 대한 변경내용을 포함하는 모든 버퍼캐쉬 블록에 대한 변경내역이 대상이 된다. 

리두 정보는 항상 실제 변경작업보다 먼저 보관되어야 어떤 상황에서도 복구가 가능해진다. 따라서 트랜잭션을 수행하는(데이터베이스 블록에 변경을 가하는) 프로세스는 우선 자신의 메모리 영역 내에서 수행하고자 하는 작업에 대한 리두 레코드를 만들며, 이를 먼저 로그버퍼에 기록하고 난 후에 실제 버퍼블록에도 리두 레코드에 담긴 내용을 따라 적용하게 된다. 또한 같은 이유로 오라클은 변경된 버퍼캐쉬 블록을 디스크에 기록하기 전에 먼저 관련된 로그버퍼를 로그파일에 기록하는 작업을 처리하게 된다. 따라서, 리두 버퍼 또는 리두 파일 (아카이브 파일을 포함해서)에 대한 쓰기 작업에 병목이 생기면 시스템에 대한 모든 작업 수행이 대기 상태로 빠지게 될 것이다.

트랜잭션 커밋을 요청한 프로세스는 우선 해당 트랜잭션에 대한 로그버퍼가 리두로그 파일에 기록되는 작업이 완료된 후에야 커밋 완료 메세지를 받을 수 있다. 그렇게 함으로써 버퍼캐쉬 변경내역을 모두 디스크에 반영하지 않고도 시스템의 비정상 종료시 리두파일에 저장된 리두 레코드로부터 커밋 트랜잭션을 보존할 수 있게 된다.

 리두 버퍼관련 Wait 이벤트

일반적으로는 로그버퍼 관련해서 심각한 Waiting이 발생하는 경우는 드물지만, 가끔 볼 수 있는 리두 관련 Wait 이벤트로는 다음과 같은 것들이 있다. 

▷ Log file parallel write

LGWR가 OS에 리두 버퍼를 로그파일에 기록하도록 요청해 둔 상태에서 대기하고 있는 이벤트이다. 이 경우에는 DML 작업시 nologging 옵션 등을 사용하여 시스템에서 발생하는 리두 레코드의 절대량을 줄이거나 하드웨어적으로 DISK IO를 개선시켜주는 것이 방안이다. 

▷Log buffer space 

프로세스가 로그버퍼를 할당하기 위해 대기하는 이벤트인데 LGWR가 로그버퍼를 비우는 것보다 더 빠른 속도로 프로세스들이 리두 레코드를 생성하고 있다는 것을 의미한다. 로그버퍼의 크기를 늘려주거나, DISK IO의 속도를 개선시켜 주어야 할 것이다. 로그버퍼는 로그파일에 대응되는 블록이 맵핑이 된 후에 사용될 수 있으므로 로그 스위치 발생시에도 log buffer space 이벤트에 대한 대기가 발생할 수 있다. 로그 스위치가 너무 잦다면 리두 로그 파일의 크기를 증가시켜주는 것이 좋다. 

▷ Log file sync

프로세스가 커밋이나 롤백을 수행할 경우 우선 LGWR에게 해당 트랜잭션까지의 로그버퍼를 Write하도록 요청하게 되는데 이때 사용자 프로세스는 LGWR가 쓰기 작업을 완료할 때까지 log file sync 이벤트를 대기하게 된다. 버전 8i 이전에서는 DBWR가 쓰기 작업을 수행하다가 아직 관련 로그버퍼가 파일에 쓰여지지 않을 경우에도 LGWR에 쓰기를 요청하고 log file sync 이벤트에 대기하였으나 8i 이상에서는 log file sync에 대기하는 대신 deferred write queue에 등록한다. 따라서 버전 8i 이상에서 log file sync 이벤트는 사용자 프로세스에 의해 요청되는 커밋, 롤백 처리 시에 발생하며 결국, 시스템 전체적으로 커밋, 롤백이 지나치게 자주 수행되거나 상대적으로 LGWR의 쓰기 속도가 느린 것이 원인일 것이다. 또는, 로그 버퍼가 너무 커서 LGWR가 백그라운드로 flush 시켜주기 전( 보통 3초 간격 및 1/3 이상의 로그버퍼가 찬 경우)에 커밋에 의한 쓰기 요청이 이루어지므로 커밋 시점에 써야 할 양이 많아 대기시간이 길어지는 경우도 있는데 이 경우엔 리두 버퍼의 크기를 오히려 줄여주어야 할 것이다. 또는, LGWR wait for redo copy 이벤트가 많이 나타난다면 redo copy latch가 너무 많아 LGWR이 사용자 프로세스가 버퍼 쓰기 작업을 마칠 때까지 기다리는 일이 잦은 경우를 뜻하며 이 경우엔 _LOG_SIMULTANEOUS_COPIES 파라미터를 사용하여 copy latch의 수를 줄여주는 조치가 필요할 것이다. 

시스템에 따라서 언급한 외의 다양한 이벤트 대기와 원인이 존재할 수 있고, 더구나 버전에 따라 redo copy latch와 redo allocation latch를 포함한 리두 운영 방식상 상이한 부분이 많이 존재하여 그에 따른 추가적인 튜닝요소가 있으나 이 글에서는 지면 관계상 8i를 기준으로 간략히 정리해 보았다.


parameter에 대한 적절한 설정은 좋은 performance를 내는 데 중요한 요인이 된다. 


이들 parameter에 대한 자세한 이해를 통해 효과적인 lob segment를 생성할 수 있다. 

참고로 lob에 대한 자세한 정보를 보려면 dba/all/user_lobs를 조회하면 알 수 있다.


/***************************************************************************************************** 

    (column list)
    [physical attributes]
    [storage clause]
    [LOB (<lobcol1> [, <lobcol2>...])
        STORE AS
            [<lob_segment_name>]
            (
                [TABLESPACE <tablespace_name>]
                [{ENABLE | DISABLE} STORAGE IN ROW]
                [CHUNK <chunk_size>]
                [PCTVERSION <version_number>]
                [ { CACHE | NO CACHE [{LOGGING | NOLOGGING}]
                          | CACHE READS [{LOGGING | NOLOGGING}]
                  }
                ]
                [<storage_clause_for_LOB_segment>]
                [INDEX [<lob_index_name>] [physical attributes] [<storage_for_LOB_index>] ]
            )
    ]
    [LOB (<lobcol1> [, <lobcol2>...]) ... ]

*****************************************************************************************************/



CREATE TABLE demolob ( A NUMBER, B CLOB )
    STORAGE (INITIAL 256 NEXT 256) 
    TABLESPACE user_data 
    LOB(b) STORE AS demolob_seg ( 
        TABLESPACE lob_tb
        STORAGE (INITIAL 6144 NEXT 6144) 
        CHUNK 4
        PCTVERSION 20
        NOCACHE LOGGING
        ENABLE STORAGE IN ROW 
        INDEX demolob_idx (
            TABLESPACE lob_tb
            STORAGE ( INITIAL 256 NEXT 256 ) 
            ) 
        );


1) TABLESPACE와 storage parameter

- lob, lob index에 대한 tablespace를 지정하지 않는 경우, 해당 table이 저장되는 tablespace에 같이 저장되게 된다. 

  lob 컬럼, lob index, table 에 대해 tablespace를 각기 지정하는 것이 contention을 줄일 수 있어 보다 효과적이다. (최소한      lob 컬럼과 다른 컬럼들을 구분하여 별개의 tablespace에 저장하도록 지정하는 것이 바람직하다.)

- lob index는 lob 컬럼의 내부적 저장 위치를 연결시켜주는 indicator를 저장한 index이다. 

  default로 제공받는 index명은 이해하기 어렵기 때문에 lob index명을 지정하여 사용하는 것이 편하다.

- lob index에 대한 parameter 변경은 alter index문을 이용하지 않고, alter table문을 이용하여야 한다. 단, index명을 바꿀 

  수는 없다.


2) PCTVERSION


- 데이타를 변경할때는 read consistency를 위해 undo 정보를 저장할 필요가 있다. 

  그러나 LOB 데이타인 경우, 그 크기가 크기때문에 undo 정보 유지하기에는 많은 어려움이 따르기 때문에, 

  대신에 old version 데이타를 유지하는 방법으로 read consistency를 제공하고 있다. 

  pctversion은 old version lob data가 차지하는 percentage를 의미한다. 


  예를들어 default value가(10) 적용되었다면, 새로운 lob data가 old version의 10%가 저장될때 까지는 old version을 간직  

  하고 있다가, 이 이상 크기가 되면 바로 old version data를 reclaim하고, 이 space를 재사용 즉, overwrite 하게 된다. 

- pctversion을 큰 값을 지정한 경우, old version을 저장하기 위해 보다 많은 space가 필요하게 된다. 

  하지만 update가 많은 작업인 경우에는 이 값을 높게 잡아 다음과 같은 에러를 피할 수 있을 것이다.

    ORA-01555: snapshot too old: rollback segment number with name "" too small
    ORA-22924: snapshot too old

- 만약 lob data가 read-only인 경우라면, pctversion은 0으로 설정할 수 있다.
  * 읽기 요청이 많으면서 동시에 LOB 변경(20% 이상)
  * 읽기 요청이 대부분이며 변경이 거의 없음(5% 이하)


- pctversion 변경 

SQL> ALTER TABLE demolob MODIFY LOB(b) (PCTVERSION 10);

3) CACHE/NOCACHE

- 자주 access되는 경우라면, cache를 선택하여 사용한다. default는 nocache이다.

- in-line lob은 영향을 받지 않는다. 즉, in-line lob은 다른 데이타와 마찬가지로 buffer cache에서 바로 읽혀지기 때문이다.

- CACHE_SIZE_THRESHOLD limit이 적용되지 않기 때문에 cache할 때는 주의해야 한다. 

- cache/nocache 변경 
    

SQL> ALTER TABLE demolob MODIFY LOB (b) ( CACHE/NOCACHE );

4) CHUNK(디폴트 block size)


- lob data를 access하는 단위로써, db_block_size의 배수로 설정한다.
  lob 데이타가 저장될 initial extent, next extent는 chunk의 배수로 설정하는 것이 좋다. 

  만약 db_block_buffer가 2K이고, chunk를 3K로 설정했다면 chunk는 4K로 조정 되어 적용된다.


- chunk는 in-line lob에는 영향을 주지 않고, out-line lob에만 영향을 준다. 

  예를들어 chuk를 32K로 설정하고, disable storage in row를 설정했다면 1K의 데이타를 저장할때도 32K가 lob segment에 

  할당된다. 


- lob table이 생성된 이후에는 변경할 수 없다. 


5) LOGGING/NO LOGGING


- redo 정보를 생성할 것인지 여부를 결정하는 parameter이다. 


- cache option을 사용하는 경우는 무조건 logging을 의미한다.


- logging, nologging에 상관 없이 undo 정보는 lob index에 대해서만 생성되고고, lob 데이타에 대해서는 생성하지 않는다.


- logging인 경우는 redo 정보를 생성하고, bulk load나 대량의 insert를 하는 경우 nologging을 설정하여 redo 정보를 생성 

  하지 않도록 할 수 있다.


- logging/no logging 변경
    

SQL> ALTER TABLE demolob MODIFY LOB(b) (NOCACHE NOLOGGING);

6) ENABLE/DISABLE STORAGE IN ROW

- 4k 이하의 data를 in-line에 저장할 지 여부를 결정한다.


- enable인 경우 (default)

  4k 이하의 lob은 in-line으로, 즉 테이블에 저장하고, 4k 보다 큰 경우에는 out-line 즉, lob segment에 저장된다. 

  이때 4K는 control 정보를 포함한 크기로써, 실제 in-line으로 저장할 수 있는 최대 크기는 3964 byte이다. 

  4K 이상의 데이타는 lob segment에 저장되지만, 36 - 84 bytes의 information 정보는 in-line에 남게 된다. 


- disable인 경우
  모든 datas는 out-line으로 저장된다. 20 byte lob locator만 in-line으로 저장되어 lob index에서 해당 lob block을 찾을 수
  있도록 해준다.


- in-line lob인 경우에는 다른 데이타 타입처럼 REDO, UNDO 정보가 기록된다. 그러나 out-line인 경우에는 column locator

  와 LOB INDEX가 변경되는 경우에만 UNDO 정보를 기록한다. 즉, lob segment에 대해서는 undo 정보를 만들지 않는다.


- lob 컬럼에 대한 access가 많지 않은 경우는 disable을 설정하는 것이 바람직하다. High Water Mark를 작게 유지될 수 있기    때문에 특히, full table scan을 자주 하는 table인 경우 유용하다.


- lob table이 생성된 이후에는 변경할 수 없다.





<LOB 생성시 주의사항>


LOB은 데이터의 속성상, 다른 데이터타입에는 없는 다양한 옵션들이 존재한다. 

부주의하게 사용할 경우 많은 성능문제를 야기할 수 있다. LOB 생성시 다음과 같은 사항들에 주의해야 한다.


1. enable storage in row 옵션을 사용하는 경우 4000 bytes 보다 작은 LOB 데이터는 로우와 같은 블록에 저장된다. 
    따라서 Row chaining을 유발할 가능성이 높다. 
    4000 bytes보다 큰 LOB 데이터는 disable storage in row 옵션을 사용한 것과 같은 효과가 있다. 
    LOB 데이터의 크기를 고려하여 만일 Row chaining이 발생할 가능성이 높다고 판단되면 disable storage in row 옵션을 

    사용하는 것이 좋다. 

    로우와 같은 블록에 저장되는 LOB 데이터를 In-line LOB이라고 부르며, LOB 세그먼트에 저장되는 경우에는 Out-of-line 

    LOB이라고 부다.


2. disable storage in row 옵션을 사용하는 경우 LOB 데이터는 별도의 LOB 세그먼트에 저장되며 Row 내에는 20 bytes의 
    LOB Locator 정보만 저장된다. 이 경우 언두 데이터는 LOB Locator에 대해서만 생성된다. LOB 데이터에 대한 실제적인 
    언두 정보는 언두 테이블스페이스에 저장되지 않고 같은 LOB 세그먼트 내에 저장됨에 유의해야 한다. 

    LOB 세그먼트의 언두는 PCTVERSION 옵션에 의해 제어되는데, 가령 PCTVERSION이 50이면 LOB Segment의 공간 중          50%가 언두 정보를 저장하는데 사용된다. 

    따라서 PCTVERSION을 낮게 주는 경우 예기치 않은 ORA-01555 : snapshot too old 에러가 생길 수 있다. 
    LOB 세그먼트의 언두 영역 부족에 따른 snapshot too old 에러의 경우 rollback segment name이 공백으로 나온다. 
    PCTVERSION이 너무 작으면 ORA-01555 에러가 발생할 확률이 높아지고, PTCVERSION이 너무 높으면 공간의 낭비가 

    심해진다. 
    이 문제를 해결하는 방법은 일반 롤백 세그먼트에서의 ORA-01555 에러의 경우와 동일하다. 

    PCTVERSION을 적절히 유지하고 불필요한 커밋을 줄인다. LOB 세그먼트의 확장공간을 적절히 확보해주는 것도 중요하다.


3. CACHE / NOCACHE, LOGGING / NOLOGGING : 만일 LOB 데이터의 크기가 크고 자주 액세스되지 않거나, 무작위적으로 
    액세스된다면 NOCACHE 옵션을 사용하는 것이 바람직하다. NOCACHE 옵션을 사용하는 경우 리두를 생성할 지의 여부도      지정할 수 
있다. 

    만일 반드시 복구가 될 필요가 없는 데이터라면 Nologging 옵션을 사용함으로써 성능개선효과를 얻을 수 있다. CACHE 

    옵션을 사용하는 경우에는 반드시 Logging 속성을 지니게 된다. NOCACHE 옵션을 사용하는 경우 버퍼 캐시를 경유하지

    않기 때문에 direct path read(lob), direct path write(lob) 이벤트를 대기하게 된다. 하지만 CACHE 옵션을 사용하는 경우에

    는 버퍼 캐시를 경유하기 때문에 db file sequential read 와 latch: cache buffers chains 대기를 유발할 수 있다.


4. 청크(chunk)의 크기 : Out-of-line LOB인 경우 청크 단위로 LOB 데이터를 저장하게 된다. 

    큰 청크 사이즈의 문제는 공간이 낭비될 가능성이 높다는 것이다. 만일 청크가 8K로 되어있는데, 1K 크기의 LOB 데이터가      삽입된다면 나머지 7K의 공간은 사용할 수 없게 된다. 따라서 청크 사이즈를 결정할 때는 대상이 되는 LOB 데이터의 크기      를 고려해서 결정해야 다. 

    청크의 크기 단위는 기본적으로 블록 사이즈의 배수가 된다. 

    만일 블록 사이즈가 8K인 경우 청크의 크기를 5000 bytes로 주면 오라클은 암묵적으로 8192 bytes로 변환한다. 청크의 

    크기를 지나치게 작게 하는 경우에는 연속적으로 청크를 할당받는 과정에서 오버헤드가 발생하게 된다.



'ORACLE > ADMIN' 카테고리의 다른 글

Performance Tuning Tools (성능 튜닝 툴)  (0) 2018.06.20
Oracle Wait Event 모니터링  (0) 2018.03.08
Compound Triggers  (0) 2018.01.23
오라클 트랜잭션(Transaction) 절차  (0) 2017.09.27
LOB SEGMENT 관리  (0) 2017.07.25

+ Recent posts