해당 내용은 쉬운코드님의 영상을 기반으로 제작되었습니다
🔌 LOCK
우리는 트랜잭션 (3) 에서 다양한 트랜잭션에 대한 문제를 만나보았다.
거기서 계속해서 진행하던 write는 과연 단순한 값 바꾸기일까? 그것보다 더욱 복잡한 과정이 생길 수 있다.
만약 다른 트랜잭션에서 같은 값을 똑같이 write하려고 한다면 어떻게 되는 걸까? 이때 예상치 못한 문제가 발생할 수 있다
이때 사용하는 것이 바로 lock이라는 것이다. 이때 lock은 운영체제의 lock과 굉장히 비슷하다.
- 데이터마다 lock 이 있어서 데이터를 변경하거나 읽으려면 lock을 취득해야한다 만약 취득하지 못한다면 얻을 때까지 기다려야한다
♬ Lock 을 이해하기 위한 예시
1.
트랜잭션 1 : x를 20으로 바꾼다
트랜잭션 2 : x를 90으로 바꾼다.
이전 데이터 ▶ x = 10
- 상황
- 1번이 write lock을 획득한다. 동시에 2번이 write lock을 획득하려 하지만 이미 lock을 취득할 수 없기 때문에 대기를 해야한다.
- 1번은 취득한 lock으로 DB의 x값을 20으로 변경하고 lock을 풀게 된다.
- lock을 기다리고 있던 2번은 해당 lock을 가지고 x의 값을 90으로 바꿔준다.
2.
트랜잭션 1 : x를 20으로 바꾼다
트랜잭션 2 : x를 읽는다
이전 데이터 ▶ x = 10
- 상황
- 1번이 write lock을 획득한다. 동시에 2번이 read lock을 획득하려 하지만 이미 lock을 취득할 수 없기 때문에 대기를 해야한다.
- 1번은 취득한 lock으로 DB의 x값을 20으로 변경하고 lock을 풀게 된다.
- lock을 기다리고 있던 2번은 해당 lock을 가지고 x의 값을 읽는다.
♬ Write Lock (Exclusive lock)
- read / write 할 때 사용한다
- 다른 트랜잭션이 같은 데이터를 read / write 하는 것을 허용하지 않는다.
♬ Read Lock (Shared lock)
- read 할 때 사용한다
- 다른 트랜잭션이 같은 데이터를 read하는 것을 허용한다. 하지만 write하는 것은 허용하지 않는다.
🔌 Lock으로 serializability를 보장할 수 있을까?
예시
- 두개의 트랜잭션은 serializability 할까?
- 1번이 먼저 진행 될 경우 ▶ x = 300, y = 500
- 2번이 먼저 진행 될 경우 ▶ x = 400, y = 300
- 이런 방식으로 트랜잭션이 진행되게 되면 결과값이 x = 300, y = 300 이라는 serializable한 결과가 나오지 않는다.
- 그렇다면 왜 lock을 사용했음에도 불구하고 이런 결과값이 나오게 된걸까 이는 위에 사진에 빨간 operation들 때문이다. 위처럼 2번 트랜잭션이 x를 read했다면 다음으로 읽는 1번 트랜잭션은 write된 y를 읽어야하는데 1번 트랜잭션에서 read lock 이 걸리면서 2번 트랜잭션이 y의 업데이트를 진행하지 못해서 그런것이다.
해결책 : 2번 트랜잭션의 unlock 과 write_lock의 순서를 바꾸는 것이다. 즉, read 에 대한 lock을 반납하기 전에 wirte_lock까지 가져와 버리는 것이다. 그러면 1번 트랜잭션은 read_lock도 가져오지 못하는 것이다. 그로인해 과정은 이런식으로 변경이 되는 것이다.
🔌 2PL protocol (two - phase locking)
위에서 오로지 lock과 관련된 operation을 보면 하나의 규칙을 가지고 있는 것을 볼 수 있다.
트랜잭션에서 모든 locking operation은 최소의 unlocking operation보다 먼저 수행된다
이를 2PL protocol 이라고 한다.
- 2 Phase 인 이유
- Expanding phase (growing phase) : lock을 취득만 하고 반환하지는 않는다
- Shrinking phase (contracting phase) : lock을 반환만 하고 취득하지는 않는 phase이다
- 특징
- Serializability를 보장한다
- DeadLock이 발생할 수 있다.
♪ DeadLock 예방하는 법
1. conservative 2PL
- 모든 lock을 취득한 뒤에 transaction을 시작한다
- 특징
- deadlock이 발생하지 않는다
- 모든 lock을 미리 받아야 트랜잭션을 시작할 수 있기 때문에 실용적이라고 보기는 어렵다
2. strict 2PL (S2PL)
- strict schedule을 보장하는 2PL이기 때문에 commit 이 완료된 것을 확인해야 나머지 락을 unlock한다.
※ strict schedule : commit / rollback 되지 않은 트랜잭션의 내용은 다른 트랜잭션에서 절대 사용하지 않는다. - 그로인해 recoverability를 보장한다.
- write-lock을 commit / rollback 될 때 반환
2. strong strict 2PL (SS2PL or rigorous 2PL)
- 똑같이 strict schedule을 보장하고 recoverability를 보장한다
- 차이는 read-lock 또한 commit / rollback 될때 unlock을 해준다.
- 그리고 unlock 자체를 가장 마지막에 진행하기 때문에 구현이 비교적 쉬워진다.
- 하지만 lock을 굉장히 오래 쥐고 있기 때문에 다음으로 사용할 트랜잭션이 오래 기다려야 한다.
🔌 근본적인 문제점
위에서 알아본 lock을 통해 seralizability를 보장하는 방법을 알아봤지만 그 보장하는 방법의 치명적인 단점이 존재한다. 바로 lock을 걸어버리게 되고 lock을 걸어도 발생하는 문제(deadlock)까지 해결하려다 보니 전체적인 처리량이 전혀 좋지 않다는 것이다.
그렇다면 어떻게 하면 좋은 성능으로 더 빠른 처리량을 가질 수 있을까 하고 고민한 결과 read 와 write 관계에서 답을 얻었다고 한다. write와 write의 관계는 어쩔수없지만 데이터를 읽기만 하는 과정에는 좀 더 빠른 작업을 할 수 있지 않을까 싶은 것이다.
이를 이용한 것이 바로 MVCC(Multi Version Concurrency Control) 이다. 다음 블로그에서 더 자세히 알아보자
'CS > 💾 DB' 카테고리의 다른 글
💾 낙관적 락(Optimistic) 과 비관적 락(Pessimisitic) (1) | 2024.04.27 |
---|---|
💾 MVCC, 트랜잭션 ( 5 ) (feat. MySQL과 PostgreSQL 비교) (1) | 2024.04.27 |
💾 Isolation Level, 트랜잭션 ( 3 ) (1) | 2024.04.23 |
💾 Concurrency control의 기초, 트랜잭션 ( 2 ) (0) | 2024.04.23 |
💾 Transaction, 트랜잭션이란? (0) | 2024.04.23 |