목차

1. Consistent 읽기와 Current 읽기

2. 재시작 메커니즘의 구현

3. 재시작 메커니즘의 중요성

1. Consistent 읽기와 Current 읽기

1-1) Consistent 읽기와 Current 읽기 차이 확인

< 수행 SQL>


-- 1. 데이터 생성
DROP TABLE T;

CREATE TABLE T(X INT);

INSERT INTO T VALUES (1);

BEGIN
    FOR i IN 1..10000 LOOP
        UPDATE T
        SET    X = X + 1;
        
        COMMIT;
    END LOOP;
END;
/

-- 2. 트레이스 활성화
EXEC DBMS_MONITOR.SESSION_TRACE_ENABLE

PL/SQL procedure successfully completed.
;

-- 3. SQL 수행
SELECT *
FROM   T 
;

         X       
----------       
     10001       

1 row selected.
;

UPDATE T T1
SET    X = X + 1
;

1 row updated.
;

UPDATE T T2
SET    X = X + 1
;

1 row updated.
;

< Trace 내용>


SELECT *
FROM   T

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0         15          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0         16          0           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 44  

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS FULL T (cr=15 pr=0 pw=0 time=149 us)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       2        0.00          0.00
  SQL*Net message from client                     2        0.02          0.02
********************************************************************************
UPDATE T T1
SET    X = X + 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.00          0          1          0           0
Execute      1      0.00       0.00          2         15          3           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.01       0.00          2         16          3           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 44  

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE  T (cr=15 pr=2 pw=0 time=1648 us)
      1   TABLE ACCESS FULL T (cr=15 pr=0 pw=0 time=165 us)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                         2        0.00          0.00
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00
********************************************************************************
UPDATE T T2
SET    X = X + 1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute      1      0.00       0.00          0         15          1           1
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      0.00       0.00          0         16          1           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 44  

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  UPDATE  T (cr=15 pr=0 pw=0 time=407 us)
      1   TABLE ACCESS FULL T (cr=15 pr=0 pw=0 time=167 us)


Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  SQL*Net message to client                       1        0.00          0.00
  SQL*Net message from client                     1        0.00          0.00

  • 위의 결과를 단계별로 분석하면 아래와 같다.
    • SELECT 시에는 15개의 query(consistent get)이 일어남.
    • 첫 번째 UPDATE에서는, query(UPDATE의 검색 부분으로서, 문장이 시작된 시점에 테이블이 존재했던 로우를 갖기 위한 작업) 뿐만 아니라,
      3개의 current가 일어남. 첫 번째는 현재 시점에 존재하는 상태 그대로 테이블 블록을 읽을 때, 두 번째는 트랜잭션 시작을 위한 언두 세그먼트 블록을 읽을 때,
      마지막 세 번째는, 언두 블록을 읽을 때 나타남.
    • 두 번째 UPDATE에서는, current가 1개만 나타나는데 이는 언두와 관련된 작업(2~3번째)을 할 필요가 없으므로, UPDATE 할 로우가 담긴 블록에 대해서만 current get이 일어남.

1-2) 일관성 읽기를 위해 오라클이 처리하는 방식

  • 일관성 읽기를 위해 오라클은 'READ COMMITTED' 모드를 사용함.
  • 아래는 이 모드로 수행 시 나타나는 Case를 설명함

<UPDATE 진행 순서>

시간Session_1Session_2설명
T1UPDATE T
SET Y = 10
WHERE Y = 5;
조건에 만족하는 한 로우를 수정한다.
T2UPDATE T
SET X = X + 1
WHERE Y = 5;
Consistent 읽기를 이용해 세션 1이 변경한 레코드를 찾을 수 있지만, 락이 걸렸으므로 update를 진행할 수 없다.
세션 2는 블로킹 상태로 락이 올라오길 기다린다.
T3COMMIT;세션 2는 블로킹 상태에서 해제된다.
드디어 세션 1이 UPDATE 대상 로우를 포함하는 블록을 CURRENT 모드로 읽을 수 있게 됐다.
UPDATE를 시작할 때 Y 값은 5 였지만, 지금 CURRENT 모드로 읽을 때 Y 값은 5가 아닌 10이다.

