📌 격리수준
Read Phenomena를 해결할 수 있는 여러가지 방법
LV1: Read-Uncommitted |
|
LV2: Read-Commited |
|
LV3: Repeatable-Read |
Repeatable-Read 의 문제는?
|
LV4: Serializable |
|
동시에 실행하는 것을 막을수록 (격리수준의 레벨이 높아질수록) 성능은 떨어진다
반대로, 격리수준의 레벨이 낮아질수록 데이터의 정합성이 낮아지니 잘 고려해서 선택해야 한다.
📌 Locking
💡 낙관적락 (Optimistic Lock)
- 내가 먼저 이 값을 수정했다고 명시하여 다른 사람이 동일한 조건으로 값을 수정할 수 없게 하는 것
- 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를 이용하기도 합니다.
낙관적 락은 version 등의 구분 컬럼을 이용해서 충돌을 예방합니다.
💡 비관적락 (Pessimistic Lock)
- 트랜잭션이 시작될 때 Shared Lock 또는 Exclusive Lock을 걸고 시작하는 방법
✅ 비관적락 종류
- 공유락(shared lock - select for share)
- Update에 대해서 Lock
- 두 다른 트랜잭션이 동시에 Read하는 것은 가능
- Lost Update 문제 가능성 존재
- ex) 상품주문의 최종 수량이 1개 -> transaction에 read && update가 있을때 -> 내 tran에서 1 read -> 타 트랜잭션이 1 read -> 내 tran에서 0으로 update -> 타 tran에서 0으로 update -> 최종 수량에 오류 발생
- 배타락(exclusive lock - select for update)
- Update에 대해서 Lock, Read에도 Lock을 걸어 Lost Update 문제 해결
- 해당 Row에 Lock을 걸어서 Update, Read모두 불가하게 만든다
- Lost Update 문제 : 한 트랜잭션에서 데이터를 변경한 뒤 아직 커밋을 하지 않은 상태에서 읽어 들일 때, 다른 트랜잭션으로 인해 내가 작성한 변경사항이 덮어씌워지는 현상
- 트래픽이 많지 않아, 동시성 이슈가 없다면 낙관적인 Lock 아니면 비관적인 Lock 사용
Shared Lock을 걸게 되면 write를 하기위해서는 Exclucive Lock을 얻어야하는데 Shared Lock이 다른 트랜잭션에 의해서 걸려 있으면 해당 Lock을 얻지 못해서 업데이트를 할 수 없습니다. 수정을 하기 위해서는 해당 트랜잭션을 제외한 모든 트랜잭션이 종료(commit) 되어야합니다.
위의 도식도를 보면서 비관적 락에 대해서 제대로 이해해보도록 합니다.
- Transaction_1 에서 table의 Id 2번을 읽음 ( name = Karol )
- Transaction_2 에서 table의 Id 2번을 읽음 ( name = Karol )
- Transaction_2 에서 table의 Id 2번의 name을 Karol2로 변경 요청 ( name = Karol )
- 하지만 Transaction 1에서 이미 shared Lock을 잡고 있기 때문에 Blocking
- Transaction_1 에서 트랜잭션 해제 (commit)
- Blocking 되어있었던 Transaction_2의 update 요청 정상 처리
이렇듯 Transaction을 이용하여 충돌을 예방하는 것이 바로 비관적 락(Pessimistic Lock)입니다.
✅ Repeatable Read 문제
Read -> [ 1️⃣ Update, Read ] -> Update -> [ 2️⃣ Update ] -> Read
빨간색 : 내 트랜잭션
파란색 : 다른 트랜잭션
공유락 => 수정에 대해서 Lock
내가 Update하기 전에 다른트랜잭션이 1️⃣ 번 위치에서 Update할 때 못하게 막는 것. Read는 허용
-> 내 Update가 끝나고 2번 위치에서는 Update가능 -> 근본적 문제 해결 X
배타적락 => 수정, 읽기 모두 Lock
데이터는 안전해지지만 성능이 떨어짐
내 트랜잭션이 동작하는 동안 다른 트랜잭션 동작X -> 근본적 문제 해결
✅ Lock을 걸면 발생하는 문제
- DeadLock = 교착상태
- 보통 배타적락에서 발생함
- mairaDB는 교착상태가 지속되면 둘 다 ROLLBACK하는 전략 사용
- author -> post 순서로만 접근하는 순방향 설계를 해야 한다. (post -> author까지 가능하면 데드락 이슈 발생하니까)
✅ 실무적 해결책
위와 같은 동시성 이슈는 일반적이지는 않은 상황이지만,
쇼핑몰이벤트 또는 예매 시스템에서는 빈번하게 발생할수 있는 가능성 존재
- Spring에서의 전략
- optimistic lock
- 버전정보 활용하여 update시에 정합성 체크
- update item set stock_count = new_count where id=1 and version = 1; 만일, version이 맞지 않다면 0rows affected
- pessimistic lock
- 공유락
- lock - PESSIMISTIC_READ
- 배타락
- lock - PESSIMISTIC_WRITE
- 특정행에 대해 lock을 걸어 read조차 막음으로서 update시에 발생하는 이슈 원천 차단.
- Serializable수준의 격리를 특정테이블과 특정데이터에 적용가능
- 공유락
- optimistic lock
- queue사용
- 이벤트, 예매 상황에서 고려될수 있는 아키텍처
- 동시성에 대한 효율을 감수해야 하지만, 시스템의 정확도와 사용자경험의 향상을 꾀할수 있음
- Redis 사용
- 싱글스레드 기반 key-value 시스템
(+) 교착상태 정리하기
- references
https://seunghyunson.tistory.com/12
https://sabarada.tistory.com/175
'Back-End 공부 > Database' 카테고리의 다른 글
[Database] 사용자 관리, 사용자 권한부여(GRANT, REVOKE) (0) | 2023.11.22 |
---|---|
[Database] DB 동시성 이슈(Dirty Read, Non-Repeatable Read, Phantom Read) (2) | 2023.11.22 |
[Database] GROUP BY절, HAVING절 (1) | 2023.11.22 |
[Database] INDEX와 VIEW 생성, 조회, 삭제 방법, 사용자 관리 방법 (0) | 2023.11.22 |
[Database] 테이블 JOIN 종류와 사용 방법 (1) | 2023.11.21 |