DB 트랜잭션
트랜잭션
정의
한 번 질의가 실행되면 질의가 모두 수행되거나 모두 수행되지 않는, 작업 수행의 논리적 단위이다.
다양한 상황에서 트랜잭션이라는 용어를 사용할 수 있지만 보통 데이터베이스와 관련하여 많이 사용한다. 이 글 또한 데이터베이스의 트랜잭션을 다루려고 한다.
사용 이유
데이터의 부정합을 방지하고자 할 때 사용한다.
보통 트랜잭션은 다른 트랜잭션과 동시에 작업 내용이 겹치는 행위(수정, 삭제)을 최대한 방지하는데, 만약 이런 행동을 허용한다면 의도치 않은 결과를 얻게 될 수 있다.
특성
원자성 (Atomicity)
All or Nothing의 표본이다.
작업 단위를 일부분만 실행하지 않는다는 뜻인데, 한 트랜잭션에 포함되어 있다면 모든 작업 내용이 반영되거나 롤백되어야 한다.
일관성 (Consistency)
트랜잭션 완료 시 일관적인 데이터베이스의 상태가 유지되어야 한다.
트랜잭션이 완료되었는데 갑자기 int 타입이 varchar 타입등으로 변경되어서는 안된다는 의미이다.
격리성 (Isolation)
트랜잭션 수행 중 다른 트랜잭션이 끼어들지 못하도록 보장한다.
서로의 작업에 최대한 간섭하지 않아야 하며, 각자의 행위가 서로 영향을 끼치지 않아야한다.
지속성 (Durability)
성공적으로 수행된 트랜잭션은 영원히 반영된다.
데이터베이스에 commit 완료시 해당 상태는 다른 쿼리가 실행되기 전까지 보장되어야 한다.
고립성 보장 방법
다양한 락 기법들을 통해 고립성을 보장하고 있다.
데이터를 읽거나 쓸 때는 문을 잠궈서 다른 트랜잭션이 접근하지 못하도록 고립성을 보장하고, 수행을 마치면 락을 해제하여 데이터를 다른 트랜잭션이 접근할 수 있도록 허용한다.
데이터를 잠그는 행위는 보통 컬럼 단위로 진행된다.
shared lock
보통 읽기 락이라고 많이 불리며, 데이터를 읽을 때(read) 사용한다.
같은 shared lock을 취득한 다른 트랜잭션들이 데이터에 접근해 정보를 읽을 수 있기 때문에 공유라는 의미를 가진 share 키워드를 사용하는 듯 하다. 하지만 아무리 같은 shared lock을 취득한 트랜잭션일지라도 데이터를 쓰는 행위는 불가능하다.
다시 정리하자면 여러 트랜잭션이 읽을 수는 있지만 쓰기는 허용하지 않는다.
exclusive_lock
쓰기 락이라고 많이 불리고, 데이터를 쓸때(insert, update) 사용한다.
‘배타적’이라는 exclusive의 단어 뜻 답게 다른 트랙잭션은 접근할 수 없으며, 읽기 작업도 허용되지 않는다.
정리하면 다른 트랜잭션이 읽을 수도 쓸 수도 없다. 접근을 허용하지 않는다.
고립 수준
트랜잭션의 조회 범위를 지정, 제한하는 것이며 고립 수준이 올라갈수록 데이터를 읽는 속도는 느려지지만 데이터 정합성의 문제는 조금씩 완화된다.
아래의 고립 수준은 낮은 단계부터 기술되어있다.
READ UNCOMMITTED
리드 언커밋티드는 말 그대로 커밋 여부에 관계없이 값을 읽어올 수 있다.
고립 수준에 따른 대표적인 문제 상황이 3가지가 존재하는데 다른 트랜잭션에 의해 변경된 값을 읽어오는 더티 리드, 쿼리를 2번 실행하는데 다른 트랜잭션이 수정, 삭제하여 다른값을 읽어오는 논 리피터블 리드, 쿼리를 2번 실행하는데 없던 레코드가 튀어나오는 팬텀 리드 현상이 존재한다.
리드 언커밋티드는 위의 3가지 상황이 모두 발생할 우려가 있다.
READ COMMITTED
리드 커밋티드는 커밋이 완료된 데이터만 읽을 수 있다.
고로 다른 트랜잭션이 조작한 값을 도중에 읽어오는 더티 리드 현상은 발생하지 않지만 여전히 논 리피터블 리드, 팬텀 리드 현상은 발생할 소지가 존재한다.
REPEATABLE READ
리피터블 리드는 트랜잭션이 시작되기 전에 커밋된 내용만 조회할 수 있게 하는 수준이다.
이게 무슨 말이냐면, 트랜잭션마다 고유의 id값이 존재하는데 이 값은 지속적으로 증가한다. 데이터베이스 테이블을 정의할 때 auto increment를 건 상황이라고 생각하면 이해가 빠를 듯 하다.
그래서 중간에 값이 수정되어도 해당 id값보다 큰 트랜잭션의 변경사항은 읽어오지 않기 때문에 다른 트랜잭션이 수정, 삭제하여 다른값을 읽어오는 논 리피터블 리드 현상을 막을 수 있다.
하지만 테이블 단위의 쿼리, select count(*) 과 같은 쿼리 실행시 집계되지 않았던 데이터가 잡히는 팬텀 리드 현상이 발생할 수 있다.
SERIALIZABLE
트랜잭션이 진행중이면 다른 트랜잭션에서의 모든 수정, 삭제, 삽입이 불가능하다.
무적의 방법 같지만 너무 많은 퍼포먼스가 희생되므로 거의 사용되지 않는다.
참고
- 내가 예전에 정리했던 자료들