<테스트>


-- 1. (Session_1) 데이터 생성
DROP TABLE T;

CREATE TABLE T AS
SELECT LEVEL X, LEVEL Y
FROM   DUAL
CONNECT BY LEVEL <= 10
;

-- 2. (Session_1) UPDATE T
UPDATE T
SET    Y = 10
WHERE  Y = 5
;

1 row updated.
;

-- 3. (Session_2) Session_1에서 UPDATE 하는 같은 레코드를 수행
UPDATE T
SET    X = X + 1
WHERE  Y = 5
;

-- 4. (Session_3) Lock 모니터링
@LOCK
;

(Node)H-Sid (Node)W-Sid Lock Status  W-Time Lock Type H L-Mode R L-Mode LOCKED_OBJ                       SID/SER#     S TR/w/b MODULE                  PGM      W_T WAIT_EVENT                  LCET SQL                         
----------- ----------- ----------- ------- --------- -------- -------- -------------------------------- ------------ - ------ ----------------------- ---- ------- ------------------------- ------ --------------------------- 
(1)9640     ▽          holding          72 TX        Ex       None     UNIQUE_GEORASTERS(I)             9640,93      I DI/F/F SQL*Plus                sqlp      72 SQL*Net message from clie     72                             
            (1)9765     waiting          48 TX        None     Ex       T(T)                             9765,45      A DI/F/F SQL*Plus                sqlp      48 enq: TX - row lock conten     48 UPDATE T SET    X =         
;                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               

-- 5. (Session_1) COMMIT
COMMIT;

Commit complete.
;

-- 6. (Session_2) 상태
0 rows updated.
;

  • 오라클은, UPDATE 시 current 블록이 변경되었다면, 아무런 경고 메세지도 없이 조용히 롤백하고 UPDATE를 재 시작함.
    (참고로, SERIALIZABLE 모드에서는 "ORA-08177(can't serialize access for this transaction)" 오류를 만나게 됨)

2. 재시작 메커니즘의 구현

1) 재시작 관련 테스트


-- 1. (Session_1) 데이터 생성
DROP TABLE T;

CREATE TABLE T (X INT, Y INT)
;

INSERT INTO T VALUES (1, 1)
;

COMMIT
;

-- 2. (Session_1) 트리거 생성
CREATE OR REPLACE TRIGGER T_BUTER
BEFORE UPDATE ON T FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE ('OLD.X = ' || :OLD.X || ', OLD.Y = ' ||  :OLD.Y ) ;
    DBMS_OUTPUT.PUT_LINE ('NEW.X = ' || :NEW.X || ', NEW.Y = ' ||  :NEW.Y ) ;
END;
/

Trigger created.
;

-- 3. (Session_1) 데이터 수정
SET serveroutput ON;

UPDATE T 
SET    X = X + 1
;

OLD.X = 1, OLD.Y = 1  
NEW.X = 2, NEW.Y = 1  

1 row updated.
;

-- 4. (Session_2) 데이터 수정
SET serveroutput ON;

UPDATE T 
SET    X = X + 1
WHERE  X > 0
;

-- 5. (Session_1) COMMIT
COMMIT;

Commit complete.
;

-- 6. (Session_2) 확인
OLD.X = 1, OLD.Y = 1   
NEW.X = 2, NEW.Y = 1   
OLD.X = 2, OLD.Y = 1   
NEW.X = 3, NEW.Y = 1   

