[Transaction] lost update problem (isolation level, deadlock, update lock)
포인트 읽기 - 포인트 차감 순으로 DB 작업이 발생하는 상황이었다.
```java
@Transactional
public foo bar() {
// SELECT
MemberRestMileageInfo member = getMemberMileageByKey(memberKey);
...
// UPDATE
memberInfoMapper.update(member);
}
```
트랜잭션 고립 수준을 SERIALIZABLE 로 설정했을 때 발생하는 deadlock 문제
SERIALIZABLE은 SELECT로 가져온 row에 대해 read lock(타 스레드에서 read는 가능, write 시 lock이 풀릴 때 까지 pending)을 걸어준다. 이후 UPDATE를 만나면 write lock을 획득하려 시도한다.
```
T1 T2
start transaction
start transaction
SELECT<read_lock_for_mem1-1 획득>
SELECT<read_lock_for_mem1-2 획득>
UPDATE<write_L1 획득하기 위해 read_lock_for_mem1-2 해제 대기>
UPDATE<wirte_L2 획득 위해 read_lock_for_mem1-1 해제 대기. deadlock 발생>
```
http://www.gurubee.net/lecture/2396 2.가. Lock 종류 부분에 이러한 문제가 잘 나와 있다.
sol
위와 같은 문제를 해결하기 위해서는 고립 수준은 기본으로 두고, 처음에 SELECT 할 때 부터 read lock(shared lock) 대신 write lock(update lock)을 획득하면 된다.
write lock은 배타적 lock이므로, 어떤 트랜잭션이 실행 중이라면 다른 트랜잭션은 기존 트랜잭션이 lock을 release할 때 까지 pending 상태가 된다.
MySQL에서 update lock을 획득하기. FOR UPDATE
Lock이 걸리는 범위?
WHERE에서 key column(index)에 대해 거르면 해당 row에 대해서만 lock이 걸린다.
index가 걸려있다면, 선택한 row들만 lock이 걸린다.
index가 안걸려 있어 전체 테이블을 서치하는 경우 테이블 전체에 lock이 걸린다.
이런 문제를 DB단이 아니라 프로그램 단에서 lock을 걸어서 해결하는 것도 가능은 하다.
1. 아예 메서드 전체에 lock을 거는 식으로 하면, 위 상황 자체를 예방할 수 있지만... 문제는 한 순간에 한 스레드만 이 메서드를 실행할 수 있게 되므로, 성능이 엄청나게 저하된다.
2. SELECT - UPDATE 작업이 어떤 Key와 관계된다면, ( 예를 들어 회원 정보 업데이트같이 회원 키로 SELECT하고 해당 레코드를 UPDATE하는 상황) Map<Key, Lock> 을 사용해서 동일 회원에 대해서만 두 스레드가 동시에 작업하지 못하게 막을 수는 있다. 이렇게 하면 여러 회원에 대한 작업은 동시에 실행될 수 있긴 하다.
근데 이 방법도, 인메모리에 Map을 유지해야 한다는 단점이 있고... redis같은걸 쓰면 좀 낫겠지만... 회원 수가 많으면? 등등 여러모로 애매하기 때문에 DB단에서 트랜잭션으로 제어하는게 좋아보인다.
SELECT - UPDATE 로직의 무결성을 유지하자.
*** PROPAGATION 은 ISOLATION과는 달리 트랜잭션 내에서 또 다른 트랜잭션을 만나는 경우 어떻게 처리할 것인지를 의미
'RDBMS > Query design' 카테고리의 다른 글
인덱스 힌트 관련 - 쿼리 플랜 결과는 무시하고 꼭 써줘야 한다. (0) | 2023.04.25 |
---|---|
MERGE INTO(UPSERT) 에서도 PK violation이 발생할 수 있다. (0) | 2022.02.10 |
[Oracle] Pagination (0) | 2021.05.17 |
[Oracle] longest match (0) | 2021.02.16 |
일단 다 가져와서 앱에서 필터링? vs 쿼리 WHERE에서 필터링? (0) | 2019.11.28 |