(sql초보자를 위한, 쿼리최적화 for...

21
오오오 SQL 오오오 TIP SQL 오오오 오오 오오 오오 작작작 : 작작작 , 작작작작작 (topcredu.co.kr)

Upload: 3-2

Post on 07-Jan-2017

798 views

Category:

Software


1 download

TRANSCRIPT

오라클 SQL 최적화 TIPSQL 튜닝을 위한 필수 지식

작성자 : 이종철 , 탑크리에듀 (topcredu.co.kr)

실습테이블 (MYEMP1)칼럼이름 데이터 타입 Key

TypeNN/

Unique FK table FK column 설명

EMPNO NUMBER PK NN,U 사번 ENAME VARCHAR2(100) NN 이름DEPTNO VARCHAR2(1) FK MYDEPT1 DEPTNO 부서코드ADDR VARCHAR2(100) 주소SAL NUMBER(7) 급여JOB VARCHAR2(20) 직무COMM NUMBER(7) 수당SUNGBYUL VARCHAR2(1) 성별

(M : 남 , F : 여 )

HIREDATE DATE 입사일자OUTDATE VARCHAR2(8) 퇴사일자MGR NUMBER FK MYEMP1 EMPNO 관리자사번

실습테이블 (MYDEPT1)

칼럼이름 데이터 타입 Key Type

NN/Unique FK table FK

column 설명DEPTNO VARCHAR2(1) PK NN,U 부서코드DNAME VARCHAR2(100) 부서명UP_DEPTNO VARCHAR2(1) FK MYDEPT1 DEPTNO 상위

부서코드

실습 데이터 생성http://ojc.asia/bbs/board.php?bo_table=LecHINT&wr_id=117&page=5

테스트 환경 : 오라클 11g R2

인덱스 생성 현황1. MYEMP1 테이블 (1000 만건 ) 의 인덱스 생성 현황SELECT A.INDEX_NAME, A.COLUMN_NAME, B.VISIBILITY

FROM USER_IND_COLUMNS A, USER_INDEXES B

WHERE A.TABLE_NAME = 'MYEMP1'

AND A.INDEX_NAME = B.INDEX_NAME;

• PK_MYEMP1 EMPNO VISIBLE

• IDX_MYEMP1_ENAME ENAME VISIBLE

• IDX_MYEMP1_SAL SAL VISIBLE

• IDX_MYEMP1_DEPTNO DEPTNO VISIBLE

• IDX_MYEMP1_JOB JOB VISIBLE

• IDX_MYEMP1_MGR MGR VISIBLE

2. MYDEPT1 테이블 (7 건 ) 의 인덱스 생성 현황SELECT A.INDEX_NAME, A.COLUMN_NAME, B.VISIBILITY

FROM USER_IND_COLUMNS A, USER_INDEXES B

WHERE A.TABLE_NAME = 'MYDEPT1'

AND A.INDEX_NAME = B.INDEX_NAME;

• PK_MYDEPT1 DEPTNO VISIBLE

1. 인덱스된 컬럼을 포함하는 표현식 , 함수 , 계산식

WHERE SUBSTR(ename,1,3) = 'SCO' WHERE ename LIKE 'SCO%'

WHERE TRUNC (hiredate) = TRUNC (SYSDATE)

WHERE hiredate BETWEEN TRUNC (SYSDATE) AND TRUNC (SYSDATE) + .99999

WHERE ename || job = 'FORDCLERK' WHERE ename = 'FORD’ AND job = ‘CLERK’

WHERE sal + 1000 < 2000 WHERE sal < 1000

인덱스된 컬럼을 포함하는 표현 (EXPRESSION), 함수 , 계산 (CALCULATIONS)은 인덱스를 사용하지 못한다 . 인덱스 칼럼 보다는 반대쪽에 변형을 가하자 .

2. SELECT 절에서 DISTINCT 사용은 피하자 .

-- 1.4 초SELECT DISTINCT d.dname

FROM mydept1 D, myemp1 E

WHERE D.deptno = E.deptno;

-- 0 초 SELECT d.dname

FROM mydept1 D

WHERE EXISTS (SELECT 1

FROM myemp1 E

WHERE D.deptno = e.deptno);[ 결과 ]

개발 1 팀기획 1 팀기획 2 팀개발 2 팀

3. WHERE 절 비교칼럼 데이터타입은 일치시켜라 .

-- deptno 칼럼은 문자칼럼 , 아래 WHERE 절은 to_number(deptno) = 1 과 –-- 동일하므로 deptno 칼럼의 인덱스는 사용되지 못한다 .-- 1.4 초SELECT COUNT(1) FROM myemp1WHERE deptno = 1;

-- deptno 칼럼의 인덱스 사용가능하다 .-- 0.2 초SELECT COUNT(1) FROM myemp1WHERE deptno = '1';

4. OUTER JOIN, IS NULL 을 이용한 안티조인 보다는 NOT IN, NOT EXISTS 를 사용하자 .

-- 1.6 초 select d.deptno,dname from myemp1 e, mydept1 d where e.deptno(+) = d.deptno and e.empno is null;

-- 0 초 select dname from mydept1 where deptno not in (select deptno from myemp1 where deptno is not null ) -- 0 초 select dname from mydept1 d where not exists ( select * from myemp1 e where e.deptno = d.deptno );

5. OUTER JOIN, UNION ALL 로 FULL OUTER JOIN 을 구현하기 보다는 FULL OUTER JOIN 구문을 사용하자 .

-- 6 초SELECT empno, ename, d.deptno, dname FROM myemp1 e, mydept1 d WHERE e.deptno(+) = d.deptnoUNION ALL-- MYEMP1 테이블에서 MYDEPT1 테이블에 없는 DEPTNO 를 가지고 있는 데이터 추출SELECT empno, ename, e.deptno, NULL FROM myemp1 e, mydept1 d WHERE e.deptno = d.deptno(+) AND d.deptno IS NULLORDER BY empno, ename, deptno, dname;

-- 4.8 초SELECT empno, ename, NVL(d.deptno,d.deptno) deptno, dname FROM myemp1 e FULL OUTER JOIN mydept1 d ON (e.deptno = d.deptno)ORDER BY empno, ename, deptno, dname;

6. IN 을 이용한 조인 보다는 EXISTS 를 사용하자 .

-- 3.6 초SELECT count(ename) FROM myemp1 e1WHERE empno IN (SELECT mgr FROM myemp1 e2);

-- 1.3 초SELECT count(ename) FROM myemp1 e1WHERE EXISTS (SELECT 1 from myemp1 e2 WHERE e1.mgr = e2.empno);

7. UNION 보다는 UNION ALL 을 사용하라 .

-- 10 초 SELECT empno, ename, d.deptno, dname FROM myemp1 e, mydept1 d WHERE e.deptno(+) = d.deptnoUNIONSELECT empno, ename, e.deptno, NULL FROM myemp1 e, mydept1 d WHERE e.deptno = d.deptno(+) AND d.deptno IS NULL;

-- 0 초SELECT empno, ename, d.deptno, dname FROM myemp1 e, mydept1 d WHERE e.deptno(+) = d.deptnoUNION ALLSELECT empno, ename, e.deptno, NULL FROM myemp1 e, mydept1 d WHERE e.deptno = d.deptno(+) AND d.deptno IS NULL;

8. 조인되는 건수가 작다면 일반적인 조인보다 스칼라 서브쿼리 사용도 고려하라 .-- 1.1 초 SELECT (d.dname) FROM myemp1 e, mydept1 dWHERE e.deptno = d.deptno AND e.sal > 5800000;

-- 0.8 초SELECT (select dname from mydept1 d where d.deptno = e.deptno) FROM myemp1 eWHERE e.sal > 5800000;

9. MYEMP1 에서 사번오름차순으로 사번 , 사원명 , 급여를 출력하는 쿼리 작성-- 3.5 초-- empno 칼럼의 PK 인덱스 사용못했다 .-- FULL SCAN 후 정렬하므로 성능저하 .SELECT empno, ename, salFROM myemp1ORDER BY empno;

-- 0 초-- 인덱스 영역에서 스캔 하도록 힌트사용SELECT /*+ index(e pk_myemp1) */ empno, ename, salFROM myemp1 eORDER BY empno; -- order by 절은 생략해도 된다 . 인덱스영역은 이미 데이터가 정렬되어 보관되므로 따로 정렬할 필요없다 .

10. 인덱스없이 COUNT 여러 번 실행시 통합해서 COUNT CASE 를 이용하자 . 

1. sal 칼럼에 인덱스가 있는 경우-- 0.071 초select count(1) from myemp1 where sal < 100000;

-- 0.14 초select count(1) from myemp1 where sal between 5000000 and 6000000;

-- 1.62 초 , 인덱스가 있을 땐 통합하면 더 느림select count( case when sal < 1000000 then 1 else null end) cnt1, count( case when sal between 5000000 and 6000000 then 1 else null end) cnt2 from myemp1;

2. sal 칼럼에 인덱스가 없는 경우alter index idx_myemp1_sal invisible ;

-- 1.8 초 select count(1) from myemp1 where sal < 100000;

-- 1.4 초 select count(1) from myemp1 where sal between 5000000 and 6000000;

-- 1.58 초select count( case when sal < 1000000 then 1 else null end) cnt1, count( case when sal between 5000000 and 6000000 then 1 else null end) cnt2 from myemp1;

11. 인덱스 칼럼을 WHERE 절에서 사용시 OR 를 UNION ALL 로

-- 3.555 초 SELECT e1.ename FROM myemp1 e1, myemp1 e2 WHERE e1.ename = e2.ename OR e1.sal = e2.sal;

-- 3.048 초 -- 인덱스가 생성된 칼럼에 대해 or 로 WHERE 절을 사용하는 경우 union all 을 사용하는것이 조금 빠름 .SELECT e1.ename FROM myemp1 e1, myemp1 e2 WHERE e1.ename = e2.ename union all SELECT e1.ename FROM myemp1 e1, myemp1 e2 WHERE e1.sal = e2.sal;

12. 그룹핑시 HAVING 보다 WHERE 절로 조건검색을-- 2.7 초-- Having 절은 SELECT 에서 사용하지 말자 .select deptno, avg(sal)from myemp1group by deptnohaving deptno = '1';

-- 1.7select deptno, avg(sal)from myemp1where deptno = '1'group by deptno;

13. 공통쿼리는 WITH 문으로 통합하자 .-- 인라인뷰를 이용한 경우 , 7 초SELECT e.ename AS employee_name, emps1.emp_count , m.ename AS manager_name, emps2.emp_count FROM myemp1 e, (SELECT deptno, COUNT(*) AS emp_count FROM myemp1 GROUP BY deptno) emps1, myemp1 m, (SELECT deptno, COUNT(*) AS emp_count FROM myemp1 GROUP BY deptno) emps2WHERE e.deptno = emps1.deptnoAND e.mgr = m.empnoAND m.deptno = emps2.deptno;

-- WITH 문을 이용한 경우 , 4.6 초WITH emp_count AS ( SELECT deptno, COUNT(*) AS dept_count FROM myemp1 GROUP BY deptno)SELECT e.ename, emps1.dept_count, m.ename, emps2.dept_countFROM myemp1 e, emp_count emps1, myemp1 m, emp_count emps2WHERE e.deptno = emps1.deptnoAND e.mgr = m.empnoAND m.deptno = emps2.deptno;

14. 여러 칼럼으로 구성된 복합인덱스 사용시 INDEX 힌트를 적절히 사용하여 인덱스를 취사선택하자 .CREATE TABLE INDEXTEST ( A1 NUMBER NOT NULL, A2 NUMBER NOT NULL, A3 VARCHAR2(50) NOT NULL, A4 VARCHAR2(100));

-- 100 만건 생성 INSERT INTO INDEXTEST SELECT MOD(ROWNUM-1, 90) * 4 A1, ROWNUM - 1 A2, TO_CHAR(ROWNUM - 1, 'RN') A3, LPAD('A',100,'A') A4 FROM DUAL CONNECT BY LEVEL<=1000000; -- 실습을 위한 인덱스 생성 CREATE INDEX IDX_IT_1_2 ON INDEXTEST(A1,A2); CREATE INDEX IDX_IT_1_2_3 ON INDEXTEST(A1,A2,A3); CREATE INDEX IDX_IT_3_1_2 ON INDEXTEST(A3,A1,A2);

실습예문 : http://ojc.asia/bbs/board.php?bo_table=LecHINT&wr_id=222&page=2

-- 먼저 힌트를 사용하지 않은 쿼리를 보자 . -- A2, A1, A3 복합 인덱스를 사용한다 . SELECT A1, A2, A3 FROM INDEXTEST

-- A1, A2 칼럼에 있는 인덱스를 사용하라는 힌트사용 -- A1, A2 복합인덱스를 이용한다 . SELECT /*+ index(INDEXTEST (A1, A2)) */ A1, A2, A3 FROM INDEXTEST;

15. TOP-N 쿼리 ( 급여 상위 5 명 ) – 인덱스 힌트를 이용하자-- 급여 상위 5 명의 이름 , 급여출력-- 1.4 초SELECT rownum, ename ,sal FROM (      SELECT ename ,sal FROM myemp1 ORDER BY sal DESC) e WHERE rownum <= 5 ;

-- 급여 상위 5 명의 이름 , 급여출력-- INDEX_DESC 힌트이용 , 0 초SELECT rownum, ename ,sal FROM (      SELECT /*+ index_desc(e1 idx_myemp1_sal) */ e1. ename ,e1.sal FROM myemp1 e1 WHERE e1.sal > 0 ORDER BY e1.sal DESC) e WHERE rownum <= 5 ;

16. PAGINATION 쿼리 – 인덱스 힌트를 이용하자-- order by 사용 , 인덱스 사용못함 , 4.5 초SELECT empno, ename, sal FROM (SELECT empno, ename, sal , ROWNUM rnum FROM (SELECT empno, ename, sal FROM myemp1 ORDER BY sal desc) e WHERE ROWNUM <= 1000*10)WHERE rnum >= 999*10+1 ;

-- 인덱스 사용하기 위한 힌트 사용 , 0 초select empno, ename, sal from ( select /*+ index_desc(e1 idx_myemp1_sal) */ rownum rnum, empno, ename, sal from myemp1 e1 where sal > 0 and rownum <= 1000*10 ) where rnum >= 999*10+1;

myemp1 테이블에서 sal 가 높은것부터 작은것 순서로 한페이지에 10 개씩 리스트를 보인다고 했을 때 1000 번째 페이지를 SELECT 하는 쿼리를 만드세요 . 

THIS PRACTICE MAKES EXPERT!

PL/SQL단기속성 (1일완성 )SQL힌트 /튜닝 (단기 2일교육 )SQL기초과정