첨부파일의 일부 인덱스명에 오타가 있으니 댓글을 참조하기 바람(2009.11.05)

SQL
튜닝을 할 때 가장 기본적으로 익혀야 하는 것을 꼽으라면 무엇일까?

첫 번째, Data Access Pattern

두 번째, Join Method

위의 두 가지는 기본 중에 기본이다. 이 두 가지는 너무도 중요하기 때문에 튜닝을 시작하는 사람 혹은 튜너를 지망하는 사람들은 보고 또 보아야 한다. 실제로 이두가지를 모두 정복한다면 SQL 튜닝중의 많은 부분을 커버할수 있다. 이것은 비단 오라클 SQL 튜닝만이 아니라 모든 DBMS가 동일 할 것이다.

오늘은 두가지 중에 첫 번째의 Data Access Pattern의 개념에 대해서 이야기 해보자.

 

Data Access Pattern을 최대한 간단하게 표현하면 아래와 같다.

 

Index Scan       : 인덱스 사용

Full Table Scan  : 인덱스 미사용

Rowid            : Rowid로 테이블 엑세스

 

하지만 위의 3가지는 너무나 추상적이다.
위의 3가지를 좀더 상세히 나타내 보자.


Rowid

Index Unique Scan    

Index Range Scan

Index Inlist Iterator

Index Skip Scan 

Index Full Scan

Index Fast Full Scan 

Bit Map Index Combination

Full Table Scan        

 

3가지가 9가지가 되었다. 이제 제법 실전의 Plan에서 볼 수 있는 모습이 나왔다고 생각할 것이다.

하지만 아직 많이 부족하다.
실전의 Plan에는 위의 모습 보다 더욱 상세한 것이 필요하기 때문이다.

 

Rowid

Index Unique Scan    

Index Range Scan

Index Range Scan Descending

Index Range Scan (min/max)    

Index Inlist Iterator

Index Inlist Iterator Descending

Index Skip Scan 

Index Skip Scan Descending

Index Full Scan

Index Full Scan Descending 

Index Full Scan (min/max)   

Index Fast Full Scan 

BIT MAP OR           

BIT MAP AND          

BIT MAP MINUS        

BIT MAP MERGE

Full Table Scan        

 

드디어 3가지가 18가지가 되었고 실전적인 모습이 되었다.

위의 18가지 중에 한가지라도 찜찜한것이 있으면 첨부파일을 다운받아서 개념을 익히길 바란다.
파일을 배포 할 경우  출처를 밝혀주기 바란다.
파일 3개를 모두 다운 받은후 압축을 풀면 된다.

사용자 삽입 이미지

사용자 삽입 이미지



PS :
여기에 나오지 않은것들이 몇가지 있다. Partition Access Pattern은 이문서에서 언급되지 않았다.
다음 기회에..
Index Join 은 Data Access Pattern에 속하는 것이 아니고 Join method 에 포함되어야 한다.

Posted by extremedb
,

일자 데이터 타입이란 YYYYMMDD 형(시/분/초 제외)을 이야기 하는 것이다. 이때 DATE 타입을 선택할 것인가 아니면 VRACHR2(8)을 선택할 것인가의 문제이다. 이것은 성능 문제이기도 하지만 물리 모델링, 개발효율성, 데이터 품질 등을 같이 생각 해야 한다. 물리모델링 시에 많은 모델러들이 일자 데이터 타입과 관련하여 이구동성으로 이야기 하는 것이 아래의 SQL 이다.

 

SELECT ...                     

  FROM ...                     

 WHERE 기준일자 = TO_DATE('20091021', 'YYYYMMDD') ;

 

위의 SQL 에서 일자 컬럼에 시//초가 포함되어 있다면 조회가 되지 않는다.

그렇다고 SQL 을 아래처럼 작성하는 것은 개발효율성이 떨어지고 성능에도 이롭지 못하다.

 

