MySQL이 SEQUENCE를 지원하지 않는 이유

2026. 4. 12. 16:41·프로그래밍

https://blog.giwon.dev/articles/mysql%EC%9D%80-%EC%99%9C-sequence%EB%A5%BC-%EC%A7%80%EC%9B%90%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94%EA%B0%80

 

Giwon's Blog

© 2026 Giwon. All rights reserved.

blog.giwon.dev

위 블로그에서도 감상 가능합니다.

 

0. JPA IDENTITY 전략 사용에 대한 의문


회사에서 batch insert 사용을 위해 application.yml에 batch 설정을 했는데도 배치 INSERT가 동작하지 않았다.

spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 50
        order_inserts: true


설정은 맞는거 같은데 왜 안 되는 걸까?

원인을 찾아보니 MySQL + JPA 조합에서 거의 관행처럼 쓰이는 GenerationType.IDENTITY가 문제였다.

`IDENTITY` 전략은 `AUTO_INCREMENT`에 의존하기 때문에, INSERT가 실행되어야만 ID를 알 수 있다.  
Hibernate는 영속성 컨텍스트에서 엔티티를 관리하려면 PK가 필요한데, `persist()` 시점에 PK가 없으니 쓰기 지연을 포기하고 즉시 INSERT를 실행한다.  
batch_size를 아무리 키워도 쿼리는 한 건씩 나간다.

해결책은 `SEQUENCE` 전략이다. 시퀀스라면 INSERT 없이 `nextval()`로 ID를 미리 채번할 수 있고, 실제 INSERT는 flush 시점에 배치로 묶을 수 있다.  
트랜잭션이 롤백되더라도 시퀀스는 non-transactional하게 동작하므로, gap만 남길 뿐 다른 트랜잭션에는 영향을 주지 않는다.

그런데... MySQL에는 시퀀스가 없다.

JPA는 이에 대한 대안으로 `채번용 테이블`을 만들어서 채번할 때 마다 해당 테이블의 row를 X-Lock을 잡고 UPDATE한다.  

즉, 여러 스레드에서 동시에 채번을 시도하면 동시성 문제가 생길 수 있다.  
이러한 제약 덕에 MySQL + JPA 조합은 IDENTITY를 사용할 수 밖에 없는 구조다.


왜 MySQL은 이런 구조를 가지게 된걸까? PostgreSQL과 함꼐 비교해보려 한다.

---

1. 태생의 차이


PostgreSQL (1986)

PostgreSQL은 UC Berkeley의 Michael Stonebraker가 시작한 프로젝트다.  
그는 이미 최초의 관계형 데이터베이스 중 하나인 INGRES를 만든 인물로, "처음부터 다시 만든다면?" 이라는 질문에서 POSTGRES(POST inGRES)개발을 시작했다.

학계에서 만들기 시작한 만큼 빠르게 동작하는 것보다 올바르게 동작하는 것을 중시했다.  
사용자 정의 타입, 테이블 상속, 규칙 시스템 등 SQL 표준을 충실히 구현하면서도 확장성(extensibility)을 1급 시민으로 설계했다.  
시퀀스 같은 독립적인 데이터베이스 오브젝트를 지원하는 것도 이 철학의 연장선으로 보인다.


MySQL (1995)

MySQL은 Michael "Monty" Widenius가 스웨덴에서 웹 애플리케이션을 위해 만든 경량 데이터베이스다.  
기존에 사용하던 mSQL이 인덱스를 지원하지 않자, mSQL 개발자에게 자신의 ISAM 핸들러와 통합을 제안했지만 거절당했고, 직접 만들고자 했다.

설계 철학은 명확했다: "Simple, Fast, Free"

