Oracle Sequence 생성과 활용 방법 - 기본부터 성능 최적화까지
Oracle 데이터베이스에서 Sequence는 일련번호를 자동으로 생성해주는 데이터베이스 객체입니다. 주로 기본 키(Primary Key)나 고유 식별자를 생성하는데 사용되며, 테이블과 독립적으로 관리되어 여러 테이블에서 공유할 수 있습니다.
이번 포스팅에서는 Sequence의 기본 개념부터 생성, 수정, 삭제 방법은 물론 성능 최적화 기법과 다른 DBMS와의 비교까지 실무에서 필요한 모든 내용을 상세히 다룹니다. 특히 CACHE 설정에 따른 성능 차이와 RAC 환경에서의 주의사항을 중점적으로 설명합니다.

Sequence는 Oracle 데이터베이스에서 제공하는 자동 번호 생성 객체로, 애플리케이션 레벨에서 MAX+1 방식으로 채번하는 것보다 훨씬 효율적이고 안전합니다.
Sequence는 일련의 숫자를 자동으로 생성해주는 독립적인 데이터베이스 객체입니다. 테이블, 뷰, 인덱스와 같은 스키마 객체의 한 종류이며, 특정 테이블에 종속되지 않아 여러 테이블에서 공유할 수 있습니다.
ID 값을 생성하는 방법은 여러 가지가 있습니다. 각 방식의 장단점을 비교해보겠습니다.
| 채번 방식 | 장점 | 단점 | 적합한 경우 |
|---|---|---|---|
| Sequence | 빠른 성능, 동시성 보장, 관리 용이 | 번호 중간에 갭(gap) 발생 가능 | 대부분의 경우 권장 |
| MAX+1 방식 | 갭 없는 연속 번호 | 성능 저하, 동시성 문제, Lock 경합 | 거래번호 등 갭이 허용되지 않는 경우 |
| 테이블 방식 | 완벽한 제어 가능 | 구현 복잡, 성능 저하 | 특수한 비즈니스 로직이 필요한 경우 |
| GUID/UUID | 완전한 유일성, 분산 환경 적합 | 16바이트 크기, 가독성 낮음 | 글로벌 분산 시스템 |
Sequence는 메모리(Shared Pool)에 캐싱되어 데이터 딕셔너리 접근을 최소화합니다. 이것이 Sequence가 빠른 핵심 이유입니다.
CACHE 20으로 설정된 Sequence의 동작 과정을 살펴보겠습니다.
이 방식으로 데이터 딕셔너리 업데이트 횟수를 1/20로 줄여 성능을 크게 향상시킵니다.
NOCACHE로 설정하면 매번 SEQ$ 테이블을 업데이트해야 합니다. 초당 1,000,000개의 시퀀스를 사용하는 시스템에서 CACHE 20이면 초당 50,000번의 데이터 딕셔너리 업데이트가 발생합니다.
Sequence를 생성하는 CREATE SEQUENCE 명령은 다양한 옵션을 제공하여 비즈니스 요구사항에 맞게 설정할 수 있습니다.
[INCREMENT BY n]
[START WITH n]
[MAXVALUE n | NOMAXVALUE]
[MINVALUE n | NOMINVALUE]
[CYCLE | NOCYCLE]
[CACHE n | NOCACHE]
[ORDER | NOORDER];
| 옵션 | 설명 | 기본값 |
|---|---|---|
| INCREMENT BY n | 시퀀스 증감 값 (양수는 증가, 음수는 감소) | 1 |
| START WITH n | 시퀀스 시작 값 | 오름차순: 1, 내림차순: -1 |
| MAXVALUE n | 최대값 지정 | 오름차순: 10^27 |
| MINVALUE n | 최소값 지정 | 오름차순: 1 |
| CYCLE | 최대/최소값 도달 시 반대쪽 값부터 재시작 | NOCYCLE |
| CACHE n | 메모리에 미리 할당할 시퀀스 개수 | 20 |
| ORDER | 요청 순서대로 번호 생성 보장 (RAC 환경) | NOORDER |
CREATE SEQUENCE employee_seq;
모든 옵션이 기본값으로 설정됩니다. 실무에서는 명시적으로 옵션을 지정하는 것이 좋습니다.
CREATE SEQUENCE order_seq
INCREMENT BY 10
START WITH 1000
MAXVALUE 99999999
NOCYCLE
CACHE 100
NOORDER;
-- 고객 ID용 시퀀스 (성능 최적화)
CREATE SEQUENCE customer_id_seq
START WITH 1
INCREMENT BY 1
NOMAXVALUE
NOCYCLE
CACHE 1000
NOORDER;
CREATE SEQUENCE countdown_seq
INCREMENT BY -1
START WITH 100
MINVALUE 1
MAXVALUE 100
CYCLE
NOCACHE;
CREATE SEQUENCE transaction_seq
START WITH 1
INCREMENT BY 1
CACHE 10000
ORDER; -- 순서 보장 (성능 저하 있음)
CACHE 크기는 시스템 트랜잭션 볼륨에 따라 결정해야 합니다.
| 초당 INSERT 횟수 | 권장 CACHE 크기 | 이유 |
|---|---|---|
| ~100 | 20 (기본값) | 기본값으로 충분 |
| 100~1,000 | 100~500 | 적절한 균형점 |
| 1,000~10,000 | 1,000~5,000 | 데이터 딕셔너리 부하 감소 |
| 10,000 이상 | 10,000~100,000 | 고성능 환경 최적화 |
CACHE 크기 계산 공식: CACHE = 초당 INSERT 수 × 예상 지속 시간(초)
기존 데이터가 있는 테이블에 Sequence를 추가할 때는 현재 최대값보다 큰 값에서 시작해야 합니다.
DECLARE
v_max_id NUMBER;
BEGIN
-- 최대 ID 값 조회
SELECT NVL(MAX(employee_id), 0) INTO v_max_id
FROM employees;
-- 기존 시퀀스 삭제 (있을 경우)
BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE emp_id_seq';
EXCEPTION
WHEN OTHERS THEN NULL;
END;
-- 최대값 + 1부터 시작하는 시퀀스 생성
EXECUTE IMMEDIATE
'CREATE SEQUENCE emp_id_seq ' ||
'START WITH ' || (v_max_id + 1) ||
' INCREMENT BY 1 ' ||
'CACHE 100 NOCYCLE';
DBMS_OUTPUT.PUT_LINE('Sequence created starting from: ' || (v_max_id + 1));
END;
/
생성된 Sequence를 사용하는 방법과 정보를 조회하는 방법을 알아보겠습니다.
Sequence에서 값을 가져오는 두 가지 의사컬럼(Pseudo Column)이 있습니다.
SELECT employee_seq.NEXTVAL FROM dual;
-- INSERT 문에서 사용
INSERT INTO employees (employee_id, first_name, last_name)
VALUES (employee_seq.NEXTVAL, 'John', 'Doe');
-- UPDATE 문에서 사용
UPDATE temp_table
SET id = employee_seq.NEXTVAL
WHERE id IS NULL;
CURRVAL은 같은 세션에서 NEXTVAL을 최소 한 번 호출한 후에만 사용 가능합니다.
SELECT employee_seq.CURRVAL FROM dual;
-- 여러 테이블에 동일한 ID 삽입
INSERT INTO employees (employee_id, name)
VALUES (employee_seq.NEXTVAL, 'Alice');
INSERT INTO employee_log (log_id, employee_id, action)
VALUES (log_seq.NEXTVAL, employee_seq.CURRVAL, 'INSERT');
Sequence는 모든 SQL 구문에서 사용할 수 있는 것은 아닙니다.
| 사용 가능한 위치 | 사용 불가능한 위치 |
|---|---|
| ✓ SELECT 문의 SELECT 목록 | ✗ VIEW의 SELECT 문 |
| ✓ INSERT 문의 VALUES 절 | ✗ DISTINCT가 있는 SELECT 문 |
| ✓ UPDATE 문의 SET 절 | ✗ GROUP BY, HAVING, ORDER BY 절 |
| ✓ 서브쿼리의 SELECT 목록 (일부) | ✗ CHECK 제약조건 |
| ✓ MERGE 문 | ✗ DEFAULT 값 (11g 이전) |
데이터 딕셔너리 뷰를 통해 Sequence 정보를 확인할 수 있습니다.
SELECT sequence_name,
min_value,
max_value,
increment_by,
last_number,
cache_size,
cycle_flag
FROM user_sequences
ORDER BY sequence_name;
SELECT *
FROM user_sequences
WHERE sequence_name = 'EMPLOYEE_SEQ';
-- DBA 권한으로 모든 시퀀스 조회
SELECT sequence_owner,
sequence_name,
last_number,
cache_size
FROM dba_sequences
WHERE sequence_owner = 'HR'
ORDER BY last_number DESC;
트리거를 사용하면 INSERT 시 자동으로 ID를 할당할 수 있습니다.
CREATE SEQUENCE invoice_seq
START WITH 10001
INCREMENT BY 1
CACHE 100;
-- 자동 ID 할당 트리거
CREATE OR REPLACE TRIGGER trg_invoice_id
BEFORE INSERT ON invoices
FOR EACH ROW
BEGIN
IF :NEW.invoice_id IS NULL THEN
:NEW.invoice_id := invoice_seq.NEXTVAL;
END IF;
END;
/
-- 이제 invoice_id 없이 INSERT 가능
INSERT INTO invoices (customer_id, amount, invoice_date)
VALUES (1001, 599.99, SYSDATE);
생성된 Sequence는 필요에 따라 수정하거나 삭제할 수 있습니다.
ALTER SEQUENCE 명령으로 대부분의 옵션을 변경할 수 있지만, START WITH 값은 변경할 수 없습니다.
[INCREMENT BY n]
[MAXVALUE n | NOMAXVALUE]
[MINVALUE n | NOMINVALUE]
[CYCLE | NOCYCLE]
[CACHE n | NOCACHE]
[ORDER | NOORDER];
ALTER SEQUENCE employee_seq INCREMENT BY 5;
ALTER SEQUENCE order_seq
CACHE 5000;
ALTER SEQUENCE countdown_seq CYCLE;
START WITH는 변경할 수 없으므로 현재 값을 특정 값으로 재설정하려면 우회 방법을 사용해야 합니다.
DECLARE
v_current_val NUMBER;
v_desired_val NUMBER := 5000;
v_increment NUMBER;
BEGIN
-- 현재 값 조회
SELECT seq_name.NEXTVAL INTO v_current_val FROM dual;
-- 목표 값과의 차이 계산
v_increment := v_desired_val - v_current_val;
-- INCREMENT BY를 차이만큼 변경
EXECUTE IMMEDIATE
'ALTER SEQUENCE seq_name INCREMENT BY ' || v_increment;
-- 한 번 더 NEXTVAL 호출하여 목표 값으로 이동
SELECT seq_name.NEXTVAL INTO v_current_val FROM dual;
-- INCREMENT BY를 원래대로 복원
EXECUTE IMMEDIATE
'ALTER SEQUENCE seq_name INCREMENT BY 1';
DBMS_OUTPUT.PUT_LINE('Sequence reset to: ' || v_current_val);
END;
/
DROP SEQUENCE employee_seq;
-- Oracle 12c 이상: 존재할 때만 삭제
DROP SEQUENCE IF EXISTS temp_seq;
-- 11g 이하: PL/SQL로 조건부 삭제
BEGIN
EXECUTE IMMEDIATE 'DROP SEQUENCE temp_seq';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -2289 THEN -- ORA-02289: sequence does not exist
RAISE;
END IF;
END;
/
Oracle 외의 주요 DBMS에서 Sequence를 사용하는 방법입니다.
CREATE TABLE customers (
customer_id INT AUTO_INCREMENT PRIMARY KEY,
customer_name VARCHAR(100)
);
-- MySQL 8.0부터 Sequence 지원
CREATE SEQUENCE customer_seq
START WITH 1000
INCREMENT BY 1;
SELECT NEXT VALUE FOR customer_seq;
CREATE SEQUENCE customer_seq
INCREMENT BY 1
START WITH 1000
NO MAXVALUE
NO CYCLE
CACHE 20;
-- 사용 방법
SELECT nextval('customer_seq');
SELECT currval('customer_seq');
CREATE SEQUENCE customer_seq
START WITH 1000
INCREMENT BY 1
MINVALUE 1000
MAXVALUE 9999999
CYCLE
CACHE 100;
-- 사용 방법
SELECT NEXT VALUE FOR customer_seq;
A: 시퀀스 값이 건너뛰는 주요 원인은 다음과 같습니다.
갭이 허용되지 않는 경우 NOCACHE를 사용하거나 테이블 기반 채번 방식을 고려해야 합니다.
A: CURRVAL은 같은 세션에서 NEXTVAL을 최소 한 번 호출한 후에만 사용 가능합니다. 새 세션을 시작했거나 NEXTVAL을 한 번도 호출하지 않았다면 이 오류가 발생합니다.
SELECT employee_seq.CURRVAL FROM dual;
-- ORA-08002: sequence EMPLOYEE_SEQ.CURRVAL is not yet defined in this session
-- 올바른 사용법
SELECT employee_seq.NEXTVAL FROM dual; -- 먼저 NEXTVAL 호출
SELECT employee_seq.CURRVAL FROM dual; -- 이제 사용 가능
A: 대부분의 경우 CACHE 사용을 강력히 권장합니다.
| 구분 | CACHE | NOCACHE |
|---|---|---|
| 성능 | 매우 빠름 (메모리 접근) | 느림 (매번 디스크 I/O) |
| 갭 발생 | 가능 (인스턴스 종료 시) | 롤백 시에만 발생 |
| 동시성 | 우수 | Lock 경합 발생 |
| 권장 사용 | 일반적인 모든 경우 | 갭이 절대 허용되지 않는 경우 |
실제 벤치마크 결과, CACHE 50000으로 50,000개의 시퀀스를 가져오는 데 16초가 걸린 반면, NOCACHE는 58초가 소요되어 약 3.6배의 성능 차이를 보였습니다.
A: ORDER는 여러 인스턴스에서 시퀀스 값의 순서를 보장하지만 성능이 저하됩니다.
단일 인스턴스 환경에서는 ORDER 옵션이 의미가 없으므로 NOORDER를 사용하세요.
A: 성능 최적화를 위한 체크리스트입니다.
CREATE SEQUENCE high_volume_seq
START WITH 1
INCREMENT BY 1
CACHE 10000 -- 대용량 캐시
NOORDER; -- 순서 보장 불필요
A: 시퀀스는 기본적으로 갭이 발생할 수 있도록 설계되었습니다. 갭 없는 번호가 법적으로 요구되는 경우 다음 방법을 고려하세요.
CREATE TABLE gapless_sequence (
sequence_name VARCHAR2(30) PRIMARY KEY,
current_value NUMBER NOT NULL
);
INSERT INTO gapless_sequence VALUES ('INVOICE_SEQ', 0);
-- 다음 값 얻기 함수
CREATE OR REPLACE FUNCTION get_next_gapless_value(
p_seq_name VARCHAR2
) RETURN NUMBER IS
v_value NUMBER;
BEGIN
UPDATE gapless_sequence
SET current_value = current_value + 1
WHERE sequence_name = p_seq_name
RETURNING current_value INTO v_value;
COMMIT;
RETURN v_value;
END;
/
단, 이 방식은 성능 저하와 Lock 경합이 발생하므로 정말 필요한 경우에만 사용하세요.
A: 시퀀스는 숫자만 생성하지만, 함수나 트리거를 사용하여 포맷을 적용할 수 있습니다.
CREATE OR REPLACE FUNCTION generate_order_number
RETURN VARCHAR2
IS
v_order_num VARCHAR2(20);
BEGIN
v_order_num := 'ORD-' ||
TO_CHAR(SYSDATE, 'YYYYMMDD') || '-' ||
LPAD(order_num_seq.NEXTVAL, 5, '0');
RETURN v_order_num;
END;
/
-- 사용 예: ORD-20241217-00001
INSERT INTO orders (order_id, customer_id, order_date)
VALUES (generate_order_number(), 1001, SYSDATE);
A: 아니요, Sequence는 테이블과 독립적인 객체이므로 테이블을 삭제해도 Sequence는 남아있습니다. 명시적으로 DROP SEQUENCE를 실행해야 삭제됩니다.
A: 네, Sequence는 여러 테이블에서 공유 가능합니다. 전체 시스템에서 유일한 ID가 필요한 경우 유용합니다.
CREATE SEQUENCE global_id_seq START WITH 1 CACHE 1000;
-- 여러 테이블에서 공유
INSERT INTO customers VALUES (global_id_seq.NEXTVAL, 'Alice');
INSERT INTO suppliers VALUES (global_id_seq.NEXTVAL, 'ABC Corp');
INSERT INTO products VALUES (global_id_seq.NEXTVAL, 'Widget');
A: Sequence도 다른 객체와 마찬가지로 권한 관리가 필요합니다.
GRANT SELECT ON employee_seq TO hr_user;
-- Sequence 수정 권한 부여
GRANT ALTER ON employee_seq TO hr_admin;
-- 권한 회수
REVOKE SELECT ON employee_seq FROM hr_user;
Oracle Sequence는 데이터베이스에서 유일한 번호를 효율적으로 생성하는 필수 객체입니다. 애플리케이션 레벨에서 MAX+1 방식을 사용하는 것보다 훨씬 빠르고 안전하며, 동시성 문제도 자동으로 해결해줍니다.
성능 최적화의 핵심은 적절한 CACHE 크기 설정입니다. 초당 트랜잭션 볼륨에 따라 CACHE 값을 조정하면 데이터 딕셔너리 업데이트 횟수를 크게 줄여 성능을 향상시킬 수 있습니다. 특히 고트래픽 시스템에서는 CACHE 값을 1,000~10,000으로 설정하는 것이 일반적입니다.
주의할 점은 다음과 같습니다.
트리거와 함께 사용하면 INSERT 시 자동으로 ID를 할당할 수 있어 개발 생산성이 향상됩니다. 또한 함수를 활용하면 "ORD-20241217-00001"과 같은 포맷팅된 번호도 생성할 수 있습니다.
실무에서는 갭이 허용되는 대부분의 경우 Sequence를 사용하고, 법적으로 연속 번호가 요구되는 경우에만 테이블 기반 방식을 고려하는 것이 좋습니다.
끝.
'Development > Database' 카테고리의 다른 글
| [DB] Oracle SQL 완벽 정리 (0) | 2022.08.31 |
|---|---|
| [DB] Oracle Listener 구성부터 관리까지 완벽 분석 (1) | 2020.04.08 |
| [DB] ROWNUM과 ROWID의 완벽한 이해 (0) | 2020.04.08 |
| [DB] ORACLE DB 사용자 계정 패스워드 없애는 방법 (0) | 2020.04.08 |
| [DB] ORACLE DB SYSTEM, SYS 암호 변경 (0) | 2020.04.08 |