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

1.1 비관적 동시성제어

  • 사용자들이 같은 데이터를 동시에 수정할 것이라고 가정한다.
  • 따라서 한 사용자가 데이터를 읽는 시점에 Lock을 걸고 조회 또는 갱신처리가 완료될 때까지 이를 유지한다.
  • Locking은 첫 번째 사용자가 트랜잭션을 완료하기 전까지 다른 사용자들이 그 데이터를 수정할 수 없게 만들기 때문에
    비관적 동시성 제어를 잘못 사용하면 동시성을 저해 받게 된다.( 잘못 사용하면.. ex> 적립포인트 계산 )
    {CODE:SQL}
    SELECT 적립포인트, 방문횟수, 최근방문일시, 구매실적 FROM 고객
    WHERE 고객번호 = :CUST_NUM FOR UPDATE;

-- 새로운 적립 포인트 계산

UPDATE 고객 SET 적립포인트 = :적립포인트 WHERE 고객번호 = :CUST_NUM

{CODE}

  • SELECT문에 FOR UPDATE를 사용해서 해당 고객 레코드에 Lock을 걸어둔다면 데이터가 잘못 갱신되는 문제를 방지할 수 있다.
  • SELECT 시점에 Lock을 거는 비관적 동시성 제어는 자칫 시스템 동시성을 심각하게 떨어뜨릴 우려가 있다.(무한정 기달림)
    {CODE:SQL}

FOR UPDATE NOWAIT --> 대기없이 Exception (ORA-00054)을 던짐
FOR UPDATE WAIT 3 --> 3초 대기 후 Exception (ORA-30006)을 던짐

{CODE}

  • 무한정 기다리지 않음
  • 다른 트랜잭션에 의해 Lock이 걸렸을 때 Exception을 만나게 되므로 "다른 사용자에 의해 변경 중이므로 다시 시도하십시오"라는 메시지를 출력하면서
    트랜잭션을 종료할 수 있다. 오히려 동시성을 증가시키게 된다.

1.2 낙관적 동시성 제어

  • 사용자들이 같은 데이터를 동시에 수정하지 않을 것이라고 가정한다.
  • 따라서 데이터를 읽을 때는 Lock을 설정하지 않는다.
  • 그런데 낙관적 입장에 섰다고 해서 동시 트랜잭션에 의한 데이터의 잘못된 갱신을 신경쓰지 않도 된다는 것은 아니다.
  • 읽은 시점에 Lock을 사용하지 않았지만, 데이터를 수정하고자 하는 시점에 앞서 읽은 데이터가 다른 사용자에 의해 변경되었는지를 반드시 검사해야한다.
  • 낙관적 동시성 제어를 이용하면 Lock이 유지되는 시간이 매우 짧아져 동시성을 향상 시킨다.
  • 하지만, 다른 사용자에 의해서 변경될 수 있기 때문에 귀찮은 절차가 따른다.(현실-동시성 제어 없는 낙관적 프로그래밍)

{CODE:SQL}
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

ALTER( '다른 사용자에 의해 변경되었습니다.');

END IF;
{CODE}

  • 앞선 SELECT문에서 읽은 컬럼들이 매우 많다면 UPDATE문의 조건절을 일일이 기술하는 것이 여간 귀찮은 일이 아닐 것이다.

{CODE:SQL}
SELECT 적립포인트, 방문횟수, 최근방문일시, 구매실적, 변경일시
INTO :A, :B, :C, :D, :MOD_DT
FROM 고객
WHERE 고객번호 = :CUST_NUM;

--새로운 적립포인트 계산

--다른 트랜잭션에 의해 설정된 Lock에 때문에 동시성이 저하되는 것을 예방할 수 있다.
SELECT *
FROM 고객
WHERE 고객번호 = :CUST_NUM
AND 변경일시 = :MOD_DT
FOR UPDATE NOWAIT; -- NOWAIT

UPDATE 고객 SET 적립포인트 = :적립포인트
WHERE 고객번호 = :CUST_NUM
AND 변경일시 = :MOD_DT;

IF SQL%ROWCOUNT = 0 THEN

ALTER( '다른 사용자에 의해 변경되었습니다.');

END IF;
{CODE}

  • 최종 변경일시( TIMESTAMP )을 관리하는 컬럼이 있다면 이를 조건절에 넣어 간단히 해당 레코드의 갱신여부를 판단 할 수 있다.
  • INSERT/UPDATE/DELETE 할 때마다 변경일시 컬럼을 변경하도록 구현해야한다.

2.1 ORA_ROWSCN(10g~)

  • 위의 예제로 본다면, INSERT/UPDATE/DELETE 할 때마다 변경일시 컬럼을 변경하는 것 번거로운 일인 것이다.
  • 게다가, 애플리케이션을 통하지 않고, 사용자가 직접 값을 바꾸는 일이 생긴다면 Lost Update가 발생한다.

  • 단, 위와 같이 ora_rowscn Pseudo 컬럼을 이용하여 동시성 제어를 하려면 다음과 같은 옵션을 사용해야 한다.

  • 기본 값은 NoRowDependencies인데, 이때는 ora_rowscn 컬럼에 블록 scn이 출력됨.
    -> 따라서 기본 값을 사용할 때는 해당블록 내 모든 레코드의 ora_rowscn이 변경되는 셈이다.
  • Timestamp를 오라클이 직접 관리 해주므로 쉽고 완벽하게 동시성을 제어할 수 있다.
  • ora_rowscn은 영구히 저장되는 값이지만 이를 시간정보로 변환하는 데에는 정해진 기한이 있다는 점이다. ( 5일 )
  • SMON 프로세스는 내부적으로 SCN과 Timestamp 간 매핑 정보를 관리하며, 오라클은 이 매핑정보을 이용해 scn_to_timestamp함수를 구현했다.
  • 따라서 5일 이전에 갱신된 레코드의 rowscn을 가지고 Timestamp값을 찾으려고 하면 매핑 테이블에서 값을 찾을 수 없어 에러가 발생하게 된다.(ORA-08181)
  • 데이타 덤프를 뜨면 각 로우마다 SCN이 찍혀 있는 것을 확인할 수 있다.