트랜젝션(Transaction)

데이터베이스 시스템에서 일련의 작업을 하나의 단위로 처리하는 것을 의미한다. 이는 여러 작업을 하나의 단위로 묶어 데이터의 일관성과 무결성을 보장하고, 시스템 장애나 오류 상황에서도 데이터를 신뢰할 수 있게 한다.

1. 트랜잭션의 특성(ACID)

  1. 원자성(Atomicity) 트랜잭션은 모든 작업이 전부 수행되거나 전혀 수행되지 않는 것을 보장한다. 중간에 실패하거나 오류가 발생하면 트랜잭션의 모든 작업은 취소되고 데이터는 원래 상태로 복구된다.

  2. 일관성(Consistency) 트랜잭션이 수행되기 전과 후의 데이터 상태는 일관성을 유지해야 한다. 트랜잭션이 완료된 후 데이터베이스는 비즈니스 규칙과 제약 조건을 모두 만족하는 상태여야 한다.

  3. 독립성(Isolation) 다중 사용자가 동시에 트랜잭션을 수행하더라도, 각 트랜잭션은 서로 간섭하지 않고 독립적으로 실행되는 것처럼 보여야 한다. 이를 통해 다른 트랜잭션의 영향을 받지 않고 일관된 결과를 보장할 수 있다.

  4. 영속성(Durability) 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 저장된다. 이후 시스템 오류가 발생하더라도 트랜잭션의 결과는 손실되지 않는다.

2. 트랜잭션의 상태

트랜잭션은 생명주기 동안 다양한 상태를 거친다. 주요 상태는 다음과 같다.

  • 활성(Active): 트랜잭션이 시작되어 작업을 수행 중인 상태.
  • 부분 완료(Partially Committed): 트랜잭션의 마지막 연산이 실행된 후의 상태로, 모든 작업이 성공적으로 완료되었지만 아직 영구적으로 커밋되지는 않은 상태.
  • 커밋(Committed): 트랜잭션이 성공적으로 완료되어 데이터베이스에 영구적으로 반영된 상태.
  • 실패(Failed): 트랜잭션이 수행 중 실패하여 중단된 상태. 이때 데이터베이스는 이전 상태로 되돌아가야 한다.
  • 철회(Aborted): 실패 후 트랜잭션이 롤백되어 데이터베이스가 트랜잭션 시작 이전 상태로 되돌아간 상태.

이러한 상태를 통해 데이터베이스는 트랜잭션의 흐름을 제어하고 복구하며, 오류 발생 시 데이터의 일관성을 유지한다.

3. 트랜잭션 제어 구문

트랜잭션 제어를 위해 SQL에서는 다음과 같은 구문을 제공한다.

  • BEGIN 또는 START TRANSACTION: 트랜잭션을 시작하는 구문이다.
  • COMMIT: 트랜잭션을 성공적으로 종료하고, 모든 변경 사항을 영구적으로 데이터베이스에 반영한다.
  • ROLLBACK: 트랜잭션 중 오류가 발생하거나 취소가 필요할 경우, 모든 변경 사항을 취소하고 데이터베이스를 트랜잭션 이전 상태로 복원한다.
  • SAVEPOINT: 트랜잭션 내 특정 시점에 저장점을 설정해 두고, 이후 이 시점으로 부분적인 롤백을 수행할 수 있다. 이는 대규모 트랜잭션의 오류 관리와 복구 시 유용하다.

4. 격리 수준(Isolation Level)

트랜잭션 격리 수준은 트랜잭션이 서로 얼마나 독립적으로 실행되는지를 결정하는 설정으로, 데이터 간섭을 조정해 데이터 일관성을 유지한다. 각 격리 수준은 성능과 일관성 간의 절충을 제공하며, 다음과 같은 네 가지가 있다.

  • READ UNCOMMITTED: 커밋되지 않은 데이터까지 읽을 수 있어 더티 리드(Dirty Read)가 발생할 수 있다.
  • READ COMMITTED: 커밋된 데이터만 읽을 수 있게 하여 더티 리드를 방지하지만, 반복적인 읽기에서 데이터가 달라질 수 있는 Non-repeatable Read 문제가 발생할 수 있다.
  • REPEATABLE READ: 트랜잭션이 시작된 후 커밋된 데이터만 읽을 수 있어 Non-repeatable Read 문제를 방지한다. 다만, 새로 추가된 데이터는 보이지 않아 팬텀 리드(Phantom Read)가 발생할 수 있다.
  • SERIALIZABLE: 가장 높은 격리 수준으로, 모든 트랜잭션이 직렬적으로 실행된 것과 동일한 결과를 보장한다. 팬텀 리드도 발생하지 않지만, 성능이 저하될 수 있다.

