인덱스 정보

INDEX NAME       COLUMN_LIST     Type
---------------  --------------- ----------
IDX_ADDRGRP_01   UKEY            Unique
IDX_ADDRGRP_02   USERID, GNAME   Normal
IDX_ADDRGRP_03   USERID, UKEY    Normal

성능 문제 SQL - DB Version: 9.2.0.4

SQL> SELECT a.gname,
           ,COUNT(a.ukey) AS cnt
     FROM  imsi.addrgrp   a
     WHERE a.userid       = :B1
     GROUP BY a.gname ;

SELECT STATEMENT CHOOSE-Cost : N/A
 SORT GROUP BY
  TABLE ACCESS BY INDEX ROWID IMSI.ADDRGRP(l)
   INDEX RANGE SCAN IMSI.IDX_ADDRGRP_02(NU) (USERID, GNAME)

문제점
  • 특정 userid가 가진 데이터를 가지고 gname별로 ukey값의 데이터 Row수를 가져 오는 쿼리이다.
    WHERE절에 userid와 SELECT-LIST에 gname 컬럼은 인덱스를 경유하여 데이터를 가지고 왔는데 COUNT하는 과정에서
    ukey값 데이터를 가지고 오기 위해서 Table Access가 발생
  • 만일 WHERE절을 통해 나온 결과 값이 1000만건일 경우 Table Access를 하기 위해 많은 Block IO가 발생
개선 후 SQL

SQL> SELECT a.gname,
           --,COUNT(a.ukey) AS cnt
           COUNT(*) AS cnt   <-- 변경
     FROM  imsi.addrgrp   a
     WHERE a.userid       = :B1
     GROUP BY a.gname ;

SELECT STATEMENT CHOOSE-Cost : N/A
 SORT GROUP BY
  INDEX RANGE SCAN IMSI.IDX_ADDRGRP_02(NU) (USERID, GNAME)

해설
  • COUNT함수는 NULL 값은 Counting되지 않고 NOT NULL 값만 Counting되는데, 현재 ukey값을 확인 한 결과 NOT NULL 제약조건이
    걸려있기 때문에 특정 컬럼 값이 아닌 WHERE, GROUP BY를 거쳐서 나온 결과 집합을 가지고 COUNT(*), COUNT(1)와 같은 형태로
    변경해 줌으로써, Table Access를 통한 성능 저하를 해소 할 수 있다.

COUNT함수가 NULL데이터 처리 방법

Create Table & Data 생성

SQL> CREATE TABLE NULL_T4
      (login_id NUMBER NOT NULL
      ,userid   VARCHAR2(10) NOT NULL
      ,login_date VARCHAR2(8));

-- Data Insert
INSERT INTO NULL_T4 VALUES ( 1, 'userl', '20100101') ;
INSERT INTO NULL_T4 VALUES ( 2, 'admin', NULL) ;
INSERT INTO NULL_T4 VALUES ( 3, 'user2', '20100301') ;
INSERT INTO NULL_T4 VALUES ( 6, 'user1', '20100601') ;
INSERT INTO NULL_T4 VALUES ( 7, 'admin', NULL) ;
INSERT INTO NULL_T4 VALUES ( 8, 'user3', '20100801') ;
INSERT INTO NULL_T4 VALUES (101, 'user1', '20100102') ;
INSERT INTO NULL_T4 VALUES (102, 'admin', NULL) ;
INSERT INTO NULL_T4 VALUES (103, 'user2', '20100302') ;
INSERT INTO NULL_T4 VALUES (106, 'userl', '20100602') ;
INSERT INTO NULL_T4 VALUES (107, 'admin', NULL) ;
INSERT INTO NULL_T4 VALUES (108, 'user3', '20100802') ;
INSERT INTO NULL_T4 VALUES (201, 'user1', '20100602') ;
INSERT INTO NULL_T4 VALUES (202, 'admin', NULL) ;
INSERT INTO NULL_T4 VALUES (203, 'user2', '20100802') ;

-- Key Commit
COMMIT;

-- 인덱스 생성
SQL> CREATE INDEX IDX_null_t4_01 ON NULL_T4(userid) ;

1). NOT NULL & NULL 모든 데이터 COUNT

Case1)
SQL> SELECT count(*) cnt
     FROM   null_t4
     WHERE  userid = 'admin';

       CNT                   
----------                   
         5

Case2)
SQL> SELECT count(1) cnt
     FROM   null_t4
     WHERE  userid = 'admin';

       CNT                   
----------                   
         5


2). COUNT()에 NOT NULL 컬럼으로 COUNT

SQL> SELECT count(login_id) cnt
     FROM   null_t4
     WHERE  userid = 'admin';

       CNT                   
----------                   
         5

3). COUNT()에 Nullable 컬럼으로 COUNT

SQL> SELECT count(login_date) cnt
     FROM   null_t4
     WHERE  userid = 'admin';

       CNT                   
----------                   
         0

COUNT 함수 선언 시, Table Access 여부의 기준
  • Oracle 9i
    : Table Access 여부의 기준은 Counting되는 대상의 컬럼이 인덱스 컬럼, 일반 컬럼 차이
  • Oracle 10g
    : Table Access 여부의 기준은 Counting되는 대상의 컬럼의 Type이 Nullable, NOT Nullable 차이
1). NOT NULL 컬럼을 COUNT - Oracle Version: 10.2.0.5

SQL> SELECT count(login_id) cnt
     FROM   null_t4
     WHERE  userid = 'admin';

------------------------------------------------------------------------------------
| Id  | Operation         | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                |     1 |     7 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |                |     1 |     7 |            |          |
|*  2 |   INDEX RANGE SCAN| IDX_NULL_T4_01 |     5 |    35 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------
                                                                                    
Predicate Information (identified by operation id):                                 
---------------------------------------------------                                 
                                                                                    
   2 - access("USERID"='admin')                                                     

2). Nullable 컬럼을 COUNT

SQL> SELECT count(login_date) cnt
     FROM   null_t4
     WHERE  userid = 'admin';

-----------------------------------------------------------------------------------------------
| Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                |     1 |    13 |     2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE              |                |     1 |    13 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| NULL_T4        |     5 |    65 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_NULL_T4_01 |     5 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
                                                                                               
Predicate Information (identified by operation id):                                            
---------------------------------------------------                                            
                                                                                               
   3 - access("USERID"='admin')                                                                

정리
  • Oracle 9i까지는 COUNT함수를 선언했을 경우 Counting 되는 필드가 인덱스 유무 여부에 따라 Table Access 결정
  • Oracle 10g부터는 COUNT함수를 선언했을 경우 Counting 되는 필드가 NOT NULL 제약 조건의 여부에 따라 Table Access 결정
  • COUNT함수는 NULL값을 Counting 하지 않는다.