공공부하자개발 · 영어 학습 노트
SQL
SQL 실무페이징·검색·이력·통계·채번·이관0/9 완료
  • 01페이징 쿼리
  • 02동적 검색 조건
  • 03이력 테이블과 시점 조회
  • 04기간별 통계 보고서
  • 05중복 데이터 찾기와 정리
  • 06트랜잭션 기초
  • 07채번과 순번 관리
  • 08저장 프로시저·함수 기초
  • 09데이터 이관·검증 쿼리
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › SQL 실무 › 07 / 9

채번과 순번 관리

일자별 일련번호, 채번 테이블, 동시 요청
섹션 6진행 0 / 9
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6정리‹ 이전다음 ›

3. 코드 예제

소스: sql-src/work_07_numbering/01_max_plus_one.sql, 02_seq_table.sql. 주문 헤더 order_h(order_no PK, ord_date, cust, amt)에는 9월 29일 2건, 9월 30일 3건이 있습니다. 채번 테이블 seq_no(biz, ymd, last_no, PK 는 biz 와 ymd 둘)는 02 에서 만듭니다.

H2 는 세션을 하나만 쓰므로 동시 요청 자체는 재현할 수 없습니다. 동시에 같은 번호를 받은 상황은 그 번호를 직접 INSERT 해서 재현하고, 두 세션의 시간 순서는 도식으로 보입니다.

예제 1: MAX + 1 로 다음 번호 만들기

번호 형태가 ORD-20260930- 다음 4자리라서 앞 13글자를 자르면 14번째부터가 일련번호입니다. 그 값의 최댓값에 1 을 더하고, 행이 없으면 NULL 이므로 COALESCE 로 0 을 줍니다. 결과는 4자리로 LPAD 합니다.

sql
SELECT
       'ORD-20260930-' || LPAD(CAST(COALESCE(MAX(CAST(SUBSTRING(order_no, 14) AS INT)), 0) + 1 AS VARCHAR), 4, '0') AS next_no
  FROM order_h
 WHERE order_no LIKE 'ORD-20260930-%';
text
NEXT_NO
-----------------
ORD-20260930-0004
(1행)

30일에 이미 0001 부터 0003 까지 있으므로 다음은 0004 입니다. 주문이 하나도 없는 10월 1일로 같은 쿼리를 돌리면 MAX 가 NULL 이라 0 이 되어 ORD-20261001-0001 이 나옵니다(소스 2번).

예제 2: 조회한 번호로 INSERT

번호 계산과 INSERT 를 한 문장으로 합치면 코드가 줄어듭니다. 결과를 30일 주문으로 확인합니다.

sql
INSERT INTO order_h
SELECT
       'ORD-20260930-' || LPAD(CAST(COALESCE(MAX(CAST(SUBSTRING(order_no, 14) AS INT)), 0) + 1 AS VARCHAR), 4, '0')
     , DATE '2026-09-30'
     , '사아전자'
     , 700
  FROM order_h
 WHERE order_no LIKE 'ORD-20260930-%';
SELECT
       order_no
     , cust
  FROM order_h
 WHERE ord_date = DATE '2026-09-30'
 ORDER BY order_no;
text
ORDER_NO          | CUST
------------------+---------
ORD-20260930-0001 | 가나상사
ORD-20260930-0002 | 마바유통
ORD-20260930-0003 | 다라물산
ORD-20260930-0004 | 사아전자
(4행)

한 세션이면 완벽하게 동작합니다. 문제는 이 문장이 같은 순간에 두 번 실행될 때입니다.

예제 3: 동시 요청의 시간 순서 (도식, 설명용)

아래 도식은 두 세션이 같은 MAX 를 읽는 상황을 보여 줍니다. 실행 결과가 아니고, 예제 1 의 상태에서 0004 까지 있다고 가정합니다.

시간 순서 도식(설명용). MAX+1 의 충돌입니다.

순서 세션 A 세션 B
1 MAX 조회 → 0004, 다음은 0005
2 MAX 조회 → 0004, 다음은 0005
3 0005 INSERT 성공
4 0005 INSERT → 중복 키 오류
5 번호를 다시 구함 → 0006
6 0006 INSERT 성공

예제 4: 같은 번호의 두 번째 INSERT 를 PK 가 막는다

H2 에서는 두 세션을 동시에 열 수 없어서 A 가 0005 를 넣은 뒤, B 가 같은 0005 를 넣는 것으로 흉내 냅니다.

sql
INSERT INTO order_h VALUES ('ORD-20260930-0005', DATE '2026-09-30', '자차식품', 900);
sql
-- @error
INSERT INTO order_h VALUES ('ORD-20260930-0005', DATE '2026-09-30', '카타상회', 400);
text
예상 오류: Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_E ON PUBLIC.ORDER_H(ORDER_NO) VALUES ( /* 7 */ 'ORD-20260930-0005' )"

PK 가 없었다면 이 INSERT 는 성공하고 같은 주문번호가 두 건 남습니다. 실패한 쪽은 번호를 다시 구해 재시도합니다.

sql
SELECT
       'ORD-20260930-' || LPAD(CAST(COALESCE(MAX(CAST(SUBSTRING(order_no, 14) AS INT)), 0) + 1 AS VARCHAR), 4, '0') AS next_no
  FROM order_h
 WHERE order_no LIKE 'ORD-20260930-%';
