인덱싱 된 뷰를 통해서만 관련된 2 개의 테이블에서 교착 상태 해결


17

교착 상태가 발생하는 상황이 있는데 범인을 좁힌 것으로 생각되지만 문제를 해결하기 위해 어떻게해야할지 잘 모르겠습니다.

이것은 SQL Server 2008 R2를 실행하는 프로덕션 환경에 있습니다.

상황에 대해 약간 단순화 된보기를 제공하려면 다음을 수행하십시오.


아래 정의 된 3 개의 테이블이 있습니다.

TABLE activity (
    id, -- PK
    ...
)

TABLE member_activity (
    member_id, -- PK col 1
    activity_id, -- PK col 2
    ...
)

TABLE follow (
    id, -- PK
    follower_id,
    member_id,
    ...
)

member_activity테이블은 기본 키가 정의 화합물을 가지고 member_id, activity_id나는 오직 그 테이블 방식으로 그 데이터를 검색해야하기 때문에.

또한 클러스터되지 않은 인덱스가 있습니다 follow.

CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes] 
ON follow ( member_id ASC ) INCLUDE ( follower_id )

또한 network_activity다음과 같이 정의 된 스키마 바운드 뷰 가 있습니다.

CREATE VIEW network_activity
WITH SCHEMABINDING
AS

SELECT
    follow.follower_id as member_id,
    member_activity.activity_id as activity_id,
    COUNT_BIG(*) AS cb
FROM member_activity
INNER JOIN follow ON follow.member_id = member_activity.member_id
INNER JOIN activity ON activity.id = member_activity.activity_id
GROUP BY follow.follower_id, member_activity.activity_id

고유 한 클러스터형 인덱스도 있습니다.

CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id] 
ON network_activity
(
    member_id ASC,
    activity_id ASC
)

이제 두 개의 교착 상태 저장 프로 시저가 있습니다. 그들은 다음 과정을 거칩니다 :

-- SP1: insert activity
-----------------------
INSERT INTO activity (...)
SELECT ... FROM member_activity WHERE member_id = @a AND activity_id = @b
INSERT INTO member_activity (...)


-- SP2: insert follow
---------------------
SELECT follow WHERE member_id = @x AND follower_id = @y
INSERT INTO follow (...)

이 두 절차는 모두 READ COMMITTED 격리에서 실행됩니다. 1222 확장 이벤트 출력을 쿼리하고 교착 상태와 관련하여 다음을 해석했습니다.

SP2가 충돌하는 (X) 잠금을 유지하는 동안 SP1 RangeS-SIX_follow_member_id_includes인덱스 에서 키 잠금을 기다리고 있습니다.

SP1이 충돌하는 (X) 잠금을 유지하는 동안 SP2가 S모드 잠금을 대기 중입니다.PK_member_activity

교착 상태는 각 쿼리의 마지막 줄 (삽입)에서 발생하는 것으로 보입니다. SP1이 IX_follow-member_id_includes인덱스 에 대한 잠금을 원하는 이유는 분명하지 않습니다 . 나에게 유일한 링크는이 색인보기에서 나온 것으로 보이므로 내가 포함 시켰습니다.

이러한 교착 상태가 발생하지 않도록하는 가장 좋은 방법은 무엇입니까? 도움을 주시면 감사하겠습니다. 교착 상태 문제를 해결 한 경험이 많지 않습니다.

도움이 될만한 더 많은 정보가 있으면 알려주세요!

미리 감사드립니다.


편집 1 : 요청마다 정보를 추가하십시오.

이 교착 상태의 1222 출력은 다음과 같습니다.

<deadlock>
    <victim-list>
        <victimProcess id="process4c6672748" />
    </victim-list>
    <process-list>
        <process id="process4c6672748" taskpriority="0" logused="332" waitresource="KEY: 8:72057594104905728 (25014f77eaba)" waittime="581" ownerId="474698706" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.287" XDES="0x298487970" lockMode="RangeS-S" schedulerid="1" kpid="972" status="suspended" spid="79" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T10:25:00.283" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698706" currentdb="8" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="" line="7" stmtstart="1194" stmtend="1434" sqlhandle="0x02000000a26bb72a2b220406876cad09c22242e5265c82e6" />
                <frame procname="" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000" />
            </executionStack>
            <inputbuf> <!-- SP 1 --> </inputbuf>
        </process>
        <process id="process6cddc5b88" taskpriority="0" logused="456" waitresource="KEY: 8:72057594098679808 (89013169fc76)" waittime="567" ownerId="474698698" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.283" XDES="0x30c459970" lockMode="S" schedulerid="4" kpid="4204" status="suspended" spid="70" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T15:04:55.870" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698698" currentdb="8" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
            <executionStack>
                <frame procname="" line="18" stmtstart="942" stmtend="1250" sqlhandle="0x03000800ca458d315ee9130100a300000100000000000000" />
            </executionStack>
            <inputbuf> <!-- SP 2 --> </inputbuf>
        </process>
    </process-list>
    <resource-list>
        <keylock hobtid="72057594104905728" dbid="8" objectname="" indexname="" id="lock33299fc00" mode="X" associatedObjectId="72057594104905728">
            <owner-list>
                <owner id="process6cddc5b88" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process4c6672748" mode="RangeS-S" requestType="wait" />
            </waiter-list>
        </keylock>
        <keylock hobtid="72057594098679808" dbid="8" objectname="" indexname="" id="lockb7e2ba80" mode="X" associatedObjectId="72057594098679808">
            <owner-list>
                <owner id="process4c6672748" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process6cddc5b88" mode="S" requestType="wait" />
            </waiter-list>
        </keylock>
    </resource-list>
