Computer Science 모아보기 👉🏻 https://github.com/seoul-developer/CS
💋 Lock이란?
Lock이란 트랜잭션 처리의 순차성을 보장
하기 위한 방법이다. 여러 사용자가 동시에 데이터베이스에 접근하거나 수정하는 것을 관리하기 위해 사용된다.
DBMS마다 Lock을 구현하는 방식과 세부적인 방법이 다르기 때문에 DBMS를 효과적으로 이용하기 위해서는 해당 DB의 Lock에 대한 이해해야 한다.
💋 Lock의 종류
✔️ exclusive lock (write lock)
- 다른 트랜잭션에서 같은 데이터를 read/wirte하는 것을 허용 X
- 다른 트랜잭션에서 같은 데이터에 대해 read lock을 가지고 있는 경우에, write lock은 획득할 수 없다.
- read/write 할 때 모두 사용 가능하다.
- 이름이 write 락이지만, 다른 모든 트랜잭션에 ‘배타적’이라는 것에 집중하자!
✔️ shared lock (read lock)
- 다른 트랜잭션에서 같은 데이터를 read하는 것은 허용
- 다른 트랜잭션에서 같은 데이터에 대해 read lock을 가지고 있는 경우에도, read lock은 획득할 수 있다.
- read할 때 사용한다.
- 이름은 read 락이지만, 다른 트랜잭션에 배타적이지 않아서 동시에 여러 트랜잭션에서 read lock을 획득할 수 있다.
✔️ lock 호환성
read lock끼리는 배타성이 없어서 동시에 여러 트랜잭션에서 read lock을 획득할 수는 있다.
하지만 하나의 자원에 대해서 한 트랜잭션에서 write lock을 획득했다면, write lock은 배타성을 지니기 때문에 다른 트랜잭션들은 읽기 락도 쓰기 락도 획득하지 못한다.
💋 Lock을 사용한 동시성 제어
✔️ Lock 사용만으로는 Nonserializable하다.
lock을 사용하는 것만으로도 serial schedule과 같은 결과를 보장하지는 않는다.
serializable하려면, #1, #2에서 나온 결과 둘 중 하나와는 같은 결과가 나와야 하는데 현재 결과는 x=300, y=300
으로 둘 중 어떤 것과도 다르다.
⇒ lock 사용만으로는 serial schedule과 conflict serializable
하지 않다.
= Nonserializable
한 스케줄
serializable schedule에서는 트랜잭션이 커밋되기 전까지 해당 트랜잭션이 변경한 데이터를 다른 트랜잭션은 읽을 수 없다. 다른 트랜잭션은 현재 진행 중인 트랜잭션의 변경 내용을 보기 위해 기다려야 한다.
Nonserializable
해진 이유를 살펴보면,
serializable하기 위해서는 Tx2가 먼저 시작했다면,
Tx1은 Tx2가 커밋한 후의 y를 읽어야 하는데,
- Tx1이 먼저 read lock을 획득해서 커밋 이전의 y를 읽어버렸다.
- Tx2는 먼저 트랜잭션을 시작했음에도, Tx1의
read_lock(y)
때문에 block이 되어서, Tx1이 y에 대한 읽기 락을 반환할 때까지 작업을 하지 못했다.
✔️ 락 반환 순서를 통해 Serializable schedule로 만들자!
serializable하게 만들기 위해서는 Tx2에서 x에 대한 락을 해제(unlock(x)
)하기 전에, y에 대한 락을 획득(write_lock(y)
)하면 된다.
Tx2에서 y락 획득과 x락 해제의 순서만 바꿔주었다.
이렇게 하니, Tx1에서 y락을 획득하려고 시도해도 Tx2에서 y 쓰기 락을 가지고 있어서 y에 모든 업데이트를 한 후 커밋할 때까지 y를 읽지 못하고 있다.
Tx1도 마찬가지로, 락을 놓아주기 전에 다음 락을 획득하는 식으로 바꿀 수 있다.
이렇게 변경하면, serializable한 schedule이 될 것이다.
serializable
해진 schedule에서 락과 관련된 operation만 모아보면 다음과 같다.
결과적으로는, tx에서 모든 locking operation이 최초의 unlock operation보다 먼저 실행되도록 하면 된다.
- 초록색의 Tx1의 경우, 최초의 unlock operation인 unlock(y)보다 먼저 모든 락 취득
- 파란색의 Tx2의 경우, 최초의 unlock operation인 unlock(x)보다 먼저 모든 락 취득
이와 같은 약속을 2PL Protocol이라고 한다.
💋 2PL Protocol (two-phase locking)
✔️ 2PL Protocol은 serializability를 보장한다.
tx에서 모든 locking operation이 최초의 unlock operation보다 먼저 실행되도록 하는 약속
⇒ 한 번 unlock을 실행하기 시작하면, 그 이후에는 새로운 락을 취득하지 않는다.
이렇게 lock 취득과 반환의 부분을 완전히 분리하게 된다.
취득하는 phase를 expanding phase라고 하고, 반환만 하는 phase를 shrinking phase라고 한다.
2PL Protocol은 serializability를 보장한다.
✔️ 2PL Protocol의 deadlock
아래 그림에서 Tx1은 y 쓰기 락을 획득한 후 x 쓰기 락을 돌려주려고 하고, Tx2은 x 쓰기 락을 획득한 후 y 쓰기 락을 돌려주려고 한다. 결국 이렇게 되면 deadlock 상태가 되어 아무도 트랜잭션 내 operation을 이어갈 수 없게 된다.
해결 방법은 Os에서의 deadlock의 해결과 유사하다.
💋 2PL Protocol의 종류
✔️ 그냥 2PL
y, z에는 쓰기 락, x에는 읽기 락을 얻는 트랜잭션인데, unlock을 시작하기 전에 모든 락을 취득하고 있다.
✔️ conservative 2PL
- 트랜잭션 시작하자마자
모든 락을 먼저 취득
하는 2PL - 데드락을 방지할 수 있다.
- 모든 락을 동시에 취득한다는 것이 어려워서 실용적이지 않다.
✔️ strict 2PL (S2PL)
strict schedule
을 보장하는 2PL- 어떤 데이터에 대해 write하는 트랜잭션이 있다면 그 트랜잭션이 commit/rollback될 때까지 다른 트랜잭션은 그 데이터에 대해 read/write 모두 안하는 스케줄
- recoverability 보장
- write lock은 commit하거나 rollback될 때 반환한다.
✔️ strong strict 2PL (SS2PL, rigorous 2PL)
- strict schedule 보장
- recoverability 보장
- read lock/write lock 모두 commit하거나 rollback 하고 나서 반환한다.
- S2PL보다 구현이 쉽다.
- read 락까지 오래 쥐고 있기 때문에, 다른 트랜잭션에서 그 데이터에 대해서 write하고 싶을 때 비효율적이다.
💋 2PL Protocol의 한계
read-read를 제외하고 나머지 lock은 서로 배타적이다.
2PL을 사용하게 되며나, 락을 오래 갖고 있게 되는데, 결과적으로 전체 처리량에는 안좋은 영향을 준다.
락을 사용한 트랜잭션 Isolation도 결과적으로는 트랜잭션을 분리해 내는 데에는 성공했지만, 처리량이 아쉬벡 되었다.
처리량을 극복하기 위해서 read lock과 write lock이 서로 배타적으로 동작하는 것만이라도 해결하게 된다.
또한 락을 획득하지 않고도 동시에 여러 트랜잭션이 데이터를 읽고 쓸 수 있도록 하게 된다.
⇒ 이렇게 MVCC
가 등장한다! (다음 포스팅에 계속)
💋 참고자료
도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!