INSERT INTO order_h VALUES ('ORD-20260930-0006', DATE '2026-09-30', '카타상회', 400);
SELECT COUNT(*) AS cnt FROM order_h WHERE ord_date = DATE '2026-09-30';
text
NEXT_NO
-----------------
ORD-20260930-0006
(1행)
text
CNT
---
6
(1행)

재시도 후 30일 주문은 6건입니다. 애플리케이션은 이 재시도를 몇 번까지 할지 정해 두어야 합니다.

예제 5: 채번 행 만들기와 UPDATE 로 올리기

02 의 데이터는 9월 29일 행(last_no 2)만 있는 상태로 시작합니다. 30일 행이 없을 때만 만들려고 MERGE 를 씁니다. 일치하는 행이 있으면 아무것도 하지 않습니다.

sql
MERGE INTO seq_no t
USING (SELECT 'ORD' AS biz, '20260930' AS ymd FROM DUAL) s
   ON (t.biz = s.biz AND t.ymd = s.ymd)
 WHEN NOT MATCHED THEN INSERT (biz, ymd, last_no) VALUES (s.biz, s.ymd, 0);
SELECT
       biz
     , ymd
     , last_no
  FROM seq_no
 ORDER BY ymd;
text
BIZ | YMD      | LAST_NO
----+----------+--------
ORD | 20260929 | 2
ORD | 20260930 | 0
(2행)

이제 UPDATE 로 1 을 올리고 그 값을 읽습니다. UPDATE 가 행 잠금을 먼저 잡으므로 두 번째 요청은 여기서 기다립니다.

sql
UPDATE seq_no SET last_no = last_no + 1 WHERE biz = 'ORD' AND ymd = '20260930';
SELECT last_no FROM seq_no WHERE biz = 'ORD' AND ymd = '20260930';
text
LAST_NO
-------
1
(1행)

번호 문자열은 세 조각을 이어 붙입니다.

sql
SELECT
       biz || '-' || ymd || '-' || LPAD(CAST(last_no AS VARCHAR), 4, '0') AS order_no
  FROM seq_no
 WHERE biz = 'ORD'
   AND ymd = '20260930';
text
ORDER_NO
-----------------
ORD-20260930-0001
(1행)

예제 6: 채번과 주문 INSERT 를 한 트랜잭션에

자동 커밋을 끄고 채번 UPDATE 와 주문 INSERT 를 묶습니다. 주문 INSERT 는 방금 올린 채번 행에서 번호를 바로 조립합니다. 다시 값을 조회하는 문장이 필요 없습니다.

sql
SET AUTOCOMMIT OFF;
UPDATE seq_no SET last_no = last_no + 1 WHERE biz = 'ORD' AND ymd = '20260930';
INSERT INTO order_h
SELECT
       biz || '-' || ymd || '-' || LPAD(CAST(last_no AS VARCHAR), 4, '0')
     , DATE '2026-09-30'
     , '가나상사'
     , 500
  FROM seq_no
 WHERE biz = 'ORD'
   AND ymd = '20260930';
COMMIT;
SELECT
       order_no
     , cust
  FROM order_h
 WHERE ord_date = DATE '2026-09-30';
text
ORDER_NO          | CUST
------------------+---------
ORD-20260930-0002 | 가나상사
(1행)

앞 예제에서 last_no 를 이미 1 로 올렸으므로 이번 UPDATE 로 2 가 되어 이 주문이 0002 를 받았습니다. 소스는 이어서 last_no 가 2 인 것을 확인합니다.

시간 순서 도식(설명용). 두 요청이 채번 행에서 줄을 서는 모습입니다.

순서 세션 A 세션 B
1 UPDATE last_no = last_no + 1 (잠금 획득)
2 UPDATE last_no = last_no + 1 (A 가 끝날 때까지 대기)
3 주문 INSERT, COMMIT (잠금 해제)
4 대기 끝, 커밋된 값에서 1 을 더해 다음 번호를 받음

같은 행을 UPDATE 하려는 세션은 한 번에 하나만 실행되므로 B 는 A 와 같은 번호를 받을 수 없습니다. 이 잠금은 A 가 COMMIT 할 때 풀리기 때문에 채번과 INSERT 사이가 길수록 B 가 오래 기다립니다.

예제 직접 실행

아래 폴더의 SQL 파일을 Git Bash 에서 H2 메모리 DB 로 실행합니다. 방언은 파일 안의 SET MODE 로 바꿉니다.

cd sql-src/work_07_numbering
ls *.sql
bash ../run.sh <파일>.sql
코드 예제
  • 예제 1: MAX + 1 로 다음 번호 만들기
  • 예제 2: 조회한 번호로 INSERT
  • 예제 3: 동시 요청의 시간 순서 (도식, 설명용)
  • 예제 4: 같은 번호의 두 번째 INSERT 를 PK 가 막는다
  • 예제 5: 채번 행 만들기와 UPDATE 로 올리기
  • 예제 6: 채번과 주문 INSERT 를 한 트랜잭션에
이전 섹션2 핵심 원리3 / 6다음 섹션4 응용 변형 예제