프로그래밍 기초

데이터베이스 트랜잭션

hs-archive 2023. 6. 21. 23:40
개인적으로 공부한 내용을 기반으로 작성하였습니다.

잘못된 내용을 반견하신 경우, 댓글로 알려주시면 감사하겠습니다.

 

 

 

트랜잭션(Transaction)

트랜잭션은 DBMS 내에서 데이터베이스에 대해 수행되는 작업 단위입니다.

 

트랜잭션은 하나 이상의 데이터베이스 조작 작업을 하나의 논리적인 작업 단위로 묶어 관리합니다. 아래에 트랜잭션의 필요성을 느낄 수 있는 예시가 있습니다.

A 사용자가 B 사용자의 계좌에 500원을 이체하는 메서드:
    A 사용자의 계좌에서 500원을 출금합니다. // 1
    B 사용자의 계좌에 500원을 입금합니다. // 2

 

위와 같이 A 사용자가 B 사용자의 계좌에 500원을 이체하는 메서드가 있다고 생각해 봅시다. 해당 메서드는 1) A의 계좌에서 500원을 출금하고, 2) B 사용자의 계좌에 500원을 입금하는 순서를 갖습니다. 누군가 해당 메서드를 호출하여 A의 계좌에서 500원을 출금한 상황에서 갑자기 서버가 꺼져서 서비스가 종료되면, A의 계좌에서 500원이 출금되었지만, B의 계좌에 500원은 입금되지 않은 상황이 벌어집니다. 최종적으로 애꿎은 A의 500원만 사라진 것입니다. 이러한 상황은 절대로 일어나선 안 됩니다. 돈을 입금하고 출금했는데 정확하게 입금되고 출금되지 않는다면 아무도 해당 금융 서비스를 사용하지 않을 것이기 때문입니다.

 

A 사용자가 B 사용자의 계좌에 500원을 이체하는 메서드:
    Transaction(
    	A 사용자의 계좌에서 500원을 출금합니다. // 1
        B 사용자의 계좌에 500원을 입금합니다. // 2
    )

 

A 사용자가 B 사용자의 계좌에 500원을 이체하는 데 필요한 모든 작업을 하나의 트랜잭션으로 묶어서 처리하면, 해당 트랜잭션이 완료되기 전까지는 실제 데이터베이스의 값이 변경되지 않습니다. 대신, 트랜잭션의 작업 내용은 메모리에 저장되며, 트랜잭션의 모든 작업이 성공적으로 수행되었을 때 해당 변경 사항이 데이터베이스에 적용됩니다.

 

따라서, 1번 작업인 A의 계좌에서 500원을 출금하는 작업을 완료한 뒤, 2번 작업인 B의 계좌에서 500원을 입금하는 도중에 서버가 중간에 꺼진다고 해도 실제 데이터베이스의 값은 변경되지 않습니다. 왜냐하면 아직 데이터베이스에 해당 변경 사항이 적용되지 않았기 때문입니다. 해당 변경 사항은 오직 메모리의 변경만 야기했습니다. 따라서 A의 500원은 그대로 남아있게 됩니다.

 

이처럼 트랜잭션은 데이터베이스의 무결성정합성을 보장합니다. 트랜잭션은 데이터베이스의 무결성과 정합성을 보장하기 위해 네 가지 성질을 - Atomicity(원자성), Consistency(일관성), Isolation(격리성), Durability(지속성) - 갖습니다. 트랜잭션의 이러한 성질은 이들의 앞 글자를 따 "ACID"라 부릅니다.

 

더보기

 

** 무결성

무결성은 데이터의 정확성과 일관성을 의미합니다. 데이터베이스에서 데이터의 무결성은 정의된 규칙과 제약 조건을 준수하여 데이터의 논리적인 일관성을 유지하는 것을 의미합니다. 예를 들어, 특정 필드는 정숫값만 가질 수 있도록 제약 조건이 설정되어 있다면, 그 필드에는 반드시 정숫값만 입력되어야 합니다. 데이터의 무결성을 유지하기 위해 데이터베이스에서는 제약 조건, 트리거, 무결성 규칙 등을 사용합니다.

 

 

