1. 인덱스를 타지 않는 경우
2. IN vs EXIST
3. SQL에 Alias가 없는 경우
4. NULL을 처리해주지 않은 경우
5. JOIN의 순서를 생각하지 않은 경우
6. 마치며
SQL을 작성하고 원하는 결과값을 받는다고 해서 그것이 끝은 아닙니다.
고객이 몰려서 속도가 느린 쿼리가 될수도 있고 유지보수 측면에서 이거저거 추가하다보면 성능이 저하되 느린 쿼리들이 생겨나게 됩니다.
그렇기에 저희는 리펙토링
을 통해 시간을 줄이고 SQL을 튜닝
하여 속도를 보장
해줘야합니다.
SQL 튜닝에 대해서 같이 알아보겠습니다.
인덱스를 타지 않는 경우는 대부분 좌변에 가공을 하는 경우에 발생합니다.
좌변에 가공을 하는 경우는 TO_CHAR()
같은 내장 함수로 감싸 사용하는 경우가 대부분입니다.
인덱스를 타지 않게되면 성능 저하가 많이 되기 때문에 체크해서 수정해주어야 합니다.
SELECT
*
FROM
TB_USER
WHERE
TRIM(USER_NO) = '0000001'
SELECT
*
FROM
TB_USER
WHERE
USER_NO = '0000001'
SELECT
*
FROM
TB_USER
WHERE
USER_NO = 0000001
위 경우처럼 좌변에 묵시적 형변환이 들어가게 되는 경우도 인덱스를 타지 않습니다.
''
를 넣어 우변에 형변환을 걸어주어 형변환이 일어나지 않게 해주어야 인덱스를 타게 됩니다.
WHERE
절에서 좌변 가공은 금지하며, 상수와 우변을 가공할 방법을 모색해야 합니다.
칼럼이나 조건 값의 데이터 형 변환이 필요한 경우 명시적 형 변환을 사용해야 합니다.
명시적 형 변환이란?
좌변과 우변의 형을 맞춰주는 과정입니다.
예를 들어 좌변의 형식이 날짜 값이면 우변에서TO_DATE
함수를 활용해서 2가지의 타입을 맞춰주는 과정입니다.
IN
과 EXIST
연산은 각 값을 찾는데 있어서 해당하는 값이 있는지에 대해 확인하는 연산입니다.
2개의 가장 큰 차이점은 IN
은 모든 집합에서 충족한 집합을 찾아내는 과정을 거치지만 EXIST
의 경우 명시된 기준을 충족하는 단일행의 조건을 찾아 검색하므로 성능 상 유리합니다.
SELECT
*
FROM
TB_ORDER A
WHERE
A.ORDER_DT BETWEEN TO_DATE('20210101' || '000000', 'YYYMMDDHH24MISS')
AND TO_DATE('20210101' || '235959', 'YYYMMDDHH24MISS')
AND A.USER_NO IN (
SELECT
X.USER_ID
FROM
TB_USER X
)
SELECT
*
FROM
TB_ORDER A
WHERE
A.ORDER_DT BETWEEN TO_DATE('20210101' || '000000', 'YYYMMDDHH24MISS')
AND TO_DATE('20210101' || '235959', 'YYYMMDDHH24MISS')
AND EXISTS (
SELECT
1
FROM
TB_USER X
WHERE
X.USER_ID = A.USER_ID
)
쿼리 속도가 느릴 경우 IN
절로 되어 있는 부분을 EXISTS
로 변경하게 되면 속도가 빨라질수 있습니다.
옵티마이저의 연산 속도가 올라가기 때문입니다.
옵티마이저란?
옵티마이저는 가장 효율적인 방법으로 SQL을 수행할 최적의 처리 경로를 생성해주는 DBMS의 핵심 엔진
SQL에서 Alias
는 상당히 중요합니다.
Alias
가 없는 경우 SQL문을 파싱할때 부하가 생기거나 옵티마이저가 실수를 할 수도 있기에 명시적으로 적어주는 것이 좋습니다.
Alias
작성규칙의 경우 이 포스팅을 참고하세요.
2개이상의 테이블을 조인했을때 파싱과정에서 칼럼명이 겹치는 경우 에러와 부하가 오기때문에 명시적으로 Alias를
작성해주어야 합니다.
SELECT
*
FROM
TB_ORDER
WHERE
ORDER_DT BETWEEN TO_DATE('20210101' || '000000', 'YYYMMDDHH24MISS')
AND TO_DATE('20210101' || '235959', 'YYYMMDDHH24MISS')
SELECT
*
FROM
TB_ORDER A
WHERE
A.ORDER_DT BETWEEN TO_DATE('20210101' || '000000', 'YYYMMDDHH24MISS')
AND TO_DATE('20210101' || '235959', 'YYYMMDDHH24MISS')
Alias
가 있는 경우가 명시적으로 알아보기 쉽기 때문에 작성할때 습관을 들여서 작성하는 것이 좋습니다.연산에 있어 NULL
처리는 정말 중요합니다.
잘못된 결과 값이 도출될 수 있기 때문입니다.
NULL
과 값을 더하면 NULL
이 출력됩니다.
SELECT
SAL AS 연봉
, INCENTIVE AS 인센티브
, SAL + INCENTIVE AS 총연봉
FROM
TB_EMP
WHERE
JOIN_DT = '20210101'
위 SQL
문에서 인센티브가 없다면 총연봉 역시 NULL로 출력됩니다.
COALESCE
함수를 통해 NULL
값을 0으로 변경해주게 되면 정상 출력됩니다.
SELECT
SAL AS 연봉
, INCENTIVE AS 인센티브
, SAL + COALESCE(INCENTIVE,0) AS 총연봉
FROM
TB_EMP
WHERE
JOIN_DT = '20210101'
NULL
은 값으로 인식이 불가능합니다.
다른 칼럼에 하는 것처럼 사용하면 SQL
에서 인식이 불가능합니다.
SELECT
SAL AS 연봉
, INCENTIVE AS 인센티브
, SAL + COALESCE(INCENTIVE,0) AS 총연봉
FROM
TB_EMP
WHERE
JOIN_DT = '20210101' OR JOIN_DT = NULL
SELECT
SAL AS 연봉
, INCENTIVE AS 인센티브
, SAL + COALESCE(INCENTIVE,0) AS 총연봉
FROM
TB_EMP
WHERE
JOIN_DT = '20210101' OR JOIN_DT IS NULL
NULL
이 포함된 칼럼에서 연산을 하거나 통계를 낼시에는 적절한 NULL
처리가 필요합니다.JOIN
은 작은 것부터 연산하는 것이 성능상 유리합니다.
JOIN
을 3개이상 하게 되면 집합 값이 작은 것부터 읽는 것이 성능상 월등히 유리해집니다.
초기에 잘 생각하여 3개이상의 테이블을 조인시 작은 집합이 먼저 읽히도록 앞에 써주고 연산합니다.
SELECT
*
FROM
TB_USER A
INNER JOIN TB_ORDER B
ON A.USER_ID = B.USER_ID
WHERE
B.ORDER_DT = '20210101'
위 SQL
문에서 하나의 회원은 여러개의 주문을 할 수 있으므로 1대多
관계입니다.
작은 집합인 USER
부터 써주고 큰 집합인 ORDER
을 나중에 써주는 것이 좋습니다.
SQL
을 작성할때 ERD
를 기반으로 해서 SQL
을 작성하거나 관계에 대해 생각하면서 SQL
문을 작성하도록 합시다.회사를 다니면서 튜닝했던 정보들을 공유합니다.
다른 분들에게도 많은 도움이 되었으면 좋겠습니다.
틀린 부분이나 추가하면 좋겠을 규칙들도 적어주시면 감사하겠습니다.
'Alias가 없는 경우 SQL문을 파싱할때 부하가 생기거나 옵티마이저가 실수를 할 수도 있다'
라고 하셨는데 혹시 어디에서 보셨던 내용인가요??