새로쓴 대용량 데이터베이스솔루션 1 (2011년)
스캔(Scan)의 기본유형 0 0 32,339

by 구루비스터디 테이블스캔 인덱스스캔 Cluster Access Hash Access ROWID scan [2023.10.14]


  1. 전체테이블 스캔(Full Tables Scans)
  2. 로우식별자 스캔(Rowid Scans)
  3. 인덱스 스캔(Indesx Scans)
  4. B-Tree클러스터 액세스(Cluster Access)
  5. 해쉬 액세스(Hash Access)
  6. 표본 테이블 스캔(Scans)


  • 어떤 하나의 스캔이 가장 최적이라 말할 수 없다.
  • 우리의 실생활에는 다양한 운송수단이 있다.
  • 이는 데이터를 액세스 하는 방법에서도 마찬가지 이다.
  • 이제 그 스캔의 유형의 내용을 살펴봅시다.


전체테이블 스캔(Full Tables Scans)

  • 전체테이블 스캔(Full Tables Scans) 테이블에 있는 모든 로우들을 읽어내는 방법.
  • 다중 블록단위로 메모리에 옮겨지며, 이 블록들은 순차적으로 읽혀진다.
  • 일반적으로 블록들은 서로 인접되어 있기 때문에 한번의 I/O 에서 처리되며 , 이것은 로우당 소요되는 운반단가를 저렴학게 만든다.
  • 한번의 액세스 하는 블록의 양을 정의하려면 DB_FILE_MULTIBLOCK_READ_COUNT 파라메터에서 지정한다.
  • 그럼 이제 옵티마이져가 Full Tables Scan 을 선택하게 되는 경우를 살펴 봅시다.


적용가능 인덱스의 부재
  • 존재하고 있는 인덱스를 전혀 사용할 수 없는 경우.
  • 1.결합인덱스의 선두컬럼이 존재하지 않을때.
  • 2.인덱스를 가졌지만 가공을 해버려 그 인덱스를 사용할 수 없을때
  • 단, 예외적으로 Function Base Index 나 Index Skip Scan이 적용되면 인덱스 사용이 가능하다.


넓은 범위의 데이터 액세스
  • 적용가능한 인덱스가 존재하더라도 처리범위가 넓어서 전체테이블 스캔이 보다 적은 비용이 든다면 Full Tables Scan을 적용할 수 있다.


소량의 테이블 액세스
  • 최고수위 표시 내에 있는 블록이 DB_FILE_MULTIBLOCK_READ_COUNT 이내에 있다면 Full Tables Scan이 일어날 수 있다. (항상 그런것은 아님)


병렬처리 액세스
  • 병렬처리는 Full Tables Scan을 더욱 효과적으로 수행하게 되므로 병렬처리로 수행되는 실행계획을 수립할때는 항상 Full Tables Scan을 선택한다.


'FULL' 힌트를 적용 했을때
  • Full 힌트를 사용했을때...단, FULL 힌트가 적절하지 않다면 옵티마이져는 이를 무시할 수 있다.


Full Table Scan 을 했을때의 실행계획
  • Full Tables Scan의 가장 단순한 형태

SELECT *
FROM   TF002NGT
WHERE  ITEM_NAME LIKE '%BF%'



callcountcpuelapseddiskquerycurrentrows
Parse10.010.010000
Execute10.000.000000
Fetch11.472.25127401275439
total31.482.53127401275439



Rows         Rows Source Operation
----------     --------------------------------------------------------------------------------
51005         TABLE ACCESS (FULL) OF 'TF02NGT' 


  • 하단의 51005는 테이블을 액세스한 로우수(전체 로우수) 이고 , WHERE 절을 체크를 통해 선별된 최종 결과의 로우수는 '9' 이다.


정렬처리를 했을때 나타나는 실행계획

Rows         Rows Source Operation
----------     -------------------------------------------------------------------------------
3548         SORT ORDER BY' 
51005           TABLE ACCESS (FULL) OF 'TF02NGT' 

  • 51005건을 Full Tables Scan 으로 액세스 하여 체크조건을 적용 하였더니 3548 건이 추출되었으며 이를 정렬한 후에 최종결과를 리턴하겠다는 것.


데이터를 Insert, Update , Delete를 하는 경우의 실행계획
callcountcpuelapseddiskquerycurrentrows
Parse10.030.040000
Execute150.7360.2510073407269628222658
Fetch00.000.000000
total250.7660.2910073407269628222658

Rows         Rows Source Operation
----------     --------------------------------------------------------------------------------
       0      INSERT STATEMENT
