DBMS

동시성 제어

JEE-JEEE 2024. 4. 17. 16:20

DBMS는 다수의 사용자가 이용할 것을 생각하고 있기 때문에, 다중 트랜잭션의 상호 간섭 작용에서 데이터베이스를 보호할 수 있어야 한다. 이것을 동시성 제어(Concurrency Control) 라고 한다.

동시성을 제어할 수 있도록 하기 위해 모든 DBMS가 공통적으로 Lock 기능을 제공하며, 이로 인해 여러 사용자가 데이터를 동시에 엑세스하는 것으로 보이더라도 내부적으로는 하나씩 실행되도록 직렬화가 진행된다. 또한, set transaction 명령어를 이용하여 트랜잭션 격리성 수준을 조정할 수 있는 기능도 제공한다.

동시성과 일관성은 트레이드 오프(Trade-off) 관계로, 동시성을 높이기 위해 Lock의 사용을 최소화할 경우 일관성 유지가 어렵고, 일관성을 높이기 위해 Lock을 적극적으로 사용하면 동시성이 저하되기 때문에, 최종적으로 동시성 제어의 목표는 동시에 실행되는 트랜잭션의 수를 최대화하면서 입력, 수정, 삭제, 검색 등의 행위 시 데이터 무결성이 유지되도록 하는 데에 있다.

 


 

 

🪡 비관적 동시성 제어

 

비관적 동시성 제어(Pessimistic Concurrency Control)에서는 사용자들이 같은 데이터를 동시에 수정할 것이라고 가정한다. 그렇기 때문에 데이터를 읽는 시점에 Lock을 걸고, 트랜잭션이 완료될 때까지 이를 유지한다.

select 시점에 Lock을 거는 비관적 동시성 제어는 자칫 시스템 동시성을 심각하게 떨어트릴 우려가 있다. 그렇기 때문에 wait 또는 nowait 옵션을 함께 사용하는 것이 바람직하다.

 

🐣 기존 SQL

SELECT 적립포인트, 방문횟수, 최근방문일시, 구매실적
FROM 고객
WHERE 고객번호 =: cust_num for update; 

UPDATE 고객
SET 적립포인드 =: 적립포인트
WHERE 고객번호 =: cust_num;

 

🐤 수정된 SQL


-- nowait : 대기 없이 Exception을 던짐
SELECT 적립포인트, 방문횟수, 최근방문일시, 구매실적
FROM 고객
WHERE 고객번호 =: cust_num for update nowait; 

UPDATE 고객
SET 적립포인드 =: 적립포인트
WHERE 고객번호 =: cust_num;


-- wate 3 : 3초 대기 후 Exception을 던짐
SELECT 적립포인트, 방문횟수, 최근방문일시, 구매실적
FROM 고객
WHERE 고객번호 =: cust_num for update wate 3; 

UPDATE 고객
SET 적립포인드 =: 적립포인트
WHERE 고객번호 =: cust_num;

 

 

 

🧶 낙관적 동시성 제어

 

낙관적 동시성 제어(Optimistic Concurrency Control)에서는 사용자들이 같은 데이터를 동시에 수정하지 않을 것이라고 가정한다.  따라서, 데이터를 읽을 때에는 Lock을 설정하지 않는다. 대신 수정 시점에 다른 사용자에 의해 값이 변경되었는지를 반드시 검사해야 한다. 

 

SELECT 적립포인트, 방문횟수, 최근방문일시, 구매시점 INTO :a, :b, :c, :d
FROM 고객
WHERE 고객번호 =: cust_num;

UPDATE 고객
SET 적립포인트 =: 적립포인트
WHERE 고객번호 := cust_num and 적립포인트 =: a 
				AND 방문횟수 =: B 
                AND 최근방문일시 =: C
                AND 구매실적 =: D
IF sql%rowcount = 0 
THEN alert('다른 사용자에 의해 변경되었습니다.');
END IF;

 

 

 

🏹  다중버전 동시성 제어

 

1) 일반적인 Locking 메커니즘의 문제점

