DBMS - 데이터베이스 락(lock) 종류와 트랜잭션 격리수준(isolation level)

RDBMS에서 데이터를 일관성을 위한 동기화(Lock)을 설정하는 방법은 크게 2가지로 나뉜다. 하나는 비관적 락, 나머지 하나는 낙관적 락이다. 아래는 각각에 대한 개략적인 설명이다.
Pessimistic lock(비관적 락)
비관적 락이란 트랜잭션이 시작될 때 데이터의 행에 Shared Lock 또는 Exclusive Lock을 걸고 시작하는 방법이다. 즉, write를 하기위해서는 Exclucive Lock을 얻어야하는데 Shared Lock이 다른 트랜잭션에 의해서 걸려 있으면 해당 Lock을 얻지 못해서 업데이트를 할 수 없다 수정을 하기 위해서는 shared lock을 설정한 트랜잭션이 종료(commit or rollback)된 후 exclusive lock을 설정해야한다.
- shared lock(s-lock)
- 목적: 데이터를 읽기 위한 락으로, 여러 트랜잭션이 동시에 동일한 데이터를 읽을 수 있도록 허용
- 특징: 공유 락이 설정된 데이터는 다른 트랜잭션이 배타 락을 설정할 수 없지만, 추가적인 공유 락은 허용.
- exclusive lock(x-lock)
- 목적: 데이터를 수정하거나 삭제하기 위한 락으로, 한 트랜잭션만이 해당 데이터에 접근하여 변경 작업을 수행할 수 있도록 보장
- 특징: 배타 락이 설정된 데이터는 다른 트랜잭션이 해당 데이터에 대한 어떠한 락도 설정할 수 없음.
- update lock(u-lock)
- 목적: 데이터를 수정하기 전에 설정되는 락으로, 데드락을 방지하기 위해 사용
- 특징: 업데이트 락은 공유 락과 배타 락의 중간 형태로, 다른 트랜잭션의 공유 락은 허용하지만 배타 락은 허용하지 않음.
- intent lock(i-lock)
- 목적: 상위 객체(ex. 테이블)에 락을 설정하기 전에 하위 객체(ex. 행)에 대한 락 의도를 명시하여 락 설정 여부를 빠르게 판단할 수 있도록함.
- 특징: 의도 공유 락(IS), 의도 배타 락(IX) 등이 있으며, 상위 수준의 락 설정시 충돌 방지

비관적 락의 트랜잭션 격리수준
- read uncommitted
- 데이터를 읽을때, shared lock을 사용하지 않기 때문에 exclusive lock이 설정된 데이터도 읽을 수 있음.
- dirty read: 한 트랜잭션이 커밋되지 않은 데이터를 다른 트랜잭션이 읽을 수 있어, 해당 트랜잭션이 롤백되면 잘못된 데이터를 읽게 될 수 있음.
- read committed
- 데이터를 읽을때, shared lock을 설정하기 때문에 다른 트랜잭션에서 데이터를 수정할 수 없음.
- non-repeatable read: 하나의 트랜잭션 내에서 동일한 데이터를 두 번 읽을 때, 중간에 다른 트랜잭션이 해당 데이터를 수정하고 커밋하면 처음과 다른 결과를 얻을 수 있음.
- repeatable read
- 데이터를 읽을 때 설정한 shared lock이 트랜잭션이 종료될 때까지 유지하여 해당 트랜잭션이 끝나기 전까지 해당 데이터를 수정하지 못함.
- phantom read: 트랜잭션이 범위 쿼리를 수행한 후, 다른 트랜잭션이 해당 범위 내에 새로운 행을 삽입하거나 삭제하면, 처음 쿼리와 동일한 범위 쿼리를 다시 수행할 때 결과 집합이 달라질 수 있음.
- serializable
- 범위 쿼리를 수행할 때 해당 범위에 shared lock을 설정하여 다른 트랜잭션이 해당 범위 내에서 데이터를 삽입, 수정, 삭제하지 못하도록함.
- dead lock, low performance

트랜잭션 격리 수준은 trade-off가 필요한 결정사항이고 서비스 요구수준에 맞춘 격리 수준을 택하는 것이 필요할 듯하다.

Optimistic lock(낙관적 락)
낙관적 락은 DB 충돌 상황을 개선할 수 있는 방법 중 2번째인 수정할 때 내가 먼저 이 값을 수정했다고 명시하여 다른 사람이 동일한 조건으로 값을 수정할 수 없게 하는 것이다. 그런데 잘 보면 이 특징은 DB에서 제공해주는 특징을 이용하는 것이 아닌 Application Level에서 잡아주는 Lock이다.
- 충돌은 잘 발생하지 않을 것이라는 전제로 데이터의 일관성을 맞추는 방법
- 버전 필드를 두어서 락(동기화) 없이 일관성을 맞춘다.

- A가 table의 Id 2번을 읽음 ( name = Karol, version = 1 )
- B가 table의 Id 2번을 읽음 ( name = Karol, version = 1 )
- B가 table의 Id 2번, version 1인 row의 값 갱신 ( name = Karol2, version = 2 ) 성공
- A가 table의 Id 2번, version 1인 row의 값 갱신 ( name = Karol1, version = 2 ) 실패
- Id 2번은 이미 version이 2로 업데이트 되었기 때문에 A는 해당 row를 갱신하지 못함
위 flow를 통해서 같은 row에 대해서 각기 다른 2개의 수정 요청이 있었지만 1개가 업데이트 됨에 따라 version이 변경되었기 때문에 뒤의 수정 요청은 반영되지 않는다. 이렇게 낙관적락은 version과 같은 별도의 컬럼을 추가하여 충돌적인 업데이트를 막는다. version 뿐만 아니라 hashcode 또는 timestamp를 이용하기도 한다.