오라클 성능 고도화 원리와 해법 I (2012년)
동시성 구현 사례 0 0 99,999+

by 구루비스터디 Concurrency 트랜잭션 [2018.03.20]


  1. 동시성 구현 사례
    1. 일련번호 채번 동시성 높이기
    2. 선분이력 정합성 유지


동시성 구현 사례

  • 일련번호 채번, 선분이력 등의 동시성 구현 사례를 살펴보고 완벽하게 이해한다


일련번호 채번 동시성 높이기

  • DBMS가 제공하는 Sequence 기능을 사용하는 것이 가장 좋으나, 이를 사용할 수 없는 경우 별도 구현해야 함


(1) 데이터가 삽입되는 시점에 실시간으로 현재의 MAX 값을 취해 1만큼 증가시킨 값을 이용하는 방식
  • 두 개의 트랜젝션이 동시에 같은 값을 읽었을 경우 insert 하려는 순간 PK 제약 위배됨
  • Exception 처리를 통해 동시성 제어


(2) MAX 값을 관리하는 별도의 채번 테이블에서 값을 가져오는 방식
  • 채번 테이블 생성 및 채번 함수 정의

create table seq_tab (
  gubun varchar2(1),
  seq   number,
  constraint pk_seq_tab primary key(gubun, seq)
)
organization index;

create or replace function 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
   where gubun = l_gubun;

  commit;
  return l_new_seq;
end;
/

  • 앞서 정의한 테이블 및 함수를 사용한 트랜젝션 예시

begin
  update tab1
     set col1 = :x
   where col2 = :y ;

  insert into tab2
  values (seq_nextval(123), :x, :y, :z);

  loop
    -- do anything ...
  end loop;

  commit;

exception
  when others then
  rollback;
end;
/

  • pragma autonomous_transaction 옵션을 사용하지 않은 경우는?
    • 메인 트랜젝션의 insert 구문 이후에 롤백이 되는 경우 앞의 update문까지 이미 커밋된 상태로 되어 데이터 일관성이 깨짐
    • DB2 9.7 버전부터 지원
    • SQL Server에서는 지원하지 않음. Savepoint 활용 권고.
  • seq_nextval 함수에서 커밋을 안하는 경우는?
    • 메인 트랜젝션이 종료될 때까지 채번 테이블에 Lock이 걸린 상태가 되어 성능저하 초래


선분이력 정합성 유지

  • 선분이력을 추가하고 갱신할 때 발생할 수 있는 동시성 이슈를 해결한 사례
  • 선분이력모델은 여러 장점이 있지만 잘못하면 데이터 정합성이 쉽게 깨질 수 있는 단점 존재
  • 정확히 핸들링하는 방법을 알아야 한다


  • 기본 최종 선분이력을 끊고 새로운 이력 레코드를 추가하는 전형적인 처리 루틴

declare
  cur_dt varchar2(14);
begin
  select 고객ID
    from 부가서비스이력
   where 고객ID = 1
     and 종료일시 = to_date('99991231235959', 'yyyymmddhh24miss')
     for update nowait ;

  select 고객ID
    from 고객
   where 고객ID = 1
     for update nowait ;

  cur_dt := to_char(sysdate, 'yyyymmddhh24miss') ; -- ①

  update 부가서비스이력 -- ②
     set 종료일시 = to_date(:cur_dt, 'yyyymmddhh24miss') - 1/24/60/60
   where 고객ID = 1
     and 부가서비스ID = 'A'
     and 종료일시 = to_date('99991231235959', 'yyyymmddhh24miss') ;

  insert into 부가서비스이력(고객ID, 부가서비스ID, 시작일시, 종료일시) -- ③
  values (1, 'A', to_date(:cur_dt, 'yyyymmddhh24miss'),
         to_date('99991231235959', 'yyyymmddhh24miss')) ;

  commit; -- ④
end;

  • 신규등록 건이면 ②번 update문에서 실패하고 ③번에서 한 건이 insert 됨
  • select for update문이 없다면?
    • 첫 번째 트랜젝션이 ①을 수행하고 ②로 진입하기 직전에 두 번째 트랜젝션이 동일 이력에 대해 ①~④를 먼저 진행해 버린다면 선분이력이 깨짐
    • 트랜젝션이 순차적으로 진행할 수 있도록 직렬화 장치 필요 : select for update문을 이용해 해당 레코드에 Lock을 설정
  • 부가서비스이력 테이블에만 select for update로 Lock을 거는 경우?
    • 기존에 부가서비스이력이 전혀 없던 고객인 경우 Lock이 걸리지 않음
    • 동시에 두 개 트랜젝션이 ③번 insert문으로 진입하여 시작일시는 다르면서 종료일시는 같은 두 개의 이력 레코드 생성
    • 상위엔티티인 고객 테이블에 select for update로 Lock을 걸어 완벽하게 동시성 제어
    • 다른 상위엔티티인 부가서비스 테이블에 Lock을 걸 수도 있지만, 여러 사용자가 동시에 접근할 가능성이 크기 때문에 동시성이 나빠질 수 있으므로 고객 테이블에 Lock 설정
코어 오라클 데이터베이스 스터디 모임 에서 2012년에 오라클 성능 고도화 원리와 해법 I 도서를 스터디하면서 정리한 내용 입니다.

- 강좌 URL : http://www.gurubee.net/lecture/3080

- 구루비 강좌는 개인의 학습용으로만 사용 할 수 있으며, 다른 웹 페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^

- 구루비 강좌는 서비스 제공을 위한 목적이나, 학원 홍보, 수익을 얻기 위한 용도로 사용 할 수 없습니다.

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입