1 row updated.
;

  • 위에서 본 것처럼, 로우 트리거는 두 가지 버전을 보여주는데, 한 번은 원래버전(X=1)에서 작동하고, 다른 한 번은 실제로 UPDATE시(X=2)에서 작동함.
  • 이렇게 두 번 이루어지는 이유는, 'BEFORE UPDATE ON T FOR EACH ROW' 트리거이므로, 오라클은 그 레코드의 current모드로 그 블록을 액세스 한 후
    트리거가 consistent모드의 값과 틀리므로 다시 UPDATE를 재 수행하게 되고 이로 인해 트리거가 2번이 수행됨.
  • 즉, 트리거가 :NEW값을 변경할 수 있기 대문에, 오라클은 트리거가 실행을 마칠 때까지 블록을 변경할 수 없게됨.
  • 하지만 트리거 내에 :OLD, :NEW 값을 확인하지 않게 되면 한 번만 수행을 함.

-- 1. (Session_1) 데이터 생성
DROP TABLE T;

CREATE TABLE T (X INT, Y INT)
;

INSERT INTO T VALUES (1, 1)
;

COMMIT
;

-- 2. (Session_1) 트리거 생성
CREATE OR REPLACE TRIGGER T_BUTER
BEFORE UPDATE ON T FOR EACH ROW
BEGIN
    DBMS_OUTPUT.PUT_LINE ('fired') ;
END;
/

Trigger created.
;

-- 3. (Session_1) 데이터 수정
SET serveroutput ON;

UPDATE T 
SET    X = X + 1
;

OLD.X = 1, OLD.Y = 1  
NEW.X = 2, NEW.Y = 1  

1 row updated.
;

-- 4. (Session_2) 데이터 수정
SET serveroutput ON;

UPDATE T 
SET    X = X + 1
WHERE  Y > 0
;

-- 5. (Session_1) COMMIT
COMMIT;

Commit complete.
;

-- 6. (Session_2) 확인
fired             

1 row updated.
;

  • 위의 결과처럼, 결국 트리거에서 :OLD, :NEW 컬럼값을 참조했는지가 UPDATE를 재 시작할지의 여부를 체크하는 기준이 되며,
    UPDATE는 이전처럼 두 번이 수행되었지만(한 번 재수행), 보여지는것은 한 번만 수행된 것으로 나왔음.

3. 재시작 메커니즘의 중요성

3-1) 첫 번째 위험사항

  • 재시작 메커니즘에서 중요한것은, 읽기일관성을 위해 트리거가 두 번 작동했다는 것임.
  • 이로 인해, 만약 트랜잭션 성격이 아닌 무언가를 하는 트리거가 있다면, 이것은 매우 중요한 이슈가 될 수 있으며 그 예는 아래와 같다.

1. 트리거에서 처리한 로우 수를 저장하는 PL/SQL 글로벌 변수를 관리하고자 할 경우,
재시작 메커니즘이 작동해 문장을 롤백하더라도, PL/SQL 변수의 값은 변경되지 않음.

2. UTL_(UTL_FILE, UTL_HTTP, UTL_SMTP 등)로 시작하는 어떤 함수로 재시작 메커니즘이 작동하는 순간, 사실상 어떤 문제에 노출될 수밖에 없다고 생각해야 함.
예를들어 문장을 재시작했다고 해서, UTL_FILE이 파일에 스고 있던 내용을 되돌리지는 않음.

3. 자율(autonomous) 트랜잭션으로 선언된 트리거는 일단 의심해 봐야 함. 문장을 재시작하고 롤백하더라도 자율 트랜잭션은 롤백처리되지 않음.

3-2) 두 번째 위험사항

  • 성능이 저하됨. 만약 큰 배치성 UPDATE를 시작해서 100,000개의 레코드를 처리했는데 재 시작해야 한다면, 성능이 좋을리 없음.
    (100,000개의 로우에 대한 변경사항을 롤백한 후, SELECT FOR UPDATE 모드로 재시작해서 100,000개의 로우를 다시 변경해야 함.)

문서에 대하여