</deadlock>

이 경우

associatedObjectId 72057594098679808는 member_activity, PK_member_activity

associatedObjectId 72057594104905728가 follow, IX_follow_member_id_includes

또한 SP1 및 SP2의 기능에 대한보다 정확한 그림이 있습니다.

-- SP1: insert activity
-----------------------
DECLARE @activityId INT

INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)

SET @activityId = SCOPE_IDENTITY();

IF NOT EXISTS(
    SELECT TOP 1 member_id 
    FROM member_activity 
    WHERE member_id = @m1 AND activity_id = @activityId
)
    INSERT INTO member_activity (member_id, activity_id, field1)
    VALUES (@m1, @activityId, @field1)

IF NOT EXISTS(
    SELECT TOP 1 member_id 
    FROM member_activity 
    WHERE member_id = @m2 AND activity_id = @activityId
)
    INSERT INTO member_activity (member_id, activity_id, field1)
    VALUES (@m2, @activityId, @field1)

SP2도 :

-- SP2: insert follow
---------------------

IF NOT EXISTS(
    SELECT TOP 1 1 
    FROM follow
    WHERE member_id = @memberId AND follower_id = @followerId
)
    INSERT INTO follow (member_id, follower_id)
    VALUES (@memberId, @followerId)

편집 2 : 주석을 다시 읽은 후 외래 키 열에 대한 정보를 추가 할 것이라고 생각했습니다 ...

  • member_activity.member_idmember테이블 의 외래 키입니다
  • member_activity.activity_idactivity테이블 의 외래 키입니다
  • follow.member_idmember테이블 의 외래 키입니다
  • follow.follower_idmember테이블 의 외래 키입니다

업데이트 1 :

나는 교착 상태를 예방하는 데 도움이 될만한 몇 가지 변경 사항을 운없이 내 렸습니다.

내가 변경 한 내용은 다음과 같습니다.

-- SP1: insert activity
-----------------------
DECLARE @activityId INT

INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)

SET @activityId = SCOPE_IDENTITY();

MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m1 as member_id, @activityId as activity_id, @field1 as field1) as source
    ON target.member_id = source.member_id
    AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
    INSERT (member_id, activity_id, field1)
    VALUES (source.member_id, source.activity_id, source.field1)
;

MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m2 as member_id, @activityId as activity_id, @field1 as field1) as source
    ON target.member_id = source.member_id
    AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
    INSERT (member_id, activity_id, field1)
    VALUES (source.member_id, source.activity_id, source.field1)
;

SP2의 경우 :

-- SP2: insert follow
---------------------

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION

IF NOT EXISTS(
    SELECT TOP 1 1 
    FROM follow WITH ( UPDLOCK )
    WHERE member_id = @memberId AND follower_id = @followerId
)
    INSERT INTO follow (member_id, follower_id)
    VALUES (@memberId, @followerId)

COMMIT

이 두 가지 변경으로 여전히 교착 상태가 발생하는 것 같습니다.

제공 할 수있는 다른 것이 있으면 알려주세요. 감사.


커밋 된 읽기는 키 범위 잠금을 사용하지 않으며 직렬화 가능합니다. 교착 상태가 실제로 읽기 커밋을 표시하면 (2) 내 주장에 따르면 외래 키를 변경하여 액세스 할 수 있습니다 (여전히 읽기 커밋이라고 함). 우리는 솔직히 전체 ddl과 sp가 필요합니다.
Sean Gallardy

@SeanGallardy, 감사합니다. 내가 잘못 해석하는 경우를 대비하여 1222 출력을 포함하도록 편집했으며 SP가 수행하는 작업에 대한 자세한 내용을 추가했습니다. 도움이 되나요?
Leland Richardson