** 무결성 규칙

데이터베이스에서 데이터의 무결성을 보장하기 위해 정의된 규칙입니다. 주요한 무결성 규칙은 다음과 같습니다.

 

개체 무결성 - 기본 키(PK)를 갖는 각 테이블의 각 행은 고유하게 식별되어야 합니다. 즉, 기본 키 필드는 NULL 값을 가질 수 없으며 중복되지 않아(UNIQUE)야 합니다. 

 

참조 무결성 - 관계형 데이터베이스에서 테이블 간의 관계를 정의할 때, 외래 키(FK)를 사용합니다. 참조 무결성은 이 외래 키와 참조되는 테이블의 PK 간의 일관성을 유지해야 합니다. 즉, 외래 키가 참조할 수 있는 값은 참조하는 테이블의 기본 키값 중 하나여야 합니다. 

 

도메인 무결성 - 데이터베이스에서 사용되는 각 열(Column)은 특정한 데이터 유형과 범위를 가져야 합니다. 도메인 무결성은 데이터 유형에 맞게 데이터가 입력되고 제약 조건에 따라 유효한 값인지 확인합니다. 예를 들어 INT만 들어가야 하는 컬럼에 char가 들어가면 안 됩니다. 

 

범위 무결성 - 범위 무결성은 데이터가 특정 범위 내에 존재하는지를 확인하는 규칙입니다. 예를 들어, 날짜 값이 특정 기간 내에 존재해야 한다는 제약 조건이 있으면, 범위 무결성은 해당 날짜 값이 유효한 범위 내에 있는지 확인합니다.

 

기타 무결성 - 이외에도 데이터베이스 시스템에 따라 추가적인 무결성 규칙이 존재할 수 있습니다.

 

 

** 정합성

정합성은 데이터베이스의 상태가 언제나 일관된 상태를 유지하는 것을 의미합니다. 데이터베이스에서 여러 개의 테이블이나 엔티티가 관계를 맺고 있을 때, 이 관계가 항상 일관된 상태를 유지해야 합니다. 예를 들어, 주문 테이블에서 유저 테이블의 ID를 사용할 때, 해당 유저가 ID를 변경한다면 주문 테이블에 있는 유저 ID도 함께 변경되어야 하는 것입니다. 정합성을 유지하기 위해 데이터베이스에서는 외래 키(FK) 관계, 제약 조건, 트랜잭션 등을 사용합니다.

 

 

트랜잭션의 ACID 성질

트랜잭션은 네 가지 속성 Atomicity(원자성), Consistency(일관성), Isolation(격리성), Durability(지속성)로 설명됩니다. 이러한 성질은 이들의 앞 글자를 따 "ACID"라 부릅니다.

 

Atomicity(원자성)

트랜잭션은 원자적 단위로 동작해야 합니다. 원자적 단위로 동작한다는 말은 트랜잭션의 작업이 더 이상 분해될 수 없는 하나의 동작이라는 말입니다. 예를 들어, 기존 이체 메서드는 (출금)과 (입금)의 두 동작으로 나누어 처리되던 반면 트랜잭션으로 이체 메서드를 묶으면 (출금, 입금)이 하나의 동작으로 처리됩니다.

 

(출금)과 (입금) 두 동작으로 나뉘었을 때는 (출금)을 성공한 뒤 모종의 이유로 (입금)이 실패할 수 있습니다. 그 반대도 가능합니다. 왜냐하면 (출금)과 (입금)은 독립적인 두 동작이기 때문입니다. 하지만 (출금, 입금)이 하나의 원자성을 띤다면 (출금, 입금)이 하나의 동작으로 간주하기 때문에, (출금, 입금)이 전부 성공하거나 (출금, 입금)이 전부 실패하거나 두 가지 경우가 전부입니다. 출금만 성공하고 입금이 실패하거나 그 반대의 경우는 일어날 수 없습니다.

 