당시 웹은 폭발적으로 성장하고 있었고, 개발자들에게 필요한 건 학문적 완결성이 아니라 당장 돌아가는 빠르고 단순한 DB였다.  
초기의 MySQL은 트랜잭션이나 시퀀스 같은 무거운 기능을 과감히 생략하고, 읽기 성능과 설치 편의성에 집중했다.  
`AUTO_INCREMENT` 하나로 PK 생성을 해결한 것도 이 맥락이다. 

별도 오브젝트 없이 테이블 컬럼 속성 하나로 끝나는 단순함을 더 중요시 했다.

---


2. 아키텍처의 차이: 2-Tier vs 모놀리식

이러한 철학의 차이는 아키텍처까지 영향을 미쳤다.

MySQL: Server Layer + Storage Engine Layer (2-Tier)

MySQL은 두 개의 독립된 계층으로 나뉘어 있고, Handler API라는 인터페이스로만 소통한다.




- Server Layer: 커넥션 처리, SQL 파싱, 옵티마이징, 쿼리 실행
- Storage Engine Layer: 실제 데이터 저장/조회, 인덱싱, 트랜잭션, 캐싱
- Handler API: C++ 추상 클래스로 정의. 각 엔진이 상속구조 (InnoDB → `ha_innobase`, MyISAM → `ha_myisam`)

이 구조 덕분에 MySQL은 스토리지 엔진을 하나의 DB 내에서 테이블 단위로 교체할 수 있다.  

 

PostgreSQL: 단일 통합 엔진 (모놀리식)

PostgreSQL는 MySQL처럼 엔진을 교체하는 개념이 없다.   단일 엔진으로만 동작하며 트랜잭션, ACID, 외래 키, MVCC가 **내장**되어 있다.  
대신 extension을 통해 기능을 확장하는 방식을 사용한다. (커스텀 연산자, 인덱스(GIN, GiST, BRIN), 확장(PostGIS, pg_trgm 등))

 

3. 결론 - MySQL이 SEQUENCE를 지원하지 않는 이유

드디어 본론으로 왔다. 이러한 구조가 어째서 SEQUENCE를 지원할 수 없는 걸까?

Handler API는 오직 "테이블"만 안다

Handler API의 핵심 문제는, handler 인스턴스가 **반드시 TABLE 객체에 바인딩**된다는 것이다.

static handler *innobase_create_handler(TABLE *table) {
    return new ha_innobase(table);
}


API 메서드 전체가 테이블/행 조작이다.

`get_next_sequence_value()`나 `create_sequence()` 같은 메서드는 **존재하지 않는다**.  
API의 명세 자체가 "테이블 열고, 행 읽고, 행 쓰고, 테이블 닫기"가 전부다.

SEQUENCE는 테이블이 아닌 독립 객체다

SEQUENCE는 SQL:2003 표준에 정의된 **독립적인 데이터베이스 객체**다.  
어떤 테이블에도 종속되지 않고, 여러 테이블이 공유할 수 있으며, 트랜잭션 롤백에도 값이 되돌아가지 않는다.  
이것을 MySQL의 2-tier 구조에 넣으려면 두 가지 선택지가 있다.


선택지 A: Server Layer에 구현

Server Layer에서 시퀀스 상태 관리
        ↕ (트랜잭션 경계 불일치!)
InnoDB에서 INSERT 실행


시퀀스 상태 업데이트와 사용자의 InnoDB INSERT가 서로 다른 트랜잭션 도메인에 존재하게 된다.  
둘 사이에 크래시가 발생하면 일관성이 깨지고, InnoDB의 redo log를 통한 크래시 복구에 시퀀스 상태가 참여할 수 없다.

선택지 B: Storage Engine Layer에 구현

이 방식은 Handler API에 시퀀스 관련 가상 메서드를 추가해야 하고, 이로 인한 모든 스토리지 엔진의 수정을 요구한다.  
InnoDB, MyISAM, ... 각각이 독립적으로 SEQUENCE 시맨틱스를 구현해야 한다.  
이미 AUTO_INCREMENT도 엔진마다 동작이 다른 상황에서 또 다른 분기가 발생한다.