5. 트랜잭션의 동시성 제어(Concurrency Control)

동시성 제어는 다중 트랜잭션 간의 데이터 일관성을 유지하기 위해 필요하며, 주로 다음과 같은 방법으로 구현된다.

  • 잠금(Locking): 특정 데이터에 대해 읽기 또는 쓰기 잠금을 설정해 다른 트랜잭션이 동시에 접근하지 못하도록 한다. 잠금은 데이터 활용성을 제한하지만, 데이터 간의 충돌을 방지한다.
  • 타임스탬프 기반 관리: 각 트랜잭션에 타임스탬프를 부여해 일관된 순서로 트랜잭션을 처리하는 방식이다.
  • 낙관적 검증(Optimistic Concurrency Control): 트랜잭션이 완료될 때까지 데이터 충돌 여부를 검사하지 않으며, 최종 단계에서 충돌을 검증해 문제가 있으면 롤백한다. 이는 트랜잭션 충돌이 적을 것으로 예상될 때 효율적이다.

6. 분산 트랜잭션(Distributed Transactions)

분산 시스템에서는 여러 데이터베이스나 시스템에 걸쳐 일관성을 유지하기 위해 분산 트랜잭션이 필요하다. 2단계 커밋 프로토콜(2PC)을 통해 모든 노드가 트랜잭션 커밋에 동의하면 커밋을 수행하고, 하나라도 거부하면 롤백하여 데이터의 일관성을 유지한다. 이는 분산 환경에서 여러 데이터베이스의 일관성을 보장하는 중요한 방법이다.

트랜잭션은 ACID 속성에 따라 데이터의 무결성을 유지하며, 특히 다중 사용자가 동시에 데이터베이스에 접근하는 환경에서 매우 중요한 개념이다. 데이터의 안정성과 일관성을 보장함으로써 트랜잭션은 안정적인 데이터베이스 시스템의 기반이 된다.

DB Lock

DB Lock(데이터베이스 락)은 데이터베이스의 무결성과 일관성을 보장하기 위해 여러 트랜잭션이 동시에 같은 데이터에 접근할 때, 이를 조정하여 충돌을 방지하는 기법이다. 락을 통해 다중 트랜잭션 환경에서도 데이터베이스의 데이터가 일관성을 유지할 수 있도록 한다. 락을 설정하면 해당 자원에 대한 접근이 제한되며, 다른 트랜잭션은 자원이 해제될 때까지 대기하게 된다.

1. DB Lock의 필요성

DB Lock은 데이터베이스가 다중 트랜잭션을 처리하는 과정에서 데이터 충돌을 방지하기 위해 필수적이다. 동시에 여러 트랜잭션이 동일한 데이터에 접근하여 변경을 시도할 때, 데이터의 일관성과 무결성이 손상될 가능성이 있다. 예를 들어, 두 개의 트랜잭션이 하나의 계좌에서 동시에 금액을 인출한다면, 예상치 못한 결과로 계좌 잔액이 잘못 기록될 수 있다. 이를 방지하기 위해 락을 설정해 트랜잭션 간의 독립성과 안정성을 확보한다.

2. DB Lock의 종류

DB Lock은 락이 적용되는 범위와 방식에 따라 여러 종류로 나뉜다.

  • 공유 락(Shared Lock): 읽기 작업에 대한 락이다. 공유 락이 설정된 데이터는 여러 트랜잭션에서 동시에 읽을 수 있지만, 쓰기 작업은 수행할 수 없다. 공유 락은 다른 트랜잭션이 데이터를 수정하지 못하게 하여 일관성을 유지한다.

  • 배타 락(Exclusive Lock): 쓰기 작업에 대한 락이다. 배타 락이 설정된 데이터는 해당 트랜잭션 외에 다른 트랜잭션이 접근할 수 없게 하며, 다른 읽기 및 쓰기 작업이 모두 차단된다. 이를 통해 데이터 수정 시 일관성과 무결성을 보장한다.

  • 행 락(Row Lock): 특정 행에 대해 설정되는 락이다. 일반적으로 특정 레코드에 대한 경쟁이 발생할 때 사용하며, 행 단위로 락을 설정함으로써 데이터베이스의 병행성과 성능을 높일 수 있다.

  • 테이블 락(Table Lock): 특정 테이블 전체에 설정되는 락이다. 트랜잭션이 특정 테이블의 다수 행을 수정할 때 사용되며, 테이블 단위로 락이 걸리므로 병행성은 떨어지지만 트랜잭션 충돌 방지에 유리하다.

  • 페이지 락(Page Lock): 데이터베이스의 저장 페이지 단위로 설정되는 락이다. 페이지는 일반적으로 몇 개의 행을 포함한 단위로, 페이지 단위의 락은 행 단위 락보다 효율적이나 테이블 락보다는 더 높은 병행성을 제공한다.

  • 테이블스페이스 락(Tablespace Lock): 데이터베이스의 특정 테이블스페이스 전체에 대해 설정되는 락이다. 테이블스페이스는 여러 테이블이 포함된 논리적 저장소로, 테이블스페이스에 락이 걸리면 해당 범위 내의 모든 테이블에 대해 접근이 제한된다.

