4. 동시성 구현 사례

조회 수 3680 추천 수 0 2013.08.21 12:33:51
토시리 *.42.166.122

(1)일련번호 채번 동시성 높이기


채번을 이용한 동시성 제어는 다음과 같이 두 종류로 볼 수 있다. 

  1. 데이터 갱신(삽입,갱신)시에 제어용 필드값의 최대치를 구해, 1증가 시킨 값을 적용.
  2. 채번용 테이블(제어용 필드값을 관리하는 데이블)을 이용하는 방법.

이 중, 2번에 대해서 살펴보자. (1번은 별 이슈화될 것이 없다.)


아래는, 채번테이블 SEQ_TAB에서 현재 채번을 취득 1증가시켜 갱신후, 갱신된 채번을 반환하는 펑션이다.

CREATE OR REPLACE FUNCTION ORCSTUDY.SEQ_NEXTVAL(l_gubun number) return number

as 

/** pragma autonomous_transaction; **/

l_new_seq seq_tab.seq%type;

begin

update seq_tab set seq = seq + 1 where gubun = l_gubun;

select seq into l_new_seq from seq_tab;

commit;

return l_new_seq;

end;

중요한건, "commit" 을 루틴내에서 행하고 있다는 점.


다음의 함수를 잘 보자.

begin

update tab1 set x = :x;

  insert into tab2 values (SEQ_NEXTVAL(1), 'abc', 'def');                                         

loop

여러가지 처리

end loop;

commit;

exception 

when others then rollback;

raise;

end;

잘 봤는가? 정리해보면.

  1. 최초 UPDATE 로 테이블 tab1을 갱신.
  2. insert문에서 SEQ_NEXTVAL함수를 실행함에 따라, 커밋이 발생 (결정적, 구멍)
  3. 만약, ?"여러가지 처리"에서 에러가 발생하여 롤백된다면.
  4. 테이블 tab12번에 의해 이미 커밋완료상태.

즉, 데이터의 정합성을 파괴하게 된다.

이 문제의 해결은  SEQ_NEXTVAL함수의 주석부분을 해제하는 것으로 해결가능하다.


자율형 트랜잭션 선언 : pragma autonomous_transaction


현재, 트랜잭션을 불러지는 트랜잭션에서 분리시키게 되어, 자신의 트랜잭션만 커밋/롤백을 하게된다.

실제 해보니, 선언하면 

ORA-06519: アクティブな自律型トランザクションが?出され、ロ?ルバックされました 
ORA-06519: Active한 자율형트랜잭션이 검출되어, 롤백되었습니다.

   와 같은 에러가 발생했다. 

   아예, 트랜잭션 내부에서 다른 트랜잭션이 커밋을 하는것 자체를 불허용하는 것 같다. ( 좀 더 찾아봐야 할 듯)

   오타 때문에 발생한 문제였음. select 문에 세미콜론이 빠져있었음. ( 음주정리는 피하는게 ㅡ..ㅡ )



(2)선분이력 정합성 유지


선분이력이란, 이력정보등 엔티티에서 전후데이터가 시간적으로 연결되는 형태를 말한다. (청구/재무/금융관련 시스템에서 많이 발생하는 모델링형태이다.)

즉, 개시일과 종료일을 필드로 갖는 엔티티가 전후데이터의 종료일과 개시일이 계속 연결되어 삽입되는 형태이다.

데이터삽입 전에 가장최신데이터의 종료일의 갱신이 발생하므로, 갱신과 삽입되는 과정의 트랙잭션의 원자성이 보장되어야 한다.


아래 서비스사용이력에 관해서 살펴보자.

고객엔티티와 서비스엔티티는 N:M의 관계로, 서비스사용이력이 릴레이션데이블 역활을 하고 있다.


   고객TBL ---- 서비스사용이력TBL ---- 서비스TBL  


다음은 xxx고객의 yyy서비스에 대해, 새사용이력데이터를 삽입하는 과정이다.

 declare

       current_date varchar2(14);

begin

       -- 해당서비스사용이력 데이터의 종료일을 갱신. (현재사용중인 이력) 

   current_date := to_char(sysdate, 'yyyymmddhh24miss');

   update 서비스사용이력 set 종료일 = :current_date

       where 고객ID = 'xxx' and 서비스ID = 'yyy'

          and 종료일 = 삽입시의초기값; (※일반적으로 9999/12/31로 한다.)

      

       -- 새 사용이력정보를 삽입

    insert into 서비스사용이력 (고객ID, 서비스ID, 시작일, 종료일) values ('xxx', 'yyy', :current_date, '99991231235959');


 commit;

end;


  1. 현재날짜를 취득.
  2. 신규데이터를 삽입하기에 앞서, 현재 서비스사용이력 데이터의 종료일(현재날짜)을 갱신.
  3. 신규데이터(서비스사용이력)를 삽입한다. (종료일은 다음데이터가 등록될때 결정되므로, 초기값인 "날짜최대값"을 셋팅)
  4. 커밋.

여기서, 트랜잭션 TX1이 ①을 실행하는 사이, 트랜잭션 TX2가 ①~④를 실행해버린다면, 선분이력은 깨지게 된다.

어딘가에 Lock을 걸어 적어도, ②~④의 처리가 보장되어야 한다.

Lock대상 테이블은 다음과 같이 생각해볼수 있디ㅏ.

  1. 서비스사용이력TBL  : 갱신대상을 직접 락을 건다.
  2. 서비스TBL VS 고객TBL : 갱신대상의 상위테이블에 락을 건다.

먼저, 1번의 경우.

Lock에 의한 동시성 저하는 가장 낮다.

그러나, 최초 데이터(서비스사용이력이 없는 고객의 경우)를 삽입할때, Lock이 걸리지 않는 꼴이 되어 시작일이 다른 2개의 데이터가 등록되어버리게 된다.


그럼, 2번의 경우.

상위 테이블을 락을 거는것을 생각할 수 있다. 물론, 1번에 비해서 동시성은 저하된다. (전 강좌들을 참조.)

그러나,  서비스사용이력이 없는 고객의 경우도 락을 걸 수 있어 완벽한 동시성제어가 가능하다.

그럼, 상위테이블 서비스와 고객중 누구를 선택할 것인가.

?앞서 언급했듯, 고객과 서비스는 N:M관계로 고객은 복수의 서비스를 사용하고 서비스는 복수의 고객에게 사용되어진다.

만약, 서비스에 락을 걸면, 해당 서비스를 사용하는 관계없는 고객들까지 락을 걸게 되는 현상이 되어 보다 동시성이 저하된다.

그렇기에, 고객에 락을 거는것이 보다 동시성을 높일 수 있다.


②번의 직전에 다음과 같이, select문을 추가하여 락을 건다.

    select 고객ID from 고객 where 고객ID = 'xxx' FOR UPDATE NOWAIT



<2장 04절 끝>