비 클러스터형 인덱스로 다른 행을 업데이트 할 때 교착 상태


13

id 필드에서 클러스터 및 비 클러스터형 인덱스를 사용할 때 잠금 동작이 다른 것을 알면서 교착 상태 문제를 해결하고 있습니다. 교착 상태 문제는 클러스터링 된 인덱스 또는 기본 키가 id 필드에 적용된 경우 해결 된 것으로 보입니다.

다른 행에 대해 하나 이상의 업데이트를 수행하는 다른 트랜잭션이 있습니다. 예를 들어 트랜잭션 A는 ID = a로 행만 업데이트하고 tx B는 ID = b로 행만 만집니다.

그리고 인덱스가 없으면 업데이트가 모든 행에 대한 업데이트 잠금을 획득하고 필요할 때 독점 잠금으로 은닉하여 결국 교착 상태로 이어진다는 것을 이해했습니다. 그러나 클러스터되지 않은 인덱스를 사용하는 경우 교착 상태가 여전히 존재하는 이유를 알 수 없습니다 (적중률이 떨어지는 것처럼 보입니다)

데이터 테이블 :

CREATE TABLE [dbo].[user](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [userName] [nvarchar](255) NULL,
    [name] [nvarchar](255) NULL,
    [phone] [nvarchar](255) NULL,
    [password] [nvarchar](255) NULL,
    [ip] [nvarchar](30) NULL,
    [email] [nvarchar](255) NULL,
    [pubDate] [datetime] NULL,
    [todoOrder] [text] NULL
)

교착 상태 추적

deadlock-list
deadlock victim=process4152ca8
process-list
process id=process4152ca8 taskpriority=0 logused=0 waitresource=RID: 5:1:388:29 waittime=3308 ownerId=252354 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.947 XDES=0xb0bf180 lockMode=U schedulerid=3 kpid=11392 status=suspended spid=57 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.953 lastbatchcompleted=2014-04-11T00:15:30.950 lastattention=1900-01-01T00:00:00.950 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252354 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=62 sqlhandle=0x0200000062f45209ccf17a0e76c2389eb409d7d970b0f89e00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(2)<c/>@owner int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
process id=process4153468 taskpriority=0 logused=4652 waitresource=KEY: 5:72057594042187776 (3fc56173665b) waittime=3303 ownerId=252344 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.920 XDES=0x4184b78 lockMode=U schedulerid=3 kpid=7272 status=suspended spid=58 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.960 lastbatchcompleted=2014-04-11T00:15:30.960 lastattention=1900-01-01T00:00:00.960 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252344 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=60 sqlhandle=0x02000000d4616f250747930a4cd34716b610a8113cb92fbc00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(61)<c/>@uid int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
resource-list
ridlock fileid=1 pageid=388 dbid=5 objectname=SQL2012_707688_webows.dbo.user id=lock3f7af780 mode=X associatedObjectId=72057594042122240
owner-list
owner id=process4153468 mode=X
waiter-list
waiter id=process4152ca8 mode=U requestType=wait
keylock hobtid=72057594042187776 dbid=5 objectname=SQL2012_707688_webows.dbo.user indexname=10 id=lock3f7ad700 mode=U associatedObjectId=72057594042187776
owner-list
owner id=process4152ca8 mode=U
waiter-list
waiter id=process4153468 mode=U requestType=wait

또한 흥미롭고 가능한 관련 발견은 클러스터형 및 비 클러스터형 인덱스의 잠금 동작이 서로 다른 것 같습니다

클러스터형 인덱스를 사용하는 경우 업데이트시 키에 대한 독점 잠금과 RID의 독점 잠금이 있습니다. 비 클러스터형 인덱스를 사용하는 경우 두 개의 서로 다른 RID에 대해 두 개의 배타적 잠금이 존재하므로 혼란 스럽습니다.

누군가도 이것에 대해 설명 할 수 있다면 도움이 될 것입니다.

SQL 테스트 :

use SQL2012_707688_webows;
begin transaction;
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
exec sp_lock;
commit;

클러스터형 인덱스로 ID를 사용하는 경우 :

spid    dbid    ObjId   IndId   Type    Resource    Mode    Status
53  5   917578307   1   KEY (b1a92fe5eed4)                      X   GRANT
53  5   917578307   1   PAG 1:879                               IX  GRANT
53  5   917578307   1   PAG 1:1928                              IX  GRANT
53  5   917578307   1   RID 1:879:7                             X   GRANT

비 클러스터형 인덱스로 id 사용