SELECT ...                     

  FROM ...                     

 WHERE 기준일자 BETWEEN TO_DATE('20091021','YYYYMMDD') AND TO_DATE('200910212359', 'YYYYMMDDHH24MISS') ;

 

아니 땐 굴뚝에 연기가 날까?

VARCHAR2(8)을 선호하는 사람들이 주로 이 문제를 제기한다. DATE 타입은 시//초가 들어감으로써 세가지 문제(데이터가 조회되지 않을 수 있고, 개발효율성과 성능이 떨어짐)가 발생함으로  VARCHAR2(8)을 사용해야 한다는 것이다.

 

하지만 과연 이 말이 사실일까? 모든 문제는 모델러가 시//초가 들어갈 수 있게 설계를 했기 때문이다. 왜 그런지 아래 스크립트를 보고 증명해보자

 

-- DATE 형과 VARCHAR2 형을 동시에 가진 테이블 생성                           

CREATE TABLE DT                                                              

( V_DT  VARCHAR2(8 BYTE),                                                    

  D_DT  DATE ) ;                                                                                                                                    

                                                                              

--일자 타입이 date 인 경우에 시//초가 입력될 경우 걸러내는 Constraint.        

--ex) //초를 포함하는 SYSDATE를 입력하면 에러를 발생시키기 위함.                        

--CHECK 절에 OR 가 있는 이유는 NULL 을 허용하기 위해서 이다.

ALTER TABLE DT                                                               

ADD CONSTRAINT D_DT_CHK                                                      

CHECK (D_DT = TRUNC(D_DT) OR D_DT IS NULL) ;        

            

이제 DATE 타입에 정상적인 데이터와 걸러져야 하는 데이터를 INSERT 해보자                            

 

--NULL을 대입해도 에러 발생하지 않음                                                                              

INSERT INTO DT (V_DT, D_DT) VALUES(NULL, NULL) ;                                                  

 

--SYSDATE는 시//초가 들어감으로 INSERT 되면 안됨                                                                              

INSERT INTO DT (V_DT, D_DT) VALUES (NULL, SYSDATE) ;                          

ORA-02290: 체크 제약조건(D_DT_CHK)이 위배되었습니다                   

                   

--에러 발생하지 않음                                                                             

INSERT INTO DT (V_DT, D_DT) VALUES (NULL, TRUNC(SYSDATE)) ;            

 

Constraint는 데이터 품질을 보장해준다

위에서 보는 바와 같이 Constraint는 시//초가 들어가지 않도록 보장해준다.

이것은 성능이 느린 VARCHAR2 타입을 사용하지 말아야 하는 이유가 될 수 있다.

 

어쩔 수 없이 VARCHAR2(8)을 사용하더라도 Constraint를 사용하라

일자 데이터 타입에 VARCHAR2(8)을 사용할 때 날짜가 아닌 데이터가 들어가는 문제도 마찬가지로 해결할 수 있다. 아래처럼 Constraint를 사용하면 된다.

 

--일자 타입이 VARCHAR2 인 경우에 잘못된 데이터를 걸러내는 Constraint .        

--ex) ‘20090230’을 걸러낸다.

--CHECK 절에 OR 가 있는 이유는 NULL 을 허용하기 위해서 이다.                                                  

ALTER TABLE DT                                                               

ADD CONSTRAINT V_DT_CHK                                                      

CHECK (V_DT = TO_CHAR(TO_DATE(V_DT,'YYYYMMDD'), 'YYYYMMDD') OR V_DT IS NULL) ;

 

이제 VARCHAR2(8)에 일자가 아닌 데이터를 넣어보자

 

--230일은 일자가 아니므로 INSERT 되면 안됨                                                                             

INSERT INTO DT (V_DT, D_DT) VALUES ('20090230', NULL) ;                       

ORA-01839: 지정된 월에 대한 날짜가 부적합합니다

 

--에러 발생하지 않음                                                                             

INSERT INTO DT (V_DT, D_DT) VALUES ('20090228', NULL) ;  

 

성공적으로 일자가 아닌 String 을 걸러 내었다.

 

