시퀀스와 채번 성능
시퀀스 CACHE와 SQ 경합, RAC ORDER와 NOORDER 및 채번 테이블과 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 비교
CREATE SEQUENCE seq_test START WITH 1 INCREMENT BY 1 CACHE 2 NOORDER;DECLARE
v NUMBER;
BEGIN
FOR i IN 1.. 100000 LOOP
SELECT seq_test.NEXTVAL INTO v FROM dual;
END LOOP;
END;
/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
ALTER SEQUENCE seq_sq_enqueue NOCACHE;
-- CACHE_SIZE 0
SELECT sequence_name, cache_size, order_flag FROM user_sequences;EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();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
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로 나왔음
SELECT sequence_name, cache_size, order_flag, last_number
FROM user_sequences
WHERE sequence_name LIKE 'SEQ\_%' ESCAPE '\'
ORDER BY 1;결과 해석:
LAST_NUMBER는 데이터 사전에 기록된 다음 묶음 시작값임
소량 채번으로는 대기 누적이 작게 보일 수 있음
#채번 테이블과 자율 트랜잭션
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를 걸었고 그 뒤 주문 저장이 느려짐
현업 확인 결과 주문 순서는 주문일시로 보고, 번호는 겹치지만 않으면 됨
CREATE SEQUENCE seq_order_no CACHE 20 ORDER;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;
/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를 씀
바꾼 뒤 번호가 노드별로 섞여 보이는 것은 정상 동작임
#확인 문제: 두 시퀀스 중 경합하는 쪽 찾기
상황: 포인트 적립 배치를 두 노드에서 동시에 돌리면 느려지고, 배치는 적립 이력 번호와 거래 번호 두 시퀀스를 씀
CREATE SEQUENCE <SCHEMA>.seq_point_hist CACHE 3 NOORDER;
CREATE SEQUENCE <SCHEMA>.seq_trade CACHE 1000 NOORDER;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;
/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로 만들어 두었음
이제는 번호가 조금 건너뛰어도 괜찮다고 함
CREATE SEQUENCE <SCHEMA>.seq_member NOCACHE NOORDER;SET TIMING ON
DECLARE v NUMBER;
BEGIN
FOR i IN 1..50000 LOOP
SELECT seq_member.NEXTVAL INTO v FROM dual;
END LOOP;
END;
/-- 부하 직전과 직후
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를 비교함