이러한 성질 즉, 작업이 전부 성공하거나 전부 실패하도록 보장하는 설질을 원자성이라고 합니다. atomicity는 쉽게 'all or nothing'으로 설명됩니다.

 

Consistency(일관성)

트랜잭션은 트랜잭션의 수행이 데이터베이스의 일관성을 보존해야 합니다. 이는 데이터베이스가 트랜잭션의 수행 전과 수행 이후에 일관된 상태를 유지해야 함을 뜻합니다.

 

좀 더 구체적으로 말하면 정의된 규칙과 제약 조건을 준수해야 하고, 값의 일관성을 유지해야 한다는 말입니다. 예를 들어, 트랜잭션 수행 후 기본 키, 외래키 제약과 같은 명시적인 무결성 제약조건들이 깨지면 안 되며, 이체의 예에서 두 계좌 잔고의 합은 이체 전후가 같아야 한다는 것이 일관성 조건입니다.

 

Isolation(격리성)

트랜잭션은 종종 동시에 실행됩니다. 여러 트랜잭션이 동시에 수행되더라도 각각의 트랜잭션은 다른 트랜잭션의 수행에 영향을 받지 않고 격리되어 수행되어야 합니다. 즉, 한 트랜잭션의 작업이 다른 트랜잭션에 영향을 주지 않고 서로 격리되어 실행되어야 한다는 의미입니다. 이러한 Isolation 성질을 보장하지 않으면 데이터의 일관성과 무결성이 깨질 수 있습니다.

 

예를 들어, A 계좌에서 B 계좌로 500원을 이체하는 이체 트랜잭션과 A와 B의 잔액 합을 계산하는 합 트랜잭션이 동시에 실행되는 상황을 생각해 볼 수 있습니다. 현재 이체 트랜잭션의 모든 작업이 성공적으로 수행되어 실제 데이터베이스 값을 수정하는 중입니다. A 계좌에서 500원 출금을 완료하였고, 이제 B 계좌에 500원을 입금하려 합니다. 바로, 이 순간에 합 트랜잭션이 수행되면 현재 A 계좌에서 빠져나간 500원이 아직 B 계좌에 입금되기 전이므로 합 트랜잭션의 결괏값은 원래 반환되어야 할 값보다 500원이 적은 값이 됩니다. 이처럼 격리성이 보장되지 않으면 문제가 발생합니다. 이러한 격리성을 준수하지 않음으로써 발생할 수 있는 문제로 Dirty Read(더티 리드), Non-Repeatable Read(반복 불가능한 읽기), Phantom Read(팬텀 리드)가 있습니다. 각 문제에 대해서는 다른 포스팅에서 말씀드리겠습니다.

 

Isolation 성질을 보장할 수 있는 가장 쉬운 방법은 모든 트랜잭션을 순차적으로 수행하는 것입니다. 하지만 병렬적 수행의 장점을 얻기 위해서 DBMS는 병렬적으로 수행하면서도 일렬(serial) 수행과 같은 결과를 보장할 수 있는 방식을 제공하고 있습니다.

 

Durability(지속성)

트랜잭션이 성공적으로 완료되면, 그 결과가 영구적으로 저장되고 유지되어야 합니다. 데이터베이스 시스템 또는 디스크 장애와 같은 예기치 않은 상황이 발생하더라도 데이터의 지속성이 보장되어야 함을 의미합니다. 이를 위해 데이터는 디스크에 영구적으로 저장되고, 복구 메커니즘을 사용하여 시스템 장애 시에도 데이터를 복구할 수 있어야 합니다.

 

트랜잭션의 상태(동작 순서)

