락과 격리성 수준

김파란·2024년 10월 18일

RDBMS

목록 보기
2/7

참고) https://velog.velcdn.com/images/paramkim/post/b7379ec4-4ace-497a-8cce-9d44237533f9/image.png

1. 락 사용하는 이유

1). 락을 사용하지 않았을 때 발생하는 문제점

  • 레이스 컨디션: 동시에 여러 쓰레드가 공유자원에 접근했을 때 발생한다
    • 아이템의 재고를 감소하는 로직이 있을 때 한번에 여러쓰레드가 동작하게 되면 결과가 제대로 나오지 않는다
    • 아이템을 찾고 재고를 감소한뒤 저장을 해야되는데 저장되기 전에 아이템을 찾고 재고를 감소하는 로직을 호출하기 때문이다
  • 정합성 오류가 생긴다
    • 읽기와 쓰기간의 일관성이 보장되지 않는다
  • 예측 불가능한 동작
    • 동시 접근으로 인해 동작이 예측 불가능해져, 신뢰성을 읽게 된다

락 사용 주요 이유

  • 레이스 컨디션 방지
  • 데이터 정합성 유지
  • 원자성 보장
  • 비즈니스 로직 보호

2. 격리성 수준

1). Uncommitted Read

  • 커밋되지 않은 읽기
  • 커밋되지 않은 데이터에 접근할 수 있게 하는 격리수준
  • 가장 저수준의 격리수준
  • 일반적으로 사용하지 않는 격리수준이다
  • 더티읽기(Dirty Read): 커밋되지 않은 트랜잭션에 접근하여 부정합을 유발할 수 있는 데이터를 읽는 것

시나리오

  • 10번 트랜잭션이 이름을 update했는데 commit을 하지 않았다
  • 하지만 13번 트랜잭션이 접근했을 때 commit하지 않은 데이터를 읽을 수 있다
  • 여기서 10번 트랜잭션에 문제가 생겨 롤백이 된다면 13번 트랜잭션의 데이터 부정합을 발생할 수 있다

2). Committed Read

  • 커밋된 읽기
  • 다른 트랜잭션에서 커밋된 데이터로만 접근할 수 있게 하는 격리 수준
  • MySQL을 제외하고 대부분 이를 기본 격리수준으로 사용한다
  • Non Repeatable Read(반복 가능하지 않은 읽기) 현상 발생
    • 동일한 select 쿼리를 두번 실행했을 때 결과가 다른 것

시나리오

  • 10번 트랜잭션이 박기영이라는 데이터를 박경으로 update을 했지만 commit을 하지 않았다
  • 13번 트랜잭션에서 조회할 때는 박기영이라는 값이 조회된다
  • 커밋된 읽기에서는 Undo 영역이라는게 있어서 Update전 값을 조회할 수 있다
  • Undo 영역: 변경 전 데이터가 저장된 영역이고, Commit 하기 전 데이터를 읽어올 수 있는 이유
    • 트랜잭션에 대한 복구는 어떻게 하는가? 로그를 기반으로 복구를 하게 된다.

3). Repeatable Read

  • 반복 가능한 읽기
  • 커밋된 데이터만 읽을 수 있되 자신보다 낮은 트랜잭션 번호를 갖는 트랜잭션에서 커밋한 데이터만 읽을 수 있는 격리수준
    • 이것이 가능한 이유가 Undo 로그 때문이다
    • MVCC: 트랜잭션 ID를 통해 Undo 영역의 데이터를 스냅샷처럼 관리하여 동일한 데이터를 보장
  • Phantom Read(유령 읽기)
    • 하나의 트랜잭션 내에서 여러 번 실행되는 Select 쿼리에 대해 결과 레코드 수가 달라지는 현상
    • update와, delete에 대한 lock을 걸어 읽기, 쓰기 작업을 막을 수 있지만 Insert에 대한 Lock을 걸 수 없다
    • 다른 트랜잭션에서 Insert 작업이 가능하다는 뜻이고, 조회되지 않았던 레코드가 조회될 수 있다는 것이다 유령처럼

시나리오

  • 10번 트랜잭션은 10번 트랜잭션보다 작은 트랜잭션에서 커밋된 데이터만 읽을 수 있다
    • 13번 트랜잭션에서 변경한 내용은 조회할 수 없다
    • 같은 Select 쿼리를 두번 실행됐을 때 같은 결과가 조회되므로 반복 가능하지 않은 읽기 현상이 발생하지 않는다
  • 10번 트랜잭션은 13번 트랜잭션이 데이터를 커밋했더라도 읽을 수 없다

(1). 오라클은 Repeatable Read를 지원하지 않는다

  • 오라클은 Repeatable Read수준을 직접적으로 지원하지 않는다
  • 그래서 오라클은 Exclusive Lock을 사용한다

