[데이터베이스] Transaction #2 병행제어

4 분 소요

병행제어


트랜잭션의 두번째 타이틀은 병행제어(동시성제어) 이다.

병행제어는 같은 데이터에 동시에 접근하는 두개이상의 트랜잭션이 있을때 이 트랜잭션들이 모두 갱신연산을 수행한다면, 이들을 순차수행으로 만들어서 문제가 발생하지 않고 정확한 수행결과를 어떠한 경우에도 얻을수 있도록 트랜잭션을 제어해주는 행위를 말한다.

두개이상의 트랜잭션이 동일한 데이터에 쓰기연산을 할때 크게 3가지로 갱신분실,모순성,연쇄복귀 문제가 발생한다.

갱신분실

동시에 일어나는 트랜잭션 T1과 T2 두개를 앞으로 T1+T2와 같이 나타내겠다. 또한 T1->T2를 순차수행으로 나타낸다.

     T1             		T2
		 
Read(x)            
x = x+1(5+1)
			Read(x)
			x = x*2(5*2)
Write(x)
			Write(x)

위 예제에서 T1에서 write하기전에 T2가 끼어들어서 x값을 두배 증가시키면서 본래 T1에서 연산한 결과값이 T2의 결과값이 쓰여져 버리면서 T1의 수행결과가 무효화 되어버렸다.

이를 갱신분실 이라고 한다.

모순성

모순성 문제는 트랜잭션의 스케줄링은 본래 OS에의해 인터리빙(interleaving) 방식으로 병행수행 되면서 T1+T2의 결과값이 수행될때마다 다르게 나오는 문제를 말한다.

     T1             		T2
		 
Read(x)            
x = x+1(5+1)
Write(x)
			Read(x)
			x = x*2(6*2)
			Write(x)
			Read(y)
			y = y*3(10*3)
			write(y)
Read(y)
y = y+5(30+5)
write(y)																	

위의 결과값은 x는 12가되고, y는 35가 된다.

     T1             		T2
		 
Read(x)            
x = x+1(5+1)
Write(x)
Read(y)
y = y+5(10+5)
write(y)
			Read(x)
			x = x*2(6*2)
			Write(x)
			Read(y)
			y = y*3(15*3)
			write(y)

하지만 위의 결과값은 x는 12가되고, y는 45가된다. 수행될때마다 결과가 두가지로 달라지게 나오는 것이다.

연쇄복귀

연쇄복귀 문제는 T1+T2에서 T1이 수행되는 도중에 T2가 간섭하여 수행되고 commit이 되었을때, T1수행에서 장애가 발생하면 T1이 Rollback되지만 T2는 Rollback되지 않는 문제점이다.

     T1     		T2
		 
Read(x)            
x = x+1(5+1)
			Read(x)
			x = x*2(5*2)
			Write(x) <-- commit완료 => Rollback 불가
																		
Write(x)<-- 장애발생																		

위의 결과값으로 롤백이되어 x가 5가되어야 하지만 12가 되어버리는 문제점이 발생한다.

위의 갱신분실,모순성,연쇄복귀 문제들은 모두 T1+T2가 갱신연산일때, 같은데이터에 병행수행으로 접근할때 발생하는 문제이다. 이는 격리성(Isolation)을 보장하지 못하였음을 의미한다.

또한 이들은 모두 수행할때마다 결과값이 다른것이 아니라, 결과값이 항상 같도록 순차수행 되어질 필요가 있다. 이는 일관성(Consistency)을 보장하기 위함이다.

이를 지키기위한 해결책으로 Locking을 수행한다.

Transaction Scheduling


Locking은 OS가 직접 인터리빙 방식으로 스케줄링 하는 것을 DBMS가 직접 스케줄링하여 순차수행하도록 만드는 행위이다.

이를 이해하기 위해 스케줄링 방식을 먼저 살펴보자.

  • 직렬 스케줄링 : 각 트랜잭션을 순차수행 시킴 (속도 ↓, 무결성 보장 o)
  • 비직렬 스케줄링 : 각 트랜잭션을 병행수행 시킴 (속도 ↑, 무결성보장 x)
  • 직렬 가능 스케줄링 : 비직렬 스케줄링으로써 병행수행 하지만, 같은 데이터에 접근시(간섭발생시) 순차수행시킴 (속도 ↑, 무결성보장 o)

Locking은 직렬가능 스케줄링 방식으로써, 동일데이터에 접근하지 않으면 병행수행하고 동일데이터에 접근(간섭)하면 상호배재(Mutual Exclusion)시키기 위해 lock과 unlock연산을 수행한다.

  • lock : 트랜잭션이 데이터에 대한 독점권을 요청하는 연산
  • unlock : 트랜잭션이 데이터에 대한 독점권을 반환하는 연산
     T1            		T2
		 
lock(x)
Read(x)            
x = x+1(5+1)
Write(x)
unlock(x)
			lock(x)
			Read(x)
			x = x*2(6*2)
			Write(x)
			unlock(x)
			lock(y)
			Read(y)
			y = y*3(10*3)
			write(y)
			unlock(y)
lock(y)
Read(y)
y = y+5(30+5)
write(y)
unlock(y)

