Skip to content
튜닝 연습Lock과 동시성 제어
LOCK-034

점심 마감 보고서의 매장별 판매 건수를 더하면 총계와 맞지 않는다

기초SELECT유통529 / 700

상황 ​

본사 레거시 유통은 매일 13:00에 오전 판매 보고서를 뽑아 MD들에게 보낸다. 보고서 프로그램은 SQL 셋을 차례로 실행해 매장별 판매 건수, 상품별 판매 수량과 금액, 오늘 총계를 한 장에 찍는다. 매장은 영업 중이라 POS가 계속 판매를 넣는다. MD가 물었다. "매장별 건수를 더하면 312건인데 총계는 315건이에요. 상품별 금액을 더해도 총계와 안 맞고요." 보고서를 다시 뽑으면 또 몇 건씩 다르다. 영업 전인 07:00에 뽑으면 맞는다.

테이블과 인덱스 ​

  • CF10 판매: 500,000행 · 4,583블록 · CF10_PK (SLE_NO) · 인덱스 IX_CF10_SLE_DT (SLE_DT) · 하루 약 700건, 13:00까지 약 310건
  • CF11 판매상품: 1,096,708행 · 8,204블록 · CF11_PK (SLE_NO, ITM_NO) · 판매당 약 2.2행
  • POS는 결제가 끝나면 CF10과 CF11을 넣고 바로 COMMIT한다. 13:00 무렵에는 분당 2 ~ 4건이다.
  • 보고서 프로그램은 세션 하나로 세 SQL을 실행하고, 결과를 하나씩 받아 양식에 채운 뒤 다음 SQL로 넘어간다(모두 약 3초).

원본 SQL ​

sql
-- ① 매장별 판매 건수
SELECT h.SHP_NO, COUNT(*) AS SLE_CNT
  FROM CF10 h
 WHERE h.SLE_DT = :today
 GROUP BY h.SHP_NO;

-- ② 상품별 판매 수량과 금액
SELECT d.ITM_NO, SUM(d.SLE_QTY) AS QTY, SUM(d.SLE_QTY * d.SLE_UPRC) AS AMT
  FROM CF10 h JOIN CF11 d ON d.SLE_NO = h.SLE_NO
 WHERE h.SLE_DT = :today
 GROUP BY d.ITM_NO;

-- ③ 오늘 총계
SELECT COUNT(DISTINCT h.SLE_NO) AS SLE_CNT, SUM(d.SLE_QTY * d.SLE_UPRC) AS AMT
  FROM CF10 h JOIN CF11 d ON d.SLE_NO = h.SLE_NO
 WHERE h.SLE_DT = :today;

실행계획 학습용 예시 ​

xplan
[보고서 한 번 — 12월 14일 13:00 · 세 SQL 의 시작 시각과 읽기 시점(SCN)]
SQL   시작           읽기 시점 SCN    결과
①    13:00:00.21    71,402,118       매장 6곳 · 합계 312건
②    13:00:01.02    71,402,190       상품 94개 · 금액 합 4,880,900원 (313건분)
③    13:00:02.40    71,402,251       315건 · 4,902,600원
      (그 사이 COMMIT 된 POS 결제: 13:00:00.6 에 1건 · 13:00:01.9 와 13:00:02.1 에 2건)

[③ 오늘 총계 — ALLSTATS LAST]
--------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name           | Starts | E-Rows | A-Rows |    A-Time   | Buffers |
--------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                |      1 |        |      1 | 00:00:00.02 |    1063 |
|   1 |  SORT GROUP BY                         |                |      1 |      1 |      1 | 00:00:00.02 |    1063 |
|   2 |   NESTED LOOPS                         |                |      1 |    680 |    693 | 00:00:00.02 |    1063 |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED | CF10           |      1 |    706 |    315 | 00:00:00.01 |       6 |
|*  4 |     INDEX RANGE SCAN                   | IX_CF10_SLE_DT |      1 |    706 |    315 | 00:00:00.01 |       3 |
|   5 |    TABLE ACCESS BY INDEX ROWID         | CF11           |    315 |      2 |    693 | 00:00:00.01 |    1057 |
|*  6 |     INDEX RANGE SCAN                   | CF11_PK        |    315 |      2 |    693 | 00:00:00.01 |     632 |
--------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("H"."SLE_DT"=:TODAY)
   6 - access("D"."SLE_NO"="H"."SLE_NO")

[같은 보고서 — 지난주]  07:00 (영업 전) 세 숫자 일치 · 13:00 다섯 번 가운데 네 번 불일치 (2 ~ 5건)

문제 ​

  1. 세 SQL의 읽기 시점을 근거로 보고서 숫자가 서로 맞지 않는 이유를 설명하시오.
  2. 세 SQL이 같은 시점의 데이터를 보도록 고치시오. POS의 판매 입력을 막지 않아야 하며, 고친 방법의 대가도 쓰시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자