앞선 글에서는 정규화(Normalization)라는 설계 기법을 통해
데이터의 중복과 불일치, 이상(anomaly) 현상을
효과적으로 방지할 수 있음을 확인했다.
하지만, 실제로 여러 사용자가 동시에 데이터를 읽고,
수정하는 “실시간 운영 환경”에서는
단순한 설계만으로는 데이터의 ‘정확성’과 ‘일관성’을
완전히 보장하기 어렵다.
엑셀이나 스프레드시트처럼 단순한 파일에서는
동시에 여러 사용자가 접근할 경우
데이터가 꼬이거나, 예기치 않은 충돌이 빈번하게 발생할 수 있다.
예를 들어,
“송금”이나 “재고 관리” 같은 중요한 비즈니스 로직에서
하나의 데이터가 여러 작업에 의해 동시에 변경된다면
잘못된 금액이 이체되거나,
재고가 음수로 떨어지는 심각한 문제가 생길 수 있다.
이런 문제를 해결하기 위해
데이터베이스는 트랜잭션(Transaction)이라는
강력한 보호장치를 제공한다.
이번 글에서는
트랜잭션이란 무엇이고, 왜 필요한지,
그리고 실전에서 어떤 원리와 규칙으로
데이터의 신뢰성을 지키는지
쉽고 구체적으로 정리해보려 한다.
트랜잭션(Transaction)의 사전적 의미는 거래이고,
컴퓨터 과학 분야에서의 트랜잭션(Transaction)은 "더이상 분할이 불가능한 업무처리의 단위"를 의미한다.
이것은 하나의 작업을 위해 더이상 분할될 수 없는 명령들의 모음,
즉, 한꺼번에 수행되어야 할 일련의 연산모음을 의미한다.
우리가 사용하는 DBMS에서 트랜잭션은 데이터베이스를 상태를 바꾸는 일종의 작업 단위이다.
INSERT, DELETE, UPDATE 등의 SQL 명령문을 통해 데이터를 상태를 바꾸는 일련의 모든 과정이 하나의 트랜잭션이다. 이때 오해하면 안되는게,각각 명령어가 하나의 트랜잭션이 될 수도 있지만, 묶여서 하나의 트랜잭션이 될 수도 있는 것이다.
원자성은 트랜잭션이 데이터베이스에 모두 반영되던가, 아니면 전혀 반영되지 않아야 한다는 것이다.
트랜잭션 내의 모든 작업이 “한 덩어리”로서 수행된다.
중간에 한 단계라도 실패하면, 그 전에 진행된 모든 변경 사항도 함께 롤백(취소)된다.
원자성의 특성을 반영하면 데이터가 꼬이는 일을 방지 할 수 있다.
일관성은 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다는 것이다.
트랜잭션 전/후의 데이터가 무결성(Integrity), 제약조건(Constraints), 비즈니스 규칙 등을 항상 지켜야 함을 의미합니다.
잘못된 트랜잭션은 아예 실행되지 않거나, 실행 도중 롤백됩니다.
독립성은 둘 이상의 트랜잭션이 동시에 실행되고 있을 경우,
어떤 하나의 트랜잭션이라도 다른 트랜잭션의 연산에 끼어들 수 없다는 점을 가리킨다.
한 트랜잭션이 완료되기 전까지 다른 트랜잭션이 그 변경사항을 볼 수 없거나, 영향을 받지 않아야 함을 의미합니다.
각각의 트랜잭션이 “내부적으로는 단독으로 실행되는 것”처럼 느껴져야 합니다.
격리 수준(4단계)마다 세부 동작이 다르지만, 기본 개념은 서로 간섭 불가능 합니다.
커밋된 트랜잭션의 결과는 데이터베이스에 영구적으로 저장되어야 한다.
일단 트랜잭션이 커밋되면, 정전, 서버 다운, 시스템 장애가 발생해도 해당 데이터는 영원히 보존되야 합니다.
대부분의 데이터베이스는 커밋 시 디스크 기록(Write Ahead Logging 등)까지 완료한 뒤
성공 신호를 줍니다.
트랜잭션은 작업을 시작해서 끝날 때까지 여러 상태를 거치는데, 대표적인 상태 변화는 아래와 같다.
상태 | 설명 |
---|---|
활성 (Active) | 트랜잭션이 시작되어 현재 연산(삽입, 삭제, 갱신 등)이 진행 중인 상태. 아직 아무것도 확정되지 않은 “진행 중” 단계. |
부분 완료 (Partially Committed) | 트랜잭션의 마지막 연산이 끝난 상태. 하지만 완전히 DB에 기록(Commit)되진 않았으며, DBMS가 내부적으로 변경사항을 점검 중인 단계. |
완료 (Committed) | 트랜잭션의 모든 작업이 성공적으로 처리되어 영구적으로 데이터베이스에 반영된 상태. 더 이상 롤백이 불가능함. |
실패 (Failed) | 트랜잭션 처리 도중 오류(예: 제약조건 위반, 시스템 오류)가 발생하여 더 이상 진행이 불가능해진 상태. |
철회 (Aborted) | 실패 상태에서 Rollback 명령에 의해 트랜잭션의 모든 변경사항이 이전 상태로 되돌려진 상태. 즉, 트랜잭션이 취소되고 데이터는 아무 변화도 없는 것처럼 유지됨. |
정의
아직 커밋되지 않은(=확정 전) 데이터를
다른 트랜잭션이 읽는 현상입니다.
문제점
만약 최초 트랜잭션이 롤백된다면,
이미 읽어간 트랜잭션은 존재하지 않는 값을 사용하게 됩니다.
예시
기본 동작
Transaction 1: x에 y를 더한다.
Transaction 2: y를 70으로 바꾼다.
- 동작 과정-
정의
하나의 트랜잭션이 같은 데이터를 두 번 읽을 때,
그 사이 다른 트랜잭션이 값을 수정하여
두 번째 읽기에서 결과가 달라지는 현상입니다.
문제점
한 트랜잭션 내에서 읽은 값이 일관성 없이 달라질 수 있음.
예시
정의
한 트랜잭션이 같은 조건으로 여러 번 조회할 때,
그 사이 다른 트랜잭션이 새로운 행을 삽입/삭제해서
결과가 달라지는 현상입니다.
문제점
한 번은 없던 데이터가, 같은 조건에서 다음 조회에는 등장
→ 데이터의 정합성에 혼란이 생김
예시
공유락(Shared Lock, S Lock)
배타락(Exclusive Lock, X Lock)
데이터 락 상태 | 신규 S Lock 요청 | 신규 X Lock 요청 | 설명 |
---|---|---|---|
락 없음 | 가능 | 가능 | 락이 없으니 누구나 락 가능 |
S Lock 한 명 있음 | 가능 | 불가 | 읽기는 OK, 쓰기는 X |
S Lock 여러 명 있음 | 가능 | 불가 | 읽기는 OK, 쓰기는 X |
X Lock 한 명 있음 | 불가 | 불가 | 모두 대기! |
트랜잭션의 격리 수준(Isolation Level)은
여러 트랜잭션이 동시에 작업할 때 발생할 수 있는
Dirty Read, Unrepeatable Read, Phantom Read
문제에 대한 허용 여부를 정의합니다.
아래 표는 각 격리 수준별로
어떤 문제가 허용(○)되고,
어떤 문제는 방지(×)되는지 나타냅니다.
이걸 보면서 궁금해진게 생겼다. 왜 다막지 않고, 일부만 막는 단계(격리 수준)가 있는 걸까? 찾아보니 주요 내용은 이러했다.
트랜잭션의 락 관리 방식 중 하나로, 일관성(Serializability)을 보장하기 위해 사용합니다.
"락을 다 얻은 후에만 해제할 수 있다"라는 두 가지 단계로 운영됩니다.
성장 단계(Growing Phase)
축소 단계(Shrinking Phase)
2PL 프로토콜을 따르면 데드락이 발생할 수 있지만,
트랜잭션의 일관성(Serializability)은 항상 보장됨
무한루프
트랜잭션이 정사적으로 종료되지 못하고 계속 실행되면, 시스템 리소스를 잡아먹고 다른 작업에 영향을 줌.
락 장기 점유
하나의 트랜잭션이 락을 오래 쥐고 있으면,
다른 트랜잭션들이 대기하면서 전체 성능이 저하됨.
심하면 데드락 위험도 커짐.
적절한 롤백 처리
오류나 예외 상황에서 Rollback을 해주지 않으면
데이터가 이상한 상태로 남게 됨.
데드락의 원인, 탐지, 해결법 등은
별도 챕터([교착상태(Deadlock)])에서 다룰 예정입니다.
참고 자료