반응형
반응형

DeadLock이란 둘 이상의 세션이 서로 맞물려 차단된 상태를 말한다. DeadLock이 발생하면 영원히 지속되기  때문에 SQL 서버가 자동으로 찾아내어 해제시켜 준다. DeadLock에는 두가지 종류가 있다.

1. 순환 교착 (Cycle Deadlock)
   교착상태를 설명할 때 보통 예로 드는 것이 이 "Cycle DeadLock"이다. 두 세션이 필요한 리소스를 얻기 위해 서로 상대방이 Lock을 풀기를 기다리는 상태라고 설명할 수 있다. 

예를 들면 다음과 같다.
  1. -- 세션 1   
  2. BEGIN TRAN   
  3.   
  4. UPDATE TAB_A SET COL1 = COL1+2 WHERE PK = 10   
  5.   
  6. WAITFOR DELAY '0:0:5'  
  7.   
  8. UPDATE TAB_B SET COL1 = COL1+2 WHERE PK = 10  
  1. -- 세션 2 : 세션 1과 연달아서 실행한다.   
  2. BEGIN TRAN   
  3.   
  4. UPDATE TAB_B SET COL1 = COL1+2 WHERE PK = 10   
  5.   
  6. WAITFOR DELAY '0:0:5'  
  7.   
  8. UPDATE TAB_A SET COL1 = COL1+2 WHERE PK = 10  

위와 같이 실행시키면 한쪽 세션에서는 다음과 같은 결과를 볼 수 있다.
(1개 행 적용됨)

서버: 메시지 1205, 수준 13, 상태 50, 줄 1
트랜잭션(프로세스 ID 54)이 lock 리소스에서 다른 프로세스와의 교착 상태가 발생하여 실행이 중지되었습니다. 트랜잭션을 다시 실행하십시오.

교착상태란 무엇인가를 직관적으로 알려주는 예이다.

2. 변환 교착 (Conversion DeadLock)
Conversion DeadLock 은 잠금모드가 SharedLock에서 UPD-Lock 혹은 X-Lock으로 전환될 때 발생하는 문제로서, 채번(일련번호 매기는 일)과 관련해서 발생하는 경우가 많다.

1) 세션 A가 트랜젝션을 건 후 어떤 Row에 공유잠금(S-Lock)을 걸었다고 가정해보자
2) 세션 B도 트랜젝션을 건 후 그 Row에 공유잠금을 걸었다. 공유잠금끼리는 서로 호환되니까 당근 가능하다.
3) 이 상태에서 세션 A는 그 Row에 Update를 시도한다. 이 Row에는 세션 B에서 공유잠금을 걸었으므로 세션 A는 배타적잠금을 을 걸기 위해 세션 B가 공유잠금을 풀어주기를 기다린다.
4) 이때, 세션 B도 그 Row에 Update를 시도한다

과연 어떻게 될까? A는 B의 공유잠금때문에 Update를 진행하지 못하고, B는 A의 공유잠금때문에 Update를 진행하지 못하게 된다. 이것이 Conversion DeadLock이다.

실제 테스트를 해보면 다음과 같다.
  1. -- 이 SQL을 세션 1, 2에서 연달아 실행한다.   
  2. DECLARE @NUM INT  
  3.   
  4. BEGIN TRAN   
  5.   
  6. SELECT @NUM = VAL + 1 FROM TAB1    
  7.   WITH (REPEATABLEREAD)    
  8.   WHERE NUMTYPE = 'TestApp'  
  9.   
  10. WAITFOR DELAY '0:0:5'  
  11.   
  12. UPDATE TAB1 SET VAL = @NUM WHERE NUMTYPE = 'TestApp'  

테스트해보면 데드락이 발생하는 것을 확인할 수 있다.

만약 위의 SELECT문에서 WITH (REPEATABLEREAD) 잠금힌트가 없다면 어떻게 될까?
머릿속으로 생각할 때는 데드락이 발생하는 것이 불가능할 것 같다. 
만약 DB가 엄청나게 바쁜 상황이라면?? 잘 모르겠다. ^^;

