트랜잭션과 고립레벨 이해하기

최근혁(GeunH)·2024년 4월 1일
post-thumbnail

서론

현재 정보들이 디지털화되는 추세 속에서, 데이터베이스를 관리하는 것은 필수적이다.

이 과정에서 데이터베이스 관리 시스템(DBMS)의 역할은 매우 중요하다.

오늘은 DBMS와 DBMS에서 처리하는 작업인 "트랜잭션"에 대해 정리해보았다.


DBMS ( Database Management System )

DBMS는 데이터베이스 내의 작업을 처리하며, 데이터의 저장, 조회, 수정, 삭제 등 다양한 기능을 제공한다.

데이터베이스 자체는 데이터들의 구조화된 집합이라 할 수 있으며, 이러한 데이터베이스의 효율적인 관리와 작업 처리를 가능하게 하는 것이 바로 DBMS이다.

DBMS의 CRUD 연산을 통해 데이터베이스에 데이터에 대한 생성, 읽기, 수정, 삭제 작업을 진행할 수 있는 것이다.


트랜잭션

DBMS는 위와같이 DB에 대한 여러 작업을 수행할 수 있는데 이러한 작업의 가장 작은 기본 단위를 "트랜잭션" 이라고 한다.

그럼 "쿼리 하나가 트랜잭션 인가?" 라고 궁금해 할 수 있다.

정답은 NO 이다

트랜잭션은 DBMS에서 가장 작은 작업의 단위라는 말은 맞지만, 쿼리 하나라고는 볼 수 없다.

트랜잭션은 Database에 작업을 수행하고 commit 또는 rollback하기전까지의 실행하는 일련의 쿼리 집합을 의미한다.

조회 -> 업데이트 -> 조회 -> 커밋 인 경우에는
커밋 전 3가지의 쿼리 작업이 하나의 트랜잭션이 되는 것이다.

DBMS에서 이러한 트랜잭션은 크게 ACID 라는 4가지의 특성을 가지고 있다.

1. Atomicity ( 원자성 )

트랜잭션은 모두 성공하거나 실패되어야 한다.

트랜잭션 내 쿼리들을 실행하다가 중간에 취소가 되거나, 무기한 정지가 되는 상황이 발생해서는 안된다는 것이다.

실패한 경우에는 반드시 롤백되어 실행하지 않은 상태가 되어야 한다.

2. Consistency ( 일관성 )

트랜잭션을 수행한 후 작업을 수행한 결과가 반영되어 해당 데이터를 이용하는 쿼리의 응답이 항상 일정해야 한다는 것을 의미한다.

트랜잭션이 완료되었는데, 이를 이용한 작업의 결과가 달라진다면 일관성을 충족하지 않았다고 할 수 있다.

3. Isolation ( 고립성 )

현재 트랜잭션을 수행하는 것이 다른 트랜잭션에 영향을 주어서는 안된다는 것이다.
커밋하기 전 ( = 트랜잭션 결과 확정 ) 에 수행한 결과가 실행하는 다른 트랜잭션의 결과에 영향을 주어서는 안된다는 특성이다.

이는 다른 트랜잭션이 현재 트랜잭션에 끼어들어서는 안된다는 것을 의미한다.

4. Durability ( 영속성 )

한번 트랜잭션이 수행되어 적용된 결과의 데이터는 설령 DB가 오류가 발생했다 하더라도 변경되지 않고 다시 작업을 수행하기 전까지 영원히 일정한 상태로 DB에 저장되어야 한다는 특성이다.



트랜잭션 고립 레벨

위에서 설명한 3번 특성인 고립성과 관련하여 트랜잭션의 고립 레벨을 사용자는 임의로 설정할 수 있다.

고립 레벨의 변경에 따라 DBMS 작업 속도와 다양한 문제 가능성이 달라진다.

크게 고립 레벨은 4가지고 Read Uncommitted, Read committed, Repeatable Read, Serializable 이 있다.

1. Read Uncommitted

말 그대로 커밋되지 않은 데이터를 읽는 것을 허용하는 범위 이다.

온라인 쇼핑몰을 예시로 들어 설명해 보겠다.

A라는 트랜잭션은 "쇼핑몰에 물품을 등록하는 것"이라고 하고,
B라는 트랜잭션은 "쇼핑몰에서 물품 리스트 수"를 확인하는 작업이라고 하겠다.

A가 물품을 등록하는 작업 ( INSERT 쿼리 ) 를 진행했지만 아직 이러한 작업을 COMMIT 하지 않은 상태에서 B가 물품을 조회하는 작업 (SELECT 쿼리 )를 진행하면 어떤 상황이 발생할까?

아직 완료되지 않은 A 트랜잭션 작업의 결과를 B가 읽게되는 것이다.

이것이 왜 문제가 될까??

A가 작업을 완료하면 어차피 B가 조금 앞당겨서 결과를 가져온 것이더라도 문제가 없지 않나? 라고 생각할 수 있다.

