데이터베이스 연구실.log

시퀀스와 채번 성능

시퀀스 CACHE와 SQ 경합, RAC ORDER와 NOORDER 및 채번 테이블과 MAX+1 방식의 비교

SEQ_POINT_HISTP2addmrpt.sql시퀀스CACHENOCACHEORDERNOORDERenq: SQ - contentionrow cache lockDFS lock handle채번 테이블자율 트랜잭션MAX+1

#핵심 요약

시퀀스는 CACHE n만큼 번호를 미리 메모리에 확보해 두고, 다 쓰면 다음 묶음을 SQ enqueue 아래에서 받아 옴
동시 요청은 enq: SQ - contention으로 기다림

CACHE가 작으면 재할당이 잦아 SQ 락, 데이터 사전 갱신(row cache lock), RAC 메시지가 늘어남
크게 잡으면 재할당이 줄지만 재기동 때 미사용 번호를 건너뛸 수 있음

RAC에서 NOORDER는 노드마다 다른 번호 범위를 써서(예: 1번 노드 120, 2번 노드 2140) 고유하지만 전체 순서는 보장하지 않음
ORDER는 요청 순서대로 번호를 주지만 노드 간 조율 대기가 생김
실습에서는 큰 CACHE + NOORDER를 RAC 권장으로 정리했음

채번 방식 선택: 동시 채번이 많지 않으면 MAX+1, 많고 PK가 단일 컬럼이면 시퀀스, PK가 "구분 속성 + 순번"이고 구분값 종류가 많으면 MAX+1, 적으면 순환(CYCLE) 시퀀스

채번 테이블은 번호 발급과 본 INSERT를 한 트랜잭션에 묶으면 커밋까지 락이 유지되어 직렬화됨
채번 함수를 자율 트랜잭션으로 만들어 바로 커밋하면 락이 짧아짐

#개념과 동작 원리

아래 NOCACHE 설명은 ORDER 비교표 기준임

시퀀스 조합 특징
NOCACHE 매번 데이터 사전을 갱신해 가장 느림
CACHE + ORDER 노드 간 순서 조율 메시지가 계속 발생
작은 CACHE + NOORDER 캐시가 금방 소진되어 SQ enqueue
큰 CACHE + NOORDER 경합 최소, RAC 권장
관련 대기 조율 대상
enq: SQ - contention 번호 묶음 재할당
row cache lock 데이터 사전(SEQ$) 갱신
DFS lock handle / SV ORDER 순서 조율

실습의 22기 수업 AWR 기록(작은 CACHE 부하): enq: SQ - contention이 DB Time의 약 80%(평균 약 9ms, 1위), row cache lock 약 10%(2위), RAC 메시지 대기(DLM cross inst call completion, KJC: Wait for msg sends to complete)는 수치 미기재
실습에서는 번호 순서가 업무상 필요하면 시간 기준 정렬에 등록일시 컬럼을 쓰라고 적었음

채번 방식 장점 단점
채번 테이블 번호를 직접 관리 커밋까지 행 락이 유지되어 동시 입력이 직렬화
시퀀스 메모리 캐시로 가장 빠름 롤백된 번호는 회수되지 않아 결번 발생, RAC ORDER는 느림
MAX+1 별도 객체 불필요, 구분 속성별 순번에 유연 동시 입력 시 같은 번호 충돌(ORA-00001)과 재시도 필요, MAX 조회용 인덱스 필수

시퀀스 문법은 시퀀스와 동의어에 있음

#필수 명령어 또는 SQL

#CACHE 2 경합과 CACHE 2000 비교

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE SEQUENCE seq_test START WITH 1 INCREMENT BY 1 CACHE 2 NOORDER;
SQL*Plus (실습 계정, 두 노드 동시)객체 또는 설정을 변경함
DECLARE
  v NUMBER;
BEGIN
  FOR i IN 1.. 100000 LOOP
    SELECT seq_test.NEXTVAL INTO v FROM dual;
  END LOOP;
END;
/
SQL*Plus (SYSDBA)조회만 함
SELECT DISTINCT s.event, o.owner, o.object_name
  FROM gv$session s
  JOIN dba_objects o ON o.object_id = s.p2
 WHERE s.event = 'enq: SQ - contention'
   AND s.state = 'WAITING' AND s.username = '<SCHEMA>';

SELECT inst_id, total_waits, ROUND(time_waited_micro / 1000000, 1) AS wait_sec
  FROM gv$system_event
 WHERE event = 'enq: SQ - contention';

결과 해석: enq: SQ - contention의 P2가 시퀀스 오브젝트 번호임
실습에서는 부하 전후 AWR 스냅샷으로 보고서를 만들어 ADDM의 캐시 확대 권고를 확인한 뒤 ALTER SEQUENCE seq_test CACHE 2000으로 바꿔 같은 부하의 Elapsed와 SQ 대기 증가분을 비교했음

#NOCACHE와 row cache lock

