03 비관적 vs. 낙관적 동시성 제어




(1) 비관적 동시성 제어


 동시성 제어를 위해 트랜잭션 고립화 수준을 변경하는 DBMS기능을 사용할 수 없는 경우가 있다.

이럴때는 개발자가 직접 구현을 해야한다.

비관적 동시성 제어(Pessimistic Concurrency Control)은 사용자들이 같은 데이터를 동시에 수정할 것이라고 가정할 때

한 사용자가 데이터를 읽는 시점에 Lock을 걸고 조회 또는 갱신 처리가 완료될 때가지 이를 유지한다

그러면 다른 사용자들은 이 트랜잭션이 완료되기 전까지는 해당 데이터를 수정할 수 없기에 비관적 동시성 제어로 인하여 동시성 제어를 받게 된다.


비관적 동시성 제어 이용시 활용법

◆ select문에 for update를 사용해서 해당 고객 레코드에 Lock을 걸어 둔다면 데이터가 잘못 갱신되는 문제를 방지한다.

◆ wait 또는 nowait옵션을 함께 사용하면 Lock을 얻기위해 무한정 기다리지 않아도 된다.

◆ wait 또는 nowait옵션을 이용하면 다른 트랜잭션에 의해 Lock이 걸렸을 때 Exception을 만나게 되므로 alert를 출력하고 트랜잭션을 종료할 수 있다.

◆ Select for update skip locked라는 기존 Locking 범위를 skip하는 옵션도  9i부터 지원되는 undocumented된 방법이며 과거부터 AQ(Advanced Queuing)기능을 위해 존재 했던 기능이기도 하다.
 

 

위의 방법들은 오히려 동시성을 증가시킬 수 있는 방법이다.

 

{*}for update nowait{*}

\-> 대기없이 Exception(ORA-00054)을 던짐

ORA-00054, 00000, "resource busy and acquire with NOWAIT specified"
// \*Cause: Resource interested is busy.
// \*Action: Retry if necessary.

{*}for update wait 3{*}

\-> 3초 대기후 Exception(ORA-3006)을 던짐

ORA-30006: resource busy; acquire with WAIT timeout expired


참고

Version 필드를 통한 체크

변경하는 테이블에 변경시간을 기록하는 컬럼을 추가 하여

Select for update로 레코드 락을 걸지 않고 변경 컬럼 값만을 체크해 해당 컬럼이 변경 되었다면 update 되지 않도록 수행하는 처리 절차를 구사하여

명시적인 락을 걸지 않고도 정합성을 유지할 수 있다.

 





(2)낙관적 동시성 제어



낙관적 동시성 제어는 (Optimistic Concurrency Control ) 는 사용자들이 같은 데이터를 동시에 수정 하지 않을 것이라고 가정한다.

읽는 시점에는 Lock을 사용하지 않지만 데이터를 수정하고자 하는 시점에 앞서 읽은 데이터가 다른 사용자에 의한 변경여부를 반드시 확인해야 한다.

낙관적 동시성 제어를 사용하면 Lock이 유지되는 시간이 매우 짧아져서 동시성을 높이는데 유리하다.

낙관적 동시성 제어에도 update전에 for update nowait 문을 select절과 함께 한번 더 수행하면 다른 트랜잭션에 의해 설정된 Lock에 의한 동시성 저하를 예방할 수 있다.



select 고객번호 from 고객 where 고객번호 = :cust_num and 변경일시 = :mod_dt for update nowait;


오라클 10g부터 제공되는 Pseudo 컬럼 ora_rowscn을 활용하면 Timestamp를 오라클이 직접 관리해 주므로 쉽고 완벽하게 동시성 제어가 가능하다.

 SQL> select e.empno, e.ename
  2  , ORA_ROWSCN
  3  , SCN_TO_TIMESTAMP(ORA_ROWSCN)
  4  from emp e;
 
     EMPNO ENAME      ORA_ROWSCN SCN_TO_TIMESTAMP(ORA_ROWSCN)
\-\--\--\--\--\- --\--\-\--\--\- --\--\-\--\--\- --\--\








-\-
      7369 SMITH          529426 05/08/30 15:05:26.000000000
      7499 ALLEN          529426 05/08/30 15:05:26.000000000
      7521 WARD           529426 05/08/30 15:05:26.000000000
      7566 JONES          529426 05/08/30 15:05:26.000000000
      7654 MARTIN         529426 05/08/30 15:05:26.000000000
      7698 BLAKE          529426 05/08/30 15:05:26.000000000
      7782 CLARK          529426 05/08/30 15:05:26.000000000
      7788 SCOTT          529426 05/08/30 15:05:26.000000000
      7839 KING           529426 05/08/30 15:05:26.000000000
      7844 TURNER         529426 05/08/30 15:05:26.000000000
      7876 ADAMS          529426 05/08/30 15:05:26.000000000
      7900 JAMES          529426 05/08/30 15:05:26.000000000
      7902 FORD           529426 05/08/30 15:05:26.000000000
      7934 MILLER         529426 05/08/30 15:05:26.000000000

ora_rowscn Pseudo 컬럼을 이용하면 특정 레코드가 변경 후 커밋된 시점을 추적할 수 있다.

단 이 값을 이용해 정확한 동시성을 제어하려면 테이블을 생성할 때 아래와 같은 옵션을 사용해야 한다.

아래와 같은 옵션을 사용해야 로우 단위로 SCN을 기록하기 때문이다.



create table t ROWDEPENDENCIES nologging as select * from scott.emp;


 
기본값은 NoRowDependencies 이며 이는 블록 단위로 기록을 한다.

이는 기본값으로 설정 되어 있을때는 한블록 내에서 레코드 하나만 변경하고 커밋하더라도 블록 내 모든 레코드의 ora_rowscn이 변경된다

\* ora_rowscn은 영구히 저장되는 값이지만 이는 시간정보로 변환하는 데는 정해진 기한이 있다.

SMON 프로세스는 내부적으로 SCN과 Timestamp간 매핑정보를 관리, 오라클은 이 매핑 정보(sys.smon_scn_time테이블)를 이용 scn_to_timestamp 함수를 구현했다.

그러나 이 매핑 테이블의 보관 주기는 5일 이다. 이 시간이 지나면 매핑값을 찾을 수 없기에 오류(ORA-08181)를 발생한다.

 ORA-08181  Invalid system change number specified.

단순히 동시성 제어를 위해서만 사용하면 ora_rowscn을 활용하는 것이 효과적이다.




그러나 변경 일시 정보를 다른 용도로 사용할 것이라면 기존 방식대로 구현해야 한다. 







위 사용된 Image는 마소7월호에서 발췌하였습니다.