1. 트랜잭션이란?

한 덩어리의 쿼리 처리 단위를 트랜잭션이라고 한다.

트랜잭션은 다음 네 가지의 특성으로 정의된다.

  1. Atomicity(원자성)
  2. Consistency(일관성)
  3. Isolation(고립성 또는 격리성)
  4. Durability(지속성)

1-1. 원자성

변경을 수반하는 일련의 데이터 조작이 전부 성공할지 전부 실패할지를 보증하는 구조.

1-2. 일관성

일련의 데이터 조작 전후에 그 상태를 유지하는 것을 보증하는 구조.

1-3. 격리성

일련의 데이터 조작을 복수 사용자가 동시에 실행해도 '각각의 처리가 모순없이 실행되는 것을 보증한다'는 것.

이를 보증하기 위해 DB에서는 DB 오브젝트인 테이블에 대해 잠금을 걸어서 후속처리를 블록하는 방법이 있다.
잠금 단위에는 "테이블 전체, 블록, 행" 등이 있다. (MySQL은 주로 행 단위 잠금을 활용)

그렇다면 어떤 상태가 모순이 없다고 할 수 있을까?

바로 복수의 트랜잭션이 순서대로 실행되는 경우와 같은 결과를 얻을 수 있는 상태를 의미한다.

이것을 DBMS에서 격리 수준으로 구현하고 제공하는 것이 Serializable이라는 사양이다. Serializable이 보장되면 모순이 없는 상태가 만들어지기 훨씬 쉽겠지만, 성능면에선 결코 실용적이지 않다.

따라서 격리 수준을 네 단계로 나누는 것을 ANSI라는 규격 단체에서 정의했다.

  1. Read Uncommitted
  2. Read Committed
  3. Repeatable Read
  4. Serializable

격리 수준에 따라 발생할 수 있는 이상 현상은 아래와 같다.

  1. Dirty Read
  2. Non-Repeatable Read
  3. Phantom Read

1-4. 지속성

시스템이 정상일 때만이 아니라 DB나 OS의 이상 종료, 즉 시스템 장애도 견딜 수 있다는 것을 의미한다.

많은 DB 구현에서는 트랜잭션 조작을 HDD에 "로그"로 기록하고 시스템에 이상이 발생하면 그 로그를 사용하여 이상 발생 전의 상태까지 복원하는 것으로 지속성을 실현하고 있다.

2. 다른 커넥션에서 테이블을 본다?

기본적으로 DDL과 DML에 의한 데이터 저장은 트랜잭션이 커밋되기 전까지는 다른 커넥션에서 보이지 않는다.

하지만

  1. DDL에 따른 암묵적인 커밋
    MySQL이나 Oracle에서는 CREATE TABLE과 같은 DDL 실행 시 암묵적인 커밋이 발행한다. 이 때문에 한 개의 커넥션에서 실행된 CREATE TABLE이 성공하면 그 이후에는 다른 커넥션에서도 참조할 수 있게 된다.

  2. 오토커밋 설정
    BEGIN TRANSACTION START TRANSACTION SET TRANSACTION과 같은 트랜잭션의 개시가 명시적으로 지정되지 않았을 때 트랜잭션을 구별하는 방법으로는 (1) 하나의 SQL문이 하나의 트랜잭션으로 구분, (2) 사용자가 커밋 또는 롤백을 실행하기 전까지가 하나의 트랜잭션이 된다 이 두 가지가 존재한다.
    이때 MySQL은 기본 설정이 오토커밋이다.

3. 트랜잭션 격리 수준에 따른 외관상의 차이

MySQL은 MVCC라는 기술을 사용하기 때문에 아래의 특징을 갖는다.

  1. 읽기를 수행할 경우 갱신중이라도 block 되지 않는다. (읽기와 읽기도 서로 block 되지 않는다.)
  2. 읽기 내용은 격리 수준에 따라 내용이 바뀌는 경우가 있다.
  3. 갱신 시 배타적 잠금을 얻는다. 잠금은 기본적으로 행 단위로 얻으며 트랜잭션이 종료할 때까지 유지한다. 격리 수준이나 InnoDB의 설정에 따라 실제로 잠금하는 행의 범위가 다른 경우가 있다.
  4. 갱신과 갱신은 나중에 온 트랜잭션이 잠금을 획득하려고 할 때 block 된다. 일정 시간을 기다리며 그 사이에 잠금을 획득할 수 없는 경우에는 lock timeout 된다.
  5. 갱신하는 경우 갱신 전의 데이터를 UNDO 로그로 "롤백 세그먼트"라는 영역에 유지한다. 이 UNDO 로그는 용도가 두 가지인데 첫 번째는 갱신하는 트랜잭션의 롤백 시 갱신 전으로 되돌리는 것이고 두 번째는 복수의 트랜잭션으로부터 격리 수준에 따라 대응하는 갱신 데이터를 참조하는 데 이용한다.(같은 행을 갱신할 때마다 UNDO 로그가 작성되어 같은 행에 대한 ㅗㄱ수 버전이 존재하며 이에 의해 1과 2를 실현하고 있다.)

