테마
고객센터 「고객 목록」은 고객 20명씩 한 페이지로 보여 주고, 줄마다 최근 주문일을 붙인다. 상담원 30명이 하루 약 4,000페이지를 넘긴다. 상담원들은 "페이지를 넘길 때마다 반 박자씩 늦다"라고 하고, 개발팀은 "실행계획을 떠 보면 4블록이라 화면 SQL은 문제가 없다"라고 한다.
DBA가 AWR에서 읽은 블록 수 1위 SQL을 보내왔다. SELECT MAX(ORD_DT) FROM T_ORD WHERE CUS_ID = :B1인데, 개발팀은 "그런 SQL은 소스에 없다"라고 답했다.
M_CUS 고객: 2,000행 · 30블록 · PK_M_CUS (CUS_ID)T_ORD 주문: 100,000행 · 896블록 · PK_T_ORD (ORD_SEQ)만 있다 · CUS_ID 인덱스 없음 · 고객 한 명에 평균 50건 · ORD_DT는 시분초가 있는 DATEFN_LAST_ORD_DT는 2021년에 만들었고, 고객 목록과 고객 상세, 주문 등록 화면 세 곳이 쓴다.CREATE OR REPLACE FUNCTION FN_LAST_ORD_DT(p_cus_id VARCHAR2) RETURN DATE IS
v_dt DATE;
BEGIN
SELECT MAX(ORD_DT) INTO v_dt
FROM T_ORD
WHERE CUS_ID = p_cus_id;
RETURN v_dt;
END;
/
-- 고객 목록 (한 페이지 20명 · 다음 페이지는 앞 페이지 마지막 고객 :last_cus_id 뒤부터)
SELECT v.CUS_ID, v.CUS_NM, v.CUS_GD,
FN_LAST_ORD_DT(v.CUS_ID) AS last_ord_dt
FROM (SELECT CUS_ID, CUS_NM, CUS_GD
FROM M_CUS
WHERE CUS_ID > :last_cus_id
ORDER BY CUS_ID) v
WHERE ROWNUM <= 20;
[화면 SQL: DISPLAY_CURSOR (ALLSTATS LAST) · 한 페이지]
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 20 |00:00:00.15 | 4 |
|* 1 | COUNT STOPKEY | | 1 | | 20 |00:00:00.15 | 4 |
| 2 | VIEW | | 1 | 20 | 20 |00:00:00.01 | 4 |
| 3 | TABLE ACCESS BY INDEX ROWID| M_CUS | 1 | 20 | 20 |00:00:00.01 | 4 |
|* 4 | INDEX RANGE SCAN | PK_M_CUS | 1 | 20 | 20 |00:00:00.01 | 2 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=20)
4 - access("CUS_ID">:LAST_CUS_ID)
[V$SQL: 함수 안의 문장 (dep=1) · 하루 누적]
SQL_TEXT EXECUTIONS ROWS_PROCESSED BUFFER_GETS ELAPSED
SELECT MAX(ORD_DT) FROM T_ORD WHERE CUS_ID = :B1 81,240 81,240 72,791,040 566 초
PROGRAM_ID → SQLP.FN_LAST_ORD_DT (PROGRAM_LINE# 4)
[그 문장의 계획: DISPLAY_CURSOR(sql_id => '그 문장', format => 'ALLSTATS LAST')]
------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 896 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 896 |
|* 2 | TABLE ACCESS FULL| T_ORD | 1 | 50 | 50 |00:00:00.01 | 896 |
------------------------------------------------------------------------------------
2 - filter("CUS_ID"=:B1)