Concurrent Control in Database
동시성 제어, 트랜잭션 격리 수준, 락과 데드락

Contents
1️⃣ 데이터 동시성과 일관성(Data Concurrent and Consistency)
2️⃣ 트랜잭션 격리 수준 (Standard of Transaction Isolation)
3️⃣ 락과 데드락(Lock and Deadlock)
1️⃣ Summary
☑️ 데이터 동시성 (Data Concurrency): 여러 사용자가 동시에 데이터베이스에 접근할 때 일관성을 유지하는 기술
☑️ 트랜잭션 격리 수준 (Transaction Isolation Level): 동시에 수행되는 트랜잭션 간 충돌을 방지하기 위해 격리 수준을 설정하여 데이터 일관성을 유지한다. 대표적인 문제로 더티 리드 (Dirty Read), 반복되지 않는 읽기 (Nonrepeatable Read), 팬텀 읽기 (Phantom Read)가 있다.
☑️ 멀티버전 읽기 일관성 (Multiversion Read Consistency): 여러 버전의 데이터를 유지하여 특정 시점의 일관된 데이터를 제공하는 방법이다. SCN (System Change Number)과 언두 세그먼트 (Undo Segment)를 통해 과거 데이터 상태를 복원하고 일관된 데이터 읽기를 보장한다.
☑️ ITL (Interested Transaction List): 데이터 블록의 트랜잭션 상태를 관리하여 트랜잭션의 락 상태와 커밋 여부를 추적하는 기능이다. 이를 통해 데이터 동시성 제어와 트랜잭션 일관성을 유지할수 있다.
2️⃣ Summary
Read Uncommitted:
설명: 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽을 수 있는 수준. 이는 가장 낮은 격리 수준이며, “더티 리드(Dirty Read)"가 발생할 수 있다.
장점: 성능이 높지만 데이터 일관성이 낮다.
단점: 데이터의 정확성이 보장되지 않음.
Read Committed:
설명: 다른 트랜잭션이 커밋한 데이터만 읽을 수 있는 수준이다. “더티 리드(Dirty Reads)"는 방지하지만 "비교적 읽기"나 "팬텀 읽기"는 여전히 발생할 수 있다.
장점: 커밋된 데이터에 대한 일관성 제공.
단점: 일부 일관성 문제 여전히 발생 가능.
Repeatable Read:
설명: 트랜잭션이 시작된 시점부터 완료될 때까지 같은 데이터를 여러 번 읽을 수 있으며, 중간에 다른 트랜잭션이 데이터를 변경할 수 없다. "팬텀 읽기"는 방지되지 않는다.
장점: 데이터의 일관성이 높음.
단점: 성능이 떨어질 수 있음.
Serializable:
설명: 트랜잭션이 서로 완전히 독립적으로 실행되며, 결과적으로 모든 트랜잭션이 직렬화된 것처럼 작동한다. 다른 트랜잭션의 모든 작업이 커밋된 후에만 데이터를 읽을 수 있다.
장점: 데이터 일관성을 가장 높게 유지.
단점: 성능 저하가 발생할 수 있으며, 더 많은 대기 시간이 필요함.
2. 주요 개념
더티 리드(Dirty Read): 커밋되지 않은 데이터를 읽는 경우.
비교적 읽기 (Nonrepeatable Read): 동일한 트랜잭션 내에서 두 번 읽은 데이터가 다른 경우.
팬텀 읽기 (Phantom Read): 트랜잭션이 반복적으로 실행될 때, 중간에 데이터가 추가되어 결과가 달라지는 경우.
3. 트랜잭션 격리 수준의 선택
성능과 데이터 일관성 간의 균형을 고려하여 적절한 격리 수준을 선택하는 것이 중요
일반적으로 높은 격리 수준(예: Serializable)은 더 많은 잠금과 대기를 요구하기 때문에 성능 저하를 초래할 수 있다.
3️⃣Summary
1. 락(Lock): 락은 데이터베이스에서 여러 트랜잭션이 동일한 데이터에 동시에 접근할 때 데이터의 일관성과 무결성을 유지하기 위해 사용하는 메커니즘이다.
공유락(Shared Lock): 데이터를 읽기 위해 사용하며, 여러 트랜잭션이 동시에 접근할 수 있다.
배타락(Exclusive Lock): 데이터를 수정할 때 사용하며, 다른 트랜잭션이 동시에 접근하지 못하게 한다.
업데이트락(Update Lock), 스키마락(Schema Lock), 대량업데이트락(Bulk Update Lock) 등의 다양한 락이 특정 상황에 맞게 사용된다.
목적: 락을 통해 여러 트랜잭션이 같은 데이터를 동시에 수정하지 못하게 하여 데이터의 무결성을 유지한다.
2. 데드락(Deadlock): 데드락은 두 개 이상의 트랜잭션이 서로가 필요로 하는 자원을 점유하며 서로 대기하게 되어 무한정 진행되지 못하는 교착 상태를 의미한다.
발생 조건: 각 트랜잭션이 상대방이 점유하고 있는 자원을 기다리면서 발생한다.
해소 방법 3가지 (3 methods to resolve Deadlock)
희생자 지정(Victim Selection): 데드락 상태에 있는 트랜잭션 중 하나를 선택하여 중단한다.
롤백(Rollback): 희생자로 지정된 트랜잭션을 롤백하여 자원을 해제한다.
에러 메시지 전송: 희생자로 선택된 트랜잭션에 에러 메시지를 전송하여 작업이 중단되었음을 알린다.
목적: 교착 상태에서 트랜잭션이 무한 대기하지 않도록 문제를 해결하고 시스템을 원활하게 운영하기 위함이다.
1️⃣ 데이터 동시성과 일관성
(Data Concurrent and Consistency)
데이터 동시성 (Data Concurrency)
💡요약: 데이터베이스는 여러 사용자가 동시에 접근하여 데이터를 처리할 수 있도록 해야 하며, 이 과정에서 동시 공유 (Concurrent Sharing) 기능을 지원하여 여러 트랜잭션이 같은 데이터를 동시에 수정할 때 일관성을 유지해야 한다. 이를 통해 트랜잭션 처리 결과가 일관되게 유지되도록 보장해야 한다.
동시 공유 (Concurrent Access): 여러 사용자가 동시에 데이터베이스에 접근하여 데이터를 수정할 수 있다.
이 과정에서 동일한 데이터에 대한 여러 트랜잭션이 동시에 발생하게 된다.
따라서, 일관성 (Consistency)을 유지하기 위해 트랜잭션 처리가 중요하다.
💡예시: 두 명의 사용자가 동시에 은행 계좌의 잔액을 조회하고자 한다. 한 사용자가 잔액에서 100달러를 출금하려고 하고, 다른 사용자가 잔액을 500달러로 수정하려고 한다. 두 트랜잭션이 동시에 수행될 경우, 일관성을 유지하기 위해 시스템은 각 트랜잭션을 차례로 수행하게 하여 최종 잔액이 올바르게 계산되도록 보장해야 한다. 이를 위해 락(lock)이나 트랜잭션 관리(transaction management)와 같은 방법을 사용한다.
데이터 동시성과 데이터 일관성
(Data Concurrency & Data Consistency)
💡요약: 데이터베이스 시스템이 다수의 사용자와 프로그램이 동시에 데이터에 접근하더라도 일관성을 유지하면서 데이터를 처리하는 것이 중요하다. 특히, 사용자 간의 데이터 불일치를 방지하기 위해 각 사용자에게 일관된 데이터 상태를 제공해야 한다.
☑️ 데이터 동시성 (Data Concurrency): 여러 사용자가 동시에 데이터에 접근할 수 있도록 보장한다. 이렇게 하면 여러 사람이 같은 데이터를 함께 사용할 수 있게 된다.
☑️ 데이터 일관성 (Data Consistency): 각 사용자가 데이터를 볼 때, 일관성 있게 보이도록 보장한다. 사용자가 자신의 데이터를 수정하면 그 수정된 내용이 본인에게 반영되어야 하고, 다른 사용자가 수정된 내용을 보려면 그 내용이 확정된 후에만 보여야 한다.
💡예시: 두 명의 사용자가 동시에 상품 재고를 관리한다고 가정해보자. A 사용자가 상품 수량을 10에서 9로 줄이려고 하고, B 사용자는 같은 상품의 수량을 10에서 11로 늘리려고 할 때, 데이터 일관성을 위해서는 A와 B의 작업이 충돌하지 않고 순서대로 반영되어야 한다.
이를 위한 간단한 알고리즘은 락(lock) 시스템을 사용하는 것이다. (잠시후 배울 예정) A 사용자가 재고 수정을 시작할 때 해당 상품에 대한 잠금을 설정하고, A의 작업이 끝난 후 B가 작업을 수행하게 함으로써 데이터 불일치를 방지할 수 있다.
💡위에서 언급된 “순서대로” 반영되어야 한다는 것은 트랜잭션의 직렬가능성을 의미함
트랜잭션의 직렬 가능성 (Serializability) - 데이터 동시성 보장
💡요약: 직렬 가능성은 여러 트랜잭션이 동시에 실행될 때 데이터 일관성을 보장하기 위해 각 트랜잭션이 독립적으로 수행되는 것처럼 보이게 하는 방법인데 이를 위해 트랜잭션 격리 수준을 설정(Transaction isolation level)하여 작업의 충돌을 방지하고 성능과 데이터 일관성을 보장할 수 있게 한다.
- 트랜잭션 격리 수준 (Transaction Isolation Level)을 정의하여 여러 트랜잭션이 동시에 일어날 때에도 데이터의 일관성 (Consistency)을 유지시킨다.
☑️ 직렬 가능성 (Serializability)은 사용자가 마치 단독으로 데이터베이스를 사용하는 것처럼 보이게 하여, 트랜잭션 간의 충돌을 방지하는 방법이다.
- 각 트랜잭션이 서로 완전히 분리되어 있으면 성능에 큰 영향을 줄 수 있기 때문에, 멀티버전 일관성 모델 (Multi version Consistency Model)과 다양한 락 (Lock)을 통해 성능과 일관성을 동시에 유지할 수 있다.
💡예시: 은행 계좌에 A 사용자가 100달러를 입금하고 B 사용자가 동시에 50달러를 출금하려고 할 때, 직렬 가능성을 통해 각 사용자가 이 작업이 서로 겹치지 않게 수행된 것처럼 보이게 해야 한다. 여기서 락을 사용하면 하나의 트랜잭션이 끝날 때까지 다른 트랜잭션이 대기하게 되어 데이터가 안전하게 업데이트된다.
이러한 상황에서는 A의 입금 트랜잭션이 완료된 후 B의 출금 트랜잭션이 수행되거나, 반대로 B가 출금을 완료한 후 A가 입금하는 순서로 실행된다.
멀티버전 읽기 일관성 (Multi version Read Consistency) - 데이터 일관성 보장
💡요약: 데이터베이스 시스템이 데이터를 읽을 때, 사용자가 원하는 시점에서 일관된 데이터(Read Consistency)를 읽을 수 있도록 지원하는 기능이다. 이러한 일관성 수준을 통해 데이터 변경이 발생하더라도 사용자가 특정 시점의 데이터 상태를 보장받을 수 있게 된다.
명령문 수준 읽기 일관성 (Statement-Level Read Consistency): 특정 명령문이 실행되는 순간에만 일관성을 보장한다. 각 명령문은 독립적으로 실행되며, 각 명령문이 실행될 때마다 데이터의 일관성이 보장된.
트랜잭션 수준 읽기 일관성 (Transaction-Level Read Consistency): 트랜잭션이 시작될 때의 데이터 상태가 트랜잭션 전체에 걸쳐 일관성을 유지한다. 트랜잭션이 완료될 때까지 다른 트랜잭션에서 발생한 데이터 변경이 반영되지 않습니다. 이는 팬텀 리드 (Phantom Read)와 같은 문제를 방지할 수 있다.
💡예시: A 사용자는 은행 계좌에서 모든 잔액이 100달러인 고객 목록을 조회하는 작업을 하고 있다고 하자. B 사용자는 계좌 잔액을 수정할 수 있다.
명령문 수준 읽기 일관성 (Statement-Level Read Consistency): A 사용자가 특정 시점의 계좌 잔액을 확인하는 명령을 실행할 때, 그 명령이 실행되는 시점에 잔액이 100달러로 보인다면, 그 명령이 실행되는 동안 B 사용자가 잔액을 변경하더라도 A 사용자는 여전히 100달러로 확인할 수 있어야 한다. 즉, 명령문이 실행되는 순간의 데이터가 고정된다. 각 명령문이 실행되는 순간의 데이터 일관성만 보장되기 때문에 A가 같은 조회를 반복할 때마다 결과가 달라질 수 있다.
트랜잭션 수준 읽기 일관성 (Transaction-Level Read Consistency): A 사용자가 트랜잭션을 시작하고 고객 목록을 조회한 후, 트랜잭션이 종료될 때까지 동일한 고객 목록을 유지한다고 가정해보자. 트랜잭션 도중에 B 사용자가 새로운 고객을 추가하더라도 A는 트랜잭션 내에서 처음 조회했던 고객 목록만 보게 되며, 새로운 고객이 추가된 것을 볼 수 없다다. 이는 팬텀 읽기를 방지하여 트랜잭션 동안 데이터의 일관성을 유지하기 위함이다.
멀티버전 읽기 일관성과 언두 세그먼트 개요 (Overview of Multi version Read Consistency & Undo Segment)
💡요약: 멀티버전 읽기 일관성은 데이터베이스에서 동일한 데이터를 다양한 시점에서 일관되게 읽을 수 있도록 여러 버전의 데이터를 유지하는 방식이다. 이를 통해 데이터 베이스가 특정 시점에서 일관된 데이터 상태를 유지하도록 도와준다.
읽기 일관성 (Read Consistency): 동일한 데이터를 여러 사용자가 동시에 조회하거나 수정할 때 일관성 있는 데이터 상태를 제공하는 것.
☑️ 언두 세그먼트 (Undo Segment): 데이터가 수정되면 이전 상태를 보관하는 공간이다. 이를 통해, 사용자가 데이터를 읽을 때 각자 특정 시점의 데이터 상태를 일관되게 볼 수 있게된다.
변경된 데이터는 트랜잭션이 완료되기 전까지 변경되기 이전 값 (Previous Version)을 언두 세그먼트에 보관하며, 특정 시점에서의 스냅샷을 제공한다.
💡예시: A 사용자가 제품 목록에서 특정 상품의 가격을 100달러에서 120달러로 변경하려고 한다. 동시에 B 사용자는 아직 변경되기 전인 100달러로 해당 상품의 가격을 조회하려고 한다.
☑️ 언두 세그먼트 사용
A 사용자가 가격을 120달러로 변경할 때, 데이터베이스는 언두 엔트리 (Undo Entry)를 생성하여 이전 값인 100달러를 언두 세그먼트에 저장한다.
B 사용자가 데이터를 조회할 때, 데이터베이스는 언두 세그먼트를 참조하여 B에게 100달러의 값을 반환한다. 이렇게 함으로써 B는 자신이 조회한 시점의 일관성 있는 데이터를 볼 수 있게 된다.
☑️ 멀티버전 읽기 일관성의 효과:
- A의 변경이 커밋되기 전까지 B는 항상 100달러의 데이터를 읽게 되며, 커밋 후에만 변경된 120달러를 보게 된다. 이로 인해 데이터베이스는 특정 시점에서의 일관된 상태를 유지할수 있다.
멀티버전 읽기 일관성에서 언두 세그먼트와 SCN (Undo Segment & System Change Number in MRC)
💡요약: 멀티버전 읽기 일관성 (Multiversion Read Consistency)을 유지하기 위해 SCN(System Change Number)과 언두 세그먼트 (Undo Segment)가 어떻게 사용되는지 알아본다.
☑️ SCN (System Change Number): 데이터베이스에서 트랜잭션의 순서를 추적하기 위해 사용되는 번호이다. 이 번호를 통해 데이터의 변경 시점을 관리할 수 있다.
읽기 일관성 (Read Consistency): 특정 시점에서의 일관된 데이터를 보장하기 위해, 과거 데이터 버전을 참조하여 필요한 경우 복원된 데이터 블록을 제공한다.
☑️ 언두 세그먼트 (Undo Segment): 데이터가 변경되기 이전의 상태를 저장하는 영역으로, 필요할 때 이전 데이터를 가져와 일관성을 유지한다.
☑️ CR (Consistent Read) Clone: 복원된 데이터 블록을 가리키는 용어로, 쿼리가 실행될 당시의 일관된 데이터를 제공한다.

