[db] transaction

트랜잭션이란

작업을 논리적 단위로 묶어서 원자성(atomic)을 보장하고자 할 때 사용하는 것이다.

원자성 : 논리적 작업 셋에 1개의 쿼리가 있든 2개 이상의 쿼리가 있든 관계없이 논리적인 작업 셋 자체가 100% 적용되거나 적용되지 않는것을 보장해주는 것

MySQL에서는 InnoDB만 지원하고, 나머지(MyISAM 등)은 트랜잭션을 지원하지 않는다.

아래와 같은 쿼리가 있다(3번째 멤버가 중복된 이름이라 에러가 발생할 것이다)

1
INSERT INTO MEMBER VALUES(1,'joont'),(2,'junyoung'),(3,'duplicated name');
  • MyISAM

    쿼리 실행시에 오류가 발생하지만 3번 멤버만 저장되지 않을 뿐, 1,2번 멤버는 롤백되지 않고 저장된 상태 그대로 있다.
    이러면 나중에 데이터 정합성을 맞추기가 매우 힘들어진다.

  • InnoDB

    1,2,3번 모두 저장되지 않는다.

위처럼 꼭 여러개의 작업 단위를 묶지 않아도 된다.
트랜잭션이 성공적으로 수행되면 모두 commit되고, 실패하면 모두 rollback 한다는 사실이 중요한 것이다.

설정법

1
2
3
4
5
START TRANSACTION; -- 또는 BEGIN;

-- do somthing

COMMIT; -- 또는 ROLLBACK;

위처럼 트랜잭션을 시작하고, 커밋이나 롤백하는 시점까지가 하나의 트랜잭션이다.

AutoCommit이 설정되어 있으면 쿼리문장 하나하나가 곧 트랜잭션이고,
AutoCommit이 설정되어 있지 않으면 COMMIT, ROLLBACK, DDL 이후 시작부터 다시 이 문장을 실행 전까지가 자동적으로 트랜잭션 하나로 잡힌다.

나는 AutoCommit을 off로 설정하면 트랜잭션 테스트가 잘 안되서 AutoCommit을 on으로 설정한 뒤 테스트했다(on은 항상 트랜잭션이 끝남을 보장해주니까)

트랜잭션 설정 주의점

트랜잭션의 단위는 특별한 이유가 없으면 최소한의 단위로 해주는 것이 좋다.
가령 아래와 같은 트랜잭션 설정은 위험하다.

1
2
3
4
5
6
7
8
9
10
11
12
1. 처리 시작  
==> DB 커넥션 생성
==> START TRANSACTION;
2. 인가된 사용자인지 확인
3. 사용자가 작성한 컨텐츠 Validation
4. 첨부파일 서버에 업로드
5. 게시글과 첨부파일 내용을 DB에 저장
6. 게시물 등록에 대한 메일 발송
7. 메일 발송 내역을 DB에 저장
==> COMMIT;
==> DB 커텍션 반납
8. 처리 완료

보다시피 트랜잭션의 범위가 너무 크다.
DB에 데이터를 저장하는 구간은 5번과 7번밖에 없는데 인증, Validation, 첨부파일 업로드, 메일 전송과 같은 행위까지 전부 트랜잭션에 같이 포함되어 있다.
즉, 위의 모든 작업들이 끝나기 까지 해당 트랜잭션을 잡고있는 커넥션은 반환되지 않을 것이며,
소유하는 시간이 길어질수록 다른 쓰레드들이 커넥션을 가져가지 못하고 대기하는 상황이 벌어질 것이다.
(특히 첨부파일 업로드, 메일 전송과 같은 네트워크 작업을 트랜잭션으로 묶는것은 아주 위험하다. 네트워크 통신이 불가능하다면 트랜잭션 소유시간이 매우 길어지게 된다)

커넥션 뿐만 아니라 CUD가 발생했을 경우 해당 레코드에 대해서 메서드가 끝날때까지 락이 걸리기 때문에 동시처리 성능 또한 매우 떨어지게 된다.
아래와 같이 변경해주는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 처리 시작  
==> DB 커넥션 생성
2. 인가된 사용자인지 확인
3. 사용자가 작성한 컨텐츠 Validation
4. 첨부파일 서버에 업로드
==> START TRANSACTION;
5. 게시글과 첨부파일 내용을 DB에 저장
==> COMMIT;
6. 게시물 등록에 대한 메일 발송
==> START TRANSACTION;
7. 메일 발송 내역을 DB에 저장
==> COMMIT;
==> DB 커텍션 반납
8. 처리 완료

트랜잭션을 최소화하는 것이 좋다는 것을 강조하기 위해 7번을 따로 분리하였다.
상황에 따라 크게 달라질 수 있다.