transaction1이 x를 20으로 바꾸는 write를 실행할때, 이것은 단순히 값 하나 바꾸는 것보다 더 복잡한 과정이다. 같은 데이터에 또다른 read/write 가 있다면 예상치 못한 동작을 할 수 있다.
→ Lock을 사용하여 해결할 수 있다
Lock 사용설명
데이터마다 Lock이 있어서 변경하거나 읽으려면 lock을 취득해야한다. lock을 취득하지 못하면 취득할때까지 기다려야 한다
Lock 사용 예시
예시1) x = 10
Transaction 1 : x를 20으로 바꾼다
Transaction 2 : x를 90으로 바꾼다
Transaction 1 | Transaction 2 |
write_lock(x) (lock획득) |
|
write_lock(x) (이미 tx1이 획득했기 때문에 lock획득 실패 → 기다림) |
|
write(x = 20) | |
unlock(x) (lock 해제) |
|
write(x = 90) (tx1이 lock을 해제하였으니 lock획득하여 진행) |
|
unlock(x) (lock 해제) |
예시2) x = 10
Transaction 1 : x를 20으로 바꾼다
Transaction 2 : x를 읽는다
Transaction 1 | Transaction 2 |
write_lock(x) (lock획득) |
|
read_lock(x) (읽을 목적으로 lock을 획득하려하지만, tx1이 쓰기목적으로 lock을 획득하고 있는 상태라 block → 기다림) |
|
write(x = 20) | |
unlock(x) (lock 해제) |
|
read(x) ⇒ 20 (tx1이 lock을 해제하였으니 진행) |
|
unlock(x) (lock 해제) |
write-lock(exclusive lock)
- read / write(insert, modify, delete) 할 때 사용
- 다른 tx가 같은 데이터를 read / write 하는 것을 허용하지 않는다
read-lock(shard lock)
- read할 때 사용
- 다른 tx가 같은 데이터를 read하는 것은 허용한다
예시) x = 10
Transaction 1 : x를 읽는다
Transaction 2 : x를 읽는다
Transaction 1 | Transaction 2 |
read_lock(x) | |
read_lock(x) (read_lock은 허용) |
|
read(x) ⇒ 10 | read(x) ⇒ 10 |
unlock(x) | unlock(x) |
Lock 호환성
read-lock | write-lock | |
read-lock | O | X |
write-lock | X | X |
lock을 써도 serializability 가 보장되지 않는다
예시) x = 100, y = 200
Transaction 1 : x와 y의 합을 x에 저장
Transaction 2 : x와 y의 합을 y에 저장
나올수 있는 결과값
- serial schedule #1 : t1 → t2 순서일때 x = 300, y = 500
- serial schedule #2 : t2 → t1 순서일때 x = 400, y = 300
Transaction 1 | Transaction 2 |
read_lock(x) | |
read(x) ⇒ 100 | |
unlock(x) | |
read_lock(y) | |
write_lock(y) → block 기다림 |
|
read(y) ⇒ 200 | |
unlock(y) | |
read(y) ⇒ 200 | |
write(y = 300) | |
unlock(y) | |
write_lock(x) | |
read(x) = 100 | |
write(x = 300) | |
unlock(x) |
이 결과 x = 300, y = 300 이상한 결과값이 된다
그런데 위의 순서에서 tx2의 unlock(x) 와 write_lock(y) 순서를 바꾸면?
Transaction 1 | Transaction 2 |
read_lock(x) | |
read(x) ⇒ 100 | |
write_lock(y) | |
read_lock(y) → block 기다림 |
|
unlock(x) | |
read(y) ⇒ 200 | |
write(y = 300) | |
unlock(y) | |
read(y) ⇒ 300 | |
unlock(y) | |
write_lock(x) | |
read(x) = 100 | |
write(x = 400) | |
unlock(x) |
x = 400, y = 300으로 serial schedule #2 결과값이 된다.
2PL protocol (two-phase locking)
tx에서 모든 locking operation이 최초의 unlock operation보다 먼저 수행되도록 하는 것
Expanding phase(growing phase) : lock을 취득하기만 하고 반환하지 않는 phase
Shrinking phase(contracting phase) : lock을 반환만 하고 취득하지 않는 phase
Serializability 보장
2PL과 deadlock
deadlock: 모든 트랜잭션들이 lock을 반납하기를 기다리며 트랜잭션 실행이 멈춰버리는 현상
위의 예시를 2PL protocol 로 진행해보자
Transaction 1 | Transaction 2 |
read_lock(x) | |
read_lock(y) | |
read(y) ⇒ 200 | |
write_lock(x) → block 기다림 |
|
read(x) ⇒ 100 | |
write_lock(y) → block 기다림 |
이렇게 tx1 은 tx2의 read_lock(x)이 해제되길 기다리고 있고, tx2는 tx1의 read_lock(y)가 해제되길 기다리고 있는 Deadlock 발생 하였다
2PL protocol의 종류
예시) Transaction : x와 y와 z를 더해서 y에 쓴다, x에 2를 곱해서 z에 쓴다
conservative 2PL
- 모든 lock을 취득한 뒤 transaction을 시작
- deadlock-free
- 실용적이진 않다conservative 2PL
- read_lock(x) - write_lock(y) - write_lock(z) - read(x) - unlock(x) - read(y) - read(z) - write(y=x+y+z) - unlock(y) - write(z=x*2) - unlock(z)
strict 2PL (S2PL)
- strict schedule을 보장하는 2PL
- recoverability 보장
- write-lock을 commit / rollback 될 때 반환strict 2PL (S2PL)
- read_lock(x) - read(x) - write_lock(y) - read(y) - write_lock(z) - unlock(x) - read(z) - write(y=x+y+z) - write(z=x*2) - commit - unlock(y) - unlock(z)
strong strict 2PL(SS2PL or rigorous 2PL)
- strict schedule을 보장하는 2PL
- recoverability 보장
- read-lock / write-lock 모두 commit / rollback될 때 반환
- S2PL보다 구현이 쉽다
- read_lock(x) - read(x) - write_lock(y) - read(y) - write_lock(z) - read(z) - write(y=x+y+z) - write(z=x*2) - commit - unlock(x) - unlock(y) - unlock(z)
출처: 쉬운코드
'STUDY > database' 카테고리의 다른 글
[DB] MVCC 설명_2 (쉬운코드) (1) | 2023.12.19 |
---|---|
[DB] MVCC 설명_1 (쉬운코드) (0) | 2023.12.19 |
transaction isolation level (0) | 2023.12.11 |
concurrency control - schedule과 serializability (0) | 2023.12.03 |
데이터베이스 Transaction 트랜잭션 / ACID (0) | 2023.12.03 |