데이터베이스에서 SCN과 언두 세그먼트를 사용하여 일관된 데이터를 제공하는 과정
SCN 표시: 그림에는 여러 SCN 번호(SCN 10006, SCN 10021 등)가 표시되어 있으며, 각 번호는 트랜잭션의 특정 시점을 나타낸다.
언두 세그먼트: 변경 전의 데이터가 언두 세그먼트에 저장되며, 이 데이터는 이후에 복원이 필요할 때 사용된다.
CR (Consistent Read) Clone 생성: SCN 10023에서 일관성을 유지하기 위해 언두 세그먼트의 데이터를 이용하여 과거의 데이터 블록을 복원하여 사용자가 요청한 시점의 일관성 있는 데이터를 제공한다.
💡예시: A 사용자가 SCN 10023에서 고객 목록을 조회하는 쿼리를 실행했다고 가정하자
이 시점에 다른 사용자가 데이터 변경 작업을 시작하여, 데이터베이스에 새로운 트랜잭션이 추가되었다.
A 사용자가 실행한 쿼리는 SCN 10023 시점의 데이터를 기준으로 일관성 있게 조회되어야 하므로, 데이터베이스는 언두 세그먼트에 저장된 이전 버전의 데이터를 참조하여 SCN 10023 시점의 일관된 데이터를 제공하게 된다.
멀티버전 읽기 일관성의 구성: ITL (Interested Transaction List in MRC)
💡요약: 멀티버전 읽기 일관성 (Multiversion Read Consistency)에서 사용하는 트랜잭션 테이블 (Transaction Table)과 ITL (Interested Transaction List)은 데이터의 일관성을 유지하고 트랜잭션이 데이터에 락을 걸었는지, 커밋되었는지를 추적하는 데 중요한 역할을 한다.
☑️ ITL (Interested Transaction List): 데이터 블록의 헤더에 저장된 정보로, 특정 트랜잭션이 커밋된 상태 (Committed)인지 아닌지를 확인한다. ITL은 어떤 트랜잭션이 어떤 데이터 행에 락을 걸고 있는지, 그리고 해당 트랜잭션이 완료되었는지에 대한 정보를 가지고 있다.
💡예시: A 사용자가 특정 데이터 행을 수정하는 트랜잭션을 실행하고, 이 시점에서 B 사용자는 같은 행을 조회하려고 할 때 ITL의 역할
A 사용자가 해당 행에 락을 걸면 ITL에 이 정보가 기록된다.
B 사용자가 데이터를 조회할 때, ITL을 확인하여 A의 트랜잭션이 완료되지 않았음을 알 수 있다.
이로 인해 B는 아직 커밋되지 않은 A의 수정 사항을 무시하고, 커밋된 이전 버전의 데이터를 조회하게 다.
트랜잭션이 동시에 하나의 데이터에 접근할 때 발생할 수 있는 문제 3가지
💡요약: 여러 사용자가 동시에 데이터를 읽고 수정할 때, 데이터 일관성이 깨져 데이터 불일치 문제들이 생기게 된다. 크게 3가지로 나뉜다. 이를 방지하기 위해 적절한 격리 수준 (Isolation Level)이 필요하다.
☑️더티 리드 (Dirty Read): 아직 확정되지 않은 데이터를 다른 트랜잭션이 읽어오게 되는 상황. 메모리에서는 변경되었지만 디스크에는 반영되지 않은 데이터를 읽는 현상이다. 이 데이터가 나중에 롤백되면 잘못된 데이터를 사용하게 된다.
☑️ 반복되지 않는 읽기 (Nonrepeatable Read): 동일한 데이터를 여러 번 읽을 때, 중간에 데이터가 변경되어 각 읽기 결과가 달라지는 상황.
☑️ 팬텀 읽기 (Phantom Read): 특정 조건에 맞는 데이터를 조회할 때, 다른 트랜잭션에서 데이터를 추가하거나 삭제하여 조회 결과가 달라지는 상황.
💡예시:
더티 리드 (Dirty Read) 예시: A 사용자가 은행 계좌 잔액을 100달러에서 80달러로 줄이려고 한다. 하지만 이 변경은 아직 확정되지 않았고, A가 작업을 취소(롤백)할 수도 있는 상황이다. 이때, B 사용자가 잔액을 조회했을 때 80달러로 읽어오면, B는 잘못된 잔액 정보를 보게 된다. A가 롤백하면 원래 잔액은 다시 100달러가 된다.
반복되지 않는 읽기 (Nonrepeatable Read)예시: A 사용자가 현재 계좌의 잔액을 100달러로 읽어왔다. 이때, B 사용자가 같은 계좌에 대해 50달러를 입금하여 잔액이 150달러로 변경되게 된다. A 사용자가 다시 잔액을 확인하면 150달러로 읽히게 되는데, 처음 읽었을 때와 값이 달라져 일관성이 깨지는 현상이 발생한다.
팬텀 읽기 (Phantom Read) 예시: A 사용자가 고객 목록에서 "VIP 고객"만 조회하는 트랜잭션을 실행하여 10명의 VIP 고객을 조회한다. 그 사이에 B 사용자가 새로운 VIP 고객을 추가하여 고객 수가 11명이 되었다. A가 같은 조회를 다시 실행하면 새로운 VIP 고객이 추가된 상태로 보여지며, 처음 조회한 결과와 다른 상태로 나타난다.
- 팬텀 읽기는 모든 상황에서 문제가 되지는 않지만, 트랜잭션의 일관성과 동작의 예측 가능성이 중요한 경우에는 문제가 될 수 있다. 특히, 금융, 재고 관리, 보고서 생성 등의 분야에서 트랜잭션 내의 상태가 처음부터 끝까지 변하지 않도록 보장해야 하는 상황에서 팬텀 읽기를 방지하는 것이 중요하다.
2️⃣ 트랜잭션 격리 수준
(Standard of Transaction Isolation)
💡요약: 트랜잭션 격리 수준은 데이터베이스에서 여러 트랜잭션이 동시에 실행될 때 서로에게 영향을 미치지 않도록 하기 위한 설정을 뜻한다. 이 표는 네 가지 격리 수준을 Dirty Read (더러운 읽기), Nonrepeatable Read (반복 불가능한 읽기), Phantom Read (팬텀 읽기)가 발생할 수 있는지 여부로 설명할 수 있다.
Read Uncommitted (읽기 미완료): 모든 종류의 읽기 이슈 발생 가능
Read Committed (읽기 완료): Dirty Read 불가능, 나머지 발생 가능
Repeatable Read (반복 가능한 읽기): Dirty Read, Nonrepeatable Read 불가능, Phantom Read 발생 가능
Serializable (직렬화 가능): 모든 종류의 읽기 이슈 발생 불가능