위와 같은 SQL이 데드락이 발생하지 않도록 하려면 어떻게 해야 할까?
위의 교착상태는 SELECT시 공유잠금이 동시에 걸렸기 때문에 발생한 것이므로,
SELECT시에 다음과 같이 잠금 힌트를 주어 처음부터 명시적으로 UPDLOCK을 걸어주면 
교착상태를 방지할 수 있다.
SELECT @NUM = VAL + 1 FROM TAB1 WITH (UPDLOCK) WHERE NUMTYPE = 'TestApp'

내가 전에 링크해왔던 글 중에
UPDATE Character SET login_count = login_count + 1 , login_time = GETDATE() WHERE name = @IN_NAME;
위와 같은 SQL이 Conversion DeadLock이 발생하기 쉬우므로
UPDATE Character WITH (UPDLOCK) SET login_count = login_count + 1 , login_time = GETDATE() WHERE name = @IN_NAME;
이렇게 써야 한다는 글이 있었는데, 이것은 잘못된 내용이었다.

UPDATE문 실행시 WHERE 필터가 진행될 때는 해당 Row에 Update Lock이 걸리며, 
실제로 WRITE가 진행될 때 Exclusive Lock으로 전환된다. 

위의 업데이트문에서는 Shared Lock이 걸리지 않으며 따라서 위의 잠금 힌트는 주나마나 한 것이 된다.


3. Transaction을 걸지 않았는데도 DeadLock이 발생하는 경우
 가끔은... 아예 BEGIN TRAN이란 문장 자체가 없는데도 데드락이 발생하는 경우가 있다. 이것은 모든 INSERT, UPDATE, DELETE 문장이 실행될 때 명시적으로 TRANSACTION이 지정되지 않을 경우 자동으로 "암시적 트랜잭션"이 생성되기 때문이다. 
즉, 
  1. UPDATE TEST_TAB SET VAL = 1 WHERE PK = 10  
이라는 문장은 실제로는
  1. BEGIN TRAN   
  2. UPDATE TEST_TAB SET VAL = 1 WHERE PK = 10   
  3. COMMIT  
과 같은 식으로 실행되는 것이다.

그렇다면 만약 한 UPDATE 문장 내에서 Shard Lock과 Update 혹은 Exclusive Lock이 함께 걸리는 SQL이 있을까?

물론 있다.

  1. -- Select와 Update가 한 SQL 내에 섞여 있어 Conversion Deadlock에 취약한 SQL   
  2. UPDATE TEST_TAB    
  3.    SET VAL = 1   
  4.    WHERE PK IN (SELECT PK FROM TEST WHERE VAL = 10)  

위와 같은 문장은 SELECT가 실행될 때 S-Lock이 걸린 후 X-Lock으로 전환된다.
실제로 세션1 에서 먼저 TEST_TAB 에 적당한 UPDATE문을 실행하여 테이블 전체에 X-Lock을 건 후 
세션2 에서 위의 Update 문을 실행하고 SP_LOCK으로 세션 2의 잠금상태를 점검해보면 다음과 같이 S-Lock을 먼저 시도하고 있는 것을 확인할 수 있다.

따라서 DB가 바쁠때 위와 같은 문장이 동시다발적으로 실행되면 DeadLock이 발생하게 된다.
(실제로 WHILE 루프로 묶어서 여러 세션에서 돌려보면 테스트 환경에서도 교착상태가 재현된다.)

위와 같이 UPDATE 문 내에 명시적으로 SELECT 서브쿼리가 들어있지 않으면서도 SharedLock을 유발하는  UPDATE 문장들이 있다.
  1. UPDATE UPDATE_TAB   
  2.    SET UPDATE_TAB.MANAGE = 1   
  3.    FROM UPDATE_TAB, UPDATE_TAB A   
  4.    WHERE A.SEQ = UPDATE_TAB.SEQ AND A.MANAGE = 1  

위와 같은 JOIN UPDATE문 역시 데드락에 취약하다.

교착상태를 방지하기 위해 가급적이면 한 SQL 내에 UPDATE문장과 SELECT 문장을 섞어 쓰는 것을 피해야 한다.



4. 교착상태 탐지방법 : 추적플래그 1204 
DeadLock이 발생했을 때 그것을 추적하여 원인을 확인하는 여러가지 방법이 있지만 가장 좋은 방법은 "추적플래그 1204"를 설정하는 것이다. 자세한 내용은 여기를 참조한다.