spid    dbid    ObjId   IndId   Type    Resource    Mode    Status
53  5   917578307   0   PAG 1:879                               IX  GRANT
53  5   917578307   0   PAG 1:1928                              IX  GRANT
53  5   917578307   0   RID 1:879:7                             X   GRANT
53  5   917578307   0   RID 1:1928:18                           X   GRANT

EDIT1 : 인덱스가없는 교착 상태에 대한 세부 사항
두 개의 tx A와 B가 있고 각각 두 개의 업데이트 문이 있고 행
tx A가 다릅니다 .

update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501

tx B

update [user] with (rowlock) set todoOrder='{3}' where id = 63502
update [user] with (rowlock) set todoOrder='{4}' where id = 63502

{1} 및 {4}는 교착 상태가 발생할 수 있습니다.

{1}에서, 테이블 스캔을 수행해야하기 때문에 행 63502에 대해 U 잠금이 요청되고 조건과 일치하므로 행 63501에서 X 잠금이 보유 될 수 있습니다.

{4}에서 행 63501에 대해 U 잠금이 요청되고 X 잠금이 이미 63502에 대해 보류됩니다.

txA는 63501을 보유하고 있으며 63502를 대기하고 txB는 63502를 대기하고 있으며 이는 교착 상태입니다.

EDIT2 : 내 테스트 케이스 의 버그가 여기에 차이가 있음을 나타 냅니다 혼란에 대한 죄송하지만 버그가 차이 상황을 만들어 결국 교착 상태를 일으키는 것으로 보입니다.

이 경우 Paul의 분석이 실제로 도움이되었으므로 답변으로 받아들입니다.

내 테스트 케이스의 버그로 인해 두 개의 트랜잭션 txA와 txB가 아래와 같이 동일한 행을 업데이트 할 수 있습니다.

tx A

update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501

tx B

update [user] with (rowlock) set todoOrder='{3}' where id = 63501

{2} 및 {3}은 다음과 같은 경우 교착 상태가 발생할 수 있습니다.

txA는 RID에 대한 X 잠금을 유지하면서 키에 대한 U 잠금을 요청합니다 ({1}의 업데이트로 인해). txB는 키에 대한 U 잠금을 유지하면서 RID에 대한 U 잠금을 요청합니다.


1
트랜잭션이 왜 같은 행을 두 번 업데이트해야하는지 알 수 없습니다.
ypercubeᵀᴹ

@ypercube 좋은 점, 그것은 내가 개선해야 할 것입니다. 그러나이 경우에는 잠금 동작에 대해 더 잘 이해하고 싶습니다.
Bood

더 많은 생각을 한 후에 @ypercube 복잡한 논리를 가진 응용 프로그램이 동일한 tx에서 동일한 행을 두 번 업데이트해야 할 수도 있다고 생각합니다. 예를 들어 다른
열일

답변:


16

... 클러스터형 인덱스를 사용하는 경우 교착 상태가 여전히 존재하는 이유 (적중률이 떨어지는 것 같습니다)

문제는 정확하게 분명하지 않다 (예를 들어, 얼마나 많은 업데이트되는 id값은 각 트랜잭션에)하지만, 하나 개의 명백한 교착 상태 시나리오의 중복이 하나의 트랜잭션 내에서 여러 단일 행 업데이트로 발생하는 [id]값 및 IDS는 다른 [id]순서로 업데이트 :

[T1]: Update id 2; Update id 1;
[T2]: Update id 1; Update id 2;

교착 상태 순서 : T1 (u2), T2 (u1), T1 (u1) 대기 , T2 (u2) 대기 .

이 교착 상태 순서는 각 트랜잭션 내에서 id 순서대로 엄격하게 업데이트하여 (같은 경로에서 동일한 순서로 잠금을 획득) 피할 수 있습니다.

클러스터형 인덱스를 사용하는 경우 업데이트시 키에 대한 독점 잠금과 RID의 독점 잠금이 있습니다. 비 클러스터형 인덱스를 사용하는 경우 두 개의 서로 다른 RID에 대해 두 개의 배타적 잠금이 존재하므로 혼란 스럽습니다.

고유 클러스터형 인덱스를 사용하면 id행 내 데이터에 대한 쓰기를 보호하기 위해 클러스터링 키에서 독점 잠금이 수행됩니다. RIDLOB text열에 대한 쓰기를 보호하려면 별도의 독점 잠금이 필요하며 , 기본적으로 별도의 데이터 페이지에 저장됩니다.