☑️ Read Uncommitted (읽기 미완료) 모든 읽기 이슈가 발생할 수 있는 최하위 격리 수준
Dirty Read (더티 리드): 발생 가능
Nonrepeatable Read (반복 불가능한 읽기): 발생 가능
Phantom Read (팬텀 읽기): 발생 가능
- 이 수준에서는 아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽을 수 있다는 뜻이다. 예를 들어, 트랜잭션 A가 데이터를 수정하고 커밋하지 않은 상태에서 트랜잭션 B가 해당 데이터를 읽을 수 있다.
☑️ Read Committed (읽기 완료) 커밋되지 않은 데이터를 읽는 것은 방지하지만, 반복 불가능한 읽기와 팬텀 읽기는 여전히 발생할 수 있는 수준
Dirty Read (더티 리드): 발생 불가능
Nonrepeatable Read (반복 불가능한 읽기): 발생 가능
Phantom Read (팬텀 읽기): 발생 가능
- 이 수준에서는 커밋된 데이터만 읽을 수 있으며, 아직 커밋되지 않은 데이터는 다른 트랜잭션에서 읽을 수 없다. 그러나 다른 트랜잭션이 데이터를 수정해 버리면 이전에 읽은 데이터를 반복해서 읽을 때 값이 달라질 수 있다.
☑️ Repeatable Read (반복 가능한 읽기) Dirty Read와 Nonrepeatable Read를 방지하지만, 팬텀 읽기는 발생할 수 있는 수준
Dirty Read (더티 리드): 발생 불가능
Nonrepeatable Read (반복 불가능한 읽기): 발생 불가능
Phantom Read (팬텀 읽기): 발생 가능
- 이 수준에서는 같은 데이터를 여러 번 읽어도 값이 변하지 않는다. 그러나 새로 추가된 데이터를 포함한 '팬텀 읽기'는 여전히 발생할 수 있다. 예를 들어, 트랜잭션 중에 다른 트랜잭션이 새로운 데이터를 추가하면 그 데이터는 보일 수 있다.
☑️ Serializable (직렬화 가능) 모든 읽기 이슈를 방지하여 가장 높은 데이터 일관성을 보장하지만, 성능에 영향을 미칠 수 있는 수준
Dirty Read (더티 리드): 발생 불가능
Nonrepeatable Read (반복 불가능한 읽기): 발생 불가능
Phantom Read (팬텀 읽기): 발생 불가능
- 가장 높은 수준의 격리로, 모든 트랜잭션이 순차적으로 실행되는 것처럼 동작한다. 따라서 다른 트랜잭션이 데이터를 읽거나 수정할 수 없으며, 읽을 때마다 동일한 결과를 얻을 수 있다. 그러나 성능에 영향을 줄 수 있다.
💡예시:
Dirty Read (더티 리드): 트랜잭션 A가 제품의 가격을 수정했으나 아직 커밋하지 않았다고 하자. 트랜잭션 B가 그 가격을 읽으면, 커밋되지 않은 임시 데이터를 읽게 된다.
Nonrepeatable Read (반복 불가능한 읽기): 트랜잭션 A가 제품의 가격을 읽고 있을 때, 트랜잭션 B가 그 가격을 수정하고 커밋한다. 트랜잭션 A가 다시 가격을 읽으면 처음 읽은 값과 다를 수 있다.
Phantom Read (팬텀 읽기): 트랜잭션 A가 특정 조건의 제품 목록을 읽고 있을 때, 트랜잭션 B가 새로운 제품을 추가하고 커밋한다. 트랜잭션 A가 다시 목록을 읽으면 새로운 제품이 추가된 것을 보게 된다.
코밋된 읽기의 격리 수준
(Isolation level of Read Committed)
💡요약: 이 격리 수준은 트랜잭션이 아직 커밋되지 않은 데이터를 읽지 않도록 보장하여 Dirty Read (더티 리드) 문제를 방지한다. 하지만 Nonrepeatable Read (반복 불가능한 읽기)와 Phantom Read (팬텀 읽기)는 여전히 발생할 수 있다.
Read Committed (코밋된 읽기)는 많은 DBMS의 기본 격리 수준이다.
트랜잭션에서 실행되는 모든 쿼리는 실행 직전까지 코밋된 데이터만 읽을 수 있다. 즉, 트랜잭션이 시작되기 전에 변경된 데이터가 아니라 쿼리가 실행될 때의 최신 데이터를 가져온다.
다른 트랜잭션이 데이터에 대해 변경 작업을 수행하는 동안에도 읽기가 가능하므로 Nonrepeatable Read (반복 불가능한 읽기)와 Phantom Read (팬텀 읽기)가 발생할 수 있다.
큰 충돌 없이 트랜잭션이 실행될 수 있는 환경에 적합하고 대부분의 DBMS에 설정되어 있어 일반적인 데이터베이스 환경에서 많이 사용되는 격리 기준이다.
💡예제:
Dirty Read 방지 예제
트랜잭션 A가 제품의 가격을 수정하고 커밋하지 않은 상태일 때, 트랜잭션 B는 이 가격을 읽지 못한다. 트랜잭션 A가 수정 작업을 커밋할 때까지 트랜잭션 B는 변경된 데이터에 접근할 수 없다.
Nonrepeatable Read 예제
트랜잭션 A가 특정 제품의 가격을 조회한 후, 트랜잭션 B가 그 제품의 가격을 수정하고 커밋하였다. 이후 트랜잭션 A가 다시 동일한 제품의 가격을 조회하면 처음 조회한 가격과 달라질 수 있다.
코밋된 읽기 격리 수준에서의 읽기 일관성
(Read Consistency in Read Committed Isolation Level)
💡요약: 읽기 일관성 (Read Consistency)은 모든 쿼리에서 일관된 결과를 보장한다. UPDATE 문과 같은 특정 쿼리에서는 일관성을 유지하지만, 변경이 일어나기 전의 데이터를 보여주므로, 최근의 변경 사항은 반영되지 않는다.
- 모든 쿼리에 대한 일관성 있는 결과를 보장한다. 이는 데이터베이스에서 데이터를 읽을 때 항상 현재의 상태를 반영하도록 한다.
UPDATE 문에 있는 Where 절과 같은 조건이 있는 쿼리는 일관된 결과를 보장한다. 즉, 업데이트가 적용되기 전의 데이터와 후의 데이터를 혼합하지 않고 일관된 데이터를 제공한다.
그러나 이러한 일관성 있는 읽기는 DML (Data Manipulation Language) 문장으로 인해 변경된 부분을 보지 못하게 된다. 즉, 데이터가 변경되기 전의 상태를 읽게 되며, 그 결과 변경이 발생하기 전의 데이터를 표시한다.
💡예제:
일관성 있는 결과 보장 예제
트랜잭션 A가 제품의 가격을 변경하는 UPDATE 쿼리를 실행하고 있을 때, 트랜잭션 B가 그 제품의 가격을 조회한다. 트랜잭션 B는 변경이 완료되기 전의 가격을 읽게 되어, 트랜잭션 A가 커밋하기 전의 상태를 반영한다.변경된 데이터 보지 못하는 예제
트랜잭션 A가 특정 고객의 정보를 업데이트한 후, 트랜잭션 B가 같은 고객의 정보를 조회 한다. 트랜잭션 B는 업데이트가 완료된 후의 데이터를 읽지 못하고, 트랜잭션 A가 커밋하기 전의 이전 상태를 읽는다. 따라서 트랜잭션 B는 업데이트된 정보를 반영하지 않은 상태의 데이터를 보게 된다.
코밋된 읽기 격리 수준에서의 쓰기 충돌
(Conflicting Writes in Read Committed Isolation Level)
💡요약: 쓰기 충돌(Conflicting Writes)은 두 개 이상의 트랜잭션이 같은 데이터를 동시에 수정하려고 할 때 발생하는 상황을 뜻한다. 코밋된 읽기 격리 수준에서는 블로킹 트랜잭션이 종료되기를 기다리며, 블로킹이 해제되면 해당 데이터를 수정할 수 있게 한다.
- 다른 트랜잭션에 의한 수정 후 아직 커밋되기 전의 로우(row)에 대해 변경을 시도할 때 충돌(conflict)이 발생한다. 이 경우, 하나의 트랜잭션이 다른 트랜잭션에 의해 블록(block)되게 된다.
코밋된 읽기 트랜잭션(read committed transaction)은 블로킹 트랜잭션이 종료되고 자원이 풀리기를 기다리게 된다.
만약 블로킹 트랜잭션이 롤백되면, 기다리던 트랜잭션은 블로킹 트랜잭션이 없었던 것처럼 처리되어, 잠금이 해제된 로우에 대해 변경을 수행할 수 있다.
반면에, 블로킹 트랜잭션이 정상적으로 종료되고 자원을 해제하면, 기다리던 트랜잭션은 새로운 변경된 로우에 대해 다시 변경을 시도한다.
💡예제:
쓰기 충돌 예제
- 트랜잭션 A가 제품 가격을 수정하고 있지만, 이 가격을 다른 트랜잭션 B가 동시에 수정하려고 할 때 발생한다. 트랜잭션 A가 가격 수정을 완료하기 전에 트랜잭션 B가 해당 데이터를 수정하려고 하면 충돌이 발생하고, 트랜잭션 B는 대기하게 된다.
블로킹 트랜잭션 롤백
- 트랜잭션 A가 제품 가격을 100으로 변경하는 동안, 트랜잭션 B가 같은 제품의 가격을 수정하려고 시도한다. 트랜잭션 A가 롤백되면, 트랜잭션 B는 가격을 수정할 수 있게 된다.
블로킹 트랜잭션 정상 종료
- 트랜잭션 A가 제품 가격을 100으로 변경하고 커밋한 후, 트랜잭션 B는 이 변경을 반영하여 가격을 120으로 수정할 수 있다.
💡요약: 데이터베이스에서 두 개의 트랜잭션이 어떻게 상호작용할 수 있는지를 보여주며, 특히 쓰기 충돌의 발생 가능성과 코밋된 읽기의 작동 방식을 명확하게 설명하고 있다. 쓰기 충돌은 두 개의 세션이 동일한 데이터에 접근할 때 발생하는 상황을 이해하는 데 도움을 준다.
쓰기 충돌의 두개의 트랜잭션 상호작용 #1
(2 transaction Interaction in Conflicting Writes)
세션1은 직원의 급여를 조회하고, 이후 Banda의 급여를 업데이트하였다.
세션2는 아무 작업을 수행하지 않고 세션1이 완료되기를 기다린다.
코밋된 읽기 격리 수준에서는 세션1이 업데이트를 완료하고 커밋할 때까지 세션2가 데이터를 수정할 수 없다.

