멈추지 않고 끈질기게

[DB] 트랜잭션(Transaction) 본문

DB

[DB] 트랜잭션(Transaction)

sam0308 2023. 4. 4. 10:18

※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.

※ 해당 포스팅은 Mycrosoft SQL Server에서 사용하는 T-SQL 문법을 기반으로 하고 있으며, 예시는 SSMS(SQL Server Management Studio) 환경에서 작성하고 있습니다. 

※ 해당 포스팅은 하기 출처들을 참조하였습니다.

- 김연희, 데이터베이스 개론, 한빛아카데미, 2022

 

 

 

 

이번 포스팅에서는 데이터베이스의 트랜잭션 개념에 대하여 알아보겠습니다.

1. 트랜잭션의 정의

 트랜잭션(transaction)이란 하나의 작업을 수행하는데 필요한 데이터베이스 연산들을 모아놓은 것을 의미하며, 데이터베이스에서 논리적인 작업의 단위가 됩니다. 예를 들어서 물건을 판매하는 과정을 생각해보면, 해당 물건의 재고를 줄이는 연산과 (판매 가격 * 판매 수)만큼 매출을 늘리는 연산이 필요할 것입니다. 이 두가지 연산을 물건 판매라는 작업을 나타내는 하나의 트랜잭션으로 묶는 것입니다. 

 

 이렇게 연산을 트랜잭션 단위로 묶는 이유는 데이터베이스의 일관성을 지키기 위함입니다. 상기 예시에서 물건의 재고를 줄이는 연산만 수행하고 매출을 늘리는 연산을 수행하지 않는다면, 마치 물건이 도둑맞은 것처럼 물건은 줄었으나 매출은 늘지 않는 상황이 발생할 것입니다. 이러한 상황을 막기 위해 논리적인 하나의 작업을 하나의 트랜잭션으로 묶어 관리하는 것입니다. 

 

 

 

2. 트랜잭션의 4대 특성

 트랜잭션이 성공적으로 처리되어 데이터베이스의 무결성과 일관성을 보장하려면 만족해야 하는 4가지 특성이 있습니다. 4가지 특성은 원자성(atomicity), 일관성(consistency), 격리성(isolation), 지속성(durability)이며, 영어 스펠링에서 앞글자를 따와서 ACID 특성이라고도 합니다.

 

 

원자성(atomicity)

 트랜잭션을 구성하는 연산들이 전부 실행되거나, 혹은 아예 실행되지 않아야 함을 의미합니다(all or nothing). 원자성을 지키려면 트랜잭션의 연산 수행 중 오류 발생 시 지금까지의 연산을 모두 취소하여야 합니다. 따라서 오류 발생 시 데이터베이스를 원래 상태로 복구하는 기능이 필요합니다.

 

그림 1. 트랜잭션의 원자성

 그림 1은 원자성을 지키지 않을 경우 발생할 수 있는 문제를 도식화한 것입니다. 물건이 판매되어 해당 물건의 재고를 줄이고 판매된 만큼 매출을 늘려야 하는데, 재고를 줄이고 나서 오류가 발생한 경우입니다. 이 때 기존 연산을 취소하지 않고 현재까지 진행된 연산만 저장한다면, 물건의 재고는 줄어들었는데 매출은 늘지 않은 상황이 발생합니다. 

 

 

일관성(consistency)

 트랜잭션이 완료된 후에도 데이터베이스가 일관된 상태를 유지해야 함을 의미합니다. 즉, 트랜잭션 수행 후에 데이터베이스의 일관성이 깨지면 안됩니다. 트랜잭션 수행 도중에는 일관성이 다소 깨질 수 있지만, 트랜잭션 완료 후에는 반드시 유지되어야 합니다.

 

 그림 1의 예시를 다시 살펴보면, 트랜잭션 수행 전 가게의 총 자산 가치는 (상품 가격 * 재고) + (매출) = 5000 * 10 + 10000 = 60,000원 입니다. 여기서 물건이 몇개가 팔리던, 정상적으로 판매된다면 총 자산 가치는 변하지 않을 것입니다. 하지만 트랜잭션 수행 후를 살펴보면 총 자산 가치가 5000 * 9 + 10000 = 55,000원으로 하락했음을 알 수 있습니다. 이렇게 트랜잭션 실행 후 데이터베이스의 일관성이 깨지는 상황이 발생해서는 안됩니다.

 

 

격리성(isolation)

 현재 실행중인 트랜잭션이 완료될 때까지 해당 트랜잭션이 생성한 중간값에 다른 트랜잭션이 접근할 수 없어야 함을 의미하며, 고립성이라고도 합니다. 데이터베이스는 대개 여러 사용자가 동시에 접근하여 여러개의 트랜잭션이 병렬로 수행될 수 있지만, 각각의 트랜잭션들이 독립적으로 수행되어야 합니다. 

 

