CREATE SEQUENCE
CREATE SEQUENCE는 자동 증가하는 숫자 값을 만들어 내는 이름 붙은 시퀀스 생성기를 정의해요.
출처: 문서
본문
**Turso 확장 기능**: CREATE SEQUENCE는 표준 SQLite에는 없는 Turso 전용 문이에요.CREATE SEQUENCE 문은 숫자 값의 계열을 만들어 내는 이름 붙은 시퀀스 객체를 정의해요. 시퀀스는 어느 테이블에도 종속되지 않고, nextval(), currval(), setval() 함수를 통해 여러 테이블과 쿼리에서 쓸 수 있어요.
문법
CREATE SEQUENCE [IF NOT EXISTS] [schema-name.]sequence-name
[START [WITH] value]
[INCREMENT [BY] value]
[MINVALUE value]
[MAXVALUE value]
[CYCLE | NO CYCLE];
설명
시퀀스는 자기 설정에 따라 정수 값의 계열을 만들어 내요. nextval()을 호출할 때마다 계열의 다음 값이 나와요. 시퀀스는 데이터베이스 스키마에 영속 저장되고 재시작 후에도 유지돼요.
INTEGER PRIMARY KEY의 AUTOINCREMENT와 달리, 시퀀스는 독립된 객체예요. 하나의 시퀀스가 여러 테이블에 값을 공급할 수 있고, 여러 시퀀스가 같은 테이블의 서로 다른 컬럼에 값을 넣어줄 수도 있어요.
매개변수
| 매개변수 | 설명 | 기본값 |
|---|---|---|
IF NOT EXISTS |
같은 이름의 시퀀스가 이미 있을 때 오류를 막아줘요. 시퀀스가 있으면 문은 아무 것도 하지 않아요. | |
schema-name |
붙은(attached) 데이터베이스 이름. 생략하면 main 데이터베이스 | |
sequence-name |
만들 시퀀스의 이름 | |
START [WITH] |
nextval()이 처음 반환할 값 |
오름차순 시퀀스는 MINVALUE, 내림차순 시퀀스는 MAXVALUE |
INCREMENT [BY] |
연속 값 사이의 간격. 내림차순 시퀀스는 음수를 쓰세요 | 1 |
MINVALUE |
시퀀스의 하한 | 오름차순 시퀀스는 1, 내림차순 시퀀스는 64비트 정수의 최솟값 |
MAXVALUE |
시퀀스의 상한 | 오름차순 시퀀스는 64비트 정수의 최댓값, 내림차순 시퀀스는 -1 |
CYCLE |
시퀀스가 경계에 도달하면 오류 대신 반대쪽 끝으로 돌아가요 | NO CYCLE |
검증
다음 규칙은 생성 시점에 강제돼요:
INCREMENT BY는 0이면 안 돼요.MINVALUE는MAXVALUE보다 작아야 해요.- 오름차순 시퀀스(
INCREMENT > 0)에서:START는MINVALUE보다 크거나 같아야 해요. - 내림차순 시퀀스(
INCREMENT < 0)에서:START는MAXVALUE보다 작거나 같아야 해요.
시퀀스 함수
nextval()
시퀀스에서 다음 값을 반환하고 시퀀스를 진행시켜요.
SELECT nextval('counter');
-- 1
SELECT nextval('counter');
-- 2
첫 호출에서 nextval()은 START 값을 반환해요. 그 후의 호출마다 INCREMENT BY만큼 증가해요.
WAL 모드에서의 시퀀스 영속화는 트랜잭션적이에요: 시퀀스 백킹 테이블 업데이트는 nextval()을 호출한 트랜잭션과 함께 커밋되거나 롤백돼요. BEGIN CONCURRENT 안의 MVCC 모드(journal_mode=experimental_mvcc)에서는 백킹 테이블 쓰기가 자율적이라서 바깥 ROLLBACK에도 살아남아요 (postgres 방식). BEGIN / BEGIN IMMEDIATE / autocommit에서는 내부 쓰기가 바깥과 묶여서 함께 롤백돼요 (SQLite 방식).
Turso는 갭 없는(gap-free) 시퀀스를 보장하지 않고, WAL 모드에서나 배타적(BEGIN IMMEDIATE / BEGIN DEFERRED) MVCC 바깥에서 롤백된 트랜잭션의 값이 PostgreSQL 스타일로 영구 소실된다는 보장도 하지 않아요.
트랜잭션이 nextval()을 호출하고 롤백하면, 커밋된 트랜잭션이 그 값이나 더 큰 값을 쓰지 않았다면 그 값은 나중에 다시 만들어질 수 있어요. 다른 동시 트랜잭션이 더 큰 값을 할당한 채 커밋하면, 롤백된 값은 갭으로 남을 수 있어요. 보장되는 건, 영속화된 시퀀스 상태가 커밋된 데이터베이스 상태와 일관성을 유지한다는 거예요.
currval()
현재 연결에서 nextval()이 마지막으로 반환한 값을 반환해요. 현재 세션에서 이 시퀀스에 nextval()을 호출한 적이 없으면 오류가 발생해요.
CREATE SEQUENCE my_seq;
SELECT nextval('my_seq');
-- 1
SELECT nextval('my_seq');
-- 2
SELECT currval('my_seq');
-- 2
setval()
시퀀스를 명시적인 값으로 설정해요.
setval(sequence_name, value)
setval(sequence_name, value, is_called)
| 매개변수 | 설명 |
|---|---|
sequence_name |
시퀀스의 이름 |
value |
설정할 값. 시퀀스의 MINVALUE..MAXVALUE 범위 안이어야 해요. |
is_called |
true(기본값)이면 다음 nextval()은 value + increment를 반환해요. false이면 다음 nextval()은 value 자체를 반환해요. |
CREATE SEQUENCE my_seq;
SELECT setval('my_seq', 100);
-- 100
SELECT nextval('my_seq');
-- 101
SELECT setval('my_seq', 200, false);
-- 200
SELECT nextval('my_seq');
-- 200 (returns the set value since is_called was false)
SELECT nextval('my_seq');
-- 201
예제
기본 시퀀스
CREATE SEQUENCE counter;
SELECT nextval('counter');
-- 1
SELECT nextval('counter');
-- 2
SELECT nextval('counter');
-- 3
커스텀 시작 값과 증분
CREATE SEQUENCE order_seq START WITH 1000 INCREMENT BY 10;
SELECT nextval('order_seq');
-- 1000
SELECT nextval('order_seq');
-- 1010
SELECT nextval('order_seq');
-- 1020
내림차순 시퀀스
CREATE SEQUENCE countdown START WITH 10 INCREMENT BY -1 MINVALUE 1 MAXVALUE 10;
SELECT nextval('countdown');
-- 10
SELECT nextval('countdown');
-- 9
SELECT nextval('countdown');
-- 8
순환 시퀀스
시퀀스가 경계에 도달하면 반대쪽으로 돌아가요:
CREATE SEQUENCE color_cycle MINVALUE 1 MAXVALUE 3 CYCLE;
SELECT nextval('color_cycle');
-- 1
SELECT nextval('color_cycle');
-- 2
SELECT nextval('color_cycle');
-- 3
SELECT nextval('color_cycle');
-- 1 (wraps around)
SELECT nextval('color_cycle');
-- 2
CYCLE이 없으면 경계를 넘어설 때 오류가 발생해요:
CREATE SEQUENCE bounded MINVALUE 1 MAXVALUE 3;
SELECT nextval('bounded');
-- 1
SELECT nextval('bounded');
-- 2
SELECT nextval('bounded');
-- 3
SELECT nextval('bounded');
-- Error: nextval: reached maximum value of sequence "bounded"
컬럼 기본값으로 쓰는 시퀀스
시퀀스는 테이블 컬럼에 기본값을 공급할 수 있어요:
CREATE SEQUENCE invoice_seq START WITH 1000;
CREATE TABLE invoices (
id INTEGER PRIMARY KEY,
invoice_number INTEGER DEFAULT (nextval('invoice_seq')),
description TEXT
);
INSERT INTO invoices (description) VALUES ('First order');
INSERT INTO invoices (description) VALUES ('Second order');
INSERT INTO invoices (description) VALUES ('Third order');
SELECT * FROM invoices;
-- 1|1000|First order
-- 2|1001|Second order
-- 3|1002|Third order
IF NOT EXISTS
-- Safe to run multiple times
CREATE SEQUENCE IF NOT EXISTS my_seq;
CREATE SEQUENCE IF NOT EXISTS my_seq;
SELECT nextval('my_seq');
-- 1
동시성
nextval()과 setval()은 시퀀스 상태를 백킹 테이블에 영속화해요.
WAL 모드(기본값)에서는 백킹 테이블 쓰기가 호출자의 쓰기 트랜잭션에 실려요. 한 번에 하나의 연결만 쓰기 잠금을 잡을 수 있어서, 다른 연결이 이미 쓰고 있다면 SELECT nextval(...)은 SQLITE_BUSY("Database is busy")를 반환해요. 이건 AUTOINCREMENT와 다른데, AUTOINCREMENT는 프로세스 로컬 할당자를 쓰고 커밋된 워터마크를 테이블 삽입과 함께 기록해요.
MVCC 모드(journal_mode=experimental_mvcc)에서는 BEGIN CONCURRENT 바깥 안의 nextval()이 백킹 테이블 쓰기를 자율적인 내부 트랜잭션으로 감싸요. 내부 트랜잭션은 즉시 커밋되고, 바깥 트랜잭션이 커밋되든 롤백되든 독립적이에요. 덕분에 여러 동시 BEGIN CONCURRENT 호출자가 각자 새 값을 얻을 수 있고(백킹 테이블 위의 표준 MVCC PK 충돌 + 유한 재시도로 직렬화됨), 이 경우 PostgreSQL의 "소실된 값(burnt value)" 의미 체계와 맞아요. autocommit, BEGIN, BEGIN IMMEDIATE, BEGIN DEFERRED에서는 바깥이 배타적 잠금을 잡고 내부 쓰기가 인라인으로 돌아가요 — 이 모드들에서는 ROLLBACK이 그 값을 되돌려요 (SQLite 방식).
롤백이 일어난 경우, 생성된 값이 재사용된다는 보장도 갭으로 남는다는 보장도 없어요. 재사용 여부는 바깥 트랜잭션 모드와, 다른 커밋된 트랜잭션이 영구 워터마크를 그 값 너머로 밀었는지에 따라 달라져요.
WAL 모드에서 많은 동시 연결로 nextval()을 호출하는 워크로드라면, 대신 AUTOINCREMENT를 쓰거나 애플리케이션 수준에서 시퀀스 접근을 직렬화하는 걸 고려해 보세요.
현재 값 복구하기
새 연결에서 접근할 수 있는 "현재 값"은 모드에 따라 달라져요:
-
WAL 모드:
sqlite_sequence에서seq를 읽거나(AUTOINCREMENT 테이블), 시퀀스 백킹 테이블을 직접 조회하세요:SELECT MAX(value) FROM "__turso_internal_seq_<sequence_name>";둘 다 커밋 시점에 동기화 상태로 유지돼요.
-
MVCC 모드:
sqlite_sequence는 체크포인트 시점에만 갱신돼요 — 체크포인트 사이에는 실제 고수위선(hight-water mark)보다 늦거나, 새로 만든 AUTOINCREMENT 테이블에서는 비어 있을 수 있어요. MVCC 모드에서 살아있는 값을 복구할 때는sqlite_sequence에 의존하면 안 돼요. 대신 내부 시퀀스 백킹 테이블을 직접 조회하세요:SELECT MAX(value) FROM "__turso_internal_seq_<sequence_name>";AUTOINCREMENT의 시퀀스 이름은
__turso_internal_autoincrement_<table>이라서, 백킹 테이블 조회는 이렇게 돼요:SELECT MAX(value) FROM "__turso_internal_seq___turso_internal_autoincrement_<table>";PRAGMA wal_checkpoint(TRUNCATE)이후에는 백킹 테이블이 한 행으로 합쳐지고,sqlite_sequence도 AUTOINCREMENT 테이블마다 정확히 한 행으로 갱신돼요 — 그 시점부터는 둘 다 평범한 SQLite 호환 쿼리로 안전하게 읽을 수 있어요.
오류
| 오류 | 원인 |
|---|---|
sequence "name" already exists |
같은 이름의 시퀀스가 이미 존재하는데 IF NOT EXISTS를 지정하지 않았어요. |
INCREMENT must not be zero |
INCREMENT BY 0을 지정했어요. |
MINVALUE (x) must be less than MAXVALUE (y) |
MINVALUE가 MAXVALUE보다 크거나 같아요. |
START value (x) cannot be less than MINVALUE (y) |
오름차순 시퀀스에서 START가 MINVALUE보다 낮아요. |
START value (x) cannot be greater than MAXVALUE (y) |
내림차순 시퀀스에서 START가 MAXVALUE보다 높아요. |
더 알아보기 (Learn more)
- DROP SEQUENCE - 시퀀스 삭제하기
- CREATE TABLE -
INTEGER PRIMARY KEY컬럼의AUTOINCREMENT