Session 1
첫 번째 쿼리 실행: SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz라는 이름을 가진 직원들의 급여를 조회한다.결과: Banda의 급여: 6200, Greene의 급여: 9500
하지만 Hintz라는 직원은 존재하지 않아서 결과에 포함되지 않는다.
업데이트 실행:
- SQL UPDATE 문을 사용하여 Banda의 급여를 7000으로 수정하는 트랜잭션을 시작한다. 이 트랜잭션은 코밋된 읽기(Read Committed) 격리 수준으로 실행된다.
Session 2
- 세션2의 행동: 세션2는 현재 어떠한 작업도 수행하지 않고 대기하고 있다. 세션2는 세션1이 Banda의 급여를 업데이트하는 동안 대기하게 된다.
쓰기 충돌의 상호작용 #2
(Interaction in Conflicting Writes)
세션2는 격리 수준을 READ COMMITTED로 설정하여, 커밋된 데이터만 조회하고 업데이트한다.
세션1에서는 아무 작업도 수행되지 않으며, 세션2가 Banda의 급여를 조회할 때 업데이트가 적용되지 않은 상태의 값을 확인한다.
세션2는 Greene의 급여를 성공적으로 업데이트하는데, 이는 세션1이 Banda의 로우만을 잠궜기 때문이다.