결론

이제 알 것이다.  Constraint가 데이터 품질을 향상시키고 개발효율성을 높이며 성능에 까지 영향을 끼친다는 사실을

반박하라. //초가 들어감으로 VARCHAR2(8)을 사용해야 한다는 주장을

사용하라. 성능과 데이터 품질을 향상 시키는 DATE 타입을

오라클에서 시/분/초는 제외하고 일자만 저장되는 데이터 타입을 제공한다면 하는 아쉬움이 진하게 남는다.  미래의 버젼에 이러한 요구사항을 해결한 데이터 타입이 나오길 기대한다.


PS

DATE 타입을 성급히 적용하면 안 된다. 기존의 시스템은 일자가 아닌 데이터도 문제 없이 처리가 되었을 것이다. 하지만 DATE 타입으로 바꾸면 에러로 떨어진다. 따라서 에러의 처리정책과 처리Logic 등이 세워진 이후에 적용하라. 이런 골치 아픈 문제 때문에 VARCHAR2(8)을 사용하는 것은 말이 안 된다. 원칙적으로 에러로 떨어지는 것이 정당한 것이고 데이터를 수정하고 다시 처리하는 것이 옳다.

  

아래는 DATE 타입과 VARCHAR2 타입의 장단점 이므로 참고하기 바란다.

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



성능상으로는 DATE 형이 우월하다

여기에는 두 가지 이유가 있다.

 

첫 번째, DATE 타입은 옵티마이져가 날짜임을 인식한다.

 