5. DeadLock을 줄이는 5가지 방법
DeadLock을 줄이기 위해서는 다음과 같은 방법들이 있다.

1) 인덱스를 설정한다. 인덱스가 없으면 Lock이 걸리는 범위가 훨씬 넓어지기 때문에 교착상태가 발생하기 쉬워진다.
2) 자원들을 한쪽으로 사용한다. A와 B라는 테이블이 있다면 모든 세션에서 A->B 순서로 사용하도록 한다.
3) 트랜잭션은 가급적이면 짧게 만든다.
4) 테이블의 크기를 작게 쪼갠다. (정규화)
5) Transaction Isolation Level을 "Read Uncommitted"로 설정한다.




반응형
반응형

격리 수준(Transaction Isolation Level)이란 SQL Server에서 잠금(Lock)을 어떤 식으로 적용할 것인가에 관한 얘기이다. 주로 SELECT시의 Lock을 어떻게 걸 것이냐에 따라 격리 수준이 구분지어진다.

1. READ COMMITTED
  글자 그대로 해석하면 "커밋된 데이터만 읽을 수 있는" 격리수준이다. 
  SQL Server의 기본 격리수준으로서 SELECT 실행시 공유잠금을 건다. 이 격리수준에서는 SELECT를 시도하려는 DATA에 다른 트랜잭션에서 업데이트를 진행한 경우, 배타적 잠금(X-Lock)이 걸린 데이터에 공유잠금(S-Lock)을 걸려고 시도하므로 업데이트의 트랜잭션이 종료될 때까지 SELECT는 Block된다. Block된 SELECT는 트랜잭션이 종료되면 자동으로 실행된다.
  예를 들어 다음과 같은 SQL을 순서대로 실행시킬 경우, 세션 2는 블럭된다.
  1. -- 세션 1   
  2. BEGIN TRAN   
  3. UPDATE TABLENAME SET VAL=1 WHERE PK = 10  
  1. -- 세션 2  : 위의 SQL 실행 후 실행   
  2. SELECT * FROM TABLENAME WHERE  PK = 10 -- 블럭된다!!!  



2. READ UNCOMMITTED
  말 그대로 "커밋되지 않은 데이터도 읽을 수 있는" 격리수준이다.
  업데이트가 커밋되지 않은 데이터에는 배타적 잠금(X-Lock)이 걸려있다. 배타적 잠금이 걸린 데이터를 SELECT할 수 있다는 것은 SELECT시 어떠한 잠금도 걸지 않는다는 것을 의미한다.
업데이트되지 않은 데이터를 읽을 수 있다는 것은 무결성을 깨트릴 위험성이 있다는 의미이다. 예를 들어 SELECT한 결과가 실제로 DB에 INSERT되지 않고 ROLLBACK될 경우, 존재하지 않는 DATA를 SELECT한 결과가 된다. 하지만 그런 경우가 매우 적고, READ UNCOMMITTED 수준에서는 동시성이 향상되는 효과가 있기 때문에 자주 사용되는 수준이다.

  예를 들어 다음과 같은 SQL을 순서대로 실행시킬 경우, 세션 2에서는 1 값이 SELECT된다..
  1. -- 세션 1   
  2. UPDATE TABLENAME SET VAL=2 WHERE PK = 10   
  3.   
  4. BEGIN TRAN   
  5. UPDATE TABLENAME SET VAL=1 WHERE PK = 10  
  1. -- 세션 2  : 위의 SQL 실행 후 실행   
  2. SELECT * FROM TABLENAME WITH (READUNCOMMITTED) WHERE  PK = 10 -- 1 값이 SELECT된다.  

READ UNCOMMITTED 격리수준은 다음과 같이 설정할 수 있다.
  1. SELECT * FROM TABLE1 WITH (READUNCOMMITTED) WHERE ... -- 해당 SELECT문장에만 적용   
  2. SELECT * FROM TABLE1 WITH (NOLOCK) WHERE ... -- 해당 SELECT문장에만 적용   
  3.   
  4. SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED -- 해당 세션에 적용  