Session 1
작업 없음: 세션1에서는 특정 작업이 수행되지 않는다.
업데이트 수행 없음: 세션1에서는 어떤 데이터도 수정되지 않는다.
Session 2
격리 수준 설정:
- 세션2는 READ COMMITTED 격리 수준으로 설정한다. 이는 세션2가 커밋된 데이터만 읽도록 보장한다.
첫 번째 쿼리 실행:
SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.결과: Banda의 급여: 6200, Greene의 급여: 9500
- Hintz는 존재하지 않으므로 결과에 포함되지 않는다.
오라클 데이터베이스는 업데이트가 커밋되지 않기 전에 Banda의 급여를 일관성 있게 보여준다.
업데이트 실행:
세션2는 Greene의 급여를 9900으로 업데이트하는 작업을 수행한다.
이는 트랜잭션1이 Banda의 로우만 잠궜기 때문에 성공적으로 업데이트된다.
쓰기 충돌의 상호작용 #3
(Interaction in Conflicting Writes)
세션1은 Hintz라는 직원을 삽입하지만, 이 변경은 커밋되지 않은 상태로 유지된다.
세션2는 세션1의 변경 사항을 반영하지 않으며, Banda와 Greene의 급여만 조회 가능하다.
코밋된 읽기 격리 수준에서는 세션2가 커밋되지 않은 세션1의 변경 사항을 볼 수 없고, 읽기 일관성을 유지한다.

Session 1
INSERT 작업:
세션1은 employees 테이블에 Hintz라는 새로운 직원의 정보를 추가하는 SQL INSERT 쿼리를 실행한다.
이 트랜잭션은 커밋되지 않은 상태로 남아 있다.
작업 없음:
- 세션1에서는 추가적인 작업이 수행되지 않는다.
Session 2
작업 없음:
- 세션2는 처음에 어떤 작업도 수행하지 않는다.
첫 번째 쿼리 실행:
SQL SELECT 쿼리를 통해 Banda, Greene, Hintz의 급여를 조회한다.
결과:
Banda의 급여: 6200
Greene의 급여: 9500
하지만 Hintz는 세션1에서 삽입된 데이터이므로, 이 데이터는 아직 커밋되지 않았기 때문에 세션2는 해당 데이터를 볼 수 없다.
세션2는 자신이 업데이트한 Greene의 급여를 확지만, 세션1의 업데이트된 Banda의 급여나 Hintz의 삽입은 보못한다.
쓰기 충돌의 상호작용 #4
(Interaction in Conflicting Writes)
세션1은 어떤 작업도 하지 않고 대기 중이다.
세션2는 Banda의 급여를 업데이트하려고 하지만, 해당 로우가 세션1에 의해 잠겨 있어서 작업을 완료할 수 없다.
이 경우 세션2는 세션1의 작업이 끝날 때까지 대기하게 된다.

Session 1
- 작업 없음: 세션1에서는 현재 어떤 작업도 수행되지 않고 있다. 이는 단순히 대기 상태에 있는 것이다.
Session 2
UPDATE 작업 시도:
세션2는 SQL UPDATE 쿼리를 실행하여 Banda의 급여를 6300으로 변경하려고 한다.
그러나 이 작업을 수행하기 위해서는 Banda에 대한 로우가 세션1에 의해 잠겨 있는 상태이다.
대기 상태:
세션2는 Banda의 로우가 현재 세션1에 의해 잠겨 있기 때문에 해당 업데이트 작업을 수행할 수 없다.
이로 인해 프롬프트가 반환되지 않고 대기하게 된다. 즉, 세션2는 세션1이 작업을 마칠 때까지 기다리는 상태가 된다.
쓰기 충돌의 상호작용 #5
(Interaction in Conflicting Writes)
세션1은 트랜잭션을 커밋하여 모든 변경 사항을 완료한다.
세션2는 Banda의 급여를 업데이트하고, 이 변경 사항이 커밋된 이후에 조회할 수 있다.
코밋된 읽기 격리 수준에서는 세션2가 세션1에서 수행한 커밋된 변경 사항을 반영하여, 업데이트된 Banda의 급여와 Hintz의 삽입을 모두 확인할 수 있게 된다.

Session 1
COMMIT 실행:
- 세션1은 SQL 쿼리를 사용하여 작업을 커밋한다. 이 단계에서 트랜잭션이 종료되고, 세션1에서 수행한 모든 변경 사항이 데이터베이스에 반영되게 된다.
작업 없음:
- 이후 추가적인 작업은 수행되지 않으며, 세션1의 트랜잭션은 성공적으로 완료된다.
Session 2
업데이트 작업:
세션2는 Banda의 급여를 6300으로 업데이트하려고 시도한다.
세션1의 트랜잭션이 종료됨에 따라 Banda의 로우에 대한 잠금이 해제되므로, 세션2는 성공적으로 해당 업데이트 작업을 수행한다.
"1 row updated."라는 메시지가 표시된다.
SELECT 쿼리 실행:
세션2는 직원의 급여를 조회하기 위해 SQL SELECT 쿼리를 실행한다. 이 쿼리는 Banda, Greene, Hintz의 급여를 조회한다.
결과: Banda의 급여: 6300 (세션2에서 업데이트된 값), Greene의 급여: 9900
- Hintz는 세션1에서 커밋된 데이터로 확인할 수 있다.
이 과정에서 Hintz에 대한 삽입이 세션1에서 완료되어 이제 세션2에서도 확인할 수 있게 되었다.
쓰기 충돌의 상호작용 #6
(Interaction in Conflicting Writes)
세션1은 Banda의 급여를 조회하고, 해당 급여가 세션2에서 업데이트된 값임을 알게 된다. 하지만, 세션1이 먼저 수행했던 Banda 급여 7000으로의 업데이트는 세션2의 커밋으로 인해 "잃어버린(lost)" 상태가 된다.
세션2는 트랜잭션을 성공적으로 커밋하여 Banda의 급여를 6300으로 설정하게 된다.

Session 1
작업 없음: 세션1에서는 현재 어떤 작업도 수행되지 않는다.
SELECT 쿼리 실행:
SQL SELECT 쿼리를 통해 Banda, Greene, Hintz의 급여를 조회한다.
결과: Banda의 급여: 6300, Greene의 급여: 9900
- Hintz는 세션1에서 커밋된 데이터로 확인할 수 있다.
이때 Banda의 급여 6300은 세션2에서 업데이트된 값이다.
세션2의 영향:
- 세션1의 Banda 급여 업데이트는 세션2의 커밋에 의해 "잃어버린(lost)" 상태가 된다. 즉, 세션1에서 Banda의 급여를 7000으로 변경하는 작업이 있지만, 세션2의 작업이 커밋되면서 업데이트가 덮어쓰게 되는 것이다.
Session 2
COMMIT 실행: 세션2는 트랜잭션을 커밋하여 작업을 완료하고 종료한다.
작업 없음: 이후 세션2에서는 추가적인 작업이 수행되지 않게된다.
직렬화 가능성의 격리 수준 (Isolation Level of Serializable)
💡요약: 직렬화 가능성은 데이터베이스에서 트랜잭션이 독립적으로 실행될 수 있도록 보장하여 데이터의 일관성을 유지하는 격리 수준이다. 이 격리 수준에서는 커밋된 데이터만 읽을 수 있으며, 더티 리드, 모호한 읽기, 팬텀 읽기를 허용하지 않는다. 이러한 특징을 통해 여러 트랜잭션이 동시에 존재하는 환경에서도 데이터의 신뢰성을 보장할 수 있게 된다.
☑️ 직렬화 가능성 정의
다른 사용자가 아무도 없이 혼자서 독점해서 사용하는 것처럼 보이는 격리 수준. 즉, 여러 트랜잭션이 있을지라도 각 트랜잭션이 서로의 영향을 받지 않고 독립적으로 처리된다.
트랜잭션이 시작되는 시점에 커밋된 데이터만 보게 되며, 자기가 변경한 데이터는 보지 못한다. 이로 인해 데이터의 일관성이 유지된다.
☑️ 적용되는 상황:
대규모의 로우 중에서 단지 몇 개의 로우 수정만 이루어지는 쿼리: 이 경우, 직렬화 가능성을 적용하여 데이터 일관성을 확보한다.
같은 로우를 동시에 수정하는 트랜잭션이 비교적 적을 경우: 이 경우에도 직렬화 가능성을 통해 충돌을 피할 수 있다.
비교적 길게 실행되는 트랜잭션의 대부분이 읽기 연산인 경우: 직렬화 가능성이 적용되어 데이터 일관성이 유지된다.
☑️ 허용되지 않는 읽기 유형:
Dirty reads (더티 리드): 다른 트랜잭션이 커밋하지 않은 데이터를 읽는 경우.
Fuzzy reads (모호한 읽기): 같은 트랜잭션이 두 번 데이터를 읽을 때, 두 번의 결과가 다를 수 있는 경우.
Phantom reads (팬텀 읽기): 트랜잭션이 반복해서 데이터를 읽을 때, 중간에 데이터가 추가되어 결과가 달라지는 경우.
직렬화 가능 읽기의 예 #1 (Example of Serializable Read)
세션1은 Banda의 급여를 조회하고, 이후 그 급여를 업데이트하는 작업을 시작한다.
세션2는 아무 작업도 하지 않다가, 트랜잭션 격리 수준을 SERIALIZABLE로 설정한다.
직렬화 가능성은 세션2가 다른 트랜잭션의 영향을 받지 않고 독립적으로 데이터를 처리할 수 있도록 보장한다.

