반응형
출처: http://www.devpia.com/DevStudy/Lecture/OffLineDetail.aspx?nSemiID=1429&lectype=evt

데브피아에 DB 튜닝관련 컬럼 연재가 올라왔네요. 내용이 괜찮아서 퍼왔습니다.

  필자가 처음에 SQL을 배울 때 SQL이 상당히 이상했다. 원하는 것만 요구할 뿐 어떻게 가져오라는 정보가 SQL에는 없었기 때문이다. FILE레벨의 I/O까지 코딩에 익숙한 필자에게 절차가 없다는 것이 오희려 더 이상했던 것이다.
물론 상세한 과정이 필요하지 않으므로 편리하고 좋았다 그러나 어떻게 가져오는지는 알지못하고 단지 사용할 뿐이었다.
그러나 SQL이 PLAN이라는 실행 계획을 만들고 그에 따라 가져오게 된다는 사실은 안것은 한참 뒤에 일이었다.
결국은 내가 하지않은 일을 Optimizer라는 프로그램이 대신 해주고 있는 것이 아닌가? 그래서 정말 고마운 놈이라고 생각했었다. 그러나 밑는 도끼에 발등을 찍힌다는 말이 있지 않은가?
Plan에 index를 달아주어도 Index를 사용하지 않고 full table scan만 하고 있으니 당체 속도가 나지를 않았다.
이래저래 해서 나중에 알게되었지만 결국 컬럼의 변형을 가하면 index를 사용하지 못한다는 것이다. 우리가 직접 사용하지는 않지만 결국 우리가 SQL을 사용한다는 것은 Optimizer라는 놈에게 SQL의 수행을 부탁하는 것이다. 따라서 우리가 Optimizer에 대해서 잘 안다면 SQL을 좀더 효율적으로 수행하도록 할 수 있지 않은가!
그러면 인덱스를 달았을 때 Optimizer가 index를 사용하지 못하는 경우를 통해서 우리가 애써(?)생성한 인덱시가 무용지물이 되지 않도록 해보자.
아래예제에 사용할 TABLE LAYOUT이다.
EMPLOYEES
---------
Rows=15,132
Empty Blocks=7
Chain Count=0
Avg Space Freelist Blocks=0
Sample Size=15,132
Partitioned=NO

Blocks=121
Avg Space=885
Avg Row Length=51
Freelist Blocks=0
Last Analyze=2009/05/04
Column Name
---------------
EMP_ID
MGR_ID
LAST_NAME
FIRST_NAME
HIREDATE
JOB
SALARY

Nullable
-----------------


NOT NULL
Column Type
-----------------
VARCHAR2(40)
VARCHAR2(40)
VARCHAR2(24)
VARCHAR2(14)
DATE
VARCHAR2(24)
NUMBER(7,2)
Distinct
-----------------
15,132
679
9,443
3,579
3,903
53
3,267
Buckets
------------------
75
75
75
75
75
53
75
INDEX
--------------------------------------------------------------------------------------
IDX_GENDER : GENDER
      Type=NORMAL, Uniq=No, Distinct=2, Rows=15,132, Last Analyze=2009/05/04
IDX_HIREDAT : HIREDATE
      Type=NORMAL, Uniq=No, Distinct=3,903, Rows=15,132, Last Analyze=2009/05/04
IDX_JOB : JOB
      Type=NORMAL, Uniq=No, Distinct=53, Rows=15,129, Last Analyze=2009/05/04
IDX_SALARY : SALARY
      Type=NORMAL, Uniq=No, Distinct=3,267, Rows=15,132, Last Analyze=2009/05/04
IDX_TYPE2 : TYPE
      Type=NORMAL, Uniq=No, Distinct=6, Rows=15,132, Last Analyze=2009/05/04
PK_EMP_ID : EMP_ID
      Type=NORMAL, Uniq=No, Distinct=15,132, Rows=15,132, Last Analyze=2009/05/04
필자가 여러군데 튜닝을 하면서 가장 많이 본것중에 하나는 INDEX를 달았으나 쓰지 못하게 되는 경우이다. 대표적인 경우가 아래와 같이 날짜타입(HIREDATE)에 TO_CHAR를 씌운 경우이다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE TO_CHAR(HIREDATE,'YYYYMMDD') = '19980518';
물론 INDEX는 아래와 같이 생성되어있다.
CREATE INDEX IDX_HIREDATE ON EMPLOYEES(HIREDATE);
우리가 원하는 것은 INDEX를 타고 테이블을 가져오기를 바란것이었다.