3. REPEATABLE READ
  직역하면 "반복가능한 읽기" 정도?
  REPEATABLE READ 수준에서는 동일 트랜잭션 내에서 한번 SELECT했던 ROW에 대해서는 UPDATE나 DELETE가 불가능하며 트랜잭션 종료시까지 다시 SELECT했을 때 같은 값이 SELECT된다는 것이 보장된다.
  내부적으로는 SELECT했을 때 해당 ROW에 걸리는 S-LOCK이 즉시 해제되지 않고 트랜잭션 종료시까지 유지된다는 것을 의미한다.

다음의 예를 보자
  1. -- 세션 1   
  2. BEGIN TRAN   
  3. SELECT VAL FROM TABLENAME WITH (REPEATABLEREAD) WHERE  PK = 10 -- VAL의 값이 1인 RECORD 1개가 출력되었다고 치자.  
  1. -- 세션 2  : 위의 SQL 실행 후 실행   
  2. UPDATE TABLENAME SET VAL = 2 WHERE PK = 10 -- 세션1 트랜잭션 종료시까지 BLOCK된다.  

이 상태에서 세션 1에서 다시 SELECT해본다
  1. -- 세션 1   
  2. SELECT VAL FROM TABLENAME WITH (REPEATABLEREAD) WHERE  PK = 10 -- 조금 전과 동일하게 1 값이 1개 출력된다.  

그러나 이 격리수준은 SELECT된 ROW의 데이터에 UPDATE할 수 없다는 의미이지, 이 테이블에 INSERT 까지 차단된다는 의미는 아니어서 SELECT했던 행의 값들은 동일하게 유지되지만, SELECT의 결과가 항상 동일하다는 의미는 아니다. (즉 SELECT 결과 RecordSet의 변경은 되지 않지만 추가는 발생할 수 있다.)
다음과 같이 해보자.
  1. -- 세션 1   
  2. BEGIN TRAN   
  3. SELECT VAL FROM TABLENAME WITH (REPEATABLEREAD) WHERE  VAL = 1 -- VAL의 값이 1인 RECORD 1개가 출력  
  1. -- 세션 2  : 위의 SQL 실행 후 실행   
  2. INSERT INTO TABLENAME (PK, VAL) VALUES (11, 1) -- 잘 실행된다.  

이 상태에서 세션 1에서 다시 SELECT해본다.  
  1. -- 세션 1   
  2. SELECT VAL FROM TABLENAME WITH (REPEATABLEREAD) WHERE  VAL = 1 -- VAL 값이 1인 레코드가 2개 출력된다.  
SELECT 결과가 동일하게 보장되지는 않음을 확인할 수 있다.

REPEATABLE READ 격리수준은 다음과 같이 설정할 수 있다.
  1. SELECT * FROM TABLE1 WITH (REPEATABLEREAD) WHERE ... -- 해당 SELECT문장에만 적용   
  2.   
  3. SET TRANSACTION ISOLATION LEVEL REPEATABLE READ -- 해당 세션에 적용  



4. SERIALIZABLE
  우리말로는 "직렬화"이다. 직렬화란 한줄로 서서 순서대로 처리되는 상황을 의미하며 동시성은 최악이 된다.
  이 경우에는 SELECT 했을 때 SELECT된 ROW에 RANGED S-LOCK이 걸리고 트랜잭션 종료시까지 유지되기 때문에, 트랜잭션 종료시까지 해당 범위에 대해서 일체의 UPDATE, INSERT, DELETE가 차단되고, 따라서 동일한 SELECT 결과 재현이 보장된다. 이것은 SQL Server에서 지원하는 가장 높은 격리수준이며 가장 높은 데이터 무결성이 제공되지만, 성능은 최악이다. COM+의 기본 수준이라고 한다.
다음의 예를 보자
  1. -- 세션 1   
  2. BEGIN TRAN   
  3.   
  4. SELECT * FROM TABLENAME    
  5. WITH (SERIALIZABLE)   
  6. WHERE VAL < 5 -- VAL값이 5이하인 RECORD에 RANGED S-LOCK이 걸린다.  
  1. -- 세션 2  : 위의 SQL 실행 후 실행   
  2. INSERT INTO TABLENAME (PK, VAL) VALUES (11, 1) -- 이 SQL은 BLOCK된다!!   
  3. INSERT INTO TABLENAME (PK, VAL) VALUES (12, 7) -- 이 SQL은 잘 실행된다.  