Session 1
SELECT 쿼리 실행: SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.결과: Banda의 급여: 6200, Greene의 급여: 9500
- 하지만 Hintz라는 직원은 존재하지 않으므로 결과에 포함되지 않는다.
UPDATE 작업 시작:
세션1은 Banda의 급여를 7000으로 업데이트하는 작업을 시작한다. 이 작업은 아직 커밋되지 않았다.
기본 격리 수준은 READ COMMITTED로 설정되어 있다.
Session 2
작업 없음: 세션2에서는 처음에 어떤 작업도 수행하지 않는다.
직렬화 가능성 설정: 세션2는 SERIALIZABLE 격리 수준으로 트랜잭션을 시작한다. 이는 세션2가 독립적으로 실행되고, 다른 트랜잭션의 영향을 받지 않도록 보장한다.
직렬화 가능 읽기의 예 #2 (Example of Serializable Read)
세션1은 Banda와 Greene의 급여를 조회하고 Hintz라는 직원을 삽입한다. 이 삽입 작업은 아직 커밋되지 않았다.
세션2는 Banda의 급여를 조회할 때 커밋되지 않은 업데이트를 반영하지 않으며, Greene의 급여를 성공적으로 업데이트한다.
직렬화 가능 읽기는 데이터의 일관성을 유지하며, 서로 다른 트랜잭션이 독립적으로 실행되도록 보장한다.

Session 1
작업 없음: 세션1에서는 현재 어떤 작업도 수행되지 않는다.
SELECT 쿼리 실행:
SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.결과: Banda의 급여: 6200, Greene의 급여: 9500
- Hintz는 존재하지 않으므로 결과에 포함되지 않는다.
INSERT 작업: 세션1은 Hintz라는 새로운 직원의 정보를 추가하는 SQL INSERT 쿼리를 실행한다. 이 트랜잭션은 아직 커밋되지 않았다.
Session 2
작업 없음: 세션2에서는 처음에 어떤 작업도 수행하지 않는다.
SELECT 쿼리 실행: SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.- 이 때, 오라클 데이터베이스는 읽기 일관성을 사용하여 Banda의 급여를 조회하며, 세션1에서 수행한 커밋되지 않은 업데이트를 반영하지 않는다.
UPDATE 작업:
세션2는 Greene의 급여를 9900으로 업데이트하는 작업을 시도한다.
Banda의 로우는 세션1에 의해 잠겨 있지만, Greene의 로우는 잠겨 있지 않기 때문에 세션2는 성공적으로 업데이트를 수행하였다.
직렬화 가능 읽기의 예 #3 (Example of Serializable Read)
세션1은 Banda의 급여를 7000으로 업데이트하고 이를 커밋한다.
세션2는 Banda의 급여를 조회할 때, 세션1의 업데이트를 반영하지 않고 이전 급여 값인 6200을 조회한다.
세션2는 이후 자신의 작업을 커밋하여 종료한다.

Session 1
COMMIT 실행: 세션1은 SQL 쿼리를 통해 작업을 커밋한다. 이 단계에서 트랜잭션이 종료되고, 세션1에서 수행한 모든 변경 사항이 데이터베이스에 반영된다.
SELECT 쿼리 실행:
세션1은
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.결과: Banda의 급여: 7000 (세션1에서 업데이트된 값), Greene의 급여: 9500
- Hintz는 존재하지 않으므로 결과에 포함되지 않는다.
Session 2
작업 없음: 세션2에서는 처음에 어떤 작업도 수행하지 않는다.
SELECT 쿼리 실행: SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.결과: Banda의 급여: 6200 (세션2에서의 이전 값), Greene의 급여: 9900
- Hintz는 여전히 존재하지 않아서 조회되지 않는다.
세션2는 세션1에서 커밋된 변경 사항을 반영하지 않으며, 읽기 일관성을 유지한다.
COMMIT 실행:
- 세션2는 트랜잭션을 커밋하여 작업을 완료한다.
직렬화 가능 읽기의 예 #4 (Example of Serializable Read)
세션1은 Banda와 Greene의 급여를 조회하고 Hintz의 급여를 업데이트하는 작업을 시작합니다.
세션2는 Banda와 Greene의 급여를 조회하며, Hintz에 대한 커밋되지 않은 업데이트를 반영하지 않습니다.
직렬화 가능 읽기는 두 세션이 각각의 작업을 독립적으로 수행하고, 커밋된 변경 사항만 반영되도록 보장합니다.

Session 1
SELECT 쿼리 실행: SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.결과: Banda의 급여: 7000, Greene의 급여: 9900
- Hintz의 급여는 아직 확인할 수 없다.
UPDATE 작업 시작: 세션1은 Hintz의 급여를 7100으로 업데이트하는 작업을 실행한다. 이 작업은 아직 커밋되지 않은 상태이다.
Session 2
SELECT 쿼리 실행: SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.결과: Banda의 급여: 7000, Greene의 급여: 9900
- Hintz의 급여는 세션1의 업데이트가 커밋되지 않았으므로 조회되지 않는다.
COMMIT 작업 없음: 세션2는 현재 어떤 작업도 수행하지 않고 대기 상태에 있다.
트랜잭션 격리 수준 설정:
- 세션2는 트랜잭션 격리 수준을 SERIALIZABLE로 설정한다. 이는 세션2가 독립적으로 실행되고 다른 트랜잭션의 영향을 받지 않도록 보장한다.
직렬화 가능 읽기의 예 #5 (Example of Serializable Read)
세션1은 특정 작업을 커밋한 후 종료되며, Hintz에 대한 업데이트를 수행하지 않는다.
세션2는 Hintz의 급여를 업데이트하려고 시도하지만, 세션1이 해당 로우를 잠궜기 때문에 업데이트가 완료되지 않고 대기 상태가 된다.
결국, 세션2는 세션1이 수행한 커밋으로 인해 ORA-08177 오류가 발생하며 작업을 할 수 없게 된다.

Session 1
작업 없음: 세션1에서는 현재 어떤 작업도 수행되지 않는다.
COMMIT 실행: 세션1이 특정 작업을 커밋하여 데이터베이스에 반영한다.
작업 없음: 세션1에서 추가적인 작업이 없다.
Session 2
UPDATE 작업 시도: 세션2는 Hintz의 급여를 7200으로 업데이트하는 SQL 쿼리를 실행하려고 시도한다. 이 때 세션1이 이미 Hintz의 로우를 잠궜기 때문에, 세션2는 대기하게 되고 프롬프트가 반환되지 않게된다. 이로 인해 세션2는 업데이트를 완료할 수 없다.
COMMIT 실행 없음: 세션2에서는 현재 어떤 작업도 수행하지 않는다.
세션 종료: 세션2는 Hintz의 급여 업데이트를 완료하지 못하고 ORA-08177 오류가 발생한다. 이는 트랜잭션 3이 세션2의 업데이트가 발생한 이후에 Hintz의 급여를 업데이트
했기 때문이다.
직렬화 가능 읽기의 예 #6 (Example of Serializable Read)
세션1은 작업을 수행하지 않고 대기 상태에 있으며, 세션2는 트랜잭션 4를 롤백한다.
세션2는 트랜잭션 격리 수준을 SERIALIZABLE로 설정한 후, Banda, Greene, Hintz의 급여를 조회한다.
직렬화 가능 읽기는 두 세션 간의 독립성을 보장하며, 세션2는 트랜잭션 1에서 커밋된 Hintz의 급여 업데이트를 확인할 수 있다.