트랜잭션이 무엇인지, 왜 필요한지를 배웠고 트랜잭션이 가져야 하는 성질에 대해 배웠습니다. 이제 그러한 트랜잭션이 가질 수 있는 상태를 상태 전이도를 보며 함께 배워봅시다.

 

 

트랜잭션 상태 전이도1)
트랜잭션 상태 전이도2)

 

활성 상태(Active state)

트랜잭션이 시작되어 연산들이 정상적으로 실행 중인 상태입니다. 모든 읽기 및 쓰기 작업(Read/Write operations)이 오류 없이 수행되면 부분 완료 상태로 전이됩니다. 만약 도중 오류가 발생한다면 실패 상태로 전이됩니다. 활성 상태에서의 읽기 및 쓰기 작업의 내용은 버퍼 풀(InnoDB에서 쓰이는 단어입니다. 다른 엔진에서는 다르게 불릴 수 있습니다.)과 로그에 저장됩니다.

 

부분 완료 상태(Partially Committed state)

트랜잭션의 모든 읽기 및 쓰기 작업이 성공적으로 완료된 상태입니다. 커밋이 되면 버퍼 풀 혹은 로그를 참고하여 변경 사항을 파악하고 해당 변경 사항을 디스크(일반적으로 HDD 혹은 SSD)에 영구적으로 반영합니다. 이 과정 또한 로그로 기록됩니다. 만약 도중 오류가 발생하여 실패하면 경우 실패 상태로 전이됩니다.

 

완료 상태(Committed state)

모든 변경 사항이 성공적으로 디스크에 반영되어 트랜잭션이 완료된 상태입니다. 변경된 데이터는 디스크에 기록되었고 해당 데이터는 다른 트랜잭션에도 가시적입니다.

 

실패 상태(Failed state)

트랜잭션 실행 중 오류가 발생하여 작업이 실패한 상태입니다. 실패한 트랜잭션에서 수행한 모든 작업과 변경 사항은 로그를 참고하여 롤백합니다.

 

철회 상태(Aborted state)

실패한 트랜잭션이 완전히 롤백 된 상태입니다. 해당 트랜잭션에서 수행한 모든 작업과 변경 사항이 취소되어 이전 상태로 돌아간 상태입니다.

 

종료 상태(Terminated state)

트랜잭션이 성공적으로 완료되거나 실패하여 롤백 된 후 종료된 상태입니다. 트랜잭션이 더 이상 실행되지 않으며, 모든 작업과 처리가 종료되었음을 나타냅니다.

 

핵심

트랜잭션이 커밋되어 변경 사항을 디스크에 반영할 때 버퍼 풀 혹은 로그를 참고합니다. 반대로, 트랜잭션이 실패하여 롤백을 실행할 때는 로그를 참고합니다. 따라서 버퍼 풀 로그가 트랜잭션의 핵심입니다.

 

버퍼 풀(buffer pool) InnoDB

DBMS는 비휘발성 저장 장치인 디스크에 데이터를 저장합니다. 따라서 데이터베이스를 통해 데이터를 읽고 수정한다는 것은 최종적으로 디스크에 있는 데이터를 읽고 수정한다는 말입니다. 아시겠지만 컴퓨터에서 디스크(HDD, SSD)는 매우 매우 느린 장치입니다. 그래서 DBMS는 HDD, SSD에 비해 굉장히 빠른 RAM에 데이터베이스에서 사용하는 데이터를 저장해 놓습니다. 이렇게 데이터베이스의 속도를 향상하기 위해 RAM에 데이터를 저장해 놓는 공간을 버퍼 풀이라고 부릅니다. 이러한 버퍼 풀 안에는 디스크에서 로드해 놓은 여러 페이지가 들어있습니다.

 

더보기

 

** 페이지

데이터베이스의 데이터 저장 및 관리의 기본 단위입니다. DBMS는 데이터를 고정 길이의 페이지로 저장하며, DBMS를 사용하여 디스크에서 데이터를 읽거나 쓸 때에 페이지 단위로 입출력이 이루어집니다.

 

