공공부하자개발 · 영어 학습 노트
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정리‹ 이전다음 ›

4. 응용 변형 예제

예제 7: 채번을 본 트랜잭션과 묶을지 나눌지

채번 UPDATE 를 주문 INSERT 와 같은 트랜잭션에 두면 주문이 실패해 ROLLBACK 할 때 번호도 함께 돌아옵니다.

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';
ROLLBACK;
SELECT last_no FROM seq_no WHERE biz = 'ORD' AND ymd = '20260930';
text
LAST_NO
-------
3
(1행)
text
LAST_NO
-------
2
(1행)

UPDATE 직후에는 3 이었지만 ROLLBACK 하자 2 로 돌아왔습니다. 번호에 구멍이 생기지 않습니다.

채번만 먼저 커밋해서 잠금을 빨리 푸는 방식도 있습니다. 이 경우는 주문이 실패해도 번호가 남습니다.

sql
UPDATE seq_no SET last_no = last_no + 1 WHERE biz = 'ORD' AND ymd = '20260930';
COMMIT;
-- (여기서 주문 INSERT 가 실패해 ROLLBACK 했다고 가정)
ROLLBACK;
SELECT last_no FROM seq_no WHERE biz = 'ORD' AND ymd = '20260930';
SELECT COUNT(*) AS order_cnt FROM order_h WHERE ord_date = DATE '2026-09-30';
text
LAST_NO
-------
3
(1행)
text
ORDER_CNT
---------
1
(1행)

카운터는 3 인데 30일 주문은 1건뿐이라 0003 번 주문이 없는 구멍이 생겼습니다.

방식 번호 구멍 잠금 대기
본 트랜잭션과 묶음 없음 채번 후 주문 처리가 끝날 때까지 길어짐
채번만 따로 커밋 주문 실패 시 생김 짧음
팁

구멍을 허용하는지가 업무 요구입니다. 세금계산서처럼 번호가 비면 안 되는 경우는 묶고 트랜잭션을 최대한 짧게 씁니다. 주문번호처럼 비어도 되는 경우는 분리해서 대기를 줄입니다.

예제 8: SELECT ... FOR UPDATE 후 UPDATE

UPDATE 가 잠금을 잡기 전에 읽기부터 하고 싶다면 SELECT 에 FOR UPDATE 를 붙여 행을 먼저 잠급니다. H2 는 이 문법을 실행합니다.

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

읽은 3 에서 UPDATE 로 4 가 되었습니다. 잠금이 SELECT 시점부터 걸려 있어 사이에 다른 세션이 끼어들지 못합니다.

예제 9: DB별 한 문장 채번 (H2 미지원, 문법 검토만)

UPDATE 와 값 읽기 사이의 틈을 없애는 DB별 문법입니다. 아래 셋 중 Oracle 의 RETURNING 과 MSSQL 의 OUTPUT 은 H2 에서 실행하지 않았습니다.

sql
-- Oracle · Tibero: PL/SQL 안에서 변수로 받는다
UPDATE seq_no SET last_no = last_no + 1
 WHERE biz = 'ORD' AND ymd = '20260930'
RETURNING last_no INTO :n;
sql
-- MySQL: 올린 값을 LAST_INSERT_ID 에 실어 세션에서 바로 읽는다
INSERT IGNORE INTO seq_no VALUES ('ORD', '20260930', 0);
UPDATE seq_no SET last_no = LAST_INSERT_ID(last_no + 1) WHERE biz = 'ORD' AND ymd = '20260930';
SELECT LAST_INSERT_ID();
sql
-- MSSQL: OUTPUT 으로 올라간 값을 결과로 돌려준다
UPDATE seq_no SET last_no = last_no + 1
OUTPUT inserted.last_no
 WHERE biz = 'ORD' AND ymd = '20260930';

MySQL 의 LAST_INSERT_ID 는 세션마다 따로 저장되므로 다른 세션이 끼어들어도 내 값이 바뀌지 않습니다. 03 에서 MySQL 모드로 실행하면 7 이던 값이 8 로 올라 LAST_INSERT_ID 로 8 을 받습니다. 다만 H2 가 MySQL 과 같다는 근거는 아닙니다.

예제 10: DB별 0 채움과 문자열 조립

소스: 03_dialects.sql. last_no 를 7 로 두고 세 모드로 같은 번호 ORD-20260930-0007 을 만듭니다.

항목 Oracle · Tibero MySQL MSSQL
문자열 연결 a || b CONCAT(a, b) a + b
0 채움 LPAD(n, 4, '0') LPAD(n, 4, '0') RIGHT('0000' + CAST(n AS VARCHAR(4)), 4)
날짜 8자리 TO_CHAR(d, 'YYYYMMDD') DATE_FORMAT(d, '%Y%m%d') CONVERT(CHAR(8), d, 112)

MSSQL 은 LPAD 가 없어서 0 을 앞에 붙인 뒤 오른쪽 4글자를 자르는 방식을 씁니다. H2 가 실행하는 것은 Oracle 모드의 TO_CHAR, LPAD, 연결과 MySQL 모드의 CONCAT, LPAD, MSSQL 모드의 RIGHT 와 + 입니다. MySQL 의 DATE_FORMAT 과 MSSQL 의 CONVERT 스타일 번호 112 는 H2 미지원이라 문법 검토만입니다.

sql
SET MODE MSSQLServer;
SELECT
       'ORD-' + ymd + '-' + RIGHT('0000' + CAST(last_no AS VARCHAR(4)), 4) AS order_no
  FROM seq_no;
text
ORDER_NO
-----------------
ORD-20260930-0008
(1행)

MSSQL 모드 결과가 0008 인 것은 소스 3번 구간의 UPDATE 가 last_no 를 7 에서 8 로 올렸기 때문입니다.

표준 모드에서도 RIGHT('0000' || CAST(n AS VARCHAR), 4) 가 동작해 공통 방법이 됩니다(소스 5번).

주의

LPAD 는 자릿수를 넘으면 오른쪽을 자릅니다. Oracle·MySQL 의 LPAD(10000, 4, '0') 은 1000 이 되고, MSSQL 의 RIGHT 도 뒤 4글자만 남깁니다. 하루 1만 건이 넘을 수 있으면 자릿수를 늘려 둡니다.

응용 변형 예제
  • 예제 7: 채번을 본 트랜잭션과 묶을지 나눌지
  • 예제 8: SELECT ... FOR UPDATE 후 UPDATE
  • 예제 9: DB별 한 문장 채번 (H2 미지원, 문법 검토만)
  • 예제 10: DB별 0 채움과 문자열 조립
이전 섹션3 코드 예제4 / 6다음 섹션5 자주 하는 실수 (Tip)