그러나 실제 PLAN은 아래와 같이 나온다.
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=28 Card=151 Bytes=3K)
1 0 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=28 Card=151 Bytes=3K)
TABLE ACCESS (FULL) 이란 뜻은 INDEX를 타지 않고 테이블을 처음부터 끝까지 읽어서 찾는다는 뜻이다. 한마디로 10건이며 10건읽고 100만건이면 100만건을 다 읽어야 결과가 나온다는 말이다.

OPEN시에는 빠르던 시스템이 시간이 지날수록 느려지는 결정적인 역할을 하는 것이 바로 위와 같은 경우이다. 그럼 어떻게 해야 제대로 인덱스를 사용할 수 있을가?
일단 간단히 SQL의 수정으로 해결할수 있다. HIREDATE는 날짜 타입이다.
따라서 인덱스를 HIREDATE로 했을 때 인덱스를 타기위해서는 INDEX를 생성한것에 변형을 주어서는 안된다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE HIREDATE = TO_DATE('19980518')
따라서 간단하게 위와 같이 고치면 INDEX를 사용하게된다.
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=4 Bytes=92)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (TABLE) (Cost=3 Card=4 Bytes=92)
2 1 INDEX (RANGE SCAN) OF 'IDX_HIREDATE' (INDEX) (Cost=1 Card=4)
물론 결과도 빠르게 나온다 그러나 중요한 점이 있다 결과가 같을까?
운이 좋으면 결과가 같을 것이고 대부분의 경우는 결과가 틀리다.
왜 그럴까?
날짜 타입은 날짜와 시분초의 정보도 가지고 있다. 따라서 TO_DATE(‘19980518’)라는 말은 정확히 1998년5월18일 0시0분0초라는 뜻이다. 그래서 우리가 원하는 1998년5월18일자와는 차이가 있다.
따라서 1998년5월18일 0시0분1초 ~ 23시59분59초까지의 데이터는 나오지 않게되는것이다.
이것은 튜닝할 때 유의할 점이다. 결과를 같게 유지해야하는것이다. 이 상황을 알고있다면 방법은 간단하다.
아래아 같이 고치면 빠른시간에 원하는 결과를 얻을 수 있을 것이다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE HIREDATE BETWEEN TO_DATE('19980518'||'00:00:00','YYYYMMDD HH24:MI:SS')
AND TO_DATE('19980518'||'23:59:59','YYYYMMDD HH24:MI:SS')
비슷하지만 함수의한 변형이 아닌 간단한 연산에의한 변형의 경우도 마찬가지이다.
$1000의 인센티브를 더주면 $10000이 넘는 사람을 찾는 SQL을 만들어보자.
아마 아래와 같을 것이다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE SALARY + 1000 > 100000;
물론 INDEX는 아래와 같이 만들었다.
CREATE INDEX IDX_SALARY ON EMPLOYEES(SALARY);
그러나 PLAN을 보자
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=29 Card=757 Bytes=13K)
1 0 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=29 Card=757 Bytes=13K)
인데스를 타지 못한다. 왜일까. 간단한 연산이지만 SALARY컬럼에 가공을 했기 때문에 OPTIMIZER는 인덱스를 타는 것을 포기해버린다.
따라서 우리가 기초적인 수학 실력을 발휘해서 이항을 해준다면 아래와 같은 조건이 될것이다.
SELECT FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE SALARY > 100000 - 1000;
이경우에 PLAN을 보자.
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=1 Bytes=17)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (TABLE) (Cost=3 Card=1 Bytes=17)
2 1 INDEX (RANGE SCAN) OF 'IDX_SALARY' (INDEX) (Cost=2 Card=1)
재미 있게도 이번에 제대로 된 인덱스를 탄다. Optimizer가 바보 같다는 생각이 들지 않는가?
물론 바보같다. 그러나 OPTIMIZER나름대로 깊은 고민이 있다. 아주 잛은 시간내에 OPTIMIZER는 많은 경우의 수를 타진해야한다. 따라서 이항연산과 같은 것 까지 검토하면 너무 많은 시간을 소모하게 된다 따라서 그런부분은 포기한것이다.