이 경우라면 크게 문제가 되지 않을 수 있지만, 만약 A가 작업을 완료하지 않고 취소 ( Rollback ) 한다면 어떻게 될까?

B는 존재하지도 않는 물품의 리스트를 잘못 읽는 결과를 불러일으킨다. "일관성" 과 "무결성" 에 위배되는 것이다.

이를 "Dirty Read"라고 한다.


2. Read Committed

아! 그럼 commit되지 않은 데이터를 읽는 것이 문제였으므로 Commit완료된 데이터만 접근하도록 하면 문제가 되지 않겠구나! 라고 생각할 수 있다.

이 또한 예시를 들어보았다.

A라는 트랜잭션은 1번 물품 조회 ( SELECT ) 를 일정한 간격두고 2번 진행하는 작업,
B라는 트랜잭션은 1번 물품의 정보를 수정 ( UPDATE ) 하는 작업이라고 하겠다.

A 트랜잭션이 먼저 수행되며 첫번쨰 조회를 마친 상태에서 B 트랜잭션이 수행되어 1번 물품 정보가 업데이트 되었다고 해보자.

이때, A 트랜잭션의 두번째 조회가 이루어지기 전에 B 트랜잭션의 commit 이 완료된다면 A 트랜잭션의 두번째 조회의 결과는 B 트랜랜잭션에서 수정한 1번 물품의 정보가 조회될 것이다.

이는 같은 트랜잭션에서 수행한 2번의 같은 쿼리의 결과가 달라질 수 밖에 없다.

이를 "Non-repeatble READ" 라고 한다.
Read Uncommited 에서 발생한 문제를 해결하기 위해 Commit된 결과만을 접근하게 해도 문제가 발생하는 것이다.


3. Repeatble Read

이 방법은 "반복해서 읽는 데이터의 일관성을 보장"하는 방법이다.
한번 조회가 이루어진 데이터는 해당 트랜잭션의 작업이 끝날때까지 수정,삭제가 이루어지지 않는다.

아! 그럼 이제 정말 다 해결이 되었구나! 라고 생각할 수 있는가?

예를 들어보겠다.

A 트랜잭션은 등록된 물품 전체 개수를 2번 확인 ( SELECT COUNT(*) )하는 작업이고,
B 트랜잭션은 새로운 물품을 등록하는 작업이라고 하겠다.

만약 A 트랜잭션의 첫번째 쿼리가 실행되고 ,B트랜잭션이 실행 및 COMMIT 완료된다면 어떻게 되겠는가? A트랜잭션의 두번째 쿼리에서는 B 트랜잭션으로 인해 추가된 물품의 개수까지 조회할 것이다.

왜 이게 가능한 것일까??

Repeatable Read는 "반복해서 읽는 데이터의 일관성을 보장"한다고 했다. 이것이 중요한데, B 트랜잭션을 생각해보자.
B 트랜잭션에서 등록된 새로운 물품은 기존에 A 트랜잭션에서 조회된 데이터에 해당하지 않는다. 따라서 일관성을 보장받지 못하는 데이터라는 것이다.

이것이 없는 데이터가 갑자기 생겼다고 하여
"Phantom-Read 팬텀 리드" 현상이라고 한다.

이처럼 Repeatable Read는 새로이 삽입된 데이터에 대한 일관성을 보장하지 못한다는 단점이 존재한다.


4. Serializable

그럼 이렇게 해보자.

모든 트랜잭션이 순차적으로 실행되게 해보자!!

지금까지의 모든 예외는 서로 다른 트랜잭션이 동시에 실행되어 발생한 문제이므로 한 트랜잭션이 끝날때까지 절대로!! 실행되지 못하게 해보자!!

이 방법에는 어떤 예외가 있을까??

없다.

예외를 처리하기 위한 확실한 방법이라고 할 수 있다.

그럼 이 방법이 최선일까??

모든 트랜잭션이 순차적으로 실행된다는 것은 성능 저하를 일으킬 수 있다.

단순히 조회하는 작업조차도 순차적으로 실행되므로 한번에 하나밖에 실행하지 못하는 작업 진행속도는 대용량 요청을 처리하기에는 매우 부적합하다.


최선의 고립

위와 같이 각각의 고립 레벨은 장점과 단점을 모두 가지고 있다. 모든 트랜잭션이 동시에 이루어질 수 있는 Read Uncommitted는 "Dirty Read"를 모든 트랜잭션의 일관성과 무결성을 최대한 보장하는 Serializable은 "성능 저하"를 부른다.

이때문에 현재 개발에서는 Read Committed, Repeatable Read 레벨을 오가며 낙관적 락( Optimistic Lock ) 과 같은 방법을 통해 예외를 최대한 처리하려 한다.

이는 나중 포스트에서 정리해보겠다.

결론

오늘은 DBMS와 트랜잭션, 트랜잭션의 특성과 고립레벨에 대해 정리해보았다.

많은 사람들이 이해하는 데 도움이 되었길 바라며 글을 마치겠다.

profile
목표 : 스스로 성장하는 개발자

0개의 댓글