간단하게 말해서 DB는 1kb, 2kb, 24kb, 6kb 와 같이 항상 가변적인 단위로 데이터를 다루는 것이 아니라 애초에 "페이지"라는 단위를 딱 정해놓고 그 단위로만 데이터를 다룬다는 것입니다. 이는 OS의 파일 시스템에서의 Block 개념과 비슷합니다.

 

페이지 크기는 4KB, 8KB, 16KB 등의 크기를 가지며 한 번 정하면 바꾸기 어렵습니다. 아래는 페이지 크기가 클 수록 갖는 장점과 단점 입니다.

 

큰 페이지의 장점

- I/O 횟수 감소: 페이지 크기가 크면 한 번의 디스크 I/O로 더 많은 데이터를 가져올 수 있습니다. 따라서 데이터베이스 시스템의 성능이 향상될 수 있습니다. 작은 페이지 크기보다 더 많은 데이터를 한 번에 읽고 쓸 수 있으므로 I/O 횟수가 감소하고 디스크 액세스 시간이 줄어듭니다.

 

큰 페이지의 단점

- 내부 조각화: 페이지 크기가 크면 작은 크기의 데이터가 페이지에 저장되는 경우 남는 공간이 큽니다. 이러한 내부 조각화가 발생할 수 있습니다.

 

- 동시성 문제: 페이지 크기가 크면 여러 트랜잭션에서 동시에 한 페이지에 액세스하는 경우 동시성 문제가 발생할 수 있습니다. 한 페이지에 여러 트랜잭션이 동시에 작업을 수행하면 잠금 충돌이 발생할 가능성이 높아집니다.

 

 

RAM은 비싼 저장 장치이고 따라서 일반적인 경우 RAM의 용량은 데이터베이스에서 사용하는 데이터를 모두 올려놓기에는 충분하지 못합니다. 그래서 데이터를 전부 올려놓지는 못하고 일부만 올려놓습니다. DBMS는 이러한 상황에서 hit ratio를 높이기 위해 LRU 알고리즘 사용 등 여러 노력을 기울입니다.

 

데이터베이스에서 데이터를 수정하거나 조작하는 작업(오퍼레이션)을 수행하면 해당 작업으로 인해 페이지들이 수정됩니다. InnoDB를 기준으로 이러한 수정된 페이지의 비율이 일정 비율을 초과하면, 버퍼 교체 알고리즘에 따라 일부 수정된 페이지들이 디스크로 출력됩니다. 즉, 아직 완료되지 않은(커밋되지 않은) 트랜잭션이 수정한 페이지들도 디스크에 반영될 수 있습니다. 심지어, 위에서는 커밋이 되는 시점에 해당 변경 사항이 디스크에 영구적으로 반영된다고 말했지만, 사실 대부분의 DBMS에서 수정된 페이지가 디스크에 기재되는 시점은 트랜잭션 커밋이 되는 시점이 아니라 이러한 버퍼 관리자의 정책과 페이지 교체 알고리즘에 따라 결정됩니다.

 

로그(log)

만약 어떤 트랜잭션이 실패하여 롤백해야 하는 경우 트랜잭션이 변경한 페이지들은 원상 복구되어야 합니다.(원자성) 이러한 복구를 UNDO라고 합니다. 작업을 취소하여 이전 상태로 되돌리는 작업입니다. 

 