Exclusive Lock(배타적 잠금, 쓰기잠금)

  • 특정 레코드나 테이블에 대해 다른 트랜잭션에서 읽기, 쓰기 작업을 할 수 없도록 하는 Lock
    • Select for update 구문을 통해 사용할 수 있다
  • 트랜잭션이 접근 시 Lock이 풀릴 때까지 대기하게 된다

(2). MySQL에서는 Phantom Read가 발생하지 않는다

  • InnoDB 엔진을 사용하는 MySQL에서는 이러한 현상이 발생하지 않는다
  • Select ~ for update를 통해 Lock을 걸때 Next Key Lock 방식을 사용하기 때문이다

Next Key Lock

  • 조회된 레코드에 대한 Lock뿐 아니라 실행 쿼리에 대한 범위를 설정하는 Lock
    • 즉 Next Key Lock은 Record Lock, Gap Lock이 조합된 Lock이다
  • 코드에 대한 Record Lock과 0<10<=10에 해당하는 범위에 Gap Lock이 걸린다
    • 또한 조회된 레코드의 Index인 ID에 대해 그다음 존재하는 ID 까지의 범위를 Gap Lock으로 설정한다
    • 만약 Id가 2인 필드 이후에 Id가 20인 레코드가 있다면 2~20까지 Gap Lock을 건다
  • Select 쿼리를 통해 정해진 GAP에 해당하는 데이터를 Insert 시도할 경우 Gap Lock로 인해 대기 상태로 들어가게 된다

4). Serializable

  • 가장 고수준의 격리 수준
  • 트랜잭션을 무조건 순차적으로 진행시킨다
    • 트랜잭션이 끼어들 수 없으니 데이터의 부정합 문제가 발생하지 않는다
  • 트랜잭션이 중간에 끼어들 수 없는 이유는 select 쿼리 실행 시 Shared Lock을 걸기 때문이다
    • Insert, Update, Delete쿼리 실행 시 Exclusive Lock(MySql의 경우 Next key Lock)을 걸기 때문이다

Shared Lock

  • 다른 트랜잭션에서의 읽기 작업은 허용하지만, 쓰기 작업을 불가능 하도록 한다
    • Select~For Share 문법을 통해 사용하며, Lock의 경우 동시에 Exclusive Lock을 허용하지 않는다
  • Select 쿼리 실행시 Shared Lock이 걸리게 된다
    • 다른 트랜잭션에서 Update, Delete, Insert와 같은 쿼리 실행시 Exclusive Lock, Next Key Lock을 얻어오려고 할텐데 Shared Lock이 이를 허용하지 않아 대기상태가 된다
    • 이러한 원리로 인해 트랜잭션들이 중간에 끼어들 수 없고 순차적으로 되는것이다

3. Lock

1). Pessimistic Lock

  • 비관적 락
  • 실제로 데이터에 Lock을 걸어서 정합성을 맞추는 방법
    • Shared Lock 또는 Exclusive Lock을 걸고 시작한다
  • 다른 트랜잭션에서는 Lock이 해제되기 전에 데이터를 가져갈 수 없게 된다
    • 데드락이 걸릴 수 있기 때문에 주의하여 사용해야 한다
  • Repeatable Read 또는 Serializable 정도의 격리성 수준을 제공한다

시나리오

  • 트랜잭션1이 Id = 2를 업데이트 요청 이후 트랜잭션2가 Id = 2 업데이트 요청
  • 트랜잭션1이 먼저 선점했기 때문에 트랜잭션2는 끝날 때까지 대기
  • 트랜잭션1이 끝나면 트랜잭션2 시작 후 종료

장점

  • 데이터의 일관성을 보장한다

2). Optimistick Lock

  • 낙관적 락
  • 실제로 락을 이용하지 않고 별도의 컬럼을 추가하여 충돌 발생을 막는다
  • 먼저 데이터를 읽은 후에 update를 수행할 때 현재 내가 읽은 버전이 맞는지 확인하여 업데이트
    • 내가 읽은 버전에서 수정사항이 생겼을 경우에는 다시 읽은 후에 작업을 수행하는 로직 추가

장점

  • 충돌이 자주 발생하지 않을 경우 성능 저하를 최소화시킬 수 있다
  • 성능이 있고, 데드락 발생 가능성이 낮다
    • 데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 변경할 수 있기 때문이다

3). Named Lock

  • Metadata Locking
  • 이름을 가진 lock을 획득한 후 해제할때까지 다른 세션은 이 lock을 획득할 수 없도록 한다
  • 트랜잭션이 종료될 때 lock이 자동으로 해제되지 않는다
    • 별도의 명령어로 해제를 수행해주거나 선점시간이 끝나야 해제된다

4). SpinLock

  • 레디스같은 걸 사용하면서 SpinLock사용

0개의 댓글