SELECT ...                     

  FROM ...

 WHERE 기준일자 BETWEEN to_date('20091020', 'yyyymmdd') and to_date('20091021', 'yyyymmdd)

 

기준일자는 DATE 타입이다. 위의 조건대로라면 옵티마이져는 정확히 이틀간(10 20일부터 21일까지)이라는 것을 인지한다. 따라서 옵티마이져는 인덱스로 SCAN 할 것인지 아니면 FULL TABLE SCAN 할 것인지 판단할 수 있다.

 

하지만 기준일자가 아래처럼 VARCHAR2(8) 타입이라면 달라진다.

 

SELECT ...                      

  FROM ...

 WHERE 기준일자 BETWEEN '20091020' and '20091021'

 

옵티마이져는 '20091020'가 일자인지 인식 하지 못한다. 따라서 이틀 치의 데이터를 조회한다는 사실을 인식하지 못한다. String 타입이므로 이것은 당연한 것이다.

 

두 번째, DATE 타입은 7 byte를 차지하고 VARCHAR2(8) 8 byte를 차지한다.

 

두 가지 이유에 의해서 성능은 DATE 타입이 조금이라도 우월함을 알 수 있다.  

 

그럼에도 불구하고 DATE 타입을 사용하지 않는 가장 큰 이유는 무엇일까? 크게 3가지 이유로 요약된다.

 

첫 번째, DATE 타입의 문제점은 sysdate 등을 입력할 경우 시//초 가 들어감으로 SQL을 실행하면 결과값이 나오지 않는다.(이 문제는 위에서 이미 언급되었음)

 

두 번째, 년도나 월 데이터를 조회할 때 LIKE를 사용하지 못하고 BETWEEN 을 사용해야 한다는 것이다. 이것은 큰 문제라고 보지 않는다. 조건절이 조금 길어질 뿐 INDEX RANGE SCAN 이라는 점은 같기 때문이다.

 

세 번째, SYSDATE 등을 INSERT할 때 TRUNCT 등의 함수를 사용하여 시//초 등을 잘라내야 한다.

                

그렇다면 VARCHAR2 타입의 문제점은 없는가?
크게 3가지의 문제점이 있다.

 

첫 번째, 성능문제(옵티마이져가 일자인지 알 수 없음)

이 문제는 ORACLE 11g를 사용하면 더욱 심각한 차이가 발생할 수 있다.

왜냐하면 Bind Aware 기능이 강화되었기 때문에 변수를 마치 상수처럼 취급할 것이고 이에 따라 DATE 타입이 성능 면에서 훨씬 우월해 질것이다. 

                 

두 번째, VARCHAR2 타입의 문제점은 날짜가 아닌 데이터가 들어갈 수 있다는 것이다. 예를 들면 'ABCDEFGH' 혹은 '20090231' 등의 잘못된 데이터가 입력될 수 있다. 이것은 데이터 품질에 치명적이다. 혹자는 'DATE 타입도 시//초가 들어가므로 마찬가지 아니냐' 라고 생각할 수 있지만 근본적으로 다르다. DATE 타입을 사용하면 시//초는 TRUNC 등의 함수를 사용하여 Cleansing할 수 있지만 VARCHAR2는 그렇게 할 수 없다.

 

예를 들어 '20091032' 라는 데이터를 Cleansing 해야 한다고 치면 10 31일로 할 것인가? 아니면 11 1일로 할 것인가? 이것은 함부로 판단할 수 없는 것이다. 혹자는 'CHECK 기능을 추가하여 모든 프로그램에서 일자가 아닌 것을 CHECK 하면 되지 않냐?' 라고 할 수 있다.

 

맞는 말이다. 하지만 프로그램을 사용하지 않고 직접 DB KEY IN 하여 INSERT 할 수도 있기 때문에 원천적으로 원인을 제거한다고 볼 수 없다.(급한 경우에는 이렇게 하기도 한다)

 

세 번째, 성능문제에도 불구하고 개발편의성을 위해 VARCHAR2를 사용하였지만 일자연산이 발생하면 오히려 개발생산성이 저하된다.

 

예를 들면 일자끼리 빼서 차이를 본다든지 아니면 일자에 며칠을 더해서 본다든지 일자에 연산이 일어날 경우 오히려 TO_DATE 등의 함수를 사용해야 한다. 이런 경우는 자주 발생되는 편이다.

 

이상으로 DATE 타입과 VARCHAR2 타입의 장단점을 살펴보았다.

이 글과 관련된 POST 도 참고하기 바란다.

http://ukja.tistory.com/265

Posted by extremedb
,

“SQL 작성시 같은 테이블을 반복해서 사용하지 마라

위와 같은 말을 많이 들어보았을 것이다. 이런 말들은 개발자에게 마치 격언, 명언처럼 취급된다. 오늘도 개발자, DBA, 컨설턴트등 모든 사람들이 위의 명언에 너무도 충실하여 반복되는 테이블을 제거하기 위해 SQL을 재작성 하고 있다. 하지만 이런 말들이 명언이나 격언이 될 수 있을까? 이제는 격언이나 훈수, 명언이라고 생각하는 말도 최소한 상황에 따라 가려서 해야 한다. 왜 그럴까? 아래의 SQL을 보자. 

 

SELECT /*+ use_hash(c s)  */

       s.prod_id, s.cust_id, s.quantity_sold,

       s.amount_sold, c.channel_desc

  FROM sales s, channels c

 WHERE c.channel_id = s.channel_id

   AND c.channel_id = 3

UNION ALL

SELECT /*+ use_hash(c s) */

       s.prod_id, s.cust_id, s.quantity_sold,

       s.amount_sold, c.channel_desc

  FROM sales s, channels c

 WHERE c.channel_id = s.channel_id

   AND c.channel_id = 9 ;

 

위의 SQL은 대용량 테이블인 판매 테이블(sales)을 비효율적으로 2 Scan할 것으로 예상된다. 하지만 아래의 Plan을 보라. 과연 그렇게 수행되는가?

-----------------------------------------------------------+-----------------------
| Id | Operation                       | Name              | Rows  | Bytes | Cost |
-----------------------------------------------------------+-----------------------
| 0  | SELECT STATEMENT                |                   |       |       |   495|
| 1  |  HASH JOIN                      |                   |  449K |   20M |   495|
| 2  |   VIEW                          | VW_JF_SET$0A277F6D|     2 |    50 |     2|
| 3  |    UNION-ALL                    |                   |       |       |      |
| 4  |     TABLE ACCESS BY INDEX ROWID | CHANNELS          |     1 |    13 |     1|
| 5  |      INDEX UNIQUE SCAN          | CHANNELS_PK       |     1 |       |     0|
| 6  |     TABLE ACCESS BY INDEX ROWID | CHANNELS          |     1 |    13 |     1|
| 7  |      INDEX UNIQUE SCAN          | CHANNELS_PK       |     1 |       |     0|
| 8  |   PARTITION RANGE ALL           |                   |  897K |   18M |   489|
| 9  |    TABLE ACCESS FULL            | SALES             |  897K |   18M |   489|
-----------------------------------------------------------+-----------------------

Predicate Information:
----------------------
1 - access("ITEM_1"="S"."CHANNEL_ID")
5 - access("C"."CHANNEL_ID"=3)
7 - access("C"."CHANNEL_ID"=9)

 

이제는 Transformer 가 튜너이다.

환상적이지 않은가? channels 테이블을 정확히 2건만 Scan 하였고 sales 테이블은 1번만 Full Scan 하였다. 오라클 Transformer SQL을 아래처럼 재작성 한 것이다.

SELECT s.prod_id prod_id, s.cust_id cust_id, s.quantity_sold,

       s.amount_sold, vw_jf_set$0a277f6d.item_2 channel_desc

  FROM (SELECT c.channel_id AS item_1, c.channel_desc AS item_2

          FROM channels c

         WHERE c.channel_id = 3

        UNION ALL

        SELECT c.channel_id AS item_1, c.channel_desc AS item_2

          FROM channels c

         WHERE c.channel_id = 9) vw_jf_set$0a277f6d,

       sales s

 WHERE vw_jf_set$0a277f6d.item_1 = s.channel_id ;

 

위와 같은 상황에서 SQL을 재작성 하는 기능을 JF(Join Factorization)라고 부른다. VW_JF_SET으로 시작되는 인라인뷰 명(Plan 상의 파란색 부분) JF가 수행되었음을 나타내는 것이다. 이것은 Oracle11g R2 에서 새로 추가된 대표적인 CBQT(Cost Based Query Transformation)기능 이다.

 

항상 수행되지는 않는다

CBQT Cost SQL튜닝을 수행할 것인지 아닌지를 판단한다. 그런데 복잡한 SQL의 경우에는 Cost Estimator가 판단을 잘못하여 JF를 수행하는 것이 비용이 더 비싸다고 판단할 수 있다. 이런 경우에는 Transformer SQL을 튜닝(재작성) 하지 않는다. 이럴 때는 아래와 같이 힌트를 사용해야 한다.

SELECT /*+ use_hash(c s) FACTORIZE_JOIN(@SET$1(S@SEL$1 S@SEL$2)) */

       s.prod_id, s.cust_id, s.quantity_sold,

       s.amount_sold, c.channel_desc

  FROM sales s, channels c

 WHERE c.channel_id = s.channel_id

   AND c.channel_id = 3

UNION ALL

SELECT /*+ use_hash(c s) */

       s.prod_id, s.cust_id, s.quantity_sold,

       s.amount_sold, c.channel_desc

  FROM sales s, channels c

 WHERE c.channel_id = s.channel_id

   AND c.channel_id = 9 ;

 

쿼리블럭명 SET$1은 전체에 해당하는 쿼리블럭이고 SET$1 의 내부에 또 다른 쿼리블럭인 SEL$1(Union All로 분리된 것 중의 윗부분), SEL$2(Union All로 분리된 것 중의 아랫부분) 이 존재한다. 만약 Union All 이 하나 더 있다면 쿼리블럭명은 SEL$3가 될 것이다.

 

JF를 자세히 분석 하려면 10053 Event Trace를 이용하라

먼저 10053 의 용어 설명부분에 JF 가 아래처럼 추가 되었다.

The following abbreviations are used by optimizer trace.

CBQT - cost-based query transformation

JPPD - join predicate push-down

중간생략

JF - join factorization


아래는 JF 부분에 해당하는 10053 Trace 정보이다.

***********************************

Cost-Based Join Factorization     

***********************************

Join-Factorization on query block SET$1 (#1)

JF: Using search type: exhaustive

JF: Generate basic transformation units

이후생략

 

결론

이제 격언이나 명언이라고 생각되는 말들도 상황을 가려서 해야 한다는 것을 알겠는가?. 그렇지 않으면 똑똑한 개발자에게 오히려 다음과 같은 말을 들을 것이다. “지금 말씀 하신 것은 예전 이야기 입니다. 요즘은 트랜스포머가 알아서 해줍니다.”  적어도 튜닝의 세계에서는 그렇다.

 

새로운 패러다임

JFTransformer가 수행하는 SQL튜닝의 하나일 뿐이다. Oracle11g R2 기준으로 SQL 튜닝(Query Transformation)의 종류는 필자의 짧은 지식으로도 70개 이상일 것으로 판단 된다. Oracle이 발전해 가면서 SQL튜닝은 사람이 관여하는 것에서 오라클이 자동으로 SQL을 변경해주는 것으로 많은 부분이 바뀌었고 앞으로 이런 추세는 점점 강화될 것이다. Query Transformation 은 단순한 Optimizer의 기능이 아니라 SQL 튜닝의 새로운 패러다임인 것이다. 이제는 직접 튜닝 하는 것에서 벗어나 Transformer가 실수하는 경우 새로운 길을 열어주는 것이 튜너가 가야할 길이 아닌가?


Posted by extremedb
,
오라클의 Regular Expressions을 정의 하면 아래와 같다
  • 유닉스의 정규식과 같음.
  • 강력한 Text 분석도구로서 Like 의 한계를 극복함.
  • Pattern-Matching-Rule의 정의가 자유로움.
  • 다양한 메타문자 제공.
Regular Expressions의 중요성은 아래와 같은 작업시 매우 증대된다
When
  • ETL/전환/이행.
  • Data Mining.
  • Data Cleansing.
  • 데이터 검증.
Regular Expressions의 기능은 다음과 같다
What
  • Text에서 특정 중복 단어의 확인
  • 특별한 상태에서 공백의 제거
  • 특정 문자의 파싱(parsing)
  • Text 에서 전화 번호, 우편 번호, 이메일 주소, 주민등록번호, IP 주소, 파일 이름, 경로 이름 등을 검증 및 추출이 가능
  • HTML 태그, 숫자, 날짜, 기타 특정 텍스트 데이터와 일치하는 패턴을 확인하고 다른 패턴으로 대체하는 것이 가능
  • Constraints 로 사용가능
이렇게 중요한 정규식(Regular Expressions)을 잘 모르거나 익숙치 못한 사람들을 위하여 개인적으로 정리한 파일을 올리니 이번기회에 반드시 정복하길 바란다.  의외로 고급 개발자들 중에 Regular Expressions의 사용을 꺼려하는 사람들이 많이 있는것 같다. 이 기능을 써야하는 경우임에도 쓰지 않는다면 코드는 길어질 것이며 유지보수 또한 힘들어 질것이다.
 
첨부파일에 정리된 내용은 다음과 같다 
  • 정규식 기본 Syntax.
  • 함수 사용법.
  • 정규식 고급 Syntax.
  • 11g New Features

주의 사항 :

  • 첨부된 파일에는 Oracle11g R1 까지의 내용을 정리한 것임.
  • 배포시에는 반드시 출처를 밝힐것.
  • Upload 시 200K 용량제한 때문에 파일을 2개로 분할 압축 하였으므로 모두 다운 받은후 압축을 풀면 됨.

invalid-file

오라클 Regular Expresssions 완전정복1





invalid-file

오라클 Regular Expresssions 완전정복2




 
사용자 삽입 이미지



Posted by extremedb
,