테이블이에 비 클러스터형 인덱스 만있는 힙인 id경우 두 가지가 발생합니다. 첫째, 하나의 RID독점 잠금은 힙 행 내 데이터와 관련이 있고 다른 하나는 이전과 같이 LOB 데이터에 대한 잠금입니다. 두 번째 효과는보다 복잡한 실행 계획이 필요하다는 것입니다.

클러스터형 인덱스와 간단한 단일 값 항등 술어 업데이트를 통해 쿼리 프로세서는 단일 경로를 사용하여 단일 연산자로 업데이트 (읽기 및 쓰기)를 수행하는 최적화를 적용 할 수 있습니다.

단일 운영자 업데이트

행은 단일 탐색 조작에서 찾아 갱신되며 독점 잠금 만 필요합니다 (업데이트 잠금 필요 없음). 샘플 테이블을 사용한 잠금 순서 예 :

acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IX lock on PAGE: 6:1:59104 -- INROW
acquiring X lock on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
acquiring IX lock on PAGE: 6:1:59091 -- LOB
acquiring X lock on RID: 6:1:59091:1 -- LOB

releasing lock reference on PAGE: 6:1:59091 -- LOB
releasing lock reference on RID: 6:1:59091:1 -- LOB
releasing lock reference on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
releasing lock reference on PAGE: 6:1:59104 -- INROW

비 클러스터형 인덱스 만 있으면 한 b- 트리 구조에서 읽고 다른 b- 트리 구조에서 써야하므로 동일한 최적화를 적용 할 수 없습니다. 다중 경로 계획에는 별도의 읽기 및 쓰기 단계가 있습니다.

다중 반복기 업데이트

이것은 읽을 때 갱신 잠금을 획득하고 행이 규정되면 독점 잠금으로 변환합니다. 주어진 스키마를 사용한 잠금 시퀀스 예 :

acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IU lock on PAGE: 6:1:59105 -- NC INDEX
acquiring U lock on KEY: 6:72057594233749504 (61a06abd401c) -- NC INDEX
acquiring IU lock on PAGE: 6:1:59104 -- HEAP
acquiring U lock on RID: 6:1:59104:1 -- HEAP
acquiring IX lock on PAGE: 6:1:59104 -- HEAP convert to X
acquiring X lock on RID: 6:1:59104:1 -- HEAP convert to X
acquiring IU lock on PAGE: 6:1:59091 -- LOB
acquiring U lock on RID: 6:1:59091:1 -- LOB

releasing lock reference on PAGE: 6:1:59091 
releasing lock reference on RID: 6:1:59091:1
releasing lock reference on RID: 6:1:59104:1
releasing lock reference on PAGE: 6:1:59104 
releasing lock on KEY: 6:72057594233749504 (61a06abd401c)
releasing lock on PAGE: 6:1:59105 

LOB 데이터는 테이블 업데이트 반복자에서 읽고 씁니다. 보다 복잡한 계획과 다중 읽기 및 쓰기 경로는 교착 상태의 가능성을 높입니다.

마지막으로, 테이블 정의에 사용 된 데이터 유형을 알아 차릴 수는 없습니다. 더 이상 사용되지 않는 text데이터 유형을 새 작업에 사용해서는 안됩니다 . 이 열에 최대 2GB의 데이터를 저장하는 기능이 실제로 필요한 경우 대안은 varchar(max)입니다. 사이의 한 가지 중요한 차이 textvarchar(max)그되고 text데이터가 오프 행 기본적으로 저장되어있는 반면 varchar(max)에 행 기본적으로 저장합니다.

유연성이 필요한 경우에만 유니 코드 유형을 사용하십시오 (예 : IP 주소에 유니 코드가 필요한 이유를 파악하기 어렵습니다). 또한 속성에 대한 적절한 길이 제한을 선택하십시오. 255 어디에서나 정확하지 않은 것 같습니다.

추가 자료 :
교착 상태 및 라이브 록 공통 패턴
Bart Duncan의 교착 상태 문제 해결 시리즈

다양한 방법으로 추적 잠금을 수행 할 수 있습니다. 고급 서비스가 포함 된 SQL Server Express ( 2014 및 2012 SP1 이상에만 해당 )에는 잠금 획득 및 해제에 대한 세부 정보를 볼 수있는 지원되는 방법 인 프로파일 러 도구 가 포함되어 있습니다 .


훌륭한 답변입니다. "acquiring ... lock"메시지와 "lock reference 잠금 해제"메시지가있는 로그 / 추적을 어떻게 출력합니까?
Sanjiv Jivan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.