그림 2. 트랜잭션의 격리성

 그림 2는 트랜잭션의 격리성을 지키지 않을 경우 발생할 수 있는 문제를 도식화한 것입니다. 그림 1과 같은 트랜잭션을 실행하는 도중에 동일 상품의 재고 수를 줄이는 다른 트랜잭션이 실행된 상황입니다. 왼쪽 트랜잭션에서는 재고를 9로 기억하고 있고 오른쪽 트랜잭션은 거기서 추가로 감소시킨 8로 기억하고 있기 때문에, 두 트랜잭션이 완료된 후에 데이터베이스를 갱신할 때 해당 상품의 재고를 명확하게 결정할 수 없는 상태가 됩니다. 따라서 격리성을 보장하려면 현재 실행중인 트랜잭션이 접근하고 있는 데이터에 접근하는 다른 트랜잭션은 순차적으로 실행해야 합니다.

 

 

지속성(durability)

 트랜잭션이 완료된 후에 수행 결과가 손실되지 않고 영구적으로 남아야 함을 의미하며, 영속성이라고도 합니다. 시스템 오류가 발생하더라도 한번 완료된 트랜잭션 수행 결과가 없어지지 않아야 하며, 이를 보장하려면 데이터베이스 복구 기능이 필요합니다.

 

 

 

3. 트랜잭션의 5가지 상태

그림 3. 트랜잭션의 5가지 상태

 트랜잭션은 그림 3의 5가지 상태값 중 하나를 가지게 되며, 최초 활동 상태에서 수행 결과에 따라 부분완료나 실패 상태로 넘어갑니다. 부분완료에서 commit 연산 후 완료 상태로, 실패에서 rollback 연산 후 철회 상태로 넘어가며 트랜잭션이 완료나 철회 상태에 도달하면 종료된 것으로 판단합니다. 각 상태별 상세 내용은 다음과 같습니다.

 

 

활동(active) 상태

 트랜잭션이 현재 수행중인 상태를 활동 상태라고 합니다. 활동 상태에서 마지막 연산까지 정상 수행되면 부분완료 상태로, 도중에 오류 발생 등으로 실패할 경우 실패 상태로 넘어가게 됩니다. 

 

 

부분 완료(partially committed) 상태

 트랜잭션의 마지막 연산까지 실행이 끝났으나, 최종 결과를 아직 데이터베이스에 반영하지 않은 상태를 부분 완료 상태라 합니다. 여기서 commit 연산(트랜잭션이 성공적으로 완료되었음을 알리는 연산) 실행 후 완료 상태로 넘어가며, 그 전에 오류가 발생할 경우 실패 상태로 넘어가게 됩니다.

 

 

완료(committed) 상태

 트랜잭션 실행 후 commit 연산(트랜잭션 수행 성공을 알리는 연산)까지 실행한 상태를 완료 상태라 합니다. 이 상태에서는 트랜잭션의 최종 결과를 데이터베이스에 반영하고 해당 트랜잭션을 종료합니다.

 

 

실패(failed) 상태

 트랜잭션을 정상적으로 수행할 수 없어 중단된 상태를 실패 상태라 합니다. 실패 상태가 되는 원인은 하드웨어나 소프트웨어상의 문제, 혹은 트랜잭션 자체의 오류 등 여러가지 이유가 있을 수 있습니다. 부분 완료 상태의 트랜잭션도 commit 연산 수행 전에 오류가 발생할 경우 실패 상태가 될 수 있습니다.

 

 

철회(aborted) 상태

 트랜잭션 실패 후 rollback 연산(트랜잭션 수행 실패를 알리고 데이터베이스 원복)을 실행한 상태를 철회 상태라 합니다. 철회 상태가 되면 해당 트랜잭션에서 실행한 모든 연산을 취소하고 데이터베이스를 트랜잭션 수행 이전의 상태로 복구하며 종료합니다(원자성 유지). 철회 상태로 종료된 트랜잭션은 하드웨어나 소프트웨어의 일시적인 오류로 인한 것이었다면 다시 실행하고, 트랙잭션 자체의 오류(연산 자체에 논리적인 오류 존재 or 존재하지 않는 데이터에 접근)였다면 그대로 폐기합니다.

 

 

 

4. SQL 예제

 SQL로는 BEGIN TRAN, COMMIT 키워드를 사용해 트랜잭션을 구성할 수 있습니다.

BEGIN TRAN
    트랜잭션 내용1
    트랜잭션 내용2
    ...
COMMIT

 BEGIN TRAN으로 하기 내용들이 하나의 트랜잭션으로 실행되어야 함을 알리고, COMMIT으로 트랜잭션의 끝을 알립니다. 해당 쿼리 실행 시 COMMIT에 도달하기 전까지는 트랜잭션의 본문에서 접근하는 테이블에 다른 사용자가 접근할 수 없게 됩니다.

 

 또한 COMMIT 대신 ROLLBACK 키워드를 사용하면 트랜잭션 실행 전으로 원복할 수 있습니다.

BEGIN TRAN
    트랜잭션 내용1
    트랜잭션 내용2
    ...
