MySQL을 사용하면 SET AUTOCOMMIT = 1;
혹은 SET AUTOCOMMIT = 0;
의 쿼리를 본 적이 있을 것이다.
이 AUTOCOMMIT
이 도대체 뭘까?
RDS를 사용하면 트랜잭션을 사용하게 된다. 트랜잭션을 간단히 설명하면 일련의 작업 단위를 하나로 묶어서 처리할 수 있도록 해주는 것을 의미하는데, 이는 한번에 묶어서 처리되는 만큼 한꺼번에 모두 성공하거나, 모두 실패해야한다.
하나의 트랜잭션은 COMMIT
이나 ROLLBACK
을 만나면 끝이 난다.
그럼, SET AUTOCOMMIT = 1
이 설정되어있는 상황을 가정해보자.
START TRANSACTION
은 여기서 생략한다.
비즈니스 로직의 요구사항 중 id가 1번인 유저의 이름을 민정
으로, id가 2번인 유저의 이름을 조앤
으로 바꿔야하며 이 두가지는 무조건 함께 실행되어야하고 하나만 성공해서는 안된다는 요구사항이 있다.
SET AUTOCOMMIT = 1;
update students set name = "민정" where id = 1; -- 이하 첫번째 쿼리
update students set name = "조앤" where id = 2; -- 이하 두번째 쿼리
위와 같이 쿼리를 수행하는 중, 만약 두번째 쿼리가 실패하게 된다면?
SET AUTOCOMMIT = 1
로 설정되어있기 때문에 이미 첫번째 쿼리는 정상적으로 COMMIT 되어 데이터베이스에 반영되었고, 실패한 두번째쿼리만 데이터베이스에 반영되지 않았다.
헉! 둘다 성공하거나 실패해야하는데 AUTOCOMMIT
속성 때문에 수행한 쿼리 단건이 자동으로 커밋되어버려 두 쿼리를 트랜잭션으로 묶지 못했다..
만약 SET AUTOCOMMIT = 0;
으로 설정되어있었다면?
SET AUTOCOMMIT = 0;
update students set name = "민정" where id = 1; -- 이하 첫번째 쿼리
update students set name = "조앤" where id = 2; -- 이하 두번째 쿼리
COMMIT;
두 쿼리가 끝나는 시점에 COMMIT
을 수행하기 때문에 두번째 쿼리가 실패했다면 모두 ROLLBACK
된다.
즉
AUTOCOMMIT
이란 모든 단건의 쿼리마다 자동으로COMMIT
이 실행되어 데이터베이스에 영구적으로 반영되도록 하는 명령을 말한다.
참고 문서: https://pkgonan.github.io/2019/01/hibrnate-autocommit-tuning
실제 사내에서 MySQL과 함께 JPA를 사용하고 있는 상황에서, DBA분께 해당 질문을 받았다.
set autocommit = 0;
set autocommit = 1;
set transaction readonly;
set transaction readwrite;
위 4가지 쿼리가 빈번하게 수행되는데, 혹시 발생하지 않도록 하는 것도 가능할까요?
간략하게 줄였다.
1개의 Transaction일 경우 setAutoCommit은 2회 호출된다. 디폴트 세팅이 set autocommit = 1;
인데, 우리가 서비스에서 @Transactional
을 통해 명시한 쿼리의 묶음이 한 트랜잭션으로 먹혀 데이터베이스에 반영되어야하기 때문에 앞서 말했던 것처럼 먼저 set autocommit = 0;
을 수행하여 오토커밋 옵션을 false
로 적용해주고, 쿼리들을 실행한 뒤, 다시 오토커밋 옵션을 true
로 복귀한 뒤 COMMIT을 수행하여 트랜잭션을 보장한다.
즉, 한 트랜잭션마다 2회씩 무조건 실행되며 Nested Transaction이 있는 경우에는 2N번 호출된다.
관련하여 설명되어있는 하이버네이트 커미터 분이 쓰신 문서의 내용을 요약하자면 다음과 같다.
Hibernate는 기본 JDBC 연결의 autocommit 상태를 확인하고, 커넥션이 오토커밋으로 설정되어있다면 비활성화한다. 이렇게 하면 Hibernate는 트랜잭션의 경계를 제어할 수 있고 작업 단위로 설정된 쿼리문이 동일한 DB 트랜잭션 컨텍스트에서 실행되도록 할 수 있다.
이러한 동작은 오토커밋 플래그가 설정되었는지 여부를 모르기 때문에 강제적으로set autocommit = 0;
을 실행해 오토커밋 옵션을false
로 변경하고 쿼리를 실행하기 때문이어서인데, 만약 autocommit이 이미false
로 설정되어있다고 Hibernate에게 알려준다면, Hibernate는 불필요한set autocommit = 0;
을 매 트랜잭션 혹은 커넥션 단위마다 실행할 필요가 없을 것이다.
DBCP session level에 setAutoCommit=false를 설정해두고,
Hibernate의 hibernate.connection.provider_disables_autocommit: true
을 통한 hint로 Connection을 통한 auto commit 여부 확인을 스킵하도록 해보자.
코드 단위에서 뜯어본 것은 위에 링크한 참고 문서에서 자세히 설명하고 있으니 확인해보면 좋을 것 같다.
간단하게 요약해보자면 우리가 hibernate.connection.provider_disables_autocommit
설정을 true
로 줌으로써, 커넥션 매니저가 begin()
메서드를 수행할 때 위 옵션 값을 확인하여 true
인 경우 determineInitialAutoCommitMode()
를 수행하지 않게 되고, 현재의 autocommit 상태를 조회하는 작업도 수행하지 않게 된다. 또한 위 옵션을 통해 실제 커넥션을 가져오는 수행 또한 뒤로 미룰 수 있는 장점이 있다.
여기서 가장 중요한 것은 DBCP 레벨에서의 setAutoCommit=false
로 지정해주는 부분이다. 만약 이 부분을 지정하지 않는다면, 커넥션 맺는 단에서는 getAutoCommit을 수행하지 않기 때문에 (hibernate에서 hint를 우리가 true로 지정했기 때문에) 당연히 쿼리를 그냥 실행할 것이고, DBCP 레벨에서의 오토커밋 옵션은 true이기 때문에 단건의 쿼리가 데이터베이스에 커밋될 것이다!
https://stackoverflow.com/questions/75021769/rabbitmq-and-hibernate-unable-to-commit-against-jdbc-connection