[DB] 군대로 알아보는 트랜잭션 - 3. 독립성 편
이번 편에서는 트랜잭션의 독립성(또는 고립성, 격리성)에 대해 알아보려고 합니다.
1. 정의
- 독립성(Isolation)은 트랜잭션을 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것을 의미한다. 이것은 트랜잭션 밖에 있는 어떤 연산도 중간 단계의 데이터를 볼 수 없음을 의미한다. (출처)
- 독립성은 ACID 원칙들 중에서 가장 유연성 있는 제약조건입니다.
2. 트랜잭션의 격리 수준
트랜잭션의 격리 수준은 크게 4가지로 나뉩니다.
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
Read Uncommitted
-
Read uncommitted는 커밋되지 않은 정보까지도 읽는 것을 허용합니다
-
Dirty Read, Non-repeatable read, Phantom read가 발생할 수 있습니다
Dirty Read의 예시를 살펴보겠습니다
입대 신청이 다음과 같은 로직으로 구동된다고 가정하겠습니다
public void 입대_신청() {
System.out.print("이름을 입력해주세요: ");
String name = sc.next();
militaryDB.입대(name);
System.out.print("정말로 입대하시겠습니까? [Y/N]: ");
String answer = sc.next();
if(answer.equals("Y")) {
System.out.println("입대 신청이 완료되었습니다.");
} else {
militaryDB.삭제(name);
System.out.println("입대 신청이 취소되었습니다.");
}
}
-
이 때 Y/N에 대답하기 전에 훈련소로 오라는 명령을 누군가가 실행한다면 다음과 같은 결과가 발생하게 될 것입니다
-
입대 신청에서 '정말로 입대하시겠습니까? [Y/N]?'에 N이라고 답하는 것은, DB Transaction에서 rollback과 같은 기능입니다.
-
그런데 Uncommitted Read로 격리 수준을 설정하면 위와 같이 커밋되지 않은 정보를 읽어오게 되는 불상사가 발생할 수 있습니다.
-
따라서 Uncommitted Read는 데이터베이스 정합성을 심각한 수준으로 침해하게 되고, 이 때문에 격리 수준으로 인정되지도 않는 수준입니다.
Read Committed
-
이제 한 트랜잭션에서 변경 내용이 commit 되어야만 다른 트랜잭션에서 조회할 수 있습니다!
-
가장 보편적인 격리 수준입니다
-
Non-repeatable read, Phantom Read 문제가 발생할 수 있습니다
Non-repeatable read의 예시를 살펴보겠습니다
훈련병들을 식당으로 입장시키는 도중, 한 훈련병의 이탈 발생!
class MilitaryDB{
int idx = 0;
HashMap<Integer, String> newSoldiers = new HashMap<>();
public void 입대(String name) {
newSoldiers.put(idx++, name);
}
public void 훈련소로_입장() {
System.out.println("훈련병들은 차례로 훈련소로 입장한다, 실시!");
ArrayList<Integer> allSoldiersId = getAllSolders(); //select id from new_soldiers;
for (int id : allSoldiersId) {
String name = newSoldiers.get(id);
if(name != null) System.out.println(name + " 훈련병, 훈련소로 입장!");
else System.out.println("번호가 " +id + "인 훈련병? 분명 있었는데 어디갔지?");
}
}
public ArrayList<Integer> getAllSolders() {
ArrayList<Integer> arr = new ArrayList<>();
for (int id : newSoldiers.keySet()) {
arr.add(id);
}
return arr;
}
public void 삭제(int id) {
newSoldiers.remove(id);
}
}
public static void main(String[] args) throws InterruptedException {
MilitaryDB militaryDB = new MilitaryDB();
militaryDB.입대("Jake");
militaryDB.입대("Sam");
militaryDB.입대("Kim");
militaryDB.입대("Park");
militaryDB.입대("Lee");
Thread thread1 = new Thread(() -> militaryDB.훈련소로_입장());
thread1.start();
Thread thread2 = new Thread(() -> militaryDB.삭제(3));
thread2.start();
}
결과
-
트랜잭션의 시작에서 읽었을 때는, 훈련병이 5명이었습니다
-
트랜잭션 중간에 삭제 메서드가 개입하여 훈련병이 4명으로 줄었습니다
-
이처럼, 동일한 트랜잭션에서 select 쿼리를 실행했을 때 결과가 달라지는 경우를 non-repeatable read라고 합니다
-
일반적으로는 크게 문제되지 않지만, 금융 서비스 등에서는 치명적일 수 있습니다
Repetable Read
-
위에서 언급했던 non-repeatable read가 해결된 상태입니다
-
자신의 트랜잭션 번호보다 낮은 트랜잭션 번호에서 커밋된 내용만 보게 됩니다
-
Repeatable Read를 구현하는 방법은 크게 2가지가 있습니다
- Lock을 이용한 Concurrecy control
- MVCC(Multi Version Concurrency Control)
-
Repeatable read에서도 phamtom read와 같은 문제가 발생합니다
Phantom Read란
public static void main(String[] args) throws InterruptedException {
MilitaryDB militaryDB = new MilitaryDB();
militaryDB.입대("Jake");
militaryDB.입대("Sam");
militaryDB.입대("Kim");
militaryDB.입대("Park");
militaryDB.입대("Lee");
Thread thread1 = new Thread(() -> militaryDB.훈련소로_입장());
Thread thread2 = new Thread(() -> militaryDB.입대("Late"));
thread1.start();
thread2.start();
}
class MilitaryDB{
int idx = 0;
HashMap<Integer, String> newSoldiers = new HashMap<>();
public void 입대(String name) {
newSoldiers.put(idx++, name);
}
public void 훈련소로_입장() {
System.out.println("훈련병들은 차례로 훈련소로 입장한다, 실시!");
HashMap<Integer, String> allSoldiersMap = newSoldiers; //select id from new_soldiers;
for (String name : allSoldiersMap.values()) {
System.out.println(name + " 훈련병, 훈련소로 입장!");
}
//조교가 잠시 한 눈을 팔다가 돌아온 사이 훈련병 한 명이 새로 들어왔다!
if(newSoldiers.size() != allSoldiersMap.size())
System.out.println("왜 훈련병 한 명이 남지?");
}
}
-
위 상황처럼, 트랜잭션 중간에 insert 쿼리로 인해 새로운 데이터가 생겼을 때 처음에는 읽지 못했던 자료가 갑자기 등장하게 되는 현상이 발생할 수 있습니다
-
이처럼 트랜잭션 과정에서 기존에 없던 자료가 새로 읽히게 되는 것을 phantom read라고 합니다
Serializable
- 읽기에서도 해당 자원에 잠금을 거는 방식입니다
- 정합성 측면에서는 완벽하지만, 동시성 측면에서는 최악의 퍼포먼스를 보이게 됩니다
- 때문에 실제로는 많이 적용하기 힘든 방식입니다
3. 정리
-
Uncommitted Read
- Dirty read, non-repeatable read, phantom read 발생 가능
- 격리 수준으로 인정받지 못하는 수준
-
Read Committed
- Non-repeatable read, phantom read 발생 가능
- 보편적인 격리 수준
-
Repeatable Read
- phantom read 발생 가능
- concurrency control을 통해 데이터의 버전을 관리해주어야 함
-
Serializable
- 데이터베이스 정합성의 무결함을 보장
- 동시성에서 최악의 퍼포먼스를 보임
Author And Source
이 문제에 관하여([DB] 군대로 알아보는 트랜잭션 - 3. 독립성 편), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@mincho920/DB-군대로-알아보는-트랜잭션-3.-독립성-편저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)