본문 바로가기
[IT] DB

[오라클] 멀티쓰레드에서 Oracle Sequence 원자성 테스트

by 오리엔탈킴 2023. 9. 3.

Oracle Sequence를 활용하여 Primary Key를 사용하는 케이스가 종종 있는데, JAVA Spring Multi Thread 환경에서 해당 시퀀스가 동시성 이슈가 발생하지 않고, 정상적으로 Insert가 되는지 간단하게 테스트를 진행한 내용 정리를 해보도록 하겠습니다. 테스트는 스프링부트 + Mybatis 환경에서 진행을 하였습니다.

테스트 환경

테스트를 위해 mId 컬럼이 PK인 MEMBERS라는 간단한 테이블을 생성해 줍니다. 그리고 MDISEQ라는 시퀀스도 생성을 해줍니다.

CREATE TABLE MEMBERS 
( 
    mId       NUMBER(4)	NOT NULL,
    name       VARCHAR2(10),
    age         NUMBER(4),
    CONSTRAINT mId_pk PRIMARY KEY (mId)
);

테이블
MEMBERS 테이블

 

그리고 실패하는 비교 케이스를 위해, mID의 Max값에 +1을 해줘서 PK값을 만들고 insert하는 쿼리와 시퀀스를 이용하여 PK값을 만들어 insert 쿼리를 아래와 같이 생성합니다.

Mapper.xml

    <insert id="setMemberUsingSubQuery" parameterType="com.example.demo.vo.Member">
        INSERT INTO MEMBERS ( mID, name, age )
            VALUES (
        ( SELECT TO_NUMBER( MAX( mID ) ) + 1 FROM MEMBERS )
        ,#{name}
        ,#{age}
        )
    </insert>

    <insert id="setMemberUsingSequence" parameterType="com.example.demo.vo.Member">
        INSERT INTO MEMBERS ( mID, name, age )
        VALUES (
        MIDSEQ.NEXTVAL
        ,#{name}
        ,#{age}
        )
    </insert>

 

Service.java

@Slf4j
@Service
@RequiredArgsConstructor
public class DemoService {

    private final DemoMapper demoMapper;

    public void setMemberUsingSubQuery (Member member) {

        log.info(member.getName());
        demoMapper.setMemberUsingSubQuery(member);
    }

    public void setMemberUsingSequence (Member member) {
        demoMapper.setMemberUsingSequence(member);
    }

}

 

테스트

위의 두 쿼리 모두 동일하게 멀티쓰레드 2개를 생성하고 쿼리를 수행합니다.

@SpringBootTest
class DemoApplicationTests {

	@Autowired
	DemoService demoService;

	@Test
	@Transactional
	void 서브쿼리로_키값_생성() throws InterruptedException {

		ExecutorService executorService = Executors.newFixedThreadPool(2);

		Member m1 = new Member( "Kim", 10 );
		Member m2 = new Member( "Lee" , 20 );

		CompletableFuture<Void> cf1 = CompletableFuture.runAsync(() -> { demoService.setMemberUsingSubQuery(m1); }, executorService);
		CompletableFuture<Void> cf2 = CompletableFuture.runAsync(() -> { demoService.setMemberUsingSubQuery(m2); }, executorService);

		CompletableFuture.allOf(cf1, cf2).join();

	}

	@Test
	@Transactional
	void 시퀀스로_키값_생성() throws InterruptedException {

		ExecutorService executorService = Executors.newFixedThreadPool(2);

		Member m1 = new Member( "Kim", 10 );
		Member m2 = new Member( "Lee" , 20 );

		CompletableFuture<Void> cf1 = CompletableFuture.runAsync(() -> { demoService.setMemberUsingSequence(m1); }, executorService);
		CompletableFuture<Void> cf2 = CompletableFuture.runAsync(() -> { demoService.setMemberUsingSequence(m2); }, executorService);

		CompletableFuture.allOf(cf1, cf2).join();

	}

}

 

결과

서브쿼리로 키값을 생성하는 경우에는 "unique constraint violated" 키 값이 중복되는 에러가 발생하였지만, 시퀀스를 이용한 쿼리는 정상적으로 수행이 되었습니다.

테스트 결과
테스트 결과

 

반응형

댓글