SQL*Plus (실습 계정)객체 또는 설정을 변경함
ALTER SEQUENCE seq_sq_enqueue NOCACHE;
-- CACHE_SIZE 0
SELECT sequence_name, cache_size, order_flag FROM user_sequences;
SQL*Plus (SYSDBA)객체 또는 설정을 변경함
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();
SQL*Plus (SYSDBA)조회만 함
SELECT inst_id, sid, event FROM gv$session
 WHERE state = 'WAITING' AND username = '<SCHEMA>'
   AND (event LIKE '%lock%' OR event LIKE '%enq%');

SELECT s.inst_id, s.sid, s.sql_id, q.sql_text
  FROM gv$session s
  JOIN gv$sqlarea q ON q.inst_id = s.inst_id AND q.sql_id = s.sql_id
 WHERE s.sid = <SID> AND s.inst_id = <INST_ID>;

@?/rdbms/admin/addmrpt.sql
@?/rdbms/admin/awrrpt.sql

결과 해석:

NOCACHE는 NEXTVAL마다 SEQ$를 갱신하므로 동시 채번에서 SQ 대기보다 데이터 사전 경합(row cache lock, library cache lock)이 앞설 수 있고, RAC에서는 노드 간 데이터 사전 조율 비용이 더해짐

실습 기록: OWI 실습 스크립트로 NOCACHE 시퀀스에 10세션 300초 부하를 주자 GV$SESSION_WAIT에 inst 1의 여러 세션이 row cache lock으로 나왔음
시나리오 이름은 sq_enqueue였지만 실제 주 대기는 row cache lock이었음

부하 전후 AWR 스냅샷으로 ADDM 권고와 원인 시퀀스를 확인한 뒤 CACHE 10으로 바꿔 같은 부하를 다시 주자 row cache lock이 거의 사라졌음
개선 효과는 같은 부하의 대기 시간과 처리량을 전후 비교해 판단함

실습 준비 단계의 DELETE BACKUP, DELETE COPY는 다른 실습 백업을 지우므로 필요할 때만 함

#ORDER와 NOORDER

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE SEQUENCE seq_ord   START WITH 1 INCREMENT BY 1 CACHE 20 ORDER;
CREATE SEQUENCE seq_noord START WITH 1 INCREMENT BY 1 CACHE 20 NOORDER;

결과 해석:

실습 기록: 두 노드에서 번갈아 NEXTVAL을 부르면 seq_ord는 1, 2, 3, 4로 요청 순서대로, seq_noord는 1번 노드가 1, 2, 2번 노드가 21, 22로 나왔음

SQL*Plus (실습 계정)조회만 함
SELECT sequence_name, cache_size, order_flag, last_number
  FROM user_sequences
 WHERE sequence_name LIKE 'SEQ\_%' ESCAPE '\'
 ORDER BY 1;

결과 해석:

LAST_NUMBER는 데이터 사전에 기록된 다음 묶음 시작값임
소량 채번으로는 대기 누적이 작게 보일 수 있음

#채번 테이블과 자율 트랜잭션

SQL*Plus (실습 계정)객체 또는 설정을 변경함
CREATE OR REPLACE FUNCTION fn_get_no_후
RETURN NUMBER
IS
  PRAGMA AUTONOMOUS_TRANSACTION;
  v_no NUMBER;
BEGIN
  SELECT 현재번호 + 1 INTO v_no
    FROM 채번관리
   WHERE 테이블명 = '주문거래'
     FOR UPDATE;

  UPDATE 채번관리 SET 현재번호 = v_no WHERE 테이블명 = '주문거래';

  -- 채번 즉시 커밋, 락 해제
  COMMIT;
  RETURN v_no;
END;
/

결과 해석: 실습의 튜닝 전 함수는 COMMIT과 PRAGMA가 없어 호출자가 커밋할 때까지 채번관리 행 락이 유지되고, 두 번째 세션이 FOR UPDATE에서 막힘
자율 트랜잭션 함수로 바꾸면 두 번째 세션이 기다리지 않고 다음 번호를 받음
자율 트랜잭션으로 받은 번호는 본 트랜잭션이 롤백되어도 되돌려지지 않는다는 점은 기재 없음

연습 문제(인덱스 없는 10만 건 테이블에서 카테고리별 MAX(순번) + 1)의 답은 기재 없음
실습 교재 정리대로라면 MAX 조회를 빠르게 하려면 (카테고리, 순번) 인덱스가 필요함

#확인 문제: 순서 보장 옵션을 건 뒤 주문 저장이 느려짐

상황: 주문번호가 두 노드에서 뒤섞여 보인다며 개발자가 시퀀스에 ORDER를 걸었고 그 뒤 주문 저장이 느려짐
현업 확인 결과 주문 순서는 주문일시로 보고, 번호는 겹치지만 않으면 됨

SQL*Plus (실습 계정, 한 창에서 한 번)객체 또는 설정을 변경함
CREATE SEQUENCE seq_order_no CACHE 20 ORDER;
SQL*Plus (실습 계정, 두 노드에서 동시)객체 또는 설정을 변경함
SET TIMING ON
DECLARE v NUMBER;
BEGIN
  FOR i IN 1..100000 LOOP
    SELECT seq_order_no.NEXTVAL INTO v FROM dual;
  END LOOP;
