(오라클 SQL튜닝을 위한 쿼리문 작성법 강좌)오라클 SQL/쿼리 튜닝은 간단한 SQL구문 최적화 부터 시작을 하게되죠, SQL을 처음 사용할 때 부터 최적화 하는 방법에 주의해서 공부하시면 저절로 튜닝 됩니다. 본 PPT 강좌는 탑크리에듀(www.topcredu.co.kr), 오라클자바커뮤니티(ojc.asia)에서 제공하는 교육강좌 입니다.
(SQL초보자를 위한, 쿼리최적화 for SQL튜닝)SQL쿼리작성Tip,최적화팁,최적화된SQL작성방법교육
1. 오라클 SQL 최적화 TIP
SQL튜닝을 위한 필수 지식
작성자 : 이종철, 탑크리에듀(topcredu.co.kr)
2. 실습테이블(MYEMP1)
칼럼이름 데이터 타입
Key
Type
NN/
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 관리자사번
3. 실습테이블(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
4. 인덱스 생성 현황
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
5. 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)은
인덱스를 사용하지 못한다. 인덱스 칼럼 보다는 반대쪽에 변형을 가하자.
6. 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팀
7. 3. WHERE절 비교칼럼 데이터타입은 일치시켜라.
-- deptno칼럼은 문자칼럼, 아래 WHERE절은 to_number(deptno) = 1 과 –
--동일하므로 deptno 칼럼의 인덱스는 사용되지 못한다.
-- 1.4초
SELECT COUNT(1) FROM myemp1
WHERE deptno = 1;
-- deptno 칼럼의 인덱스 사용가능하다.
-- 0.2초
SELECT COUNT(1) FROM myemp1
WHERE deptno = '1';
8. 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
);
9. 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.deptno
UNION ALL
-- MYEMP1 테이블에서 MYDEPT1 테이블에 없는
DEPTNO를 가지고 있는 데이터 추출
SELECT empno, ename, e.deptno, NULL
FROM myemp1 e, mydept1 d
WHERE e.deptno = d.deptno(+)
AND d.deptno IS NULL
ORDER 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;
10. 6. IN을 이용한 조인 보다는 EXISTS를 사용하자.
-- 3.6초
SELECT count(ename) FROM myemp1 e1
WHERE empno IN
(SELECT mgr FROM myemp1 e2);
-- 1.3초
SELECT count(ename) FROM myemp1 e1
WHERE EXISTS (SELECT 1 from myemp1 e2
WHERE e1.mgr = e2.empno);
11. 7. UNION 보다는 UNION ALL을 사용하라.
-- 10초
SELECT empno, ename, d.deptno, dname
FROM myemp1 e, mydept1 d
WHERE e.deptno(+) = d.deptno
UNION
SELECT 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.deptno
UNION ALL
SELECT empno, ename, e.deptno, NULL
FROM myemp1 e, mydept1 d
WHERE e.deptno = d.deptno(+)
AND d.deptno IS NULL;
12. 8. 조인되는 건수가 작다면 일반적인 조인보다 스칼라
서브쿼리 사용도 고려하라.
-- 1.1초
SELECT (d.dname)
FROM myemp1 e, mydept1 d
WHERE e.deptno = d.deptno
AND e.sal > 5800000;
-- 0.8초
SELECT (select dname from mydept1 d
where d.deptno = e.deptno)
FROM myemp1 e
WHERE e.sal > 5800000;
13. 9. MYEMP1에서 사번오름차순으로 사번, 사원명, 급여
를 출력하는 쿼리 작성
-- 3.5초
-- empno 칼럼의 PK 인덱스 사용못했다.
-- FULL SCAN 후 정렬하므로 성능저하.
SELECT empno, ename, sal
FROM myemp1
ORDER BY empno;
-- 0초
-- 인덱스 영역에서 스캔 하도록 힌트사용
SELECT /*+ index(e pk_myemp1) */
empno, ename, sal
FROM myemp1 e
ORDER BY empno;
-- order by절은 생략해도 된다. 인덱스영역은 이미 데이
터가 정렬되어 보관되므로 따로 정렬할 필요없다.
14. 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;
15. 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;
16. 12. 그룹핑시 HAVING 보다 WHERE절로 조건검색을
-- 2.7초
-- Having절은 SELECT에서 사용하지 말자.
select
deptno, avg(sal)
from myemp1
group by deptno
having deptno = '1';
-- 1.7
select
deptno, avg(sal)
from myemp1
where deptno = '1'
group by deptno;
17. 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) emps2
WHERE e.deptno = emps1.deptno
AND e.mgr = m.empno
AND 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_count
FROM myemp1 e,
emp_count emps1,
myemp1 m,
emp_count emps2
WHERE e.deptno = emps1.deptno
AND e.mgr = m.empno
AND m.deptno = emps2.deptno;
18. 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;
19. 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 ;
20. 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하는 쿼리를 만드세요.