시퀀스를 사용하여 경쟁 상태 피하기

Dope
Webdev TechBlog
Published in
3 min readSep 1, 2021

서론

테이블의 PK 를 인조키로 잡고, PK 생성 방식을 MAX+1 이나 SEQUENCE 로 사용하는데, 두 방식의 차이점에 대해서 알아보고 경쟁 상태를 피하기 위해서 어떤 방식을 사용해야 하는지 배워보겠습니다.

안티패턴 MAX+1

Spring 과 Mybatis 를 사용하고 있다고 하고, 테이블 PK 생성 방식을 MAX+1 로 사용하고 있다고하면 SQL INSERT 구문에는 다음과 같은 형식으로 되어있을 것입니다.

<selectKey resultClass="java.lang.Long" keyProperty="id">
SELECT NVL(MAX(ID),0)+1 AS ID FROM ARTICLE
</selectKey>

MAX+1 방식의 문제는 PK 의 MAX 값을 구하기 위해서 해당 테이블을 FULL-SCAN 하게 됩니다. 또한, 경쟁 상태(race condition) 가 발생할 수 있습니다.

여러 클라이언트에서 같은 값을 사용하게 될 수도 있는데 이러한 것을 경쟁 상태(race condition)라고 한다.

예를 들어, 어떤 신청 프로그램에서 10명의 사용자가 동시에 등록을 하게되면 등록하는 과정에서 10명이 같은 PK 값을 받게되고 1명만 등록되고 9명은 등록에 실패하게 됩니다.

MAX+1 사용시 생성된 PK 값을 등록이 성공하던 실패하던 세션(Session) 객체에는 절대 가지고 있게 해서는 안됩니다.

예를 들어, 등록 실패 후 뒤로가기를 눌러 다시 등록하려고하면 세션 객체에는 다른 누군가가 이미 등록한 PK 값을 가지고 있게되어서 잘못된 로직을 실행하거나 심각한 버그가 발생할 수 있습니다.

정말 정말 중요합니다.. 잘 못된 프로그램 설계로 인해서 많이 힘든 상황을 겪을 수 있습니다.

경쟁 상태를 피하기 위해서는 동시에 삽입하는 것을 막아야 하고 최댓값을 구한 다음 이를 이용해 행을 삽입해야 합니다. 이렇게 하기 위해서는 전체 테이블 잠금(lock) 을 사용해야 합니다. 행 수준의 잠금(row-level locking) 으로는 충분하지 않습니다. 테이블 잠금은 클라이언트의 동시 접근을 막고 DB 요청을 한 줄로 세워 차례로 접근하게 하므로 병목의 원인이 됩니다.

시퀀스(SEQUENCE)

시퀀스(Sequence)는 트랜잭션 범위 밖에서 동작해 이 문제를 해결합니다. 시퀀스는 여러 클라이언트에 절대 같은 값을 할당하지 않고, 삽입할 행에 사용한 값을 커밋했는지 여부와 상관없이 한 번 할당한 값을 되돌리지도 못합니다. 따라서 여러 클라이언트가 동시에 유일한 값을 할당받을 수 있고 중복된 값을 할당 받지 않는다고 확신할 수 있습니다.

따라서, 경쟁 상태가 발생할 우려가 있는 프로그램에서는 PK 생성 방식을 시퀀스로 가져가는 것을 추천 드립니다.

참고

  • SQL-Antipatterns

--

--

Dope
Webdev TechBlog

Developer who is trying to become a clean coder.