Skip to content
튜닝 연습파티션
PART-033

결제 완료 처리가 ORA-14402로 막혀 ROW MOVEMENT를 켰더니 결제 한 건의 redo가 세 배가 되었다

중급UPDATE한빛유통408 / 700

상황 ​

상담원 화면의 「결제 대기 목록」은 5분마다 새로 고친다. 이 화면을 빠르게 하려고 지난달 주문 테이블을 상태별 LIST 파티션 복사본 T_ORD_LS로 옮겼다. 결제 대기(PENDING) 칸만 읽으면 되기 때문이다. 옮긴 첫날 결제 완료 처리가 모두 ORA-14402로 실패했고, 조민우가 ENABLE ROW MOVEMENT로 급히 막았다. 그 뒤로 에러는 없지만 결제 처리가 1.1ms에서 2.9ms로 늘고 redo가 세 배가 되었다. 금요일 타임세일에는 5분 동안 결제가 약 1만 건 몰린다. 상담원들은 "금액 정정을 눌렀는데 가끔 저장되지 않는다"고 한다. 상담원 화면은 목록을 띄울 때 받아 둔 ROWID로 금액을 고친다(오래된 프레임워크의 기본 동작이다).

테이블과 인덱스 ​

  • T_ORD_LS 주문(파티션 복사본, 문제 설정): T_ORD 100,000행을 PARTITION BY LIST (ORD_ST) 세 칸으로 나눴다. P_PENDING 8,000행(약 72블록) · P_COMPLETE 90,000행(약 806블록) · P_CANCEL 2,000행(약 18블록)
  • 주문은 PENDING으로 들어와 결제되면 COMPLETE가 되고, 결제되지 않은 채 오래된 주문은 야간 배치가 CANCEL로 바꾼다. ORD_DT는 주문 시각(시분초 있음)이고 2023년 1월 ~ 2024년 12월이다.
  • PK PK_T_ORD_LS (ORD_SEQ)는 파티션 키가 없어 GLOBAL이다. 다른 인덱스는 없다. 통계는 최신이다.
  • 「결제 대기 목록」은 최근 사흘의 결제 대기 주문(약 33건)을 보여 준다.

원본 SQL ​

sql
CREATE TABLE T_ORD_LS
PARTITION BY LIST (ORD_ST)
( PARTITION P_PENDING  VALUES ('PENDING'),
  PARTITION P_COMPLETE VALUES ('COMPLETE'),
  PARTITION P_CANCEL   VALUES ('CANCEL') )
AS SELECT * FROM T_ORD;
ALTER TABLE T_ORD_LS ADD CONSTRAINT PK_T_ORD_LS PRIMARY KEY (ORD_SEQ);
ALTER TABLE T_ORD_LS ENABLE ROW MOVEMENT;          -- 옮긴 첫날 ORA-14402 를 막으려고

-- 결제 완료 처리 (결제마다)
UPDATE T_ORD_LS
   SET ORD_ST = 'COMPLETE', PAY_DT = SYSDATE, PAY_TP = :pay_tp, PAY_AMT = :pay_amt
 WHERE ORD_SEQ = :ord_seq;

-- 「결제 대기 목록」 (상담원 화면 · 5분마다 · :v_from = 사흘 전 0시)
SELECT ROWID AS rid, ORD_SEQ, CUS_ID, ORD_DT, ORD_AMT
  FROM T_ORD_LS
 WHERE ORD_ST = 'PENDING'
   AND ORD_DT >= :v_from
 ORDER BY ORD_DT;

-- 상담원 금액 정정
UPDATE T_ORD_LS SET ORD_AMT = :amt WHERE ROWID = :rid;

실행계획 학습용 예시 ​

xplan
[옮긴 첫날 09:00 · 결제 완료 처리 · ROW MOVEMENT 를 켜기 전]
  ORA-14402: updating partition key column would cause a partition change

[지금 · 결제 완료 처리 한 건]
--------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name        | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
--------------------------------------------------------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT                     |             |      1 |        |      0 | 00:00:00.01 |      14 |        |       |
|   1 |  UPDATE                              | T_ORD_LS    |      1 |        |      0 | 00:00:00.01 |      14 |        |       |
|   2 |   TABLE ACCESS BY GLOBAL INDEX ROWID | T_ORD_LS    |      1 |      1 |      1 | 00:00:00.01 |       3 |  ROWID | ROWID |
|*  3 |    INDEX UNIQUE SCAN                 | PK_T_ORD_LS |      1 |      1 |      1 | 00:00:00.01 |       2 |        |       |
--------------------------------------------------------------------------------------------------------------------------------

   3 - access("ORD_SEQ"=:ORD_SEQ)

[세션 통계 · 결제 1,000건]
                                옮기기 전 T_ORD    지금 T_ORD_LS (ROW MOVEMENT)
  redo size                          0.41 MB             1.32 MB
  undo change vector size            0.13 MB             0.47 MB
  db block changes                     4,060              11,930
  결제 처리 평균                        1.1 ms              2.9 ms

[「결제 대기 목록」]
----------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name     | Starts | E-Rows | A-Rows |      A-Time | Buffers | Pstart | Pstop |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |          |      1 |        |     33 | 00:00:00.01 |      72 |        |       |
|   1 |  SORT ORDER BY          |          |      1 |     33 |     33 | 00:00:00.01 |      72 |        |       |
|   2 |   PARTITION LIST SINGLE |          |      1 |     33 |     33 | 00:00:00.01 |      72 |      1 |     1 |
|*  3 |    TABLE ACCESS FULL    | T_ORD_LS |      1 |     33 |     33 | 00:00:00.01 |      72 |      1 |     1 |
----------------------------------------------------------------------------------------------------------------

   3 - filter("ORD_DT">=:V_FROM)

[상담원 금액 정정 · 그사이 결제된 주문]
  UPDATE T_ORD_LS SET ORD_AMT = :amt WHERE ROWID = :rid    → 0 행 (에러 없음)

문제 ​

  1. ORA-14402가 난 까닭과, ROW MOVEMENT를 켠 뒤 결제 한 건이 무거워지고 금액 정정이 가끔 0행을 고치는 까닭을 설명하시오.
  2. 「결제 대기 목록」을 빠르게 하려던 목적은 지키면서 이 문제가 생기지 않는 설계를 DDL과 SQL로 쓰시오.
  3. 고친 뒤 결제 처리와 「결제 대기 목록」의 숫자가 어떻게 바뀌는지 어림하시오.
원본 SQL을 고쳐 쓰세요. 인덱스 DDL과 힌트도 쓸 수 있습니다.
무엇이 문제였고 왜 이렇게 고쳤는지 쓰세요.0자