150842         TABLE ACCESS (FULL) OF 'BT_REG_BASE' 

  • 'BT_REG_BASE' 테이블을 Full Tables Scan 하여 150842 건을 액세스 하여 조건절을 체크하였더니 2658건이 되었고 이를 다른 테이블에 입력하였다는 것을 의미.
  • 이때 Select 한 테이블은 실행계획에 나타나지만 입력된 테이블은 해당 SQL 에서만 확인할 수 있다.
  • 또한 'Execute' 란에만 숫자가 나타난다. 액세스를 한 테이블에 대해서는 'Fetch'가 발생하였지만 실행통계에는 액세스와 입력 작업에서 발생한 것을 모두 합해서 두 번째 라인에 기록한다.


조인에서 발생하는 Full Tables Scan.
  • NestedLoops 조인시 선행 처리에서 Full Tables Scan이 발생하는경우

Rows         Execution Plan
---------------      ------------------------------------------------------------------------------
④     7701      NESTED LOOPS
①   148046         TABLE ACCESS (FULL) OF 'ITEM_BASE'
③     7719         TABLE  ACCESS (BY INDEX ROWID) OF 'CS_SPEC'
②     7724            INDEX (UNIQUE SCAN) OF 'PK_CS_SPEC'


  • ① 'ITEM_BASE'를 Full Tables Scan으로 액세스한 로우수는 148046. 이중 조건절을 통과한 로우수는 7724. 이것은 'PK_CS_SPEC' 에 연결을 시도한 회수를 보고 알아낼 수 있음
  • ② 선행테이블의 조건을 통과한 7724건이 'CS_SPEC' 테이블의 기본키를 이용하여 연결을 시도. 이중 5건은 실패. 이는 7724 - 7719 를 해보면 알 수 있음.
  • ③ 기본키의 ROWID 로 액세스 하였다. 근데 'CS_SPEC' 에도 체크조건이 있었다는걸 알 수 있음. 이는 7719 - 7701 를 해보면 알 수 있음.
  • ④ 최종결과가 7701 임을 알 수 있음.


조인에서 발생하는 Full Tables Scan.
  • NestedLoops 조인시 후행 처리에서 Full Tables Scan이 발생하는경우

Rows         Execution Plan
--------      ------------------------------------------------------------------------------
④    280       NESTED LOOPS (OUTER)
①  74861         TABLE  ACCESS (BY INDEX ROWID) OF 'BAL_ITEM'
③ 210991             INDEX (RANGE SCAN) OF 'PK_BAL_ITEM' (UNIQUE)
②  53200         TABLE ACCESS (FULL) OF 'TPF_INFO' 

  • ① 'BAL_ITEM' 테이블의 기본키를 범위 스캔하면서 Rowid 로 테이블을 액세스한 건이 210991 이지만. 실제 테이블을 액세스 한것은 74861건.
  • 이러한 현상이 나타나는 이유는 최소 두개 이상의 컬럼에 조건이 부여되었지만 이들이 결합인덱스로 구성된 기본키에서 연속된 순서를 가지고 있지 않다는것을 나타낸다.
  • 즉, 이 컬럼들 사이에 조건으로 부여하지 않은 하나이상의 컬럼이 존재 한다는것.
  • ② 테이블을 액세스한 78461 중에서 체크조건에 의해서 다시 걸러지고 남은것은 280건.
  • 그 이유는 이 조인은 아우터 조인이기 때문에 연결에 실패 하였더라도 조인은 언제나 성공이므로 그 조인결과 집합인 280과 동일하다.
  • 또한. 선행테이블(BAL_ITEM)이 이미 범위 처리를 했기때문에 나중에 연결되는 집합은 그 테이블의 기본키로 유일하게 액세스 되어야 한다.
  • 그러나 연결고리에 인덱스가 없기때문에 연결 대상마다 매번 Full Tables Scan를 하였고.
  • ③ 이 테이블(TPF_INFO)을 스캔한 로우스는 53200이지만 연결을 시도한 횟수는 280 이다.
  • 그렇다면 TPF_INFO 의 총 로우 수는 190(=53200/280) 이다.
  • ④ 연결을 시도한 280건이 아우터 조인에 의해 모두 성공하게 되므로 최종결과는 280건.


로우식별자 스캔(Rowid Scans)

  • ROWID는 그 로우를 포함하고 있는 데이터파일과 데이터 블록, 그리고 블럭 내에서의 위치를 가지고 있다.
  • 그러므로 하나의 로우를 찾는 가장 빠른 방법.
  • 대부분의 ROWID 스캔은 인덱스를 경유하여 테이블을 액세스 하는 과정에서 발생.


인덱스 스캔(Indesx Scans)

  • 실제적으로 가장 많이 발생하는 방식.
  • 로우를 추출할때 결과를 보면 로우를 찾는것이지만 실제 내부적인 I/O는 언제나 블록을 액세스 한다.
  • 따라서 옵티마이져가 비용을 산정할때도 블록을 기준으로 계산한다.
  • 이는 클러스터링 팩터가 얼마나 좋으냐에 따라 액세스 효율에 커다란 영향을 미치게 된다.