ROLLBACK

 ROLLBACK을 만나는 순간 BEGIN TRAN 이후의 내용들을 전부 원복하여 데이터베이스를 트랜잭션 실행 전 상태로 되돌리게 됩니다. COMMIT과 ROLLBACK을 통해 트랜잭션의 원자성(ALL or Nothing)을 보장하게 됩니다.

 

 다만 트랜잭션 수행 중에 이상이 없다면 COMMIT, 에러가 발생했다면 ROLLBACK을 실행하는 식으로 분기를 나눌 필요성이 있습니다. 이 경우 BEGIN TRY ~  END TRY / BEGIN CATCH ~ END CATCH 구문을 사용할 수 있습니다.

BEGIN TRY
    내용1
    내용2
    ...
END TRY
BEGIN CATCH
    내용1
    내용2
    ...
END CATCH

 다소 길지만 사실 내용은 기존의 C++, C# 등에서 사용하는 try ~ catch 구문과 다를 바 없습니다. BEGIN TRY ~  END TRY 사이의 내용들을 실행하여 에러가 없을 경우 END TRY에서 종료, 에러 발생 시 BEGIN CATCH ~ END CATCH로 이동하여 그 안의 내용을 실행하는 식입니다. 따라서 TRY 구문 안에 트랜잭션 내용과 COMMIT을 작성하고, CATCH 구문 안에 ROLLBACK을 작성하면 에러 발생 여부에 따라 COMMIT / ROLLBACK이 이루어지도록 할 수 있습니다.

 

BEGIN TRY
    BEGIN TRAN
	INSERT INTO players
	VALUES(1002, '왼손은활들뿐', 75, '서버종결자');
	INSERT INTO players
	VALUES(1003, '치유의근원', 55, NULL);
    COMMIT
END TRY
BEGIN CATCH
    ROLLBACK
END CATCH

그림 4. 트랜잭션 성공

 위 예시는 임시로 만든 players 테이블에 추가 데이터를 삽입하는 트랜잭션을 TRY ~ CATCH 구문을 이용하여 구성한 것입니다. players 테이블은 playerID가 1001인 초기 데이터만 삽입하고, playerID를 기본키로 설정한 상태입니다. 트랜잭션 내부의 내용들이 별다른 에러를 일으키지 않아 2개의 데이터가 모두 정상적으로 실행된 모습입니다.

 

BEGIN TRY
    BEGIN TRAN
        INSERT INTO players
	VALUES(1004, 'ArC_MagE', 25, '친목길드');
	INSERT INTO players
	VALUES(1003, '치유의근원', 55, NULL);
    COMMIT
END TRY
BEGIN CATCH
    ROLLBACK
END CATCH

그림 5. 트랜잭션 실패

 이번 예시는 트랜잭션이 실패하는 경우를 보여주고 있습니다. 트랜잭션 내용을 보면 playerID가 1003으로 중복되는 데이터를 삽입하려 하고 있습니다. playerID는 중복 비허용이므로 CATCH 구문 안의 ROLLBACK이 실행되었고, 실행 결과 players 테이블에는 아무런 변화도 없음을 확인할 수 있습니다. 주목할 부분은 먼저 삽입되었을 playerID가 1004인 데이터까지 원복되어 테이블에 존재하지 않는 점입니다. 이렇듯 트랜잭션은 해당 내용의 원자성을 보장합니다.

 

 주의할 점은, BEGIN TRAN 사용 시 반드시 COMMIT이나 ROLLBACK으로 트랜잭션을 끝내야 합니다. 

BEGIN TRAN
        INSERT INTO players
	VALUES(1004, 'ArC_MagE', 25, '친목길드');
	INSERT INTO players
	VALUES(1003, '치유의근원', 55, NULL);

 위와 같은 쿼리 실행 시, 트랜잭션에서 players 테이블에 접근하고 있기 때문에 트랜잭션 종료 전까지 다른 사용자의 players 테이블 접근을 차단합니다. 그런데 COMMIT도 ROLLBACK도 없기 때문에 트랜잭션이 계속 실행중인 상태로 유지되고, 아무도 players 테이블에 접근할 수 없는 상태가 계속되는 것입니다. 이는 마치 멀티쓰레드 프로그래밍에서 어떤 스레드가 작업이 끝나고도 자원을 반환하지 않는 상태와 비슷합니다. 따라서 트랜잭션 사용 시 반드시 종료 키워드가 누락되지 않도록 주의해야 합니다.

 

 또한 반드시 원자성을 보장해야 하는 내용만 트랜잭션으로 작성하는 것이 바람직합니다. 위에서 언급했듯이 트랜잭션 실행중에는 해당 테이블의 접근을 차단하므로, 시간이 오래걸리는 작업을 트랜잭션으로 실행하거나 너무 빈번하게 사용 시 데이터베이스의 성능 저하를 유발할 수 있습니다. 따라서 쿼리 작성 시 반드시 트랜잭션으로 작성해야 하는 부분을 판별하기 위해 노력해야겠습니다.

 

 

 

'DB' 카테고리의 다른 글

[DB][SQL] 데이터의 검색  (0) 2023.05.02
[DB] 관계 데이터 모델  (0) 2023.04.22
[DB] 데이터 모델링  (0) 2023.04.17
[DB] 데이터베이스 관리 시스템(DBMS)  (0) 2023.04.09
[DB] 데이터베이스의 정의  (0) 2023.02.26