또다른 경우중에 하나가 DB의 내부적인 변형이다. 이는 개발자가 의도하지 않게 문제를 야기하는 경우이다.
여기 PK 조건으로 검색하는 SQL이 있다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE EMP_ID = 200383;
그러나 PLAN은 아래와 같이 나왔다.
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=29 Card=1 Bytes=19)
1 0 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=29 Card=1 Bytes=19)
분명히 아래와 같은 INDEX를 생성하였다.
CREATE INDEX PK_EMP_ID ON EMPLOYEES(EMP_ID);
왜 인덱스를 안타는 것일까?
그 이유은 OPTIMIZER의 내부 변형 규칙에 있다.
일반적으로 비교를 하려면 두개의 데이터 형이 같아야 한다.
그런데 EMP_ID는 VARCHAR2(40)이다 그리고 비교하려는 것은 200383이라는 숫자이다.
따라서 숫자와 문자는 비교할수 없기 때문에 내부적으로 변형이 이루어진다.
문자보다 숫자가 우선순위가 높아서 문자와 숫자를 비교하게되면 문자쪽이 숫자로 변형되어 비교하게 되는 것이다.
따라서 위의 SQL은 OPTIMIZER는 아래와 같은 SQL로 수행하게된다.
EMP_ID를 TO_NUMBER(EMP_ID) = 2000393과 같이 처리하게 된다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE TO_NUMBER(EMP_ID) = 200383;
이는 처음 예제에서 날짜 컬럼에 TO_CHAR를 씌원것과 같은 효과이다. 따라서 이문제를 해결하기위해서는 반대쪽, 즉 2000293을 문자로 변환해주면 문자대 문자의 비교이므로 내부적 변형이 발생하지 않게된다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE EMP_ID = ‘200383’;
 
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=1 Bytes=19)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMPLOYEES' (TABLE) (Cost=2 Card=1 Bytes=19)
2 1 INDEX (RANGE SCAN) OF 'PK_EMP_ID' (INDEX) (Cost=1 Card=1)
아래 SQL을 보자 JOB에 NULL인 조건을 검색하는 것이다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE JOB IS NULL
아래 SQL을 보자 JOB이 NULL인 조건을 검색하는 것이다.
물론 아래와 같은 JOB INDEX를 생성하였다.
CREATE INDEX IDX_JOB ON EMPLOYEES (JOB);
아래 PLAN을 보자 왜 IDX_JOB INDEX를 타지 못하는가?
Execution Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=29 Card=3 Bytes=63)
1 0 TABLE ACCESS (FULL) OF 'EMPLOYEES' (TABLE) (Cost=29 Card=3 Bytes=63)
이경우에는 Oracle의 경우 일반적으로 index를 생성할 때 null값은 index항목에 넣지 않는다. 따라서 null은 index에 없기 때문에 null조건을 준다면 그것은 index를 탈수 없다.
따라서 위와 같은 경우 반드시 index를 타려거든 job컬럼을 NOT NULL로 설정하고 NUL대신 특정값 (예를 들면 : ‘NOT ASSIGN’ ) 으로 설정하고 QUERY를 아래와 같이 수정한다면 인덱스를 탈수 있을 것이다.
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE JOB = ‘NOT ASSIGN’;
아래 SQL를 하나 더 보자
SELECT LAST_NAME,FIRST_NAME
FROM EMPLOYEES
WHERE JOB NOT IN ( 'INSTRUCTOR','STAFF');
이번의 NULL을 비교한것도 아닌데 INDEX를 사용하지 못한다. 이것은 일반적인 INDEX가 =이나 <, > , BETWEEN조건에 만 인덱스를 탈수 있고 부정형으로 비교했을때는 인덱스를 탈수 없기때문이다.
생각해보자 어떤 것을 순서대로 정리해 놓았는데 그것이 아닌 것을 찾으려고 한다면 전체를 다 읽어봐야지만 아니것을 알수 있지 않은가?
따라서 가급적 프로그램 구성에서 부정형 조건이 들어가게 한다는 것은 성능을 저하시킬 가능성이 매우 높기 때문에 이런 조건이 되지 않도록 설계단설계부터 고려해야한다.