인덱스 스캔을 좀더 세부적으로 분류하면 다음과 같다.
  • 인덱스 유일 스캔 (Index unique Scan)
  • 인덱스 범위 스캔 (Index Range Scan)
  • 인덱스 역순 범위 스캔 (Index Range Scans Descending)
  • 인덱스 스킵 스캔 (Index Skip Scan)
  • 인덱스 전체 스캔 (Ful Scan)
  • 인덱스 고속 전체 스캔 (Fast Full Index Scan)
  • 인덱스 조인 (Index Join)
  • 비트맵 인덱스 (Bitmap Index)


인덱스 유일 스캔 (Index unique Scan)
  • 단 하나의 Rowid를 추출한다.
  • 인덱스가 기본키나 유일인덱스 (Unique index)로 생성되어 있어야 하며
  • 인덱스를 구성하는 모든 컬럼들이 모두 조건절에서 '=' 비교 되어야 한다.
  • 인덱스 유일 스캔 (Index unique Scan)로 유도해야 하는 경우 특정 인덱스 사용을 권고하는 'INDEX(Table_Alias Index_Name)' 힌트를 준다.


인덱스 범위 스캔 (Index Range Scan)
  • 가장 보편정인 액세스 형태.
  • 시작과 종료를 가진 경우와 하나 이상이 끝을 가지지 않은 경우가 있다.
  • 이 스캔을 경유 하여 추출되는 로우는 인덱스 구성 컬럼의 정렬 순서와 동일하게 나타난다.
  • 최초의 시작점을 찾을 때만 랜덤 액세스를 사용하고 그 후로 종료시 까지는 스캔을 한다.
  • 즉, Branch Block 를 경유 하여 Leaf Block 을 찾은후 연결된 다음 Leaf Block 를 스캔 하다 종료점을 만나면 멈춘다.
  • 스캔 범위가 넓어질때 부하가 증가하는것은 인덱스 탓이 아니라 인덱스의 ROWID로 테이블을 랜덤 액세스 해야 하는 부분이다
  • 따라서 인덱스 범위 스캔 (Index Range Scan) 클러스터링 팩터에 직접적인 영향을 받는다.
  • 인덱스 범위 스캔 (Index Range Scan)로 유도하는 힌트는 'INDEX(Table_Alias Index_Name)' 이다.


다) 인덱스 역순 범위 스캔 (Index Range Scans Descending)
  • 역순으로 데이터를 액세스 한다는 것을 제외 하면 인덱스 범위 스캔 (Index Range Scan)와 동일.
  • 인덱스는 순차적으로 정렬되어 저장된다.
  • 따라서 이 스캔은 가장 최근의 값을 가장 처음 스캔할 수 있다.
  • 이는 실무적으로 많은 도움이 된다.
  • 인덱스 역순 범위 스캔 (Index Range Scans Descending)을 유도하는 힌트는 'INDEX_DESC(Table_Alias Index_Name)' 이다


인덱스 스킵 스캔 (Index Skip Scan)*
  • sal_tp(매출유형) + item_cd(상품코드) + sal_dt(매출일자) 로 구성된 인덱스가 있고 쿼리의 조건에는 item_cd , sal_dt만 사용되었다. sal_tp는 D,E,L 세종류만 있다고 가정한다.
  • 인덱스 스킵 스캔이 적용 되었다면 마치 조건절에 sal_tp IN ('D','E','L') 을 추가한 것과 동일한 효과를 얻을 수 있다.
  • 여기서 D,E,L 을 논리적 서브 인덱스 라고 부른다. 인덱스 스킵 스캔은 서브인덱스의 종류가 많지 않고 뒤에 오는 컬럼은 종류가 많을때 가장 좋은 결과를 얻을수 있다. 이말은 이런 경우가 아니라면 큰 효과를 얻을 수 없다는 뜻이다.
  • 인덱스 스킵 스캔으로 유도 하는 방법은 'INDEX_SS','INDEX_SS_ASC','INDEX_SS_DESC' 등이 있고 인덱스 스킵 스캔을 하지 않고 싶다면 'NO_INDEX_SS'를 사용한다.


인덱스 전체 스캔 (Ful Scan)
  • 조건절에서 그 인덱스의 컬럼이 적어도 하나이상 사용 되었을 때 적용이 가능하다. 즉, 반드시 선행컬럼이 사용되어야 할 필요는 없다는 것.
  • 근데 만약 아래 두 조건을 모두 만족한다면 조건절에 전혀 사용된 컬럼이 없어도 적용 가능하다.
  • 쿼리 내에 사용된 어떤 테이블들의 모든 컬럼들이 그 인덱스에 모두 존재하고
  • 인덱스 컬럼 중에서 최소한 NOT NULL 인 컬럼이 하나는 존재할 때


