시간 | Session_1 | Session_2 |
---|---|---|
09:00 | 로컬 메모리에서 한 로우 데이터를 쿼리하고, 그것을 User_1에게 보여줌. | |
09:10 | Session_1과 같은 로우를 검색하고, 그것으로 User_2에게 보여줌. | |
09:20 | User_1은 어플리케이션을 사용하여, 해당 로우를 변경한 후 데이터베이스를 수정하고 커밋함. | |
09:30 | User_2 또한 해당 로우를 변경하고, 데이터베이스를 수정하고 커밋함. |
-- 0. 테스트 테이블 생성
DROP TABLE XSOFT_T_1;
CREATE TABLE XSOFT_T_1 (CNT NUMBER, VAL VARCHAR2(1000));
INSERT INTO XSOFT_T_1
SELECT ROWNUM CNT,
CHR(CNT) VAL
FROM (SELECT LEVEL CNT
FROM DUAL
CONNECT BY LEVEL <= 100) T
WHERE CNT BETWEEN 65 AND 74
ORDER BY 1
;
COMMIT;
SELECT *
FROM XSOFT_T_1
;
CNT VAL
---------- ------
1 A
2 B
3 C
4 D
5 E
6 F
7 G
8 H
9 I
10 J
;
-- 1. Session_1 에서 10번째 데이터 수정하기 위해 현재 데이터 확인
var v_val VARCHAR2(1000)
column val format a20
BEGIN
SELECT VAL
INTO :v_val
FROM XSOFT_T_1
WHERE CNT = 10;
END;
/
SELECT :v_val VAL
FROM DUAL
;
VAL
--------------------
J
;
-- 2. Session_2 에서도 10번째 데이터 수정하기 위해 현재 데이터 확인
var p_val VARCHAR2(1000)
column val format a20
BEGIN
SELECT VAL
INTO :v_val
FROM XSOFT_T_1
WHERE CNT = 10;
END;
/
SELECT :v_val VAL
FROM DUAL
;
VAL
--------------------
J
;
-- 3. Session_1 에서 10번째 데이터 수정
UPDATE XSOFT_T_1
SET VAL = 'SESSION_1_J'
WHERE CNT = 10
;
COMMIT;
SELECT VAL
FROM XSOFT_T_1
WHERE CNT = 10
;
VAL
--------------------
SESSION_1_J
;
-- 4. Session_2 에서도 10번째 데이터 수정
UPDATE XSOFT_T_1
SET VAL = 'SESSION_2_J'
WHERE CNT = 10
;
COMMIT;
SELECT VAL
FROM XSOFT_T_1
WHERE CNT = 10
;
VAL
--------------------
SESSION_2_J
;
-- 1. 테스트 테이블 생성
DROP TABLE EMP;
CREATE TABLE EMP
(EMPNO NUMBER(4) NOT NULL,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7, 2),
COMM NUMBER(7, 2),
DEPTNO NUMBER(2));
INSERT INTO EMP VALUES
(7369, 'SMITH', 'CLERK', 7902,
TO_DATE('17-DEC-1980', 'DD-MON-YYYY'), 800, NULL, 20);
INSERT INTO EMP VALUES
(7499, 'ALLEN', 'SALESMAN', 7698,
TO_DATE('20-FEB-1981', 'DD-MON-YYYY'), 1600, 300, 30);
INSERT INTO EMP VALUES
(7521, 'WARD', 'SALESMAN', 7698,
TO_DATE('22-FEB-1981', 'DD-MON-YYYY'), 1250, 500, 30);
INSERT INTO EMP VALUES
(7566, 'JONES', 'MANAGER', 7839,
TO_DATE('2-APR-1981', 'DD-MON-YYYY'), 2975, NULL, 20);
INSERT INTO EMP VALUES
(7654, 'MARTIN', 'SALESMAN', 7698,
TO_DATE('28-SEP-1981', 'DD-MON-YYYY'), 1250, 1400, 30);
INSERT INTO EMP VALUES
(7698, 'BLAKE', 'MANAGER', 7839,
TO_DATE('1-MAY-1981', 'DD-MON-YYYY'), 2850, NULL, 30);
INSERT INTO EMP VALUES
(7782, 'CLARK', 'MANAGER', 7839,
TO_DATE('9-JUN-1981', 'DD-MON-YYYY'), 2450, NULL, 10);
INSERT INTO EMP VALUES
(7788, 'SCOTT', 'ANALYST', 7566,
TO_DATE('09-DEC-1982', 'DD-MON-YYYY'), 3000, NULL, 20);
INSERT INTO EMP VALUES
(7839, 'KING', 'PRESIDENT', NULL,
TO_DATE('17-NOV-1981', 'DD-MON-YYYY'), 5000, NULL, 10);
INSERT INTO EMP VALUES
(7844, 'TURNER', 'SALESMAN', 7698,
TO_DATE('8-SEP-1981', 'DD-MON-YYYY'), 1500, 0, 30);
INSERT INTO EMP VALUES
(7876, 'ADAMS', 'CLERK', 7788,
TO_DATE('12-JAN-1983', 'DD-MON-YYYY'), 1100, NULL, 20);
INSERT INTO EMP VALUES
(7900, 'JAMES', 'CLERK', 7698,
TO_DATE('3-DEC-1981', 'DD-MON-YYYY'), 950, NULL, 30);
INSERT INTO EMP VALUES
(7902, 'FORD', 'ANALYST', 7566,
TO_DATE('3-DEC-1981', 'DD-MON-YYYY'), 3000, NULL, 20);
INSERT INTO EMP VALUES
(7934, 'MILLER', 'CLERK', 7782,
TO_DATE('23-JAN-1982', 'DD-MON-YYYY'), 1300, NULL, 10);
COMMIT;
-- 2. (Session_1) 수정할 데이터 확인
var v_empno NUMBER
var v_ename VARCHAR2(1000)
var v_sal NUMBER
COLUMN EMPNO format NUMBER
COLUMN ENAME format a20
COLUMN SAL format NUMBER
BEGIN
SELECT EMPNO,
ENAME,
SAL
INTO :v_empno,
:v_ename,
:v_sal
FROM EMP
WHERE EMPNO = 7934;
END;
/
SELECT :v_empno EMPNO,
:v_ename ENAME,
:v_sal SAL
FROM DUAL
;
EMPNO ENAME SAL
---------- -------------------- ----------
7934 MILLER 1300
;
-- 3. (Session_1) 수정 데이터 변수에 넣음
EXEC :v_sal := 2000
-- 4. (Session_1) LOCK 설정
SELECT EMPNO,
ENAME,
SAL
FROM EMP
WHERE EMPNO = 7934
FOR UPDATE NOWAIT
;
-- 5. (Session_2) 같은 로우 데이터 UPDATE 시도
SELECT EMPNO,
ENAME,
SAL
FROM EMP
WHERE EMPNO = 7934
FOR UPDATE NOWAIT
;
ERROR at line 4:
ORA-00054: resource busy and acquire with NOWAIT specified
;
-- 5. (Session_1) UPDATE 수행
UPDATE EMP
SET SAL = :v_sal
WHERE EMPNO = 7934
;
COMMIT;
SELECT EMPNO,
ENAME,
SAL
FROM EMP
WHERE EMPNO = 7934
;
EMPNO ENAME SAL
---------- -------------------- ----------
7934 MILLER 2000
;
-- 1. EMP 테이블에 LAST_UPDATE_DATE 컬럼 추가
ALTER TABLE EMP ADD (LAST_UPDATE_DATE TIMESTAMP DEFAULT SYSDATE NOT NULL );
SELECT EMPNO,
SAL,
LAST_UPDATE_DATE
FROM EMP
;
EMPNO SAL LAST_UPDATE_DATE
---------- ---------- ------------------------------
7369 800 25-OCT-12 10.28.22.000000 PM
7499 1600 25-OCT-12 10.28.22.000000 PM
7521 1250 25-OCT-12 10.28.22.000000 PM
7566 2975 25-OCT-12 10.28.22.000000 PM
7654 1250 25-OCT-12 10.28.22.000000 PM
7698 2850 25-OCT-12 10.28.22.000000 PM
7782 2450 25-OCT-12 10.28.22.000000 PM
7788 3000 25-OCT-12 10.28.22.000000 PM
7839 5000 25-OCT-12 10.28.22.000000 PM
7844 1500 25-OCT-12 10.28.22.000000 PM
7876 1100 25-OCT-12 10.28.22.000000 PM
7900 950 25-OCT-12 10.28.22.000000 PM
7902 3000 25-OCT-12 10.28.22.000000 PM
7934 2000 25-OCT-12 10.28.22.000000 PM
;
-- 2. (Session_1) 수정할 데이터 확인 후 수정작업 진행
var v_empno NUMBER
var v_sal NUMBER
var v_last_update_date VARCHAR2(1000)
COLUMN empno format NUMBER
COLUMN sal format NUMBER
COLUMN last_update_date format a30
BEGIN
SELECT EMPNO,
SAL,
TO_CHAR(LAST_UPDATE_DATE, 'DD-MON-YY HH.MI.SSXFF AM') LAST_UPDATE_DATE
INTO :v_empno,
:v_sal,
:v_last_update_date
FROM EMP
WHERE EMPNO = 7934;
END;
/
SELECT :v_empno EMPNO,
:v_sal SAL,
:v_last_update_date LAST_UPDATE_DATE
FROM DUAL
;
EMPNO SAL LAST_UPDATE_DATE
---------- ---------- ------------------------------
7934 2000 25-OCT-12 10.28.22.000000 PM
;
EXEC :v_sal := 4000
-- 3. (Session_2) Session_1에서 수정할 같은 데이터를 수정 후 먼저 완료
var v_empno NUMBER
var v_sal NUMBER
var v_last_update_date VARCHAR2(1000)
COLUMN empno format NUMBER
COLUMN sal format NUMBER
COLUMN last_update_date format a30
BEGIN
SELECT EMPNO,
SAL,
TO_CHAR(LAST_UPDATE_DATE, 'DD-MON-YY HH.MI.SSXFF AM') LAST_UPDATE_DATE
INTO :v_empno,
:v_sal,
:v_last_update_date
FROM EMP
WHERE EMPNO = 7934;
END;
/
SELECT :v_empno EMPNO,
:v_sal SAL,
:v_last_update_date LAST_UPDATE_DATE
FROM DUAL
;
EMPNO SAL LAST_UPDATE_DATE
---------- ---------- ------------------------------
7934 2000 25-OCT-12 10.28.22.000000 PM
;
EXEC :v_sal := 3000
UPDATE EMP
SET SAL = :v_sal,
LAST_UPDATE_DATE = SYSTIMESTAMP
WHERE EMPNO = :v_empno
AND LAST_UPDATE_DATE = :v_last_update_date
;
COMMIT;
SELECT EMPNO,
SAL,
TO_CHAR(LAST_UPDATE_DATE, 'DD-MON-YY HH.MI.SSXFF AM') LAST_UPDATE_DATE
FROM EMP
WHERE EMPNO = 7934
;
EMPNO SAL LAST_UPDATE_DATE
---------- ---------- -----------------------------
7934 3000 25-OCT-12 10.45.52.435180 PM
;
-- 4. (Session_1) 수정작업 완료 후 COMMIT 하려고 하나 실패함
UPDATE EMP
SET SAL = :v_sal,
LAST_UPDATE_DATE = SYSTIMESTAMP
WHERE EMPNO = :v_empno
AND LAST_UPDATE_DATE = :v_last_update_date
;
0 rows updated.
;
종류 | 설명 |
---|---|
OWA_OPT_LOCK_CHECKSUM | 이 방법은 오라클 8i 버전 8.1.5 이후 버전에서 이용 가능함. 주어진 문자열로 16비트 체크섬을 리턴하는 함수와 주어진 ROWID로 해당 로우의 16비트 체크섬을 계산하고 동시에 락을 거는 함수도 있다. 충돌 가능성은 65,536 문자열 중 하나다(거짓 양상 가능성이 가장 큼) |
DBMS_OBFUSCATION_TOOLKIT.MD5 | 이 방법은 오라클 8.1.7 이후 버전에서 이용 가능함. 128비트 메세지 다이제스트(message digest)를 계산함. 충돌 확률은 약 1/3.4028E+38(매우 낮음) |
DBMS_CRYPTO.HASH | 이 방법은 오라클 10g 릴리즈 1 이후 버전에서 이용 가능함. SHA-1(Secure Hash Algorithm 1) 또는 MD4/MD5 메세지 다이제스트를 계산할 수 있음(필자 추천) |
ORA_HASH | 이 방법은 오라클 10g 릴리즈 1 이후 버전에서 이용 가능함. ORA_HASH는 한개의 VARCHAR2 타입의 함수 인자와 선택적으로 입력이 가능한 리턴값을 제어하는 도 다른 한 쌍의 함수 인자를 취하는 오라클 내장 함수. 리턴값은 숫자임(기본값은 0 ~ 4294967295) |
-- 0. 테스트 데이터 생성
CREATE TABLE DEPT
(DEPTNO NUMBER(2),
DNAME VARCHAR2(14),
LOC VARCHAR2(13) );
INSERT INTO DEPT VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO DEPT VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO DEPT VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO DEPT VALUES (40, 'OPERATIONS', 'BOSTON');
COMMIT;
/
-- 1. ORA_HASH 사용하여 데이터 확인
var deptno NUMBER
var dname VARCHAR2(1000)
var loc VARCHAR2(1000)
var hash NUMBER
COLUMN DNAME format a20
COLUMN LOC format a20
BEGIN
SELECT DEPTNO,
DNAME,
LOC,
ORA_HASH(DNAME || '/' || LOC) HASH
INTO :DEPTNO,
:DNAME,
:LOC,
:HASH
FROM DEPT
WHERE DEPTNO = 10;
END;
/
SELECT :DEPTNO DEPTNO,
:DNAME DNAME,
:LOC LOC,
:HASH HASH
FROM DUAL
;
DEPTNO DNAME LOC HASH
---------- -------------------- -------------------- ----------
10 ACCOUNTING NEW YORK 401273349
;
-- 2. ORA_HASH 사용하여 UPDATE #1(UPDATE 가능)
EXEC :dname := lower(:dname);
UPDATE DEPT
SET DNAME = :DNAME
WHERE DEPTNO = :DEPTNO
AND ORA_HASH(DNAME || '/' || LOC) = :HASH
;
1 row updated.
SELECT D.*,
ORA_HASH(DNAME || '/' || LOC) HASH
FROM DEPT D
WHERE DEPTNO = :DEPTNO
;
DEPTNO DNAME LOC HASH
---------- -------------------- -------------------- ----------
10 accounting NEW YORK 2818855829
;
-- 3. ORA_HASH 사용하여 UPDATE #2(UPDATE 불가)
UPDATE DEPT
SET DNAME = :DNAME
WHERE DEPTNO = :DEPTNO
AND ORA_HASH(DNAME || '/' || LOC) = :HASH
;
0 rows updated.
-- 4. 11g 버전
ALTER TABLE DEPT ADD HASH AS ORA_HASH(DNAME || '/' || LOC);
SELECT *
FROM DEPT D
WHERE DEPTNO = :DEPTNO
;
DEPTNO DNAME LOC HASH
---------- -------------------- -------------------- ----------
10 accounting NEW YORK 2818855829
;
-- 1. (Session_1) 샘플 테이블 및 트리거 생성
CREATE TABLE DEMO (X INT PRIMARY KEY);
CREATE OR REPLACE TRIGGER DEMO_BIFER
BEFORE INSERT ON DEMO
FOR EACH ROW
DECLARE
l_lock_id NUMBER;
resource_busy EXCEPTION;
PRAGMA EXCEPTION_INIT (resource_busy, -54);
BEGIN
l_lock_id := dbms_utility.get_hash_value(to_char(:new.x), 0, 1024);
IF(DBMS_LOCK.REQUEST(ID => l_lock_id,
LOCKMODE => DBMS_LOCK.X_MODE,
TIMEOUT => 0,
RELEASE_ON_COMMIT => TRUE) NOT IN (0, 4)) THEN
RAISE resource_busy;
END IF;
END;
/
Trigger created.
-- 2. (Session_1) 데이터 생성
INSERT INTO demo(x) VALUES(1);
-- 3. (Session_2) AUTONOMOUS_TRANSACTION 사용
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO demo(x) VALUES(1);
COMMIT;
END;
/
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified
ORA-06512: at "APPS.DEMO_BIFER", line 12
ORA-04088: error during execution of trigger 'USER.DEMO_BIFER'
ORA-06512: at line 4
Action | 비고 |
---|---|
Session_1에서 A 테이블에 UPDATE | 이슈 없음 |
Session_2에서 B 테이블에 UPDATE | 이슈 없음 |
Session_2에서 A 테이블에 UPDATE | Session_1에 의해 Session_2 블로킹 됨. |
Session_1에서 A 테이블에 UPDATE | 2개 세션 중 랜덤으로 데드락 발생시키고 롤백시킴 |
-- 0. 테스트 데이터 생성
DROP TABLE XSOFT_A;
DROP TABLE XSOFT_B;
CREATE TABLE XSOFT_A (A_1 NUMBER);
CREATE TABLE XSOFT_B (B_1 NUMBER);
INSERT INTO XSOFT_A VALUES(1);
INSERT INTO XSOFT_B VALUES(2);
COMMIT;
-- 1. (Session_1) 테이블 A를 수정함
UPDATE XSOFT_A
SET A_1 = 10
;
-- 2. (Session_2) 테이블 B를 수정함
UPDATE XSOFT_B
SET B_1 = 10
;
-- 3. (Session_2) 테이블 A를 수정함
UPDATE XSOFT_A
SET A_1 = 10
;
-- 4. (Session_3) Lock Monitering
@LOCK
;
(Node)H-Sid (Node)W-Sid Lock Status W-Time Lock Type H L-Mode R L-Mode LOCKED_OBJ UserName SID/SER# S TR/w/b MODULE PGM W_T WAIT_EVENT
----------- ----------- ----------- ------- --------- -------- -------- -------------------------------- -------- ------------ - ------ ----------------------- ---- ------- -------------------------
(1)9898 ▽ holding 24 TX Ex None APPS 9898,102 I DI/F/F SQL*Plus sqlp 24 SQL*Net message from clie
(1)9831 waiting 6 TX None Ex XSOFT_A(T) APPS 9831,86 A DI/F/F SQL*Plus sqlp 6 enq: TX - row lock conten
;
-- 4. (Session_1) 테이블 A를 수정함
UPDATE XSOFT_B
SET B_1 = 10
;
-- 5. (Session_2) 확인
UPDATE XSOFT_A
SET A_1 = 10
;
UPDATE XSOFT_A
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
;
-- 4. (Session_3) Lock Monitering
(Node)H-Sid (Node)W-Sid Lock Status W-Time Lock Type H L-Mode R L-Mode LOCKED_OBJ UserName SID/SER# S TR/w/b MODULE PGM W_T WAIT_EVENT
----------- ----------- ----------- ------- --------- -------- -------- -------------------------------- -------- ------------ - ------ ----------------------- ---- ------- -------------------------
(1)9831 ▽ holding 42 TX Ex None XSOFT_A(T) APPS 9831,86 I DI/F/F SQL*Plus sqlp 42 SQL*Net message from clie
(1)9898 waiting 45 TX None Ex XSOFT_B(T) APPS 9898,102 A DI/F/F SQL*Plus sqlp 45 enq: TX - row lock conten
;