[Oracle] Insert ~ Select시 PK를 Max로 추출시 값이 동일한 문제

Q0. 개요

흔히 사용하는 DML (Data Manipulation Language)에서 기존 자료를 조합해서 한번에 여러개를 저장하는 경우가 있다. 나의 경우에는 Insert ~ Select를 이용하여 기존에 존재하는 Select에 조회조건을 걸어 Insert할 때, PK를 비롯한 몇 가지의 데이터를 제외하고는 동일하게 생성시키는데 사용했다. 그런데, Insert ~ Select가 N건으로 실행되면서 Max(PK)+1가 계속 동일한 값으로 출력되는 문제가 발생되었다. 오늘은 이 문제가 발생하였을 때 처리했던 방법에 대해 정리하였다.

 

- INSERT ~ SELECT문

INSERT ~ SELECT란, 이미 존재하는 테이블을 조회해서 나온 데이터를 일괄 등록하는 방법이다.
(단, 전체 복사를 하려면 A와 B테이블명이 동일할 때, 컬럼을 생략하면 된다)

INSERT
INTO 테이블명1 A(
      컬럼1
    , 컬럼2
    , 컬럼3

SELECT 
      컬럼1
    , 컬럼2
    , 컬럼3
FROM 테이블명2 B
WHERE 1=1

 

 

- N건 데이터 등록하는 방법

왜 사용하는지 알고 나니깐 본격적으로 발생되는 문제에 대해 언급하려고 한다.

데이터를 등록할 때, 대량의 N건 데이터를 등록하기 위하여 아래와 같은 방법들이 있다.

1. 자바에서 반복문을 통해 1건씩 DB에 저장하는 방법
자바에서 for, while를 이용하여 1건씩 DB저장


2. 자바에서 SQL부문에 배열 자체를 통으로 넘겨서 DB에서 반복해서 1건씩 저장하는 방법

자바에서 배열을 전달하여 Mybatis의 <foreach>로 1건씩 데이터 저장


3. 클라이언트에서 전달받은 저장데이터가 아니라 이미 등록된 N건의 데이터를 재조회해서 저장하는 방법

 

- PK를 채번하는 방식

위와 같은 방법들을 통해 N건의 데이터를 등록할 수 있는데, PK를 채번하는 방법들에도 다양하다.

1. 원 쿼리로 내부에서 MAX(PK컬럼)+1로 바로 채번하는 방식.
2. 별도로 PK를 추출하는 로직을 별도로 분리해서, 등록할 때 파라미터로 받는 방식.
3. 위 방식을 Mybatis의 <selectKey>에서 사용하는 방식
4. 시퀀스를 1번과 같이 이용하는 방식 

 

Q1. 결론적으로

다시 원점으로 돌아오면 대량의 데이터를 등록하는데 위의 PK를 채번하는 방식은 대부분 사용하는데 문제가 없다.

다만, Insert ~ Select문에서는 원쿼리로 내부에서 MAX+1로 바로 채번하는 방식은 정상적으로 동작하지 않는 문제가 있다.

찾아본 원인으로는 MAX+1은 동시성을 보장하지 않는다고 한다.

이 말은 병렬로 움직이는게 아니라 싱글 스레드가 멀티 스레드처럼 보일 뿐이다.

JDBC가 끊어지지 않고 하나의 원 쿼리에서  동시 다발적으로 발생되는 경우에는  동시성이 보장되지 않게 되어 동일하게 MAX(PK)+1의 값이 추출되는 것이라고 보면 된다.

때문에 오라클에서는 제공되는 시퀀스를 이용하면 원인을 편리하게 해결할 수 있고,

만약 시퀀스가 없는 언어의 경우에는 Select ~ Insert문을 변경할 필요가 있다....