동시성 제어의 목표는 동시에 실행되는 트랜잭션 수를 최대화하면서도 입력, 수정, 삭제, 검색 시 데이터 무결성이 유지되도록 하는 데에 있다.

그런데, 읽기 작업에 공유 Lock을 사용하는 일반적인 Locking 메커니즘에서는 읽기 작업과 쓰기 작업이 서로 방해를 일으키기 때문에 종종 동시성에 문제가 생기곤 한다. 또한, 데이터 일관성에 문제가 생기는 경우도 있어 이를 해결하기 위해서는 Lock을 더 오랫동안 유지하거나 테이블 레벨 Lock을 사용해야 하므로 동시성을 더 심각하게 떨어뜨리는 결과를 낳는다.

 

2) 다중버전 동시성 제어

읽기 작업과 쓰기 작업이 서로 방해하는 바람에 동시성을 떨어트리고, 공유 Lock을 사용함에도 불구하고 데이터 일관성이 훼손되는 문제를 해결하기 위해 Oracle은 버전 3부터 다중버전 동시성 제어(Multiversion Concurrency Control, MVCC) 메커니즘을 사용한다. MVCC 메커니즘을 간단히 요약하면 다음과 같으며, 문장수준과 트랜잭션 수준 두가지가 있다.

- 데이터를 변경할 때마다 그 변경사항을 Undo 영역에 저장해 둔다

- 데이터를 읽다가 쿼리(또는 트랜잭션) 시작 시점 이후에 변경된(변경이 진행 중이거나 이미 커밋된) 값을 발견하면, Undo 영역에 저장된 정보를 이용해 쿼리(또는 트랜잭션) 시작 시점의 일관성 있는 버전(CR Copy)을 생성하고 그것을 읽는다.

 

3) 문장수준 읽기 일관성 

문장수준 읽기 일관성(Statement-Level Read Consistency)은, 다른 트랜잭션에 의해 데이터의 추가, 변경, 삭제가 발생하더라도 단일 SQL문 내에서 일관성 있게 읽는 것을 말한다. 일관성 기준 시점은 쿼리 시작 시점이 된다.

 

4) 트랜잭션 수준 읽기

트랜잭션 수준 읽기 일관성(Transaction-Level Read Consistency)은, 다른 트랜잭션에 의해 데이터의 추가, 변경, 삭제가 발생하더라도 트랜잭션 내에서 일관성 있게 값을 읽는 것을 말한다.

기본 트랜잭션 격리성 수준(Read Committed)에서 완벽한 문장수준의 읽기 일관성을 보장하는 MVCC 메커니즘도 트랜잭션 수준의 읽기 일관성은 보장하지 않는다. 물론, 일반적인 Locking 메커니즘도 동일하다.

트랜잭션 수준으로 완벽한 읽기 일관성을 보장받기 위해서는 격리성 수준을 Serializable Read로 올려주어야 한다. 그럴 경우 일관성 기준 시점은 트랜잭션 시작 시점이 되며, 트랜잭션이 진행되는 동안 자신이 발생시킨 변경사항은 그대로 읽는다.

 

5) Snapshot too old

이 에러는 MVCC에서만 나오는 에러로, Undo 영역에 저장된 Undo 정보가 다른 트랜잭션에 의해 재사용되어 필요한 CR Copy를 생성할 수 없을 때 발생한다. 이와 같은 에러 발생 가능성을 줄이는 방법은 다음과 같다

- Undo 영역의 크기를 증가시킨다

- 불필요한 커밋 수행을 줄인다

- fetch across commit 형태의 프로그램 작성을 피해 다른 방식으로 구현한다.

- 트랜잭션이 몰리는 시간대에 오래 걸리는 쿼리가 같이 수행되지 않도록 나누어 읽고, 단계적 실행이 가능하도록 한다.

- 같은 블록을 여러 번 방문하는 Nested Loop 형태가 존재하는지 체크하고, 되도록 다른 방법으로 수행한다.

- 소트 부하를 감수하더라도 order by 등을 강제로 삽입하여 소트연산을 발생시킨다

- 대량 업데이트 후 바로 테이블 또는 인덱스를 Full Scan 하도록 쿼리를 수행한다.