BAD: 16999.476ms, hit=14590, read=337219
서로 다른 테이블의 OR 조건
SELECT o.ord_no
,o.ord_dtm
,m.mbr_gd
FROM startdbpg.tr_ord_big o
INNER JOIN startdbpg.ms_mbr_big m
ON m.mbr_id = o.mbr_id
WHERE 1 = 1
AND (
(o.ord_dtm >= '2024-01-01' AND o.ord_dtm < '2024-01-02')
OR (m.mbr_gd = 'PLAT' AND m.nick_nm LIKE 'Ice%')
)
;
SQL
복사
실행계획 보기
•
OR가 서로 다른 테이블에 걸쳐 있어서, DB는 어느 테이블에서도 조건으로 범위를 줄이지 못합니다.
•
주문 2,600만 건 전체를 회원과 조인한 뒤 조건을 검사해서, 2,626만 건을 버리고 26,009건만 남겼습니다(Rows Removed by Join Filter).
GOOD: 102.424ms, hit=7646
조건을 분리하여 검색
SELECT o.ord_no
,o.ord_dtm
,m.mbr_gd
FROM startdbpg.tr_ord_big o
INNER JOIN startdbpg.ms_mbr_big m
ON m.mbr_id = o.mbr_id
WHERE 1 = 1
AND (o.ord_dtm >= '2024-01-01' AND o.ord_dtm < '2024-01-02')
AND NOT EXISTS(
SELECT *
FROM startdbpg.ms_mbr_big x
WHERE x.mbr_id = o.mbr_id
AND x.mbr_gd = 'PLAT'
AND x.nick_nm LIKE 'Ice%'
)
UNION ALL
SELECT o.ord_no
,o.ord_dtm
,m.mbr_gd
FROM startdbpg.ms_mbr_big m
INNER JOIN startdbpg.tr_ord_big o
ON m.mbr_id = o.mbr_id
WHERE 1 = 1
AND (m.mbr_gd = 'PLAT' AND m.nick_nm LIKE 'Ice%')
;
SQL
복사
실행계획 보기
•
① 2024-01-01 하루 주문과 ② 'PLAT·Ice' 회원의 주문으로 나눠, 각자 인덱스로 범위를 줄입니다.
◦
① 하루 주문 21,708건을 날짜 인덱스로 바로 찾습니다.
◦
② PLAT·Ice 회원 10명을 먼저 찾고, 그 회원들의 주문 4,302건만 회원 인덱스로 찾습니다.
•
②에는 날짜 조건이 없습니다. 원래 조건이 OR라, PLAT·Ice 회원의 주문은 날짜와 상관없이 모두 포함되어야 하기 때문입니다.
•
2024-01-01에 주문한 PLAT·Ice 회원은 ①과 ② 양쪽에 다 나옵니다. 그래서 ①에서 NOT EXISTS로 그 회원을 빼서 중복을 막습니다(이 예제에서는 1건).
KEY POINT
•
서로 다른 테이블의 조건을 OR로 묶으면, 어느 쪽 조건으로도 범위를 줄이지 못해 전체를 조인한 뒤 걸러냅니다.
•
이럴 때는 조건별로 SQL을 나눠 UNION ALL로 합칩니다.
•
나눌 때는 두 결과에 같은 행이 겹치지 않도록, 한쪽에서 다른 쪽 조건을 제외합니다.
TIP. UNION으로 쓰면 안 되나?
•
UNION으로 합쳐도 중복은 제거되지만, 결과 전체를 비교하는 비용이 추가로 듭니다.
•
겹치지 않게 나눌 수 있다면 UNION ALL이 낫습니다.
DEEP DIVE — SQL STARTER for MySQL