3. DB Lock의 동작 방식

DB Lock은 트랜잭션이 특정 데이터에 접근을 요청할 때 설정된다. 트랜잭션이 데이터베이스에 락을 요청하면, 데이터베이스는 현재 락 상태를 확인한 후 트랜잭션의 요청에 따라 락을 설정한다. 만약 다른 트랜잭션이 이미 락을 보유하고 있다면, 요청한 트랜잭션은 락이 해제될 때까지 대기한다. 락이 설정되면 다른 트랜잭션은 해당 데이터에 접근하지 못해 데이터 충돌을 방지할 수 있다.

트랜잭션이 완료되면, 데이터베이스는 트랜잭션의 락을 해제하여 다른 트랜잭션이 자원에 접근할 수 있도록 한다. 이러한 락의 설정과 해제는 트랜잭션의 상태에 따라 자동으로 수행된다.

4. DB Lock의 문제점과 해결 방법

DB Lock은 데이터의 무결성을 보장하는 데 필수적이지만, 불필요한 대기 시간과 병목 현상을 초래할 수 있다. 락이 오래 걸리면, 다른 트랜잭션이 자원을 대기해야 하므로 전체 시스템의 성능이 저하될 수 있다. 대표적인 문제점은 다음과 같다.

  • 데드락(Deadlock): 두 개 이상의 트랜잭션이 서로가 필요한 락을 대기하면서 무한히 대기 상태에 빠지는 상황이다. 데드락을 방지하기 위해 타임아웃을 설정하거나 데드락 회피 알고리즘을 적용할 수 있다.

  • 락 경합(Lock Contention): 여러 트랜잭션이 동일한 자원에 접근하려 할 때 발생하는 문제로, 병행성이 저하되어 성능이 저하된다. 락 범위를 줄이거나 적절한 락 전략을 사용해 경합을 줄일 수 있다.

  • 격리 수준(Isolation Level): 락의 격리 수준을 조정해 성능과 데이터 일관성 간의 균형을 맞출 수 있다. 예를 들어, 트랜잭션 격리 수준을 낮추면 병행성은 높아지지만 일관성 문제 발생 가능성도 커진다.

5. 트랜잭션 내 DB Lock 동작 예시

트랜잭션에서 DB Lock을 사용하는 예시는 다음과 같다.

  1. 트랜잭션 A가 공유 락(읽기 락)을 특정 데이터에 설정한다. 다른 트랜잭션도 해당 데이터에 대해 공유 락을 설정할 수 있지만, 배타 락을 설정할 수는 없다.

  2. 트랜잭션 B가 동일한 데이터에 대해 배타 락을 설정하려고 하면, 트랜잭션 A가 락을 해제할 때까지 대기해야 한다.

  3. 트랜잭션 A가 완료되면 COMMIT 명령어를 통해 작업을 확정하고, 데이터베이스는 락을 해제하여 트랜잭션 B가 데이터에 접근할 수 있게 한다.

  4. 트랜잭션 B가 배타 락을 설정하고 데이터를 수정한 후 COMMIT 또는 ROLLBACK을 통해 트랜잭션을 종료한다. COMMIT되면 수정된 데이터가 데이터베이스에 반영되고 락이 해제되며, ROLLBACK되면 수정된 내용이 취소된다.

DB Lock은 다중 트랜잭션 환경에서 데이터의 무결성을 보장하며, 동시에 여러 트랜잭션이 안정적으로 수행될 수 있도록 관리한다. 락의 전략과 수준을 적절히 설정함으로써 데이터베이스의 성능과 일관성을 동시에 유지할 수 있다.