또한, 커밋된 트랜잭션의 수정은 어떤 경우에도 유지되어야 합니다.(지속성) 대부분의 DBMS는 트랜잭션이 수정한 페이지들을 트랜잭션이 커밋된 시점에 곧바로 디스크에 반영하지는 않습니다. 대신 버퍼 관리자의 정책과 페이지 교체 알고리즘에 따라 결정됩니다. InnoDB의 경우 앞서 말씀드린 것처럼 수정된 페이지의 비율이 일정 비율을 초과할 때 페이지 플러싱(변경된 페이지를 디스크에 반영하는 것)이 동작합니다. 커밋은 되었지만, 아직 디스크에 플러싱되진 않은 페이지가 서버 다운이나 비정상적인 종료 등의 상황이 발생하여 그들이 갖고 있는 내용이 모두 날아갔다면(RAM이니까 종료되면 값은 사라집니다.) 트랜잭션의 지속성을 보장하기 위해 이미 커밋한 트랜잭션의 수정 내용을 다시 페이지에 재반영하는 복구 작업이 이루어져야 합니다. 이러한 복구 작업을 REDO 복구라고 합니다.

 

한마디로 트랜잭션이 실패하여 롤백해야 할 때 UNDO 복구를 하고 트랜잭션이 커밋되어 완전 종료되었지만, 해당 트랜잭션의 수정 내용이 아직 디스크에 플러싱 되지 않은 상황에서 비정상적인 종료 등의 상황이 발생하여 페이지에 수정 내용을 다시 기재해야 할 때 REDO 복구를 합니다. 이러한 UNDO 복구와 REDO 복구를 위해서 가장 널리 쓰이는 구조가 로그(log)입니다.  

 

로그는 데이터베이스의 모든 갱신 작업을 기록합니다. 로그는 덧붙이는(append) 방식으로 기록되며, 각 로그 레코드는 고유의 식별자를 갖습니다. 로그는 로그 타입과 관계 없이 다음의 규칙에 따라 써집니다.

 

- 해당 업데이트가 데이터베이스에 써지기 전에 먼저 관련된 UNDO 정보가 로그에 써져야 합니다. 이 원칙을 WAL(Write Ahead Logging)이라고 부릅니다. 어떤 경우에도 UNDO 복구가 되기 위해서는 반드시 WAL 규칙이 준수되어야 합니다.

 

- 트랜잭션이 정상적으로 종료 처리되기 위해서는 먼저 REDO 정보가 로그에 써져야 합니다. 역시 어떤 경우에도 REDO 복구를 할 수 있으려면 REDO 로그가 적어도 커밋 시점에는 써져야 합니다.

 

정리

트랜잭션은 DBMS 내에서 데이터베이스에 대해 수행되는 작업 단위입니다. 트랜잭션은 데이터베이스의 무결성과 정합성을 보장하기 위해 ACID 속성을 갖습니다.

 

트랜잭션의 작업 내용은 버퍼 페이지에 기록됩니다. 대부분의 DBMS에서 변경된 페이지가 디스크에 기입되는 시점은 트랜잭션이 커밋될 때가 아니라 버퍼 관리자의 정책과 페이지 교체 알고리즘에 따라 결정됩니다.

 

트랜잭션이 정상적으로 종료되기 위해서는 REDO 로그가 적어도 커밋 시점에는 기록되어야 합니다. REDO 로그는 변경 사항을 재실행하는 데 사용됩니다. 트랜잭션이 실패하면 롤백을 위해 UNDO 복구가 실행됩니다. 트랜잭션이 실패하면 롤백을 위해 UNDO 복구가 실행됩니다. UNDO 복구는 UNDO 로그를 사용하여 트랜잭션 이전 상태로 되돌립니다.

 

 

 

 

 


https://velog.io/@leesilverash/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-Transaction

 

[데이터베이스] 트랜잭션 - Transaction

트랜잭션 (Transaction) A라는 사람이 B라는 사람의 계좌에 5천원을 입금하는 기능을 구현할 때 개발자가 주의해야할 점이 있다. 입금이라는 하나의 논리적 기능은 여러 가지 작업의 단위를 포함하

velog.io

https://mariadb.com/kb/en/innodb-page-flushing/

 

InnoDB Page Flushing

Configuring when and how InnoDB flushes dirty pages to disk.

mariadb.com

https://d2.naver.com/helloworld/407507