위의 예제에서 처럼 T1이 x에대해 lock연산을 하면 unlock연산을 할때까지 T2는 x에 접근할수없다.

이러한 lock 과 unlock 연산을 수행하여 동일데이터에 대한 독점권을 획득하고 반납하는 행위를 기본 로킹 규약 이라고 한다.

하지만 기본로킹규약을 이행한다고 해서 모순성을 제거할수는 없다.

위의 예제에서 T1+T2에는 스케줄링 방식이 두가지가 존재할수 있다. 만약, y에대해 lock을 T1이 먼저걸게되면

     T1			T2
		 
lock(x)
Read(x)            
x = x+1(5+1)
Write(x)
unlock(x)
			lock(x)
			Read(x)
			x = x*2(6*2)
			Write(x)
			unlock(x)
																		
lock(y)
Read(y)
y = y+5(10+5)
write(y)
unlock(y)
			lock(y)
			Read(y)
			y = y*3(15*3)
			write(y)
			unlock(y)

위와같은 다른 결과값이 나올수도 있다.

즉, 기본로킹규약으로는 완전한 직렬가능한 스케줄링 방식을 보장할수 없다.

그렇다면 직렬가능한 스케줄링 방식을 보장하려면 어떡해야하는가? 2PL(Phase Locking)을 지키면 된다.

2 Phase Locking


2PL은 확장단계와 축소단계로 나누어 lock과 unlock 연산을 몰아서 수행한다.

확장단계에서는 lock연산을 수행하고 unlock연산을 할수 없으며, 축소단계에서는 unlock 연산을 수행하고 lock연산을 수행할수 없다.

즉, 트랜잭션에서 모든 lock연산이 unlock연산보다 앞서야만 한다.

     T1             		T2
		 
lock(x)
Read(x)            
x = x+1(5+1)
Write(x)
lock(y)                               
--------------T1의 확장단계
unlock(x)
			lock(x)
			Read(x)
			x = x*2(6*2)
			Write(x)
					
Read(y)
y = y+5(10+5)
write(y)
unlock(y)
---------------T1의 축소단계
			lock(y)
			------------------------ T2의 확장단계
			unlock(x)
			Read(y)
			y = y*3(15*3)
			write(y)
			unlock(y)
			------------------------ T2의 축소단계

병행성을 최대화 하면서 직렬 가능한 스케줄은 보장할수 없다. 2PL을 지키면 어느정도의 병행성을 가지면서 직렬 가능한 스케줄링을 보장할 수는 있다.

Locking 단위


lock연산을 실행하는 대상의 크기는 크게는 데이터베이스 전체부터 작게는 특정테이블의 속성 하나까지도 가능하다.

Locking 단위를 작게하면 작게할수록 lock연산과 unlock연산의 사용횟수가 증가함에따라 트랜잭션의 제어는 어렵지만, 병행성이 높다.

하지만 locking단위를 크게하면 트랜잭션의 제어가 쉽지만, 병행성이 떨어진다.

즉 다중사용자의 환경에서는 locking단위가 작을수록 좋고, 단일사용자 환경에서는 locking을 크게하는게 유리하다. 이는 DBA가 상황에따라 결정한다.

하지만 lock이 걸린 데이터가 많으면 많을수록 신뢰성,무결성은 올라가겠지만 그만큼 다른트랜잭션들은 동일데이터에 갱신할때 lock이 걸린시간만큼 기다려야 하기 때문에 수행속도가 떨어진다.

로킹과 병행효율성은 반대개념이 되는 것이다.

병행 효율성


병행효율성을 높이기 위해서는 locking이 된 데이터가 적어야 한다. locking이 걸린 데이터를 기다리는 wait가 많을수록 병행효율성이 떨어지기 때문이다.

따라서 병행 효율성을 높이기 위해서 공유 lock과 전용lock의 개념이 등장한다.

  • 공유lock : 해당데이터에 read는 가능하지만 write은 못함. 또한 다른 트랜잭션도 read만 가능 => 공유+공유 = 수행가능
  • 전용lock : 해당데이터에 read와 write 모두 가능. 반면에 다른 트랜잭션은 공용이든 전용이든 모두 불가능 => 공유+전용 or 전용+전용 = 수행불가능

공유lock + 공유lock 의 조건은 동일데이터에 대해서 두개이상의 트랜잭션들이 read만 수행한다면 모두 병행수행이 가능하다는 것이다.

즉, read+read는 병행수행하고, read+write 혹은 write+write는 locking해야 한다.

사실상 DB에서는 select연산이 전체연산의 거의 70% 이상으로 사용되어지기 때문에 이를 병행수행 시키는것은 성능상의 큰 이점이 된다.

교착상태(Dead Lock)


교착상태는 흔히 운영체제에서도 나타나는 중요한 개념이다.

이는 멀티스레딩 방식과 흡사한 트랜잭션의 병행제어 에서도 발생하는데,

T1에서 x에대해 lock하고 T2에서 y에대해 lock 하였다. 동시에 T1은 y가 unlock될때까지 기다리면서, T2도 x가 unlock될때까지 기다리면 교착상태(Dead Lock)이 발생한다.

이때는 보통 oracle에서는 한쪽을 강제로 취소시켜 버려서 해결한다.

태그:

카테고리:

업데이트:

댓글남기기