이상은 간단하게 INDEX를 주었을 때 일반적으로 INDEX를 타지 못하는 경우를 든것이다. 사실 위예 예처럼 실제 프로젝트에서 많은 부분이 INDEX를 생성하고도 OPTIMIZER의 특성을 몰라서 INDEX를 쓰지 못한채 APPLICATION이 돌고 있다. 이는 곧바로 자원의 과도 사용으로 나타나고 느린 응답시간으로 나타나게 된다. 항상 시스템을 OPEN하고 마음을 조리지 않으려면 내가 생성된 INDEX를 잘 탈수 있게 내가 SQL을 잘 작성했는지 검토해 보기 바란다.
아래 4개의 항목은 반드시 기억해 두기 바란다.
인덱스를 사용하지 못하는 경우는 아래와 같다.
  • 인덱스 컬럼에 변형이 일어난 경우
    • WHERE TO_CHAR(HIREDATE,'YYYYMMDD') = '19980518';
    • WHERE SALARY + 1000 > 100000;
  • 내부적인 변형이 일어난 경우
    • WHERE EMP_ID = 200383;
  • NULL을 비교하였을 경우
    • WHERE JOB IS NULL;
  • 부정형으로 조건을 기술한 경우
    • WHERE JOB NOT IN ( 'INSTRUCTOR','STAFF');

물론 이 경우 이외에 Optimizer의 판단에 따라서 인덱스를 사용하지 못하는 경우도 있다. 그러나 대부분의 경우에는 위에 항목을 만족한다면 원하는 index를 타는 효율적인 sql작성에 좋은 기준이 될것이다. 마지막으로 sql을 작성한후 반드시 plan을 확인해 보기 바란다.
실제 plan이 어떻게 되는냐를 확인해보지 않으면 무심코 과거의 실수를 답습할 수 있기때문이다.



반응형

리사 퀘스트 지음 ㅣ 신정길 옮김

호이테 북스 2009.02.10

여성들이여 스스로 커리어를 업그레이드하라!
성공적인 커리어를 쌓고 경쟁력을 강화시킬 수 있는 비즈니스 공식!

 

이번에 도서 이벤트에 당첨이 되었다고 해서 이메일이 왔습니다.

"오바마 이야기" 이후 두번째 도서 이벤트 당첨입니다. ㅎㅎ

아직 책은 받지 못했고 이벤트 당첨됬다는 메일만 받은 상태입니다. 

책 이름은 딱 보고 맘에 들었습니다. 그런데 이게 웬일... ㅎㅎ 여성분들을 위한 책인듯 합니다.

어찌 되었든 받은 뒤에 잘 읽어보고 아는 여성 지인들에게 넘기도록 하던지 해야겠습니다.

현재 저의 독서 스타일은 다독을 추구하는 상태이기 때문에 득템의 기쁨을 만끽중입니다.^^


서평 <2009.05.09>

책은 휴대성이 편하도록 보통 책보다 조금 작은 사이즈에 두께도 얇은 책이다.

출퇴근 시간 틈틈히 들고 다니며 읽어도 금방 읽을수 있다. 하지만 쉽게 읽고 넘어갈 내용이기도 하지만

책에 나오는 내용을 그대로 적용하여 책에 있는 표를 작성한다던가 자신의 계획을 문서로 남기고자 한다면

차분한 시간을 가지고 책을 읽는 것이 필요할것 같다.

  나는 성공의 열쇠가 무엇인지 알지 못한다.
  하지만 실패의 열쇠만큼은 알고 있다. 그건 다른 사람을
  기쁘게 하느라 시간을 허비하는 일이다.
                                                              -빌 코스비

이렇든 책의 내용 틈틈히 문맥에서 나타내는 바를 여느 명언들을 인용하여 간략히 정의 하기도 한다.

 당신의 노력을 고의로 방해하는 사람도 있을 것이다. 당신은 가식적인 말로 하는
 칭찬과 응원을 받아본 경험이 있을 것이다.
 당신이 성공하여 즐거움이 커질수록 그 즐거움을 일부러 파괴하려는 사람도 많아질 것이다.
 힘과 능력을 얻을수록 당신이 자신감을 잃도록 하려는 사람도 만날 것이다.
 그들은 당신이 더욱 성공할 것이며 더 큰 책임을 맡게 될 것이라는 생각에 두려움을 느끼거나
 자신이 손해를 볼 것이라고 여기는 것이다.  그러므로 그들의 행동심리를 탐색할 필요가 있다.
 그러한 방해는 끊임없이 일어날 것이기 때문이다.
 물론 이러한 방해공작은 당신의 행동과는 무관하게 발생할 것이다.

 @항상 자신의 기술과 능력에 자신감을 가져라. 다른 사람이 자신에게 갖고 있는 믿음을 훼손하지 마라.
 @부정적인 상황이 발생했을 때 상대방에게 직접적으로 접근하는 것을 두려워하지 마라.
 @모든 사람을 즐겁게 할 수 없다는 것을 기억하라. 자기 자신에게 진실하라