Session 1
작업 없음: 세션1에서는 현재 어떤 작업도 수행되지 않는다.
작업 없음: 추가적인 작업이 없다.
작업 없음: 계속해서 어떤 작업도 진행되지 않는다.
Session 2
ROLLBACK 실행: 세션2는 트랜잭션 4를 롤백하여 작업을 취소한다. 이 단계에서 트랜잭션이 종료되고, 세션2에서 수행한 변경 사항이 모두 되돌려지게 된다.
트랜잭션 격리 수준 설정: 세션2는 SERIALIZABLE 격리 수준으로 새로운 트랜잭션(트랜잭션 5)을 시작한다. 이는 세션2가 다른 트랜잭션의 영향을 받지 않고 독립적으로 실행되도록 보장한다.
SELECT 쿼리 실행: SQL 쿼리를 통해
employees테이블에서 Banda, Greene, Hintz의 급여를 조회한다.결과: Banda의 급여: 7000, Greene의 급여: 9900, Hintz의 급여: 7100 (세션1의 업데이트가 커밋된 후의 값)
세션2에서는 Hintz의 급여 업데이트가 트랜잭션 3에서 커밋된 값으로 확인된다.
직렬화 가능 읽기의 예 #7 (Example of Serializable Read)
세션1에서는 아무 작업도 수행되지 않으며, 대기 상태이다.
세션2는 Hintz의 급여를 성공적으로 업데이트하고 이를 커밋한다.
직렬화 가능 읽기는 데이터의 일관성을 보장하며, 여러 트랜잭션이 독립적으로 실행되도록 한다.

Session 1
- 작업 없음: 세션1에서는 현재 어떤 작업도 수행되지 않는다.
Session 2
UPDATE 작업 실행: 세션2는 Hintz의 급여를 7200으로 업데이트하는 SQL 쿼리를 실행한다. 이 업데이트가 성공적으로 수행되며 "1 row updated."라는 메시지가 표시된다.
COMMIT 실행: 세션2는 세션1과의 충돌 없이 업데이트를 커밋하여 트랜잭션을 종료한다.
설명: 트랜잭션 5는 Hintz의 급여를 다른 값으로 업데이트한다. 이때, 이전 트랜잭션(트랜잭션 3)의 업데이트가 트랜잭션 5 시작 전에 커밋되었기 때문에, 직렬화 접근 문제는 회피된다.
3️⃣ 락과 데드락(Lock and Deadlock)
락(LOCK) 개념
💡요약: 락(LOCK) 은 여러 사용자가 데이터를 동시에 변경하려는 상황에서 데이터의 일관성(Consistency) 을 유지하기 위한 방법이다.
락은 데이터의 일관성(Consistency) 을 유지하기 위한 중요한 요소로, 트랜잭션(Transaction) 간의 간섭을 막기 위해 사용된다. 한 사용자가 데이터를 사용 중일 때 다른 사용자가 해당 데이터를 변경하지 못하도록(Prevent Changes) 한다.
💡예시: 은행 계좌에서 잔액(Balance) 을 조회하고 변경하는 상황을 생각해보자. 사용자가 계좌 잔액을 확인하고 있는 동안에 다른 사용자가 동시에 잔액을 인출하거나 입금하지 못하도록 락을 걸어 데이터의 정확성을 유지할 수 있다.
락(LOCK)의 종류(Types of Lock) #1
💡요약: 공유락(Shared Lock) 은 데이터를 읽을 때 여러 사용자가 동시에 접근할 수 있고, 배타락(Exclusive Lock) 은 데이터를 수정할 때 다른 사용자의 접근을 제한한다. 업데이트락(Update Lock) 은 공유락이 걸린 자원에 대해 업데이트가 필요할 때 사용된다.
☑️공유락 (Shared Lock): 데이터 읽기 작업(예: SELECT 문)에 사용된다. 여러 사용자가 동시에 데이터를 읽을 수 있으며, 서로 충돌 없이 데이터를 SELECT 할 수 있다.
☑️배타락 (Exclusive Lock): 데이터 수정 작업(예: INSERT/UPDATE/DELETE)에 사용된다. 하나의 사용자가 데이터를 수정 중일 때, 다른 사용자가 동시에 수정 작업을 할 수 없도록 막는다. 이는 데이터의 무결성을 보장하는 데 필요하다.
☑️업데이트락 (Update Lock): 공유락이 있는 자원에 대해 업데이트(Update) 작업이 필요할 때 사용된다. 공유락과 배타락의 중간 단계로, 데이터를 수정할 수 있는지 확인하는 과정에서 다른 사용자의 접근을 제한한다.
💡예시:은행 시스템에서 잔액(Balance) 을 읽는 경우에는 공유락을 사용해 여러 사용자가 동시에 잔액을 조회할 수 있다. 하지만 누군가가 잔액을 수정하려고 하면 배타락이 걸려, 해당 수정 작업이 끝날 때까지 다른 사용자가 접근하지 못하게 한다.
락(LOCK)의 종류 #2
💡요약: 의도락(Intent Lock) 은 특정 작업을 수행하기 전에 다른 락과의 충돌을 방지하기 위한 계층 구조의 일부이다. 스키마락(Schema Lock) 은 테이블이나 인덱스가 참조 중일 때 구조적인 변경을 제한한다. 대량업데이트락(Bulk Update Lock) 은 대량의 데이터를 한 번에 업데이트 할 때 사용된다.
☑️의도락 (Intent Lock): 락의 계층 구조(Hierarchy) 를 만드는 데 사용된다. 이 락은 상위 수준의 자원에 걸리는 락이므로 하위 자원에 대한 다른 락 작업을 막기 위한 의도(Intent) 를 나타낸다. 예를 들어, 의도공유(IS), 의도배타(IX), 의도배타공유(SIX) 등이 있다.
☑️스키마락 (Schema Lock): 테이블이나 인덱스(Index) 를 다른 세션이 참조 중일 때, 구조적 변경(예: 삭제나 변경(Deletion or Alteration))을 막는다. 이는 데이터베이스의 스키마(Schema) 안정성을 유지하는 데 필요하다.
☑️대량업데이트락 (Bulk Update Lock): 데이터를 테이블로 대량 복사(Bulk Copy) 할 때 사용된다. TABLOCK 힌트가 지정된 경우 테이블 전체에 락을 걸어 대량 업데이트가 충돌 없이 수행될 수 있도록 한다.
💡예시: 은행 시스템에서 특정 계좌 정보(Account Information) 를 다른 사용자들이 조회하고 있는 상황에서, 관리자 세션이 해당 테이블의 구조(Structure) 를 변경하려고 할 때, 스키마락을 사용하여 현재 참조 중인 다른 세션의 작업을 보호하고 구조적 변경을 방지한다.
락(LOCK)의 호환성(Compatibility)
💡요약: 각 락은 특정 조건에서 다른 락과 호환될 수도 있고, 그렇지 않을 수도 있습니다. 여기서 중요한 공유락(Shared Lock) 과 배타락(Exclusive Lock) 부분만 설명한다.

☑️ 공유락 (Shared Lock):
공유락(S) 은 데이터를 읽는 작업에서 사용되며, 다른 공유락과 호환된다. 즉, 여러 사용자가 동시에 데이터 읽기 작업을 수행할 수 있다.
하지만, 배타락(X) 과는 호환되지 않는다. 따라서 한 사용자가 데이터를 읽고 있는 동안, 다른 사용자가 그 데이터를 수정할 수 없다.
☑️배타락 (Exclusive Lock):
배타락(X) 은 데이터를 수정하는 작업에 사용되며, 어떤 다른 락과도 호환되지 않는다. 배타적(Exclusive) 으로 접근이 제한되기 때문에, 데이터 수정 중에는 다른 사용자가 데이터에 접근할 수 없게된다.
이는 데이터 일관성을 유지하기 위해 매우 중요한 부분이다. 한 사용자가 데이터를 수정 중일 때 다른 사용자가 접근하여 데이터를 수정하거나 읽지 못하도록 한다.
💡예시: 은행 계좌에서 잔액(Balance) 을 조회하려는 사용자가 여러 명일 때는 공유락이 걸려 각 사용자 모두 잔액을 동시에 확인할 수 있다. 그러나 누군가가 잔액을 인출하려고 한다면 배타락이 걸려 그 동안 다른 사용자는 잔액을 조회하거나 인출할 수 없게 된다.
트랜잭션(Transaction) 이 동시에 실행될 때 데이터의 충돌을 어떻게 피할 수 있는지 알아보자
락(LOCK)의 예(Example of Lock) #1
💡요약: 락(LOCK) 을 통해 서로 다른 트랜잭션이 독립적으로 작업을 수행할 수 있음을 보여주는 예시이다. 데이터베이스에서 특정 데이터만 락 할 수 있어 효율적이며, 다른 트랜잭션이 다른 행을 업데이트할 때 충돌을 피할 수 있다.

Session 1에서는 직원 번호 100번(Employee 100) 의 급여를 10% 증가시키는 업데이트 작업을 수행한다. 이 작업은 해당 행(row)에만 락(LOCK) 을 걸기 때문에, 다른 데이터에는 영향을 미치지 않게 된다.
Session 2에서는 직원 번호 200번(Employee 200) 의 급여를 10% 증가시키는 작업을 수행한다. 이 역시 해당 행에만 락을 걸어 작업을 진행한다.
락(LOCK)의 예(Example of Lock) #2
💡요약: 아래의 예시는 교착 상태(Deadlock) 가 발생하는 원인을 설명하며, 트랜잭션 관리 시 교착 상태를 방지하는 것이 중요하다는 점을 알수있다.