4. 잠금 타임아웃과 교착 상태가 발생하는 이유

잠금 타임아웃이란?

갱신과 참조는 서로를 block 하지 않지만, 갱신과 갱신이 부딪치는 경우에는 나중에 온 갱신이 잠금 대기 상태가 된다.
이때 어느정도 기다릴지를 설저할 수 있다. MySQL과 같은 경우엔 innodb_lock_wait_timeout 이란 시스템 변수로 설정한다.

잠금 대기로 타임아웃이 발생하는 경우 DBMS로부터 롤백되는 단위가 다를 때가 있는데, 해당 트랜잭션 전체를 롤백하는 경우와 쿼리만 롤백하는 것이다.
MySQL에서는 잠금 대기로 타임아웃이 발생하면 롤백되는 것은 기본으로 오류가 발생한 쿼리이다. 트랜잭션 전체를 롤백하고 싶다면 다음 방법으로 할수 있다.

  • 타임아웃 오류 후 명시적으로 rollback을 실행한다.
  • innodb_rollback_on_tiemout 시스템 변수를 설정한다.

교착상태란?

잠금을 유지한 채 서로 잠금을 건 자원에 잠금이 필요한 처리를 실행하면 아무리 기다려도 상황이 바뀌지 않는 상태가 된다. 이것이 바로 교착상태이다.

MySQL은 교착 상태가 일어나면 이를 즉시 인식해 시스템에 영향이 작은 쪽의 트랜잭션을 트랜잭션 개시 시점까지 롤백한다.

교착상태 빈도 낮추는 대책

교착 상태는 완벽하게 없앨 수는 없다. 따라서 어플리케이션 쪽에서는 항상 트랜잭션이 교착 상태를 일으켜 롤백되는 경우에 트랜잭션을 재실행할 수 있는 구조로 만들어야 한다.

DBMS 전반적인 대책
1. 트랜잭션을 자주 커밋한다.
2. 정해진 순서로 테이블에 액세스하게 한다.
3. 필요 없는 경우에는 읽기 잠금 획득의 사용을 ㅣ한다.
4. 쿼리에 의한 잠금 범위를 더 좁히거나 잠금 정도를 더 작은 것으로 한다.
5. 한 테이블의 복수 행을 복수의 연결에서 순서 변경 없이 갱신하면 교착 상태가 발생하기 쉽다. 동시에 많은 연결에서 갱신 때문에 교착 상태가 자주 발생한다면 테이블 단위의 잠금을 획득해 갱신을 직렬화하면 동시성은 떨어지지만 교착 상태는 회피할 수 있어서 전체 처리로 보면 좋은 예도 있다.

MySQL의 대책
1. 테이블에 적절한 인덱스를 추가해 쿼리가 이를 이용하게 한다. 인덱스가 사용되지 않는 경우에는 필요한 행의 잠금이 아닌 스캔한 행 전체에 대한 잠금이 걸리게 된다.

5. 해서는 안되는 트랜잭션 처리

1. 오토커밋

오토커밋이란 쿼리 단위로 커밋하는 설정이다.
이 설정은 커밋의 부하가 너무 높아서 적절한 단위와 트랜잭션 격리 수준에서 트랜잭션을 이용하 오토커밋을 사용하지 않도록 해야한다.

2. 긴 트랜잭션

DB 트랜잭션의 동시성이나 자원의 유효성을 저하한다.
-> 또한 대량의 갱신 처리를 한 개의 트랜잭션으로 실행하면 대량 갱신 처리를 롤백하기 위한 대량의 UNDO 로그를 트랜잭션 종료까지 유지해야 한다. 따라서 대량 처리는 적당한 크기의 트랜잭션으로 나눠서 실행하는 것을 추천다한다
-> 한 번 SELECT를 하고 아무것도 하지 않고 트랜잭션을 열린 채로 두면 같은 테이블에 갱신을 실행할 때 UNDO 로그가 계속 유지된 상태가 되기 때문에 이러한 상태를 피해야 한다.
-> 사용자와의 대화 처리를 트랜잭션에 넣게 되면 처리 대기 시간이 길어지고 이는 시스템 전체의 효율을 떨어뜨리게 된다.
-> 부하실험을 통해 적절한 트랜잭션의 수를 측정하여 이에 맞게 실행해야 한다.

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN