READ COMMITTED CLASS with READ COMMITTED INSTANCES
이 격리 수준은 트랜잭션이 다른 트랜잭션에서 수정 중인 객체(클래스 또는 인스턴스)를 읽지 못하게 한다. 그리고 트랜잭션이 읽은 클래스와 인스턴스를 다른 트랜잭션이 갱신하지 못하게 막지 않는다. 트랜잭션은 클래스와 인스턴스에 대한 읽기가 반복 불가능(non-repeatable read)할 수도 있다. 이것은 트랜잭션이 동일한 객체(클래스 또는 인스턴스)를 두 번 읽게 되면 다른 두 커밋된 값을 읽을 수 있음을 의미한다. 마찬가지로, 한 질의가 두 번 수행되면 두 수행 결과가 다를 수 있다. 결과의 차이는 질의된 클래스의 갱신과 두 질의가
수행되는 간격 사이에 커밋된 인스턴스의
삽입, 갱신, 삭제로 인한 것이다. 질의되는 클래스는 더 이상 존재하지 않을 수도 있고 속성 일부는 추가되었거나 삭제되었을 수도 있다. REPEATABLE READ CLASS 격리 수준에서는 이것이 불가능한데, 그 이유는 트랜잭션이 끝까지 그 클래스의 잠금을 가지고 있기 때문이다.
다음은 이 격리 수준의 규칙이다.
- 트랜잭션은 다른 트랜잭션에서 수정 중인 객체를 덮어쓰지 않는다.
- 트랜잭션은 트랜잭션이 끝날 때까지 객체를 커밋하지 않는다.
- 트랜잭션은 다른 트랜잭션에서 수정 중인 객체를 읽지 않는다.
이 격리 수준은 배타 잠금에 대하여 2단계 잠금을 한다. 클래스와 인스턴스에 대한 읽기 잠금은 읽기가 끝나면 해제된다.
예제
한 트랜잭션은 클래스에 인스턴스를 삽입, 삭제하고 클래스의 이름을 갱신하며 다른 트랜잭션은 다양한 관점에서 질의를 한다. 트랜잭션 T1은 임의의 격리 수준이며 트랜잭션 T2는 READ COMMITTED CLASS with READ COMMITTED INSTANCES 격리 수준이다. 이 예제에서는 participant2 클래스가 이전에 존재하지 않는 것으로 가정하고 생성한다.
- 트랜잭션 T1은 participant2 클래스를 생성하고 클래스에 인스턴스를 삽입한다. 트랜잭션 T1이 커밋되자마자 CUBRID는 트랜잭션 T1이 가지고 있던 잠금을 해제한다. 트랜잭션 T2는 participant2 클래스에 질의를 한다.
- User1 (T1):
- SET TRANSACTION ISOLATION LEVEL READ COMMITTED CLASS, READ COMMITTED INSTANCES;
;xrun
;autocommit off
create class participant2 (host_year integer, nation_code char(3));
insert into participant2 (host_year, nation_code) values (2008, ‘AUS’);
commit work;
;xrun
1 rows inserted.
- User2 (T2):
- SET TRANSACTION ISOLATION LEVEL READ COMMITTED CLASS, READ COMMITTED INSTANCES;
;xrun
;autocommit off
select host_year, nation_code from participant2;
;xrun
=== <Result of SELECT Command in Line 2> ===
host_year nation_code
===================================
2008 'AUS'
1 rows selected.
- 트랜잭션 T1은 participant2 클래스에 또 다른 인스턴스를 삽입한다. 트랜잭션 T2가 몇 초 후 participant2 클래스에 질의를 시도하지만 트랜잭션 T2는 일시 정지한다. 그 이유는 트랜잭션 T1이 participant2 클래스의 인스턴스(새로운 인스턴스)에 대한 잠금을 가지고 있기 때문이다.
- User1 (T1):
- insert into participant2 (host_year, nation_code) values (2012, ‘KOR’);
;xrun
1 rows inserted.
- User2 (T2):
- select host_year, nation_code from participant2;
;xrun
- 트랜잭션 T1이 participant2 클래스에 다른 인스턴스를 삽입하고 커밋을 하면 모든 잠금이 해제된다. 트랜잭션 T2는 다시 진행되고 새로운 인스턴스를 보게 된다.
- User1 (T1):
- insert into participant2 (host_year, nation_code) values (2012, ‘JPN’);
commit work;
;xrun
1 rows inserted.
- User2 (T2):
- === <Result of SELECT Command in Line 1> ===
host_year nation_code
===================================
2008 'AUS'
2012 'KOR'
2012 'JPN'
3 rows selected.
- 트랜잭션 T1이 participant2 클래스의 첫 번째 인스턴스를 삭제한다. 트랜잭션 T2가 participant2 클래스에 질의를 시도하지만 트랜잭션 T2는 대기상태가 된다. 왜냐하면 트랜잭션 T1이 삭제된 인스턴스에 대한 잠금과 클래스에 대해 적어도 하나의 의도 배타 잠금(intention execlusive lock)을 유지하고 있기 때문이다.
- User1 (T1):
- delete from participant2
where host_year=2008;
;xrun
1 rows deleted.
- User2 (T2):
- select * from participant2;
;xrun
- 트랜잭션 T1이 연산을 커밋하고 잠금을 해제한다. 그러면 트랜잭션 T2는 다시 진행되고 두 번째와 세 번째 INSERT 문의 결과를 보게 된다.
- User1 (T1):
- commit work;
;xrun
1 command(s) successfully processed.
- User2 (T2):
- === <Result of SELECT Command in Line 1> ===
host_year nation_code
===================================
2012 'KOR'
2012 'JPN'
2 rows selected.
- 트랜잭션 T1이 participant2 클래스의 이름을 nation_medals로 갱신하고자 하면 성공한다. 그 이유는 트랜잭션 T2가 어떤 인스턴스나 클래스(반복 불가능한 스키마)에 대한 어떤 읽기 잠금도 가지고 있지 않기 때문이다. 하지만 트랜잭션 T2가 데이터를 갱신하거나 삽입하려는 것과 같은 쓰기 연산을 수행하는 경우에는 트랜잭션 T1은 대기 상태가 된다. 트랜잭션 T2가 끝까지 쓰기 잠금을 가지고 있기 때문이다. 트랜잭션 T2가 클래스에 다시 질의를 하면 일시 정지되는데 트랜잭션 T1이 클래스의 이름을 갱신하고 있기 때문이다. 트랜잭션 T2는
단지
커밋된 갱신만 볼 수 있음을 주의해야 한다. 이 경우에 RENAME은 커밋되거나 취소된다.
- User1 (T1):
- rename class participant2 as nation_medals;
;xrun
1 command(s) successfully processed.
- User2 (T2):
- commit work;
select * from participant2;
;xrun
- 트랜잭션 T1이 연산을 커밋하고 잠금이 해제된다. 트랜잭션 T2가 질의를 다시 시도한다. 그러나 location 클래스가 place로 이름이 갱신되었기 때문에 진행되지 못한다. 대신에 트랜잭션 T2는 location 클래스가 존재하지 않는다는 구문 에러 메시지를 받게 된다.
- User1 (T1):
- commit work;
;xrun
1 command(s) successfully processed.
- User2 (T2):
- In line 2, column 2,
ERROR: Unknown class "participant2".
1 command(s) successfully processed.