Session 1에서는 직원 번호 200번(Employee 200) 의 급여를 업데이트하려고 한다. 그러나 해당 행(row)에 락 이 걸려 있어서 작업이 완료되지 않고 대기 상태에 있다.
Session 2에서는 직원 번호 100번(Employee 100) 의 급여를 업데이트하려고 하지만, 이 행 역시 다른 트랜잭션에 의해 락 이 걸려 대기 상태이다.
이 상황에서 Session 1은 Session 2가 가지고 있는 Employee 100에 대한 락을 기다리고, 반대로 Session 2는 Session 1이 가지고 있는 Employee 200에 대한 락을 기다리기 때문에 두 트랜잭션 모두 진행할 수 없는 교착 상태(Deadlock) 가 된다. 각 트랜잭션이 필요한 자원(Resource) 을 확보할 수 없어, 무한 대기 상태에 빠지게 된다.
락(LOCK)의 예(Example of Lock) #3
💡요약: 아래 예시를 통해 교착 상태 가 발생했을 때 데이터베이스가 트랜잭션을 어떻게 관리하는지를 알수 있다. 교착 상태가 발생하면 오류와 함께 한 트랜잭션을 취소하여 시스템의 안정성을 유지한다.

Session 1에서 업데이트(UPDATE) 명령어를 실행하려고 했지만, 교착 상태 가 발생하면서 ORA-00060: deadlock detected 오류가 나타난다. 이 오류는 Session 1이 작업을 계속 진행하지 못하고 대기 중에 있을 때 발생한다.
교착 상태가 감지되면, Session 1에서 실행 중이던 트랜잭션이 롤백(Rollback) 되며, 일부 업데이트가 취소된다. 다만, 교착 상태가 발생하기 전에 진행된 부분은 롤백 되지 않고 커밋(COMMIT)을 통해 저장된다.
Session 1에서 커밋(COMMIT) 명령어가 실행되면, 교착 상태 발생 전의 변경 사항은 저장되고, 실패한 업데이트는 반영되지 않는다.
💡교착 상태(Deadlock) 가 발생하면, 두 트랜잭션 중 하나에서 오류가 발생하며 트랜잭션이 취소된다. 이 오류는 두 트랜잭션 중 하나에서만 발생하며, 어떤 트랜잭션이 오류를 받을지는 무작위이다.
락(LOCK)의 예(Example of Lock) #4
💡요약:락이 해제되었을 때 다른 트랜잭션이 어떻게 처리되는지를 알수있다. 트랜잭션 간의 락 해제 순서에 따라 작업이 완료된다.

Session 2에서 트랜잭션 2가 진행되었지만, Session 1에 의해 락이 걸려 대기 상태에 있었다.
이후 Session 1의 락이 해제되면서 Session 2의 업데이트가 성공적으로 실행된다.
Session 2에서 커밋(COMMIT) 명령어가 실행되면 모든 변경 사항이 저장되고 트랜잭션이 종료된다.
💡락(LOCK) 이 해제되면 대기 중이던 다른 트랜잭션이 정상적으로 실행될 수 있다.
💡커밋(COMMIT) 을 통해 변경 사항이 데이터베이스에 저장되며 트랜잭션이 종료된다.
교착상태(데드락, Deadlock)의 개념
💡요약: 데드락 은 두 개 이상의 트랜잭션(Transaction) 이 서로가 필요로 하는 자원을 잠근 상태에서, 상대방이 가지고 있는 자원을 기다리며 무한히 대기하게 되는 상황을 말한다.

Transaction 1은 Supplier 테이블에 락(Lock) 을 걸어 사용 중이다. 동시에 Part 테이블에 락을 걸고 싶지만, 이 자원은 이미 Transaction 2가 사용하고 있어 대기 상태에 있다.
반대로, Transaction 2는 Part 테이블에 락을 걸고 사용 중이며, Supplier 테이블의 락을 원하지만 Transaction 1이 이 락을 가지고 있어 대기 중이다.
이와 같은 상황에서는 서로의 자원을 기다리며 대기 상태가 영원히 지속될 수 있어, 두 트랜잭션이 모두 진행될 수 없는 상태가 되는데, 이것이 바로 데드락(Deadlock) 이다.
데드락의 해소법 (Resolution of Deadlock)
💡요약: 데드락 이 발생하면, 가장 적은 처리 시간을 가진 트랜잭션을 희생자(Victim) 로 지정하여 중단시킨다. 또한 해당 트랜잭션을 롤백(Rollback) 하여 자원을 해제하고, 에러 메시지를 전송하여 트랜잭션을 중단한다.
☑️데드락 희생자(Victim) 선정: 데드락 상태에 있는 트랜잭션들 중에서 가장 적은 처리 시간(Processing Time) 이 소요된 트랜잭션을 희생자로 지정한다. 이 트랜잭션을 중단함으로써 다른 트랜잭션들이 계속 진행할 수 있도록 한다.
☑️롤백(Rollback): 데드락 을 발생시킨 트랜잭션의 문장을 롤백(Rollback) 한다. 롤백은 트랜잭션을 되돌려서 자원을 해제하게 하여 다른 트랜잭션이 계속 진행될 수 있도록 한다.
☑️에러 메시지(Error Message) 전송: 데드락 으로 인해 중단된 트랜잭션에 에러 메시지를 보내고, 나머지 트랜잭션들은 정상적으로 계속 진행될 수 있도록 한다.
💡예시:
데드락 희생자(Victim):
Transaction A와 Transaction B가 각각 다른 자원을 잠그고 서로의 자원을 기다려 데드락 이 발생했다고 가정하자.
시스템은 더 적은 작업을 수행한 트랜잭션 을 선택하여 희생자로 지정하고 중단시킨다. 예를 들어, Transaction A가 희생자로 선택된다.
데드락 롤백(Rollback):
Transaction A가 희생자로 지정되면, 롤백 되어 수행했던 작업이 취소된다.
Transaction B는 자원을 확보하고 작업을 계속 진행할 수 있게 된다.
에러 메시지(Error Message):
Transaction A는 중단되면서 사용자에게 에러 메시지를 보내 데드락이 발생해 작업이 중단되었음을 알린다.
사용자에게 알림을 주어 데이터베이스의 안전성을 유지한다.
데드락 대비책(Deadlock Prevention Measures)
💡요약: 모든 트랜잭션이 같은 순서로 자원에 접근하도록 하여 데드락을 예방한다. 트랜잭션의 명령어 수를 줄여 자원을 빠르게 해제할 수 도있다. 여러 트랜잭션에 영향을 미치는 자원은 짧은 트랜잭션 내에서만 사용한다.
☑️자원 접근 순서 일관성(Maintain Consistent Resource Access Order): 모든 트랜잭션이 자원을 같은 순서로 접근하도록 설정한다. 예를 들어, A, B, C가 자원이라면 모든 트랜잭션이 A → B → C 순서로 자원에 접근하도록 하면, 서로 다른 트랜잭션이 자원 접근 순서가 엇갈려 데드락이 발생할 가능성을 줄일 수 있다.
☑️명령어 수 최소화(Minimize Number of Commands): 트랜잭션이 처리하는 명령어 수를 최소화하여 트랜잭션 처리 시간을 줄임으로써 자원을 빠르게 해제하게 한다. 이렇게 하면 다른 트랜잭션이 대기하는 시간이 줄어들어 데드락 가능성이 낮아진다.
☑️트랜잭션 내 자원 사용 제한(Limit Resource Usage in Transaction): 여러 트랜잭션에 영향을 줄 수 있는 질의를 가급적 짧은 트랜잭션 안에서 사용한다. 이를 통해 자원을 짧은 시간 동안만 점유하여 다른 트랜잭션이 대기하지 않도록 할수있다.
💡예시: 은행 시스템에서 두 트랜잭션이 계좌(Account) 와 잔액(Balance) 테이블에 접근한다고 가정하자
예방책 적용 전:
Transaction 1이 계좌(Account) 테이블을 먼저 잠그고, 그 후 잔액(Balance) 테이블을 잠그려고 대기한다.
동시에 Transaction 2는 잔액(Balance) 테이블을 먼저 잠그고, 그 후 계좌(Account) 테이블을 잠그려고 대기한다.
이로 인해 서로의 자원을 대기하며 데드락 이 발생하게 된다.
예방책 적용 후:
모든 트랜잭션이 계좌(Account) 테이블을 먼저 잠근 후, 잔액(Balance) 테이블을 잠그도록 설정한다.
이렇게 하면 트랜잭션들이 같은 순서로 자원에 접근하여 데드락 이 발생하지 않게된다.