AUTO_INCREMENT가 가능했던 이유

AUTO_INCREMENT는 SEQUENCE와 달리 **테이블에 종속적**이기 때문에 Handler API와 충돌하지 않는다:
- InnoDB: 클러스터드 인덱스 안에 카운터 저장, redo log로 영속화
- MyISAM: `.MYI` 파일 헤더에 카운터 저장
- Memory: 테이블 메타데이터에 카운터 저장

각 엔진이 자기 테이블의 일부로 관리하므로 기존 API 안에서 자연스럽게 동작한다. 즉, AUTO_INCREMENT는 "테이블의 속성"이라 Handler API에 맞지만, SEQUENCE는 "독립 객체"라 맞지 않는다.

 

PostgreSQL에서는 왜 자연스러운가

PostgreSQL에서 시퀀스는 `pg_class` 시스템 카탈로그에 `relkind = 'S'`로 등록되는 1급 릴레이션이다.  
즉, 테이블, 인덱스 같은 개념과 동등한 자격으로 취급된다. 
PostgreSQL은 엔진 추상화가 없으므로, executor가 테이블이든 시퀀스든 동일한 내부 인프라를 통해 직접 접근한다. 

 

결론

처음의 질문으로 돌아가자. `batch_size: 50`을 설정했는데 왜 배치 INSERT가 동작하지 않았을까?

MySQL의 2-tier 아키텍처에서 Handler API는 테이블 단위로만 동작하기 때문에, 특정 테이블에 종속되지 않는 시퀀스는 Server Layer에 넣어도, Storage Engine Layer에 넣어도 깔끔하게 맞지 않는다.  
시퀀스가 없으니 IDENTITY 전략을 쓸 수밖에 없고, IDENTITY는 INSERT 전에 PK를 채번할 수 없으니 쓰기 지연이 원천적으로 불가능하다.  

설정의 문제가 아니라 아키텍처의 차이였다.

반응형
저작자표시 (새창열림)

'프로그래밍' 카테고리의 다른 글

@Transactional - 스냅샷은 첫 쿼리에 만드는데, 커넥션은 왜 미리 잡을까  (2) 2026.04.05
'프로그래밍' 카테고리의 다른 글
  • @Transactional - 스냅샷은 첫 쿼리에 만드는데, 커넥션은 왜 미리 잡을까
Giwonnnnnnn
Giwonnnnnnn
개발, 일상 등 나에 대한 모든 것을 기록합니다.
    250x250
  • Giwonnnnnnn
    기록하는 곳
    Giwonnnnnnn
  • 전체
    오늘
    어제
    • 분류 전체보기 (89)
      • 프로그래밍 (35)
        • 객체지향 설계 (7)
        • git (1)
        • Network (2)
        • 후기 (2)
        • 기타 (10)
        • 발생한 에러들 (11)
      • Language (18)
        • Java (0)
        • Python (2)
        • JavaScript (13)
        • TIL (3)
      • 알고리즘 (11)
      • BackEnd (1)
        • 데이터베이스 (1)
        • Nest.js (4)
        • Node.js (2)
      • 자격증 공부 (1)
        • 정보처리기사 (0)
      • 영어공부 (3)
        • 문법 기초 (3)
      • Cloud (3)
        • GCP (3)
      • 일상 (4)
        • 리뷰 (1)
        • 맛집 탐방 (1)
        • 일상 (2)
        • 일기장 (0)
  • 블로그 메뉴

    • 홈
    • 방명록
  • 링크

    • Github
    • LinkedIn
    • Instagram
  • 공지사항

  • 인기 글

  • 태그

    JavaScript
    알고리즘
    자바
    파이썬
    프로그래머스
    java
    Object
    오브젝트
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Giwonnnnnnn
MySQL이 SEQUENCE를 지원하지 않는 이유
상단으로

티스토리툴바