첫번째 INSERT문이 BLOCK되는 것은 INSERT하려는 VAL값이 세션1에서 RANGE LOCK을 건 범위에 포함되기 때문이다. 그러나 두번째 INSERT문은 RANGE LOCK의 범위 밖의 값이므로 INSERT가 가능하다.

여기서 RANGE-LOCK이 걸리는 것은 해당 컬럼에 인덱스가 있을 경우에 한한다. 위의 예제에서 VAL 컬럼에 인덱스가 없다면 테이블 전체에 LOCK이 걸리기 때문에 첫번째 INSERT문은 물론이고 두번째 INSERT문도 BLOCK된다.

VAL 컬럼에 인덱스가 있을 경우 위의 SELECT를 실행한 후 SP_LOCK 프로시져를 실행해 LOCK 상태를 살펴보면 아래와 같다.

위의 이미지를 보면 Range Lock (RangeS-S)이 걸려있는 것을 볼 수 있다.

이 상태에서 인덱스만 삭제한 후 동일한 SELECT문을 실행시키면 다음과 같이 된다.

전체 테이블에 S-Lock이 걸린 것을 볼 수 있다. (인덱스의 유무는 잠금의 크기에 이정도로 큰 영향을 준다!!!)

SERIALIZABLE 격리수준은 다음과 같이 설정할 수 있다.
  1. SELECT * FROM TABLE1 WITH (SERIALIZABLEWHERE ... -- 해당 SELECT문장에만 적용   
  2.   
  3. SET TRANSACTION ISOLATION LEVEL SERIALIZABLE -- 해당 세션에 적용  

반응형
반응형

데이터의 무결성을 보장하기 위해 SQL Server에서는 데이터에 Lock을 건다. 사흘에 걸쳐 책을 읽고 테스트를 하면서 공부한 끝에 이 Lock의 매커니즘을 어느정도 이해할 수 있게 되었다. 이에 이해한 내용을 정리해보고자 한다.

1. 잠금(Lock)의 개념

  데이터에 잠금(Lock)을 건다고 하면 언뜻 생각하기엔 데이터가 들어있는 방에 들어가지 못하게 방문을 걸어 잠근다는 느낌이 들지만, 사실은 방문에 "이 방에는 U-Lock이 걸려있음" 이라고 써 붙이는 개념에 가깝다.
  그 방에 누군가 SELECT를 시도한다면, 시도하는 사람은 그 방에 또 "S-Lock이 걸려있음"이라고 써붙이게 되는데 이때 이전에 걸려있던 "U-Lock"과 지금 걸려고 시도하는 "S-Lock"의 호환성을 비교하게 된다. 다행히도 "U-Lock"은 "S-Lock"과 호환되므로 "S-Lock"을 걸 수 있게 되고, SELECT에 성공하게 된다.
  만약 누군가가 그 방에 UPDATE를 시도한다면 그 사람은 그 방에 U-Lock을 걸려고 시도하겠지만 U-Lock은 U-Lock과 호환되지 않으므로 Lock을 걸지 못하고 기다리게 된다. 말하자면 이 Update시도는 기존 트랜잭션이 끝날때까지 "Block"되게 되는 것이다.

  걸어잠그는 것과 걸어잠갔다고 써붙이는 것이 무슨 차이가 있냐고?

  이 방에 들어있는 Data에 접근할 때는 서로간의 약속이 되어 있다. 예를 들면 "Select를 시도할 때는 S-Lock을 걸어야 한다"라던지... "X-Lock이 걸려있을 때는 S-Lock을 걸 수 없다"라던지... 뭐 이런 약속들이다. 그런데 만약 이 약속을 어기는 사람이 있다면 어떻게 될까? 위에서 언급했듯이 실제로 방 문을 걸어잠그는 것이 아니기 때문에 약속을 무시하는 사람은 방에 마음대로 들어갈 수 있게 된다. 예를 들어서... 이미 X-Lock이 걸려있는 Row에

  1. SELECT * FROM TEST_TAB WHERE ID=3  
을 시도하면 BLOCK되겠지만
  1. SELECT * FROM TEST_TAB WITH (READUNCOMMITTED) WHERE ID=3  
를 시도하면 Data를 읽을 수 있게 된다. (WITH (READUNCOMMITTED) 라는 잠금 힌트는 Select할 때 S-Lock을 걸지 말라는 의미이다.)

현재 걸려있는 Lock을 확인하려면


을 실행하면 된다. 만약 특정 세션의 Lock 상태를 확인하려면 세션 ID를 인자로 주면 된다.
  1. EXEC SP_LOCK @@spid  

2. 잠금의 세기
  잠금의 "세기" 라는 것은 여러가지 종류의 Lock들 간의 "호환 관계"라고 정리할 수 있다. S-Lock과 U-Lock이 "호환된다"라 함은 "S-Lock"이 걸려있는 상태에서 제 3의 세션에서 그 데이터에 "U-Lock"을 걸 수 있다는 의미이다.

2.1 공유 잠금 (Shared-Lock, S-Lock)
  공유잠금은 가장 낮은 강도의 잠금으로서, 일반적으로 Select를 할 때 공유잠금이 발생하며, Select가 완료되는 즉시 공유잠금은 해제된다. 공유잠금은 서로 다른 공유잠금과 호환된다. 이 말은 바로 동일한 데이터를 서로 다른 세션에서 동시에 Select할 수 있다는 의미이다. 반면에 공유 잠금은 배타적잠금과는 호환되지 않는데 이 의미는 다른 트랜잭션에서 Update를 수행한 레코드(Uncommitted Data)에 대해 Select를 할 수 없다는 의미로 해석하면 된다.

2.2 배타적 잠금 (Exclusive-Lock, X-Lock)
  배타적잠금은 가장 높은 강도의 잠금으로서, Update가 행해진 시점부터 그 트랜잭션이 Commit될 때까지 배타적 잠금이 걸린다. 배타적 잠금은 다른 모든 종류의 잠금과 호환되지 않는다. 이 의미는 어떠한 약한 잠금이라도 걸려있는 레코드에 대해서는 Update가 불가능하며, 반대로 Update가 진행중인 레코드에 대해서는 Select를 포함한 어떠한 작업도 불가능하다는 의미가 된다.

2.3 업데이트 잠금 (Update-Lock, U-Lock)
  업데이트잠금은 공유잠금과 배타적잠금의 중간 강도의 잠금이다. 공유잠금과는 호환되지만 다른 업데이트잠금이나 배타적 잠금과는 호환되지 않는다. 일반적으로는 Update의 Filter(Where절)가 수행되는 단계에서 업데이트 잠금이 걸리며, Filter된 결과에 대해 실제로 Update를 시도할 때 업데이트잠금은 배타적 잠금으로 전환된다. (만약 테이블에 인덱스가 없거나 Where절이 인덱스를 탈 수 없게 되어 있다면, 테이블을 풀스캔하면서 모든 레코드에 업데이트잠금을 걸 것이다.)
  업데이트잠금은 잠금힌트를 통해 업데이트문이 아닌 Select문에도 걸 수 있다. 보통 컨버젼 데드락을 방지하기 위해 Select문에 업데이트 잠금을 거는 경우가 많다.
  1. SELECT ColA, ColB FROM TAB_NAME WITH (UPDLOCK) WHERE ColA = 'AA'  


3. 잠금의 크기

3.1 잠금의 크기
  잠금의 크기라 함은 어느 정도의 범위를 잠글 것인가에 관한 이야기이다. 대체로 Row Lock (Key Lock), Page Lock, Table Lock 정도가 있다.
  "잠금 비용"이란 잠금을 거는 과정에서 발생하는 성능 손실을 말한다. 만약 Lock을 걸어야 할 페이지가 너무 많다면, 차라리 Table 전체에 Lock을 거는 것이 "잠금 비용"이 훨씬 낮을 것이다.
  "동시성 비용"이란 잠금을 걸면서 동시성이 낮아져서 발생하는 성능 손실을 의미한다. Page Lock 여러개를 Table 하나로 대체하였다면 "잠금 비용"은 낮아지겠지만 대신 "동시성 비용"은 높아질 것이다.
  SQL Server는 "잠금 비용"과 "동시성 비용" 간에 균형을 적절히 고려하여 잠금의 범위를 결정하게 된다. 일반적으로 약 40%의 페이지에 Lock을 걸어야 한다면 테이블 Lock으로 대체된다고 한다
  잠금의 크기를 줄이기 위해서는 적절한 인덱스를 사용하는 것이 중요한데, 인덱스를 타지 못하여 Table Full Scan이 발생한다면 업데이트할 데이터를 찾는 과정에서 테이블 전체에 Lock을 걸게 되기 때문이다.

3.2 내재된 잠금 (Intent-Lock)
  내재된 잠금은 앞의 세가지 잠금과는 약간 다른 차원의 이야기이다. 내재된 잠금을 이해하기 위해서는 "잠금의 크기"를 먼저 이해해야 한다. 
  만약 세션1에서 어떤 Row에 잠금을 걸었다고 가정하자. 그 상태에서 세션2에서 그 Row가 속한 테이블 전체에 테이블잠금을 걸려고 시도하면 어떻게 될까? 당연히 테이블잠금이 걸리면 안된다. (만약 테이블락을 걸 수 있다면 세션1은 레코드잠금을 건 상태에서 다음 작업을 못한 채 꼼짝도 못하게 될 것이다. )
  그렇다면, 세션2는 어떻게 이 테이블의 Row 중 하나에 락이 걸려있다는 것을 알 수 있을까? 테이블에 락을 걸기 전에 모든 페이지와 모든 Row를 다 조사해야 할까? 실제로 SQL Server는 그 반대로 구현되어 있다. 즉, 세션 1이 그 Row에 업데이트 잠금을 걸 때, 해당 레코드가 속한 상위 페이지와 상위 테이블에 내재된 잠금을 함께 걸게 된다. 이렇게 함으로서 세션 2는 테이블에 락을 걸기 전에 해당 테이블만 확인해보면 락을 걸어도 될지를 결정할 수 있게 된다.
내재된 잠금은 IX, IU 와 같이 표기한다. (Intent Exclusive Lock, Intent Update Lock)

다음과 같이 테스트해보자.
  1. BEGIN TRAN   
  2.   
  3. UPDATE test   
  4. SET val = 2   
  5. WHERE pk = 100   
  6.   
  7. EXEC SP_LOCK @@spid  
SP_LOCK 프로시져로 살펴보았을 때 내재된 잠금은 다음과 같이 확인된다.

위의 결과는 52번 세션에서 인덱스(KEY)에 배타적잠금(Mode = X)를, 그것이 속한 페이지와 테이블에는 내재된 배타적잠금을 (Mode = IX)를 걸었다는 의미가 되겠다.


4. 잠금의 호환성 (Lock Compatibility)
  위의 잠금들 간의 호환 관계는 다음과 같은 간단한 테이블로 정리된다.
  위에서 언급한 바와 같이 Update Lock과 Shared Lock이 호환된다 함은 Update Lock이 걸려있는 페이지에 Shared Lock을 또 걸수 있다는 의미가 된다.

Requested mode

Existing granted mode

IS

S

U

IX

SIX

X

Intent shared (IS)

Yes

Yes

Yes

Yes

Yes

No

Shared (S)

Yes

Yes

Yes

No

No

No

Update (U)

Yes

Yes

No

No

No

No

Intent exclusive (IX)

Yes

No

No

Yes

No

No

Shared with intent exclusive (SIX)

Yes

No

No

No

No

No

Exclusive (X)

No

No

No

No

No

No




5. 잠금의 길이
  잠금의 길이란 잠금이 지속되는 시간을 의미한다. 
  일반적인 공유잠금(S-Lock)은 SELECT 문이 끝나면 자동으로 풀린다. 반면에 단독잠금(X-Lock)과 업데이트잠금(UPD-Lock)은 트랜잭션 종료시까지 지속된다.
  공유잠금(S-Lock)의 길이는 경우에 따라 달라질 수 있어, 격리 수준이 Serializable이나 Repeatable Read인 경우 S-Lock도 트랜잭션 종료시까지 지속된다. 


반응형

+ Recent posts

반응형