인덱스 고속 전체 스캔 (Fast Full Index Scan)
  • 만약 쿼리를 위해 사용된 어떤 테이블의 컬럼이 모두 그 인덱스에 포함되어 있다면 인덱스 고속 전체 스캔은 전체테이블 스캔의 대안으로 사용될 수 있다.
  • 단, 인덱스 전체 스캔 (Ful Scan) 과 마찬가지로 NOT NULL 제약조건의 컬럼이 반드시 하나 이상 존재해야 한다.
  • 이 스캔으로 유도 하고 싶다면 'INDEX_FFS' 그렇지 않다면 'NO_INDEX_FFS' 로 해제 한다.


인덱스 조인 (Index Join)*
  • 별도의 장에서 상세히 설명


비트맵 인덱스 (Bitmap Index)*
  • 별도의 장에서 상세히 설명


B-Tree클러스터 액세스(Cluster Access)

  • 1:M 관계를 가진 두 테이블을 클러스터링 하면 동일한 클러스터 키값을 가진 두 테이블의 모든 로우는 같은 클러스터 내에 저장된다.
  • 이때 1쪽의 데이블을 클러스터키로 액세스 하면 하나의 로우가... M 쪽을 액세스 하면 여러개가 나타날 것이다.


해쉬 액세스(Hash Access)

  • 인덱스를 이용하여 데이터를 액세스 하는 방법은 반드시 인덱스 I/O 와 테이블 I/O를 거쳐야 하지만 해쉬클러스터의 데이터 접근경로는 해쉬함수를 생성하는 것과 테이블 I/O로만 구성되므로 그만큼 I/O를 줄일 수 있다.
  • 따라서 넓게 산포된 테이블의 액세스에서 디스크 I/O를 줄임으로써 시스템 성승향상을 기대할 수 있다.
  • 이 스캔은 해쉬함수를 통해서 데이터를 액세스 해야하므로 액세스 형태가 다양하지 않고 주로 '=' , 'BETWEEN', 'IN'으로 적용할 수 있는 테이블에 적용해야 한다.
  • 또한 해쉬키로 지정된 컬럼이 자주 손상되지 않는것이 좋고 대량으로 데이터가 증가하지 않는것이 바람직 하다.


표본 테이블 스캔(Scans)

  • 테이블의 데이터 중에서 사용자가 부여한 비율 만큼의 데이터를 읽고 그중에서 조건을 만족하는 로우들을 리턴 한다.
  • 테이블의 표본 데이터를 스캔하는 방식은 아래와 같다.

SELECT .....
  FROM   tabel_name SAMPLE _BLOCK option_ (Sample Percent)
 WHERE  ...
 GROUP  BY ....
HAVING ....
 ORDER  BY .....

  • SAMPLE BLOCK(sample percent) 를 사용하면 전체 액세스 대상 블록에서 지정한 비율(sample percent) 만큼의 블록을 읽은 후 조건을 만족하는지 확인한다. 여기서 숫자는 확률값을 의미 하므로 수행할 때마다 다른 블록이 나타날수 있다.
  • SAMPLE BLOCK(sample percent)를 지정하면 모든 블록이 액세스되지만 각각의 블록에서 지정한 비율만큼의 로우들을 임의로 선택한 후 이를 대상으로 조건을 체크하여 결과를 리턴한다.
  • 비율(Sample Percent)는 0.000001 과 99.999999 값을 지원하며 , 0 또는 100을 지원하지 않는다.
  • 주의사항은 로우 수가 작은 테이블에서 견본 데이터를 액세스하면 일정 비율의 데이터가 리턴되지 않을 수도있다.
  • 이 기능은 과거에는 하나의 테이블에서 쿼리를 할 때만 사용가능 하였으나 10g 부터는 이런 제한이 없어졌다. 또한 비용기준 옵티마이져를 사용하게 되며 'RULE' 힌트를 사용하더라도 비용기준으로 수행된다.
"구루비 데이터베이스 스터디모임" 에서 2011년에 "새로쓴 대용량 데이터베이스 솔루션1" 도서를 스터디하면서 정리한 내용 입니다.

- 강좌 URL : http://www.gurubee.net/lecture/4447

- 구루비 강좌는 개인의 학습용으로만 사용 할 수 있으며, 다른 웹 페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^

- 구루비 강좌는 서비스 제공을 위한 목적이나, 학원 홍보, 수익을 얻기 위한 용도로 사용 할 수 없습니다.

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입