너무 많은 사람들이 다른 사람을 의식하느라 자신의 전략을 추구하는데 방해받고 있다. 부디 기억하라
가장 좋은 것은 가장 어려운 법이다. 당신은 특별할 뿐 아니라 끈기와 확신 그리고 자신에 대한
믿음을 통해 불가능한 것을 이루어낼 수 있다.

변화가 없음을 허락하는 것은 나쁜 계획이다.  -푸블릴리우스 시리우스

책의 내용을 간략히 하자면 자신의 커리어 향상을 위해서 어떠한 방법들이 있는지를 알려주는 내용 ^^;

여타 다른 자기계발서의 내용과 많은 부분이 유사한 것은 사실이다.

허나 이책은 그 방법에 대해 설명을 하고 예시를 보이며 또 그안에 간단히 작성할수 있는 도표와 같은

템플릿등을 포함하여 책에 직접 글을 써 넣을수 있게 해 놓았다.

어떠한 방법을 설명만 구구절절 늘어 놓고 깨닿게 하는 책 보다는 직접 커리어를 쌓기 위한 목표나 전략을

글로써 남기도록 유도한 면이 이 책의 특징이라 할수 있다.

무엇을 알고 깨달은 것과 그것을 행동으로 실행에 옮기는 것은 별개의 문제이다.

목표에 대한 계획을 세우고 그 진행 방향을 설정하며 그 실천의 시작은 증거를 남기는 것이다.

이에 가장 효과적인 방법은 문서로서 남기는 것이다. 즉 글로써 남기는 것이다.

이에 대한 설명은... 음 잘 생각이 나지 않는다. ㅋㅋ 예전에 설득의 심리학에 나왔던 내용이다. ㅋ

일관성의 법칙 이였던가? ㅋㅋ  공식적인 입장을 표명함으로서 그것에 대한 입장을 변경하기 어려움에 관한

심리학적 효과에 관한 것이였던것 같다.  책의 내용을 다 기억하지 못하고 그 의미만 머릿속에 남은 상태라..^^;

므튼 그런 심리적 효과를 볼수 있도록 한것이 아닌가 한다. 계획하면 글로 남기고 실천하라!! 이런..

책에 보면 떡하니 모든 여성들을 위한 탁월한 지침서!.  라고 되어 있지만 내용은 여성에게만 적용되는건 아니다.

책의 내용을 읽어보고 템플릿대로 작성을 해보고 실천 계획을 세우고 실행에 옮기는 것도 많은 도움이 되리라..

틈틈히 읽으면서 책의 내용대로 하진 못했지만 조금 다른 나만의 방식으로 책의 내용을 적용해 보려한다.

마인드 맵을 통하여 로드맵을 그리고 그에 따른 구체적인 세부사항을 정하고 거기에 필요한 전략을 세우는 등..

반응형

블루투스를 이용하여 휴대폰과 PC를 페어링 하고 그 거리에 따라 자동으로 잠금 기능을 제공

여러모로 쓸모도 있고 재미있는 유틸(Freeze)이기에 포스팅합니다.

보안적 요소와 Green Screen Mode로 모니터 화면을 끄거나 Sleep 상태로 전환하여 절전 효과

일단 블루투스가 되는 휴대폰과 PC가 있어야 되겠습니다.




뭐 PC에만 설치하면 되고 휴대폰에는 별도의 설치 과정이 필요치 않습니다.

Freeze 14일 동안만 무료로 사용 할수 있습니다.  구매시에는 한화로 약 13000원 정도

다운로드 링크 Freeze


Freeze 설명 동영상
 

사용후기..

Freeze를 설치하여 사용해보면서 신기하다는 생각에 자꾸 노트북 앞을 알짱거렸습니다.

잠깐 자리를 비울때마다 멀리서 꺼지나 지켜보고 다른 사람에게 말하며 보여주기도 하면서..ㅋㅋ

헌데 특이한 것은 블루투스가 거리를 측정하면서 설정거리를 벗어난후 작동하는 것인데

설정거리를 벗어나기 이전에 블루투스의 신호가 끊어지면  정상작동이 되지 않는다는 것입니다.ㅎㅎ

그리고 윈도우 작업관리자 및 로그인 부분의 설정이 약간 바뀌게 되는 것도 있습니다.

므튼 잘 사용하면 편하게 사용하리라 생각됩니다.

 

+ Recent posts