END;
/
SQL*Plus (SYSDBA, 1번 노드)객체 또는 설정을 변경함
SELECT inst_id, event, COUNT(*)
  FROM gv$session
 WHERE state = 'WAITING' AND username = '<SCHEMA>'
   AND wait_class <> 'Idle'
 GROUP BY inst_id, event;

SELECT sequence_name, cache_size, order_flag
  FROM dba_sequences
 WHERE sequence_name = 'SEQ_ORDER_NO';

ALTER SEQUENCE <SCHEMA>.seq_order_no NOORDER CACHE 1000;

결과 해석:

예상값: 대기는 DFS lock handle이나 enq: SV 계열, ORDER_FLAG = Y

ORDER는 인스턴스 사이에 채번 순서를 맞추는 비용이 들며, 고유성만 필요하면 NOORDER와 충분한 CACHE를 씀

바꾼 뒤 번호가 노드별로 섞여 보이는 것은 정상 동작임

#확인 문제: 두 시퀀스 중 경합하는 쪽 찾기

상황: 포인트 적립 배치를 두 노드에서 동시에 돌리면 느려지고, 배치는 적립 이력 번호와 거래 번호 두 시퀀스를 씀

SQL*Plus (실습 계정, 한 창에서 한 번)객체 또는 설정을 변경함
CREATE SEQUENCE <SCHEMA>.seq_point_hist CACHE 3 NOORDER;
CREATE SEQUENCE <SCHEMA>.seq_trade CACHE 1000 NOORDER;
SQL*Plus (실습 계정, 1번 노드 2창 + 2번 노드 2창 동시)객체 또는 설정을 변경함
SET TIMING ON
DECLARE v1 NUMBER; v2 NUMBER;
BEGIN
  FOR i IN 1..50000 LOOP
    SELECT seq_point_hist.NEXTVAL INTO v1 FROM dual;
    SELECT seq_trade.NEXTVAL INTO v2 FROM dual;
  END LOOP;
END;
/
SQL*Plus (SYSDBA, 1번 노드)객체 또는 설정을 변경함
SELECT DISTINCT o.owner, o.object_name
  FROM gv$session s
  JOIN dba_objects o ON o.object_id = s.p2
 WHERE s.event = 'enq: SQ - contention'
   AND s.state = 'WAITING' AND s.username = '<SCHEMA>';

ALTER SEQUENCE <SCHEMA>.seq_point_hist CACHE 1000;

SELECT inst_id, total_waits
  FROM gv$system_event
 WHERE event = 'enq: SQ - contention';

결과 해석:

부하 전에 SQ 대기 누적값을 적어 두고, 고친 뒤 같은 배치의 Elapsed와 SQ 대기 증가분을 비교함

예상값: 경합 시퀀스 SEQ_POINT_HIST(CACHE 3), SEQ_TRADE(CACHE 1000)는 경합 대상이 아님

enq: SQ - contention의 P2가 시퀀스 오브젝트 번호라 두 시퀀스 중 문제 쪽만 골라 고칠 수 있음

#확인 문제: 캐시 없는 회원번호 시퀀스로 가입이 멈춤

상황: 회원가입 이벤트 중 가입 처리가 거의 멈췄고, 번호가 건너뛰는 것이 싫어 회원번호 시퀀스를 NOCACHE로 만들어 두었음
이제는 번호가 조금 건너뛰어도 괜찮다고 함

SQL*Plus (실습 계정, 한 창에서 한 번)객체 또는 설정을 변경함
CREATE SEQUENCE <SCHEMA>.seq_member NOCACHE NOORDER;
SQL*Plus (실습 계정, 4창 동시)객체 또는 설정을 변경함
SET TIMING ON
DECLARE v NUMBER;
BEGIN
  FOR i IN 1..50000 LOOP
    SELECT seq_member.NEXTVAL INTO v FROM dual;
  END LOOP;
END;
/
SQL*Plus (SYSDBA, 1번 노드)객체 또는 설정을 변경함
-- 부하 직전과 직후
EXEC dbms_workload_repository.create_snapshot();
@?/rdbms/admin/addmrpt.sql
ALTER SEQUENCE <SCHEMA>.seq_member CACHE 1000;

결과 해석:

확인 항목 기준과 조치
주 대기 AWR과 ADDM에서 row cache lock 확인
번호 요구 건너뜀 허용이므로 큰 CACHE, 순서 요구가 없으므로 NOORDER 유지
측정 구간 AWR 스냅샷 구간에 실제 부하가 들어 있는지 확인

바꾼 뒤 같은 부하를 다시 돌려 Elapsed를 비교함

SEO_HKabsumveritas@gmail.comBuilt with Claude Code & Codex

마지막 갱신 개인 학습 기록이며 운영 환경 적용 전 공식 문서 확인 필요

두 글자 이상 입력, 위아래 화살표로 이동하고 Enter로 열기