REPEATABLE READ CLASS with READ UNCOMMITTED INSTANCES
이 격리 수준은 트랜잭션이 다른 트랜잭션에서 수정 중인 클래스를 읽지 못하게 하며, 이 트랜잭션에서 접근한 클래스(스키마의 일부)를 다른 트랜잭션이 갱신하지 못하게 한다. 이 격리 수준은 트랜잭션이 커밋된 인스턴스 뿐만 아니라 더티 인스턴스(dirty instances, 이후에 갱신되거나 롤백될 수 있음)도 읽을 수 있도록 허용한다. 트랜잭션은 동일한 인스턴스를 두 번 읽으려고 할 때 서로 다른 두 개의 값을 읽을 수도 있다. 이것은 두 번 읽으려고 하는 간격 사이에 다른 트랜잭션이 인스턴스를 갱신할 때 발생한다. 마찬가지로, 같은 질의가 두 번 수행되는 경우 이전의 수행 결과와 다른 결과를 얻을 수 있다. 두 질의를 수행하는 간격 사이에 커밋되지 않을 지도 모르는 인스턴스의 삽입, 갱신, 삭제가 일어날 수 있기 때문이다. 이전의 격리 수준과는 반대로 인스턴스 갱신이 진행중인 경우 트랜잭션은 갱신 잠금을 가지고 있는 트랜잭션이 끝날 때까지 기다리지 않는다. 이 격리 수준에서 트랜잭션은 다른 트랜잭션에 의해 갱신 중인 새로운 인스턴스를 읽게 된다(커밋되지 않은 상태의 갱신된 값을 읽게 된다). 처음 CUBRID 설치 시 cubrid.conf에 설정된 격리 수준은 REPEATABLE READ CLASS, READ UNCOMMITTED INSTANCES이다.
아래의 규칙이 적용된다.
- 트랜잭션은 다른 트랜잭션에서 수정 중인 객체를 덮어쓰지 않는다.
- 트랜잭션은 트랜잭션이 끝날 때까지 객체를 커밋하지 않는다.
- 트랜잭션은 다른 트랜잭션에서 수정 중인 클래스를 읽지 않는다. 이 규칙은 인스턴스에는 적용되지 않는다.
- 다른 트랜잭션은 현재 트랜잭션이 끝나기 전에 읽거나 갱신한 어떤 클래스도 더럽히지 않는다. 이 규칙은 인스턴스에는 적용되지 않는다.
이 격리 수준은 배타 잠금에 대해서 2단계 잠금을 한다. 하지만 공유 잠금은 인스턴스에 대해 획득되지 않는다. 클래스에 대한 의도 잠금은 다른 트랜잭션이 클래스 정의(클래스에 대한 반복 가능한 읽기)를 갱신하는 것을 막기 위해 트랜잭션의 끝까지 유지된다.
예제
한 트랜잭션이 클래스에 인스턴스를 삽입, 삭제하고 클래스의 이름을 갱신하며, 다른 트랜잭션이 다양한 관점에서 질의를 한다. 트랜잭션 T1은 임의의 격리 수준을 가지고 있으며 트랜잭션 T2는 REPEATABLE READ CLASS with READ UNCOMMITTED INSTANCES 격리 수준이다. 이 예제에서는 participant2 클래스가 이전에 존재하지 않는 것으로 가정하고 생성한다.
- 트랜잭션 T1은 participant2 클래스를 생성하고 클래스에 인스턴스를 삽입한다. 트랜잭션 T1이 커밋되자마자 CUBRID는 트랜잭션 T1이 가지고 있던 잠금을 해제한다. 트랜잭션 T2는 participant2 클래스에 질의를 한다.
- User1 (T1):
- SET TRANSACTION ISOLATION LEVEL REPEATABLE READ CLASS, READ UNCOMMITTED 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
- User2 (T2):
- SET TRANSACTION ISOLATION LEVEL REPEATABLE READ CLASS, READ UNCOMMITTED 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 클래스에 다른 인스턴스를 삽입한다. 트랜잭션 T1이 새로 추가한 인스턴스 (2012, 'KOR')가 트랜잭션 T2의 질의를 수행하기 전에 아직 출력되지 않았다고 가정한다. 트랜잭션 T2가 질의를 수행할 때 이 새로운 인스턴스는 보이지 않는다.
- User1 (T1):
- insert into participant2 (host_year, nation_code) values (2012, 'KOR');
;xrun
- User2 (T2):
- select host_year, nation_code from participant2;
;xrun
=== <Result of SELECT Command in Line 2> ===
host_year nation_code
===================================
2008 'AUS'
1 rows selected.
- 참고 이전의 격리 수준과 달리, 트랜잭션 T2가 participant2 클래스에 다시 질의를 하려고 할 때 트랜잭션 T2는 중단되지 않는다. 대신 트랜잭션 T2는 커밋되었거나 몇몇 커밋되지 않은 인스턴스를 볼 수 있다. 트랜잭션 T2가 보는 커밋되지 않은 인스턴스는 데이터베이스에 출력되는 것에 의존한다. CUBRID는 다양한 상황에서 워크스페이스에 있는 더티 인스턴스를 데이터베이스에 출력한다. 이것은 CUBRID에서 더티 인스턴스(dirty instance)를 다루는 방법에서 설명한다.
- 다음으로 트랜잭션 T1이 participant2 클래스에 SELECT 질의를 한다. 이 트랜잭션 동안 시스템은 커밋되지 않은 인스턴스 (2012, 'KOR')를 출력한다. 트랜잭션 T2가 이전의 질의를 반복하면 해당 인스턴스를 보게 된다.
- User1 (T1):
- select host_year, nation_code from participant2;
;xrun
=== <Result of SELECT Command in Line 1> ===
host_year nation_code
===================================
2008 'AUS'
2012 'KOR'
2 rows selected.
- User2 (T2):
- select host_year, nation_code from participant2;
;xrun
=== <Result of SELECT Command in Line 2> ===
host_year nation_code
===================================
2008 'AUS'
2012 'KOR'
2 rows selected.
- 트랜잭션 T1이 마지막 인스턴스가 커밋되지 않게 하기 위해 갱신을 롤백한다. 그러면 트랜잭션 T2가 질의를 다시 수행했을 때 인스턴스 (2012, 'KOR')는 보이지 않는다. 커밋된 인스턴스와 커밋되지 않은 인스턴스에 대한 반복 불가능한 읽기로 인해 질의 결과가 달라지는 것에 주의해야 한다.
- User1 (T1):
- rollback work;
;xrun
1 command(s) successfully processed.
- User2 (T2):
- select host_year, nation_code from participant2;
;xrun
=== <Result of SELECT Command in Line 1> ===
host_year nation_code
===================================
2008 'AUS'
1 rows selected.
- 트랜잭션 T1이 participant2 클래스에 다른 인스턴스를 삽입하고 첫 인스턴스를 삭제한다. 트랜잭션 T2가 participant2 클래스에 다시 질의를 할 때 여전히 첫 번째 인스턴스가 보일 수도 있으나, 이 예제에서는 삭제가 데이터베이스에 반영되었다. 삽입된 인스턴스는 출력되었다. 둘 다 더티 인스턴스임을 주의해야 한다.
- User1 (T1):
- insert into participant2 (host_year, nation_code) values (2012, 'KOR');
delete from participant2 where host_year=2008 and nation_code='AUS';
;xrun
1 rows inserted.
1 rows deleted.
- User2 (T2):
- select host_year, nation_code from participant2;
;xrun
=== <Result of SELECT Command in Line 1> ===
host_year nation_code
===================================
2012 'KOR'
1 rows selected.
- 변경된 것은 데이터베이스에 영구화하기 위해 트랜잭션 T1이 커밋한다. 트랜잭션 T2는 participant2 클래스에 다시 질의하고 이전과 같은 결과를 얻는다.
- User1 (T1):
- commit work;
;xrun
1 command(s) successfully processed.
- User2 (T2):
- select host_year, nation_code from participant2;
;xrun
=== <Result of SELECT Command in Line 1> ===
host_year nation_code
===================================
2012 'KOR'
1 rows selected.
- 트랜잭션 T1이 location 클래스의 이름을 place로 갱신 하려고 하지만 트랜잭션 T2가 location 클래스(반복 가능한 스키마)를 보고 있기 때문에 중단된다. 이것은 클래스에 대한 의도 공유 잠금을 트랜잭션의 끝까지 가지고 있기 때문이다. 트랜잭션 T2가 커밋을 하고 트랜잭션 T1이 다시 진행된다. 트랜잭션 T2가 다시 질의를 수행할 때 트랜잭션 T1이 클래스의 이름을 갱신하고 아직 커밋하지 않았기 때문에 일시 정지한다.
- User1 (T1):
- rename class participant2 as nation_medals;
;xrun
- User2 (T2):
- commit work;
select host_year, nation_code from participant2;
;xrun
- 트랜잭션 T1이 연산을 커밋하면 트랜잭션 T2가 클래스에 질의를 할 수 있다. 트랜잭션 T2는 location 클래스가 place로 이름이 바뀌었기 때문에 진행되지 못한다. 대신에 트랜잭션 T2는 location 클래스가 존재하지 않는다는 구문 에러를 전달 받는다.
- User1 (T1):
- commit work;
;xrun
- User2 (T2):
- In line 2, column 37,
ERROR: Class participant2 does not exist.