고객센터 「통합 검색」은 상담원이 고객 이름이나 주문번호 가운데 아는 것 하나를 넣으면 주문을 고객 정보와 함께 보여 준다. 처음에는 주문번호 칸만 있었는데, 고객 이름 칸을 더하면서 두 조건을 OR로 이었다. 상담원 40명이 하루 약 1만 2천 번 쓴다.
한 번에 926블록이라 하루 1,100만 블록이 넘는다. 박팀장은 "OR를 없애는 방법을 두세 개 놓고, 결과가 틀릴 위험과 운영 부담까지 비교해 와라"고 했다.
SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.PAY_TP, o.PAY_DT, o.ORD_AMT, c.CUS_ID, c.CUS_NM, c.RGN_ID FROM T_ORD o, M_CUS c WHERE c.CUS_ID = o.CUS_ID AND (c.CUS_NM = :v_nm OR o.ORD_SEQ = :v_ord) ORDER BY o.ORD_DT DESC
-- ① 채택 : 갈래마다 드라이빙 테이블이 다르다 (커서 하나 · 넣지 않은 칸의 갈래는 = NULL이라 바로 끝난다)-- 이름 갈래 : M_CUS(IX_M_CUS_NM) → T_ORD(IX_T_ORD_CUS)SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.PAY_TP, o.PAY_DT, o.ORD_AMT, c.CUS_ID, c.CUS_NM, c.RGN_ID FROM M_CUS c, T_ORD o WHERE c.CUS_NM = :v_nm AND o.CUS_ID = c.CUS_IDUNION ALL-- 주문번호 갈래 : T_ORD(PK) → M_CUS(PK) · 이름 갈래에 이미 나온 행은 뺀다SELECT o.ORD_SEQ, o.ORD_DT, o.ORD_ST, o.PAY_TP, o.PAY_DT, o.ORD_AMT, c.CUS_ID, c.CUS_NM, c.RGN_ID FROM T_ORD o, M_CUS c WHERE o.ORD_SEQ = :v_ord AND c.CUS_ID = o.CUS_ID AND LNNVL(c.CUS_NM = :v_nm) ORDER BY ORD_DT DESC
OR의 두 갈래가 서로 다른 테이블의 조건이다. 이름은 M_CUS에, 주문번호는 T_ORD에 있다. 어느 한 테이블에서 OR 전체를 먼저 걸러 낼 수 없으므로, 옵티마이저는 이 조건을 어느 쪽의 access로도 쓰지 못하고 두 테이블을 다 읽어 조인한 뒤에야 2번 줄에서 거른다(filter). 그래서 이름을 넣든 주문번호를 넣든 M_CUS 30블록과 T_ORD 896블록을 다 읽는다(Buffers 926). 이 계획에서는 옵티마이저가 OR를 펼치지 않았다(VW_ORE_ 없음).
① 손으로 UNION ALL을 쓰는 방법이 가장 확실하다. 이름 갈래는 M_CUS에서 출발해 IX_M_CUS_NM으로 고객을 찾고, IX_T_ORD_CUS로 그 고객의 주문을 찾는다. 주문번호 갈래는 T_ORD에서 출발해 PK로 주문 한 건을 찾고 PK_M_CUS로 고객을 붙인다. 넣지 않은 칸은 NULL이라 그 갈래의 = NULL 조건은 바로 0건으로 끝난다. 두 칸을 다 넣었을 때 같은 행이 두 갈래에 다 나오지 않도록 둘째 갈래에 LNNVL(c.CUS_NM = :v_nm)을 붙인다. :v_nm이 NULL이면 LNNVL이 참이라 주문번호 갈래의 행을 지우지 않는다. 정렬(ORDER BY)은 UNION ALL 바깥에서 전체에 한 번 건다. 이름 검색은 Buffers 926 → 108, 주문번호 검색은 926 → 6이 된다. 대가는 SQL이 두 배로 길어지고, 화면에 칸이 늘면 갈래도 는다는 점이다.
② USE_CONCAT 같은 힌트로 펼치게 하는 방법은 SQL을 거의 고치지 않아도 되고, 펼치면 옵티마이저가 LNNVL을 스스로 붙여 결과도 맞다. 하지만 펼칠지는 여전히 비용으로 정해지므로 힌트가 무시될 수 있고, 갈래마다 드라이빙 테이블을 바꾸는 계획까지 나온다는 보장이 없다. 버전이 바뀌면 계획이 달라질 수 있어 배포마다 계획을 확인해야 한다.
③ 화면에서 칸마다 SQL을 따로 부르면 SQL은 가장 단순하다(갈래마다 조인 하나). 그러나 두 칸을 다 넣었을 때 결과를 합치고, 겹친 행을 지우고, 다시 정렬하는 일이 화면 코드로 간다. 같은 검색을 쓰는 화면이 여럿이면 같은 로직이 여러 곳에 퍼진다.
그래서 ①을 고른다. 결과의 정확성(LNNVL, 바깥 정렬)을 SQL 한 곳에서 보장하고, 계획이 힌트나 버전에 기대지 않는다.