2
@SeanGallardy 인덱싱 된 뷰를 유지 관리하는 쿼리 계획의 일부는 내부적으로 실행됩니다 SERIALIZABLE(그것보다 조금 더 있지만 응답이 아닙니다 :)
Paul White 9

@PaulWhite 통찰력에 감사드립니다, 나는 그것을 몰랐습니다! 빠른 테스트를 수행하면 저장 프로 시저 (RangeI-N, RangeS-S, RangeS-U)에 삽입하는 동안 인덱스 된 뷰로 직렬화 가능한 잠금 모드를 확실히 얻을 수 있습니다. 저장 프로 시저의 인서트가 잠금 경계 안에 들어간 경우 (예 : 범위 잠금으로 유지되는 영역에서) 서로 맞지 않는 잠금 모드에서 교착 상태가 발생하는 것처럼 보입니다. 타이밍과 입력 데이터 충돌 모두 생각합니다.
Sean Gallardy

질문 : SELECT 문에 HOLDLOCK 힌트를 추가하면 삽입시 잠금이 발생하지 않습니까?
Leland Richardson

답변:


5

충돌 network_activity은 DML 문에서 (내부적으로) 유지되어야하는 인덱싱 된 뷰로 요약됩니다 . 이것이 SP1이 IX_follow-member_id_includesView에서 사용했을 때 인덱스 에 대한 잠금을 원하는 이유 일 것입니다 (View 에 대한 인덱스로 보입니다).

두 가지 가능한 옵션 :

  1. 더 이상 인덱싱 된 뷰가되지 않도록 뷰에서 클러스터형 인덱스를 삭제하는 것이 좋습니다. 유지 관리 비용보다 이점이 있습니까? 충분히 자주 선택합니까, 아니면 가치가있는 색인의 성능 이점이 있습니까? 이러한 procs를 다소 자주 실행하면 비용이 이익보다 높을 수 있습니다.

  2. View의 인덱스 생성 이점이 비용보다 큰 경우 해당 View의 기본 테이블에 대해 DML 작업을 격리하는 것이 좋습니다. 이것은 응용 프로그램 잠금을 사용하여 수행 할 수 있습니다 ( sp_getapplocksp_releaseapplock 참조 ). 응용 프로그램 잠금을 사용하면 임의의 개념에 대한 잠금을 만들 수 있습니다. 즉, @Resource저장된 Procs 모두에서 "network_activity"로 정의하여 차례를 기다리게 할 수 있습니다. 각 프로세스는 동일한 구조를 따릅니다.

    BEGIN TRANSACTION;
    EXEC sp_getapplock @Resource = 'network_activity', @LockMode = 'Exclusive';
    ...current proc code...
    EXEC sp_releaseapplock @Resource = 'network_activity';
    COMMIT TRANSACTION;

    ROLLBACK링크 된 MSDN 설명서에 명시된대로 오류 / 자체 를 관리해야 하므로 일반적인 내용을 입력하십시오 TRY...CATCH. 그러나이를 통해 상황을 관리 할 수 ​​있습니다.
    참고 : sp_getapplock / sp_releaseapplock드물게 사용해야합니다. 응용 프로그램 잠금은 매우 편리 할 수 ​​있지만 (예 : 이와 같은 경우) 반드시 필요한 경우에만 사용해야합니다.


도와 주셔서 감사합니다. 옵션 # 2에 대해 조금 더 읽어보고 그것이 우리에게 효과가 있는지 살펴 보겠습니다. 뷰는 꽤 많이 읽었으며 클러스터형 인덱스는 꽤 큰 도움이됩니다 ... 그래서 아직 제거하지는 않겠습니다. 이 샷을 한 번에 다시 업데이트하겠습니다.
Leland Richardson

sp_getapplock을 사용하면 효과가 있다고 생각합니다. 아직 프로덕션 환경에서 시도해 볼 수 없었지만 현상금이 만료되기 전에 현상금을 수여했는지 확인하고 싶었습니다. 작동하는지 확인할 수있을 때 업데이트하겠습니다!
Leland Richardson

감사. Application Locks의 좋은 점 중 하나 member_id@Resource값 으로 연결하는 세분화 수준을 변경할 수 있다는 것 입니다. 그것은이 특정 상황에는 적용되지 않는 것 같지만 그것을 사용하는 것을 보았습니다. 특히 프로세스를 고객별로 단일 스레드로 제한하려는 다중 테넌트 시스템에서 매우 편리하지만 여전히 여러 고객에게 멀티 스레드되어 있습니다.
Solomon Rutzky

내가 업데이 트를주고 싶어하고이 말 않은 우리의 생산 환경에서 작업 끝. :)
Leland Richardson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.