Buffer Cache Management
Oracle은 cache buffer를 관리하기 위해서는 세가지의 내부적인 structure를 사용하는데, 그 각각은 cache buffer chain, dirty list, LRU(Least Recently Used list)이며, 아래에 자세히 설명하다. buffer cache management의 기본을 이루는 이 세가지 list를 관리하면서 사용자에게 필요한 buffer를 사용가능하도록 제공하여 주는 역할을 하는 것은 DBWR이다. 또한 DBWR은 startup시 각 online datafile에 대해서 Media Recovery (MR) lock을 획득하는 등 database file의 관리자로도 간주된다.
설명 중에 포함된 여러가지 buffer cache와 관련된 parameter 및 다른 값들은 sys.X$KVII와 sys.X$KVIT에서 확인가능하며, 이 table들의 모든 내용은 이 문서의 끝에 첨부하였다. 또한 hidden parameter의 설명 및 지정된 값을 확인하는 방법도 첨부하였다.
Cache Buffer Chain
cache buffer chain list는 hashed chain list라고도 하며, doubly-linked hash list로 연결된 hash table (또는 hash bucket)로 구성되어진다. 이 has bucket은 instance startup시에 할당되며, 실제 buffer block으로 구성된 것은 아니고 buffer header만을 포함하게 된다. hash bucket의 갯수는 기본적으로 db_block_buffers/4보다 큰 최소의 소수(prime number)가 되며, init.ora file에 명시적으로 _db_block_hash_buckets parameter에 의해 지정할 수 있다. 이 cache buffer chain에 존재하는 buffer들은 뒤에서 설명할 LRU list나 LRUW list (dirty list) 중의 하나에 위치하게 되며, 두 list 모두에 포함되지는 않는다.
buffer들은 data block address(DBA)에 의해서hash되어 hash table에 할당된다. buffer header와 buffer block은 일대일 대응되며, buffer header에는 다음과 같은 정보들이 나타난다. 여기에서 괄호안에 적은 약자들은 buffer dump후 trace file에 나타나는 형태이다.
- buffer status (st): xcurrent, CR, reading
- lock mode (md): exclusive (excl), shared (shr), null (null)
- buffer holder와 waiters list
- hash chain list에서의 위치, LRU와 LRUW list상에서의 buffer header의 위치를 가리키는 pointer와 실제 buffer block을 가리키는 pointer(ba)
- buffer에 수행될 연산들, recovery정보 등을 나타내는 flags
- 그 외에 dba, inc&seq, version, block type
[그림 1]
LRU list
least recently used list 혹은 replacement list라 불리는 것으로, 이 LRU list의 head부분에는 가장 최근에 사용된 MRU buffer들을 포함한다. 특별한 경우를 제외하고는, 모든 새로운 block들은 모두 MRU end에 위치하며, LRU의 끝부분은 최근에 사용되지 않은, 곧 재사용될 buffer들이 위치한다. 그러므로 foreground process는 빈 buffer를 얻기 위해 LRU의 끝부분부터 찾기 시작한다. 이 LRU의 buffer들은 free, pinned, dirty 세가지 중 하나를 가지며 각각은 다음과 같은 특성을 가진다.
- pinned buffer: 현재 user에 의해 사용중이어서, 재사용될 수 없는 상태의 buffer이며, pinned clean 혹은 pinned dirty로 다시 나뉘어질 수 있다.
- free buffer: 사용되지 않은 buffer, 즉 disk block이 읽혀져서 할당되어 사용될 buffer이다.
- dirty buffer: dirty buffer는 pinned dirty buffer와 마찬가지로 user가 사용하여 내용이 변경된 buffer이다. 그러나 pinned buffer가 현재 사용중이서 재사용될 수 없는 반면에, dirty buffer는 현재 사용중인 user나 waiter는 없기 때문에 LRUW list로 옮겨질 수 있고, 결국은 disk로 write될 buffer이다.
위에서 언급한 새로 읽어 들인 block중 MRU end부분에 위치하지 않는 특별한 경우란, 바로 full table scan이다. full table scan으로 읽은 table은 LRU list의 끝부분에 위치하고 그 크기도 db_block_multi_read_count만큼의 buffer에만 읽혀진다. 이렇게 LRU end에 위치시키는 이유는 full table scan으로 읽은 block은 다시 access할 확률이 적어서 곧 다시 재사용될 수 있도록 한 것이며, db_block_multi_read_count로 제한한 이유는 full table scan으로 읽힌 table이 전체 LRU list의 대부분을 사용하게 되는 것을 막기 위한 것이다.
그러나 full table scan의 경우에도 매우 중요하고 자주 사용되어 MRU end에 전체 table의 내용을 모두 cache시키고 싶은 경우도 있을 수 있다. 이러한 경우에는 다음과 같이 CACHE을 사용하면 된다.
SQL> alter table dept cache;
SQL> select /*+ cache(a) */ * from emp a;
이러한 CACHE절은 작은 table에만 사용하는 것이 바람직하며, 큰 table에 사용하게 되면 buffer의 MRU end쪽의 대부분의 buffer를 이 하나의 table이 차지하게 되는 현상이 발생가능하다. 그리고 이 CACHE절로 MRU end에 위치한 table도 이후 다른 TABLE이 계속 사용되어짐에 따라 LRU end쪽으로 점차 이동하다 disk로 write되고 memory에서는 사라질 수 있다. 즉 이것을 사용한다해서 실제 table의 내용을 계속해서 cache시키는 것을 보장하는 것은 아니다.
[참고] Oracle7.2 version까지는 _small_table_threshold라는 parameter가 존재하여, 여기에서 지정된 크기보다 작은 table은 full table scan의 경우라도 MRU end에 위치하게 된다. 이 값은 default로 max(4, db_block_buffers/50)을 갖는다.
LRUW list
dirty list라고도 불리며, DBWR는 이 list의 buffer의 내용을 disk에 write하여 빈 buffer로 만든다. 어떻게 buffer가 LRUW list로 옮겨지고 결국은 disk에 write되는지가 DBWR의 기능의 기본이라 할 수 있다. DBWR가 buffer를 disk에 보내는 것은 아래 query에서 나타난 signal을 받는 경우이다.
SVRMGR> select * from x$messages where indx in ('9','10','11');
ADDR INDX DESCRIPTION DEST
-------- -------- -------------------------------------------- -----------
100F42FC 9 write dirty buffers when idle - timeout action DBWR
100F430C 10 write dirty buffers/find clean buffers DBWR
100F431C 11 write checkpoint-needed buffers/recovery end DBWR
이 각각의 signal 경우에 대해서 아래에 자세히 설명한다.
DBWR write dirty buffers/find clean buffers (indx :10)
foreground process가 buffer cache에 존재하지 않는 block을 disk에 읽기 위해서는 buffer cache 내에 free buffer가 존재하여야 한다. 이 free buffer를 찾기 위한 간단히 시나리오는 다음과 같다.
- foreground process가 LRU list에 lock을 걸고 LRU의 끝부분부터 찾기 시작한다.
- 찾는 중에 dirty buffer를 만나면, 이 dirty buffer를 LRUW list에 옮긴다.
_db_block_max_scan_count값에 의해 결정되는 foreground scan depth 갯수의 buffer내에서 free buffer를 찾지 못하면, foreground process는 더 이상 LRU list를 읽지 않게 된다.
- DBWR로 하여금 message를 보내는 작업이 수행되고, LRU latch는 해제된다.
- DBWR에게 보내진 message는 DBWR에게 LRU의 꼬리 부분에 위치한 buffer들을 비우는 large batch write를 수행하도록 한다.
- DBWR은 LRU latch를 잡고, LRU list를 읽으면서 disk에 write시킬 dirty buffer들을 모은다. 이때 DBWR이 읽는 buffer의 갯수를 DBWR scan depth라고 하며, 이 값은 뒤에서 자세히 설명한다.
위의 시나리오 중, 만약 foreground process가 LRU에서 dirty buffer를 찾은 후 LRUW list에 옮기려고 할 때, LRUW list가 full이 되어 옮길 수 없을 수 없는 경우가 발생할 수 있다. 여기에서 LRUW list가 full이라는 것은 _db_large_dirty_queue나 2*_db_block_write_batch (_db_large_dirty_queue가 지정되지 않은 경우)에 의해서 정해지는 max dirty queue 값에 의해 결정된다. 이렇게 LRUW list가 더 이상의 dirty buffer를 포함할 수 없게 되면, DBWR는 large batch write 만큼의 buffer를 비우도록 신호를 받게 된다. 이러한 상태는 매우 심각하게 간주되어, DBWR는 LRUW 와 LRU list를 비우는데 전념하게 되고, foreground process들은 LRU list에 읽거나 쓰는 것이 금지된다. 이러한 상황은 OS에서 최소한의 limit이하로 free page의 갯수가 줄어드는 경우 demanded paging이 발생하는 것과 유사하다.
위에서 설명한 것과 같이 DBWR는 batch write (또는 IO clumps)를 수행하는데 이 최대값을 max write batch라고 하며 _db_block_write_batch값에 의해서 결정되었다. 그러나 tuning이 잘된 시스템에서는 이 값이 더 작은 것이 바람직한 경우도 있어서 Oracle 7.2부터는 이 parameter의 값의 기본값을 0으로 하였으며, min(1/2*db_block_simultaneous_writes (default 4)*db_files, max_batch_size, db_block_buffers/4)의 값에 의해 동적으로 정해지게 되었다. 이때 max_batch_size란 _db_block_write_batch를 명시적으로 설정한 경우의 값이다. 이 _db_block_write_batch는 max batch write와 같은 의미로 사용되며 DBWR buffer scan depth 등과 같이 다른 작업에도 영향을 주기 때문에 직접 변경하는 것은 권장되지 않으며, 현재 시스템의 동적으로 할당된 값은 다음과 같이 x$kvii table을 통해 조회할 수 있다. (단 underbar parameter가 정보가 있는 x$ksppsv에는 사용자가 직접 수정한 것이 아니므로, 여전히 파라미터의 기본값이 조회된다)
[참고] 아래 5.4절에 이러한 control variable에 대한 값이 어떻게 결정되는지 정리하였다.
SVRMGRL> select kviival from x$kvii where kviidsc = 'DB writer IO clump';
DVIIVAL
-------------
40
DBWR write dirty buffers when idle(indx:9)
DBWR는 아무런 작업이 없는 idle한 상태가 최대 3초로 지정되었다. 그래서 idle한 상태이후 3초가 지나면 깨어나 disk에 write시켜도 될 current block이나 dirty block (temporary나 sort block은 제외)을 찾기 위해 buffer header를 읽는다. 이때 읽는 buffer의 갯수를 DBWR scan size라고 하며 max scan size는 2*_db_block_write_batch가 된다. dirty list에 buffer가 있으면, 이것은 non-idle한 상태로 간주된다.
이러한 timeout설정을 없애기 위해서는 _db_block_no_idle_writes값을 설정할 수 있으나 이 값은 debugging이나 test를 제외하고는 설정하지 않아야 한다.
DBWR write checkpoint -needed buffers/recovery end (indx:11)
checkpoint가 발생하면, LGWR은 DBWR에게 checkpoint flag가 설정되어 있고 disk로 씌여져야 하는 current, dirty, non-temporary buffer header의 array와 함께 signal을 보낸다. 한번에 write해야 하는 크기는 db_block_checkpoint_batch parameter에 의해 결정된다.
DBWR가 current와 dirty buffer를 disk에 write하는 것은 DBWR write dirty buffers when idle event와 같지만, checkpoint가 발생하여 disk에 write된 buffer는 free로 mark되지는 않는다. 이렇게 하는 것이 hit ratio를 향상시키고, physical I/O는 줄여준다.
foreground process가 buffer cache를 사용하여 dirty buffer로 만듦에 따라, free buffer는 점차 줄어든다. 이렇게 dirty buffer가 너무 커지는 것을 막기 위해, LRU 중 dirty 나 pinned buffer가 아닌 clean buffer의 갯수를 세기 위한 변수가 정의되어 있다. 이 값을 known clean buffers count라고 하며, dirty buffer가 LRUW list로 옮겨진 후 disk에 write되면 값이 증가하고, 반대로 foreground process가 free buffer를 사용하면 줄어든다.
[참고] 실제 known clean buffers count는 정확한 값을 가지는 것이 아니라 동적으로 항상 변하는 근사값을 가지게 된다.
이 known clean buffer count값이 일정한 값 이하가 되면, DBWR이 LRU list를 clean-out하도록 신호를 받고, DBWR scan depth만큼 buffer를 읽는다. DBWR scan depth는 DBWR이 LRU list의 끝에 clean buffer를 어느만큼 유지하느냐에 따라 동적으로 값이 변경된다. 예를 들어, foreground process가 dirty buffer를 발견하고 LRUW list로 옮기면, DBWR은 자신이 clean buffer를 충분히 유지하지 못했다는 것을 알게 된다. 또한 만약 known clean buffer count가 DBWR scan depth보다 작으면, 이것도 DBWR이 clean buffer를 충분히 유지하지 못한 것이기 때문에 이 두 경우 모두 scan depth increment size에 기초하되, max scan depth size는 넘지 않는 값으로 DBWR scan depth를 증가시킨다. 반대로, known clean buffer count가 DBWR scan depth보다 크고 LRUW list가 빈 경우에는 DBWR scan depth를 scan depth decrement만큼 감소시킨다. scan depth decrement는 scan depth increment보다 클 수는 없으며, 다음과 같이 현재의 값이 확인가능하다.
SVRMGR> select kvitdsc, kvitval from x$kvit where kvitdsc like 'DBWR scan depth%';
KVITDSC, KVITVAL
--------------------------- ----------
DBWR scan depth increment 5
DBWR scan depth decrement 1
일반적으로 DBWR이 dirty list를 clean-out하도록 신호를 받으면, 먼저 LRUW list에서 buffer를 pin시키면서 _db_block_write_batch 만큼의 buffer를 모은다. 이때, 만약 _db_block_write_batch만큼의 buffer가 LRUW list에 없는 경우에는 부족한 갯수만큼의 dirty buffer를 LRU list에서 읽어 LRUW list로 옮긴다. 이렇게 필요한 만큼의 buffer를 disk에 write하고 난 후에는 write complete acknowledgment를 찍고 disk에 write된 buffer들을 unpin시킨다. 만약 foreground process가 LRUW list에 있는 block이 disk에 write되어져서 disk로 부터 다시 읽어야만 한다면, write complete wait event값이 증가된다. 그리고 foreground process가 LRU list에 free buffer가 전혀 없어서 DBWR가 buffer들을 disk에 write할 때가지 기다려야 한다면 free buffer waits 통계값이 증가된다. 그런데 Oracle7.2 이전에서는 전체 buffer가 disk에 write될 때까지 기다린 반면, 7.2부터는 buffer가 disk에 write되면 바로 독립적으로 unpin되기 때문에 이전보다 큰 문제가 되지는 않는다. 일단 dirty buffer가 disk로 write되면 이 buffer는 빈 것으로 간주되어 LRU의 끝부분에 위치하게 된다.
DBWR가 _db_block_write_batch 만큼의 write를 수행하고 난 후에 새로운 dirty buffer가 다시 존재함을 발견할 수 있다. 이렇게 새로 생성된 dirty buffer들의 길이를 summed dirty queue length라고 부르며, 이 값은 write batch request를 성공적으로 끝낸 후의 dirty queue의 크기가 된다. 이 queue가 write batch보다 크다면, 이것은 DBWR이 따라가지 못할 정도로 빠르게 dirty list가 생김을 의미하게 된다. 평균 queue 길이는 summed dirty queue length/write request와 같다. 이 값은 DBWR의 한번의 write request후에 dirty buffer write queue의 평균 길이이다. 이 queue의 길이가 _db_block_write_batch보다 두배 이상 크다면, max write batch를 증가시켜 queue의 길이를 줄이는 것이 도움이 될 수 있다. 이것은 _db_block_write_batch나 db_files_similtaneous_writes값을 변경함에 따라 달라질 수 있는데 , 이 값이 너무 크면 DBWR가 I/O bound가 되는 등의 문제가 될 수도 있으므로 신중히 고려하여야 한다.
Process Flow
이제까지 DBWR가 buffer cache를 관리하는 것에 대해서 주로 살펴보았고, 여기에서는 foreground process입장에서의 cache buffer management에 대해서 살펴보도록 한다.
buffer cache내의 buffer header에는 다음의 두가지 list를 포함하고 있다.
- user list: doubly linked list로 연결되어 있는 "handle"을 포함한다. 여기에서 handle은 해당 buffer를 사용하고 있는 oracle process를 가리키는 정보를 담고 있다.
- waiter list: 이 list도 doubly linked 되어 있는 "handle"의 list를 포함하고 있는데, 여기에서의 handle은 해당 buffer를 사용하기 위해 기다리고 있는 oracle process에 대한 정보를 담고 있다.
일반적으로 user가 특정한 block을 읽고자 할 때 foreground process가 작업하는 순서를 간단히 정리하면 다음과 같다.
- cache buffer handle latch를 확보한 뒤, process에 buffer handle을 할당하고나서 latch를 푼다.
- cache buffer chain latch를 얻은 후 주어진 DBA와 SCN값을 가지고 cache buffer chain list에서 필요한 buffer를 찾은 후 cache buffer chain latch를 다시 release한다.
- 만약 원하는 DBA가 발견되면, 그 buffer block을 pin한 후 읽는다. 그런데 이때 만약 buffer block이 dirty이면, 즉 buffer의 SCN이 원하는 SCN값보다 크다면, before image를 구성하는 CR operation이 수행된다. 이러한 경우에 db block gets와 consistent gets값은 증가되어 logical hit로 간주된다. 읽은 buffer는 LRU에 head부분으로 위치가 변경된다.
- DBA가 cache buffer chain에 존재하지 않는다면, disk로부터 다시 읽어야 한다. 이러한 경우에 먼저 LRU latch를 얻은 후 LRU list에서 free bufffer를 찾는다. 만약 정해진 범위내에서 free buffer가 발견되지 않는 경우에 수행되어지는 작업은 이미 앞에서 살펴 보았다. 만약 free buffer가 발견되면, free buffer는 pin되어지고, LRU의 head 부분으로 옮겨진 후 disk로부터 읽는다. 그리고 cache buffer chain latch를 확보한 후 새로운 buffer header의 정보를 hash list에 포함시킨 후 latch를 푼다.
- (1)에서 확보한 buffer handle을 해당 buffer의 user list에 첨가한다.
buffer operation을 줄이면 전체적인 database 성능에 도움이 되는데 buffer operation을 줄이는 방법은 다음과 같이 요약할 수 있다.
(1) temporary tablespace를 전용으로 사용
(2) direct sort reads
(3) direct sqload
(4) direct export
위에서 살펴본 바와 같이 필요한 block이 cache되어 있는 경우 매우 많은 code부분을 수행하지 않아도 되므로, buffer의 hit ratio을 높게 유지하는 것이 매우 중요하다.
Multiple LRU Latches
process가 buffer cache의 list들을 access하기 위해서는 먼저 latch를 얻어야 한다. latch는 빠르게 잡고 풀 수 있는 lock의 일종이라고 볼 수 있으며, lock(enqueue)이 주로 둘 이상의 process가 동시에 같은 data structure를 access하는 것을 막기 위한 것이라면 latch는 동시에 같은 code(oracle source code)부분을 실행하는 것을 막기 위한 것이라고 할 수 있다. LRU와 LRUW 는 모두 LRU LATCH라고 불리는 같은 latch를 사용한다.
7.3 아래 version에는 하나의 LRU LATCH를 이용하여 전체 buffer cache를 관리하였으나, 여러개의 CPU, Parallel Query Option(PQO)의 사용 및 다수의 동시 사용자 등으로 인해 LRU latch contention이 발생하는 경우가 많다. 그 외에도 DBWR은 dirty block을 disk에 write하기 위해 다른 foreground process들과도 경쟁하기도 하고 db_lru_extended_statistics와 같은 parameter를 설정한 경우에도 LRU latch에 대해서 과도한 경쟁을 일으킨다. 그래서 Oracle7.3부터는 db_block_lru_latches parameter를 사용하여 하나 이상의 LRU LATCH를 지정할 수 있도록 하였으며, 주로 이 값은 CPU의 갯수로 지정한다.
이러한 multiple latch는 set이라는 개념을 이용하여 구현되었다. 즉 하나의 latch에 의해 보호되는 LRU list와 LRUW list의 묶음이 하나의 set이 되는 것이다. 결국 set의 갯수는 LRU LATCH의 갯수와 같으며, 이것은 기본적으로 CPU의 갯수/2이며 최대 CPU의 갯수 *2(Oracle7.3) 혹은 CPU의 갯수*6 (Oracle8)개까지 늘릴 수 있다. 이 파라미터를 지정할 때는 하나의 latch당 최소 50개 이상의 buffer가 할당되도록 지정한다. 이렇게 복수개의 set이 사용되는 경우 round robin 방식으로 각 set에 buffer가 할당되며, 이렇게 하여야 모든 set에 대해서 buffer가 균등하게 나누어지게 된다.
user process가 free buffer를 얻고자 하면 random하게 set이 지정되어 buffer를 찾게 되며, 만약 latch를 얻지 못하면 그 set을 시작으로 round robin 방식으로 다른 set들도 찾게 된다. 만약 모든 set을 방문한 뒤에도 얻지 못하면 latch miss가 되며 이 miss된 횟수를 확인하기 위해서는 다음과 같은 query가 이용되어질 수 있다.
SQL> select child#, sleeps/gets ratio
from v$latch_children
where name = 'cache buffers lru chain';
만약 이 결과의 ratio가 1% 보다 크게 된다면 set의 갯수를 늘려주어야 한다.
모든 DBWR/buffer cache에 관련된 parameter는 set의 갯수에 맟추어져야 한다는것을 제외하고는 모든 방식이나 계산은 하나의 latch인 경우와 같다. 즉 예를 들어 user가 DBWR로 하여금 dirty buffer를 disk에 write하도록 요청하기 전에 읽어야 하는 buffer의 갯수는 _DB_BLOCK_MAX_SCAN_CNT/#sets이 된다.
Multiple DBWRs와 Async I/O
DBWR process의 주요 기능은 buffer cache안에 있는 dirty buffer를 disk에 기록하고 buffer를 clean한 상태로 유지하는 것이라고 할 수 있다. DBWR가 dirty buffer를 빨리 disk에 write하기 위해 multiple DBWR process를 유지하는 방법과 asynchronous I/O 방법을 제공한다.
Multiple DBWR
DBWR의 write작업을 병렬로 하도록 해주고, I/O를 분산시키기 위해 DBWR process를 복수개로 지정가능하다. DBWR slave process의 갯수는 db_writers parameter에 의해 결정되며, 기본적으로 이 값은 1이며, 최대 50(platform dependent)까지 증가시킬 수 있다. 만약 system이 I/O작업이 많은 편이라면, min(datafile이 위치한 disk의 갯수, 2*CPU의 갯수)값으로 설정하는 것이 바람직하다.
DBWR slave process는 SMON이 초기화되고 난 후 startup되며, 각 salve process마다 약 SGA내에서 3400 bytes를 차지한다. slave process가 초기화되고 난 후에는, slave process는 DBWR timer event를 기다리면서 master DBWR이 data를 전달해주기를 기다리고, master DBWR은 salve process들이 I/O가 끝난다는 것을 알려주는 DBWR I/O to slave event를 기다리게 된다.
DBWR salve processing은 main DBWR process가 각 salve process들에게 I/O write를 나누어 주도록 하는 것이다. master DBWR이 batch write가 필요하게 되면, 각 slave process들에게 round-robin 방식으로 write할 block들을 나누어 준다. 이때 master DBWR은 실제 I/O에는 참여하지 않으며, 단지 감독만 하면 된다. 그리고 모든 salve process들이 모두 I/O request에 참여하는 것은 아니며, write할 크기와 salve process의 현재 활동에 따라 달라지게 된다.
DBWR은 초기화되면 바로 SGA내에 ssafaonfy라고 하는 구조를 할당한다. 이 structure가 db_writers의 갯수, datafile의 정보를 기술하는 SFFIB(File Identification Structure), 그리고 open datafile status를 나타내는 slave I/O vector array status를 관리한다. multiple DBWR가 구동되면, 각 salve process가 PGA내에 I/O vector array (IOV) structure를 할당하고, 이 structure의 내용은 master DBWR의 SGA structure로부터 IOV array data를 복사하여 채워진다. master DBWR은 SGA내에 ssfaorq라고 하는 다른 structure도 생성하는데, 이 structure는 master DBWR이 slave process와 write/close operation에 대해 통신하기 위한 것이다. 각 slave마다 하나씩의 ssfaorq structure가 존재하며, SGA와 slave process들의 PGA내에 존재한다. SGA내의 ssfaorq structure는 각각 ssfaorqa array형태로 참조된다.
그러므로 DBWR이 write request를 받으면 다음과 같은 순서로 처리가 이루어진다.
- master DBWR이 ssfaorqa array를 뒤져 idle slave를 찾는다.
- idle slave가 발견되면, master DBWR은 file#, block#, SFFIB file identification structure로 구성된 이 I/O request를 PGA내에 있는 slave의 ssaorq structure에 복사한다.
- (2)번과 같이 request가 초기화되고 난 후, master DBWR은 write pending I/O flag를 setting한다.
- slave process는 전달되어진 SFFIB file identification structure 정보를 이용해 자신의 PGA내에 이미 가지고 있던 정보 가 올바른지를 점검한다.
- 이렇게 점검하는 이유는 datafile의 상태가 offline, 혹은 drop되는 등 변경이 이루어졌는지 확인하기 위해서이다.
- 위에서 수행한 점검이 성공하면 I/O가 이루어진다.
- I/O가 끝나면, slave process는 (3)번에서 설정된 SGA내의 write pending flag를 해제한다.
Oracle 7.2 version에서는 open datafile의 수가 100개가 넘으면 자동으로 multiple DBWR가 불가능하게 되었다. Oracle8에서 db_writes를 복수개로 지정하면 Oracle7과는 다르게 작동하며, 이것은 뒤에서 자세히 설명한다.
asynchronous I/O
위에서 설명한 DBWR이 parallel write processing을 제공하는데 반해 asynchronous I/O는 non-blocking I/O를 제공한다. 이 기능은 OS에서 async I/O를 지원해야만 사용가능한데, 운영체제에 따라 raw device에 대해서만 async I/O를 제공하는 경우도 있다. AIX의 경우, async I/O는 자동으로 사용가능하며, 다른 OS은 device driver configuration을 수행한 후 사용하여야 한다. Oracle에서 이러한 OS에서 제공하는 asynchrounous I/O를 사용하기 위해서는 init.ora file에 필요한 parameter를 지정하여야 하는데, 이 parameter는 platform마다 조금씩 다르다. 예를 들어 Sun Solaris의 경우에는 async_read와 async_write가 별도로 필요한 데 반해, AIX는 use_aync라는 하나의 parameter만이 존재한다. Oracle에서 async I/O가 설정되면, read나 write (혹은 pread나 pwrite)대신 aioread나 aiowrite가 호출된다.
Oracle에서의 buffer cache management (Multiple Buffer Pool)
table이나 index 등 segment는 그 사용 빈도나 중요도 등에 따라 memory에 buffering되는 것을 달리 할 필요가 있다. Oracle8에서는 buffer cache에 대해서 multiple buffer pool이라는 새로운 특성의 개념을 제공하여 segment마다 다른 buffer를 사용할 수 있도록 하고 있다. multiple buffer pool은 'keep', 'recycle', 그리고 'default' buffer pool로 구성되며, 이것을 control하기 위한 internal algorithm은 하나의 buffer pool을 사용할 때와 대부분 마찬가지다. 즉, 기존의 CACHE option이나 full table scan시 LRU end에 위치시키는 것 등은 모두 변함이 없으며, 단지 그러한 기법들이 각 buffer마다 별도로 적용된다는 것 뿐이다.
multiple buffer pool의 주요 목적은 서로 다른 형태로 사용되는 것을 나누어 놓아 서로 방해가 되지 않도록 하는 것으로 정리할 수 있으며, 각각 다음과 같은 경우에 사용하도록 한다.
- KEEP buffer pool : 가능한 한 memory에 오랫동안 유지되어져야 하는 segment를 위해 사용되어져야 한다. 자주 사용되어지고 cache size의 약 10%전 후의 크기를 가진 segment가 이 pool을 사용하기에 적당하다. 그러나 여기에서도 Oracle7.3 의 CACHE option과 마찬가지로 새로이 access되는 segment에 의해 LRU end쪽으로 이동하는 것이 가능하므로 항상 cache된다고 보장할 수는 없다. 적당한 크기로 지정하는 것이 중요한데 당연히, 동시에 memory에 올려지기를 바라는 object들의 크기의 합보다는 커야 한다.
- RECYCLE buffer pool: 자주 사용되어지지 않거나, buffer pool의 두배보다 큰 정도의 큰 segment가 index search를 하는 작업 등에 사용되어지도록 한다.
- DEFAULT buffer pool: 위의 두 buffer pool에 할당되지 않은 나머지는 default buffer pool이 된다. 그러므로 KEEP이나 RECYCLE buffer pool은 없어도 반드시 default buffer pool은 존재하게 된다. 이 buffer pool은 Oracle7의 하나의 buffer pool과 같다.
이러한 종류의 buffer pool을 지정하기 위해서 BUFFER_POOL_KEEP과 BUFFER_POOL_RECYCLE이라는 parameter가 존재하며, DB_BLOCK_BUFFERS와 DB_BLOCK_LRU_LATCHES parameter도 함께 고려하여야 한다.
syntax는 다음과 같다.
BUFFER_POOL_KEEP=(buffers:<value>,lru_latches:<value>) 혹은
BUFFER_POOL_KEEP=<value>
BUFFER_POOL_RECYCLE=(buffers:<value>,lru_latches:<value>) 혹은
BUFFER_POOL_RECYCLE=<value>
위의 syntax에서 보는 바와 같이 각 pool에 대해서 buffer의 갯수 뿐 아니라 LRU latch의 갯수도 지정할 수 있다. 만약 지정하지 않으면 그 pool에 대해서 하나의 latch가 할당되는 것이다.
DEFAULT pool에 대해서는 명시적으로 block의 갯수나 latch의 갯수를 지정할 수 없고, 대신 전체 block의 갯수 (DB_BLOCK_BUFFERS)와 전체 LRU latch의 갯수 (DB_BLOCK_LRU_LATCHES)에서 KEEP과 RECYCLE에 할당된 각각의 값을 뺀 것만큼 default pool에 할당된다.
간단한 예제로 설명하면 다음과 같다.
예를 들어 initSID.ora file에 다음과 같이 parameter가 설정되어 있다고 가정한다.
DB_BLOCK_BUFFERS=1000
DB_BLOCK_LRU_LATCHES=6
BUFFER_POOL_KEEP=(buffers:400,lru_latches:2)
BUFFER_POOL_RECYCLE=100
이러한 경우 KEEP pool에 대해서는 400개의 block과 2개의 LRU latch가 할당되고 RECYCLE pool에는 100개의 block과 1개의 LRU latch가 할당된다. 그리고 DEFAULT pool에는 500 (1000-400-100) 개의 block과 3 (6-2-1)개의 LRU latch가 할당되게 된다. 각 LRU queue에 대해서 block은 균등하게 배분된다. 즉, 이 예에서 DEFAULT queue는 LRU 1번이 167개의 block을 LRU2도 167개, 그리고 LRU3은 166개의 block을 가지게 되며, KEEP queue는 두개의 latch가 각각 200개씩의 block을 그리고 RECYCLE queue는 100개의 block을 가지게 된다. 이러한 정보는 v$buffer_pool을 통해 확인이 가능하며, 이 예의 경우 다음과 같이 조회된다. 여기에서 set_count가 각 pool에 할당된 latch의 갯수이며, lo_bnum과 hi_bnum이 buffer의 range이다.
SQL> select * from v$buffer_pool;
NAME LO_SETID HI_SETID SET_COUNT BUFFERS LO_BNUM HI_BNUM
---------- -------- -------- ---------- ---------- ---------- ----------
0 0 0 0 0 0
KEEP 4 5 2 400 0 399
RECYCLE 6 6 1 100 400 499
DEFAULT 1 3 3 500 500 999
각 queue는 최소 50개의 block은 할당받아야 하며, 그렇지 않은 경우에는 오류가 발생한다. 즉 예를 들어, BUFFER_POOL_KEEP=(buffers:100, lru_latches:3)과 같이 설정하면 alert.log file에 "Incorrect parameter specification for BUFFER_POOL_KEEP"이라는 오류 메시지가 적히게 되며, 100개의 block에 대해서 최대 두개의 LRU latch만이 가능하게 된다.
BUFFER_POOL이라는 Oracle8에서 새로 추가된 storage 절의 parameter를 이용하여 segment가 사용할 default pool을 지정할 수 있다. segment의 모든 block은 지정된 pool을 사용하게 되며, 아래의 예제와 같이 사용하면 된다.
CREATE TABLE keep_table(t NUMBER(10)) STORAGE (BUFFER_POOL KEEP);
ALTER TABLE recycle_table storage(BUFFER_POOL RECYCLE);
BUFFER_POOL은 tablespace나 rollback segment에 대해서는 지정할 수 없으며, clustered table에 대해서는 cluster level에서만 지정이 가능하다. partition table에 대해서는 각 partition별로 pool을 지정하는 것이 가능하다.
일단 segments가 적당한 pool에 할당이 되고 난 후에는, logical hit ratio나 free buffer waits와 같은 다양한 통계정보가 확인가능하다. 이러한 통계 정보를 담과 있는 view는 v$buffer_pool_statistics이며, 이 view는 $ORACLE_HOME/rdbms/admin/catperf.sql을 수행하면 생성된다.
[참고]현재까지는 utlbstat/utlestat에서는 각 pool별로 통계정보를 생성하지 못하고 있다.
에서의 DBWR
Oracle에서의 db_writers는 master-slave processing을 통해, async I/O를 simulate하기 위해 사용되었다고 볼 수 있다. Oracle8에서 DBWR의 write processing에 더 나은 성능을 제공하기 위해 복수개의 database writer를 사용하는 방법은 다음과 같이 두가지로 나눌 수 있으며 이 두 방법은 mutual exclusive하다.
[참고] 실제 test해본 결과 함께 사용하면 dbwr_io_slaves만 효과가 있었다. 이것은 dbwr_io_slaves는 master dbwr process를 db_writer_proceses에 관계없이 하나만 가지도록 되어 있기 때문이다.
DBWR IO slaves
Oracle7에서의 mulitple DBWR process들은 단순히 slave process로써, async I/O call을 수행할 수는 없었다. Oracle 8.0.3부터, slave database writer code가 kernal에 포함되었고, slave process의 async I/O가 가능하게 되었다. 이 것은 init.ora file내에 dbwr_io_slaves라는 parameter를 통해 가능하며, IO slave가 asynchronous I/O가 가능하여 I/O call이후에 slave가 block되지 않아 더 나은 성능을 제공한다는 것을 제외하고는 Oracle7과 매우 유사하다. slave process는 instance생성시기가 아닌 database open시에 start되기 때문에 oracle process id가 9번부터 할당되며, os에서 확인되는 process이름도 ora_i10n_SID와 같은 형태가 된다.
dbwr_io_slaves=3으로 지정한 경우, 아래와 같은 oracle background process가 구동되며, ora_i101_V804, ora_i102_V804, ora_i103_V804이 dbwr의 slave process들이다.
tcsol2% ps -ef | grep V804
usupport 5419 1 0 06:23:53 ? 0:00 ora_pmon_V804
usupport 5429 1 1 06:23:53 ? 0:00 ora_smon_V804
usupport 5421 1 0 06:23:53 ? 0:00 ora_dbw0_V804
usupport 5433 1 0 06:23:56 ? 0:00 ora_i101_V804
usupport 5423 1 0 06:23:53 ? 0:00 ora_arch_V804
usupport 5431 1 0 06:23:53 ? 0:00 ora_reco_V804
usupport 5435 1 0 06:23:56 ? 0:00 ora_i102_V804
usupport 5437 1 0 06:23:56 ? 0:00 ora_i103_V804
usupport 5425 1 0 06:23:53 ? 0:00 ora_lgwr_V804
usupport 5427 1 0 06:23:53 ? 0:00 ora_ckpt_V804
Multiple DBWR
multiple database writer는 init.ora file내의 db_writer_processes라는 parameter에 의해 구현되며, 이것은 Oracle 8.0.4부터 제공되었다. 이것은 기존의 master-slave관계가 아닌 진정한 의미의 복수개의 database writer를 사용하는 것이며, database writer process들은 PMON이 start된 후에 start되어진다. 이름은 ora_dbwn_SID형태이며, 아래에 db_block_lru_latches=2, db_writer_processes=2로 지정한 경우 구동된 oracle background process들의 예이다. 여기에서 ora_dbw0_V804, dbw1_V804이 dbwr process들이다. 만약 db_writer_processes를 지정하지 않으면 기본값은 1인데 이때도 Oracle7과 같이 ora_dbwr_SID형태가 아닌 ora_dbw0_SID형태의 process가 구동된다.
usupport 5522 1 0 06:31:39 ? 0:00 ora_dbw1_V804
usupport 5524 1 0 06:31:39 ? 0:00 ora_arch_V804
usupport 5532 1 0 06:31:39 ? 0:00 ora_reco_V804
usupport 5528 1 0 06:31:39 ? 0:00 ora_ckpt_V804
usupport 5530 1 0 06:31:39 ? 0:00 ora_smon_V804
usupport 5526 1 0 06:31:39 ? 0:00 ora_lgwr_V804
usupport 5520 1 0 06:31:39 ? 0:00 ora_dbw0_V804
usupport 5518 1 0 06:31:38 ? 0:00 ora_pmon_V804
db_writer_processes로 지정된 각 writer process는 하나의 latch set에 할당된다. 그러므로 db_writer_processes를 db_lru_block_latches으로 지정되는 LRU latch의 갯수와 같은 값으로 지정하는 것이 권장할만하며, 단 CPU의 갯수를 초과하는 것은 바람직하지 않다.
[참고] 실제 test해본 결과 init.ora file내에 구동되는 dbwr의 갯수는 db_block_lru_latches parameter에 의해 제한되었다. 즉 db_writer_processes값을 db_block_lru_latches보다 크게 하여도 db_block_lru_latches값 이상으로 db_block_lru_latches로 지정된 수의 dbwr process가 기동되며, 이것은 현재 bug인지, 원래의 action인지가 bugdb내에서도 등록만된 상태고 결정이 나지 않은 상태이다. PR에는 db_block_lru_latches problem solution으로 등록되어 있다.
Oracle에서 DBWR I/O slave나 복수개의 DBWR를 제공하는 방법 중 좋은 점은 이 기법을 제공하는 것이 kernal안에 포함되어 기존의 OSD layer로 구현되었던것보다 port specific한 부분이 없고 generic하다는 것이다.
이러한 두가지 형태의 DBWR 기법이 모두 도움이 되기는 하지만, 일반적으로 어느것을 사용할 것인지는 OS level에서 asynchronous I/O가 제공하는지와 CPU갯수에 의존한다. 즉, system이 복수개의 CPU를 가지고 있으면 db_writer_processes를 사용하는 것이 바람직하며, aync I/O를 제공하는 경우 두가지 모두 사용에 효과를 얻을 수 있다. 그런데 여기서 주의할 것은 db_io_slaves가 약간의 overhead가 있다는 것이다. slave IO process를 가능하게 하면, IO buffer와 request queue의 할당을 위해 부가적인 shred memory가 필요하다. multiple writer processes와 IO slave는 매우 부하가 많은 OLTP환경에서 적합하며, 일정 수준 이상의 성능을 요구할 때만 사용하도록 한다. 예를 들어 async I/O가 사용가능한 경우, I/O slave도 사용하지 않고 하나의 DBWR만을 async I/O mode로 사용하는 것이 충분하고 바람직할 수 있다. 현재의 성능을 조사하고 bottleneck이 되는 부분이 DBWR부분인지 정확히 조사한 후 사용하여야 한다.
[참고] 실제 Oracle7에서부터 async mode에서 복수개의 DBWR를 사용시에 Oracle bug을 포함한 많은 문제가 있어 왔으므로 이것을 사용시엔 정확한 관찰과 test가 요구되어진다.
첨부
underbar parameter의 값을 확인하는 방법
_ parameter를 포함한 parameter의 이름과 의미는 X$KSPPI table에 들어 있으며, 그 값은 X$KSPPSV 혹은 X$KSPPCV에 들어 있다. 예를 들어 _db_block_write_batch의 값을 확인하려면 다음과 같이 조회가능하며, 이때 X$KSPPSV대신 X$KSPPCV를 사용하여도 같은 결과를 얻게 된다.
SQL> select a.ksppinm, b.ksppstvl from x$ksppi a, x$ksppsv b
where a.indx=b.indx
and a.ksppinm like '%batch%';
KSPPINM KSPPSTVL KSPPDESC
------------------------- ------- ----------------
_trace_write_batch_size 32 trace write batch size
_db_block_write_batch 0 Number of blocks to group in each DB Writer IO
db_block_checkpoint_batch 8 Max number of blocks to checkpoint in a DB Writer IO
Buffer Cache에 관해 database내에 지정된 값 확인
SQL> select kviidsc, kviival from x$kvii;
KVITDSC KVITVAL
------------------------------------------------ ----------
large dirty queue if kcbclw reaches this 3
free buffer wanted request sent flag 0
number buffer headers 5000
number of recent buckets 0
DBWR blocks to scan looking for dirty 0
DBWR blocks to scan lowest value 10
DBWR blocks to scan highest value 160
DBWR scan depth increment 19
DBWR scan depth decrement 1
foreground blocks to scan looking for free 1250
background completion count 0
Error Log Number for thread open 0
SGA: true if a CheckPoint is Active 0
SGA: true if a Global Checkpoint is Waiting 0
SGA: true if ChkPt buf writes Done 1
SGA: true if CheckPointing Fast 0
number of latches 53
event range base 2185
# of base events 104
number of CPUs in the system 2
number of hash queue latch structures 1259
number of current buckets 0
DB writer IO clump 40
DB writer checkpoint clump 8
# background lock processes 1
number of hash locks 613
true if Statically Allocated Thread 0
THRead mounted by this instance - zero if none 1
sga shadow value of instance_number 0
min # of transaction free list 25
max undo class 46
number of MTS queues 12
v$sysstat의 통계 정보의 의미 정리
아래에 나열된 것은 v$sysstat이나, bstat/estat에서 buffer cache management에 영향을 미치는 것들에 대해 간단히 나열하였다.
- cluster key scan block gets/cluster scans = cluster key chaining에 대한 chaining.
만약 이 값이 1보다 크면 chaining이 존재하는 것이다.
- cluster Key Scan Block Gets - acess된 cluster block의 갯수
- cluster Key Scans - cluster block를 scan한 횟수
- consistent gets + db_block_gets = logical reads
(logical_reads / (logical_reads + physical reads ) ) * 100 = logical hitratio
이 값은 filesystem에 대해서는 85-90%, raw device에 대해서는 90-95% 정도는 유지하여야 한다.
- DBWRs free buffers found - DBWR가 free buffer를 만들기 위해 LRU list의 끝부분부터 조회시 발견한 free buffer의 갯수. fee buffer를 확보하기 위해 scan하는 경우가 아닌 다른 목적의 LRU scan에 대해서는 이 값이 영향을 받지 않는다.
- DBWR make free requests - DBWR로 하여금 LRU list내에 free buffer를 확보하기 위해서 dirty buffer를 LRUW로 옮기도록 request한 횟수
- DBWR free buffers found / DBWR make free requests = LRU list의 끝부분에 위치한 reusable buffer의 평균 값
- dirty buffers inspected - foreground process가 재사용할 buffer를 찾기 위해 LRU list에서 만난 dirty buffer의 갯수. 이 값이 작아야 DBWR가 foreground process에 뒤지지 않게 수행되고 있는 것이다.
- free buffer inspected / DBWR buffers scanned = Free Buffer Scan Ratio라고도 불리며, buffer cache내에서 변경된 block의 비율이다.
만약 이 값이 크다면 buffer cache내에 unusable buffer가 너무 많아서 DBWR이 쫒아가지 못하는 것이다. 이런 경우에 db_block_buffer의 값을 늘려야 할 필요가 있다.
- dirty buffers inspected - user가 잡고 있는 buffer의 갯수
- free buffers inspected - free buffer를 찾기 위해 DBWR가 skip 한 buffer의 갯수
이 값은 dirty buffers inspected 값을 포함하는 것으로 pinned buffer나 dirty buffer를 포함한다.
- DBWR buffers scanned - LRU list에서 buffer를 비우기 위해 scan한 전체 buffer의 갯수
- free buffers requested - block을 생성하고 load하기 위해 free buffer가 요청된 횟수
- summed dirty queue length/write requests = dirty list의 평균 길이. 이 값은 _db_block_write_batch parameter에 의해 설정될 수 있다.
- table fetch by rowid - rowid에 의해 table에서 fetch된 row의 갯수. 즉 rowid= 연산이나 index를 통한 조회인 경우가 해당된다.
- table fetch continued row - 발견된 chained row의 갯수
- table fetch continued row to table fetch by rowid 비율이 1:1000정도는 되어야 하며, database가 long datatype을 가지지 않는 경우는 예외이다.
- table scan blocks gotten 과table scan rows gotten
각각 full table scan시 fetch된 전체 block의 수와 row의 수이다. 한 block당 평균 row의 수를 구하기 위해 이용된다.
full table scan의 경우
table scan rows gotten / table scan blocks gotten * 100 - DSS system의 경우 이 값이 커야 한다.
- Table Scans (short) * 5 blocks = Blocks Scanned (short)
- Table Scan Blocks Gotten - Blocks Scanned (short) = Blocks Scanned (long)
- locks Scanned (long) / Table Scans (long tables) = 하나의 long table에 대해서 scan한 block의 평균 갯수
- table scans (long tables) - 5개 이상의 block을 가지고 있는 table에 대해서 full table scan이 일어난 횟수. "per trans"에 대한 이 값, 즉 하나의 transaction당 이루어진 횟수가 0보다 커서는 안된다. 만약 0보다 크다면 application이 index를 사용하도록 검토하여야 한다.
- table scans (short tables) - 5 block미만인 table에 대해 수행된 full table scan의 횟수
- free buffer waits - 사용 가능한 buffer가 없어서 foreground process가 기다린 횟수. 이것은 DBWR가 free buffers inspected limit에 도달한 경우 발생할 수 있는데, 이 값이 너무 크면, DBWR가 disk에 충분히 write를 수행하지 못함을 나타낸다.
- buffer busy waits - process가 buffer를 기다린 횟수. buffer가 다른 user에 의해 cache내에 읽혀지는 중이거나, buffer의 status가 요청한 mode와 호환되지 않는 경우이다.
(buffer busy waits)*100 / (db blocks gets + consistent gets) 값은 5%보다 커서는 안된다.
"select * from v$waitstat " 를 수행하여 어떤 type의 object가 waiting 중이었는지 확인하고 적적한 tuning을 수행하여야 한다.
- db file sequential read, db file scattered read, db file single write and db file parallel write
data files header, control file data file에 대해 수행된 I/O에 대한 모든 event들. 만약 만약 이 wait event들이 Average Time에 비해 크다면, sar나 iostat을 이용하여 I/O contention을 조사해야 한다.
- cache buffer handles - This latch protects the State Objects that are needed by a process to make a change to a block/buffer. Before the change a buffer handle is acquired either from the process' private pool or the global pool if none are present. The access to the global pool is protected by this latch.
- cache buffer chains - foreground process가 buffer를 변경하기 전에 잡아야 하는latch로 복수 사용자에 의해 동시에 변경되는 것을 막아준다. 하나의 latch에 대해서 여러개의buffer가 DBA를 이용하여 hash되어진다.
latch contention이 심한 경우, 특정한 particular hash list가 크게 증가하였거나, 하나의 block에 대해서 CR copy가 여러개 존재하는 경우이다. 다음과 같은 query를 이용하여 그러한 경우인지를 확인한다.
select dbafil "File #", dbablk "Block #",count(*)
from x$bh
group by dbafil, dbablk
having count(*) > 1 ;
- cache buffer lru chain - LRU list를 보호하기 위한 latch이다. buffer를 이 list에 옮기려면 일단 이 latch를 잡아야 한다. 만약 이 latch에 대한 contention이 크다면, _db_block_write_batch를 증가하거나 _db_writer_max_scan_cnt를 감소하여야 한다. SNP system의 경우 LRU latch는 db_block_lru_latches라는 instance당 LRU latch의 개수를 나타내는 initial parameter에 의해 제어된다. 이 값은 기본적으로 ½ * CPU개수이며 최대값은 2* CPU의 갯수이다.
- cache protection latch - buffer cache가 변경되어 CR copy를 유지하기 위해 buffer를 복사하는 경우 새로이 복사된 buffer의 위치가 변경되었으므로 이 정보를 저장하기 위해서 buffer head의 내용이 자동으로 변경된다. 이렇게 buffer를 copy하는 것을 한 순간에 하나만 수행하기 위한 latch이다.
Control variables:
- foreground scan depth : 최대 ¼ * buffer cache.
- max batch write : min(1/4 * buffer cache, OS specific)
- max dirty queue : 2* max batch write = ½ buffer cache
- max scan depth : min(4* max batch write, ¼ buffer cache)
- min scan depth : min(max batch write, ¼ buffer cache)
- DBWR scan depth : 초기에는 min scan depth이며, 이후에 동적으로 변경된다.
'DB - ORACLE > DB Tunning(TABLE,SQL,...)' 카테고리의 다른 글
테이블 별 I/O 확인 쿼리 (0) | 2015.11.24 |
---|---|
SE 버전에서 테이블 I/O 확인하는 쿼리 (0) | 2015.11.24 |
Oracle Shared Pool (0) | 2015.11.06 |
조인의 튜닝 방법 -1 (0) | 2015.11.05 |
오라클 Touch Count 알고리즘 (0) | 2015.11.03 |