การแก้ไขการหยุดชะงักจาก 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เพราะฉันเท่านั้นที่เคยต้องดูข้อมูลในตารางที่ว่าวิธีการ

ฉันยังมีดัชนี nonclustered ที่follow:

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

นอกจากนี้ฉันมีมุมมอง Schema-bound 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 และได้ตีความต่อไปนี้เกี่ยวกับการหยุดชะงัก:

SP1 กำลังรอการRangeS-Sล็อกคีย์บนIX_follow_member_id_includesดัชนีในขณะที่ SP2 เก็บการล็อค (X) ที่ขัดแย้งกัน

SP2 กำลังรอการSล็อคโหมดPK_member_activity ขณะที่ SP1 เก็บการล็อค (X) ที่ขัดแย้งกัน

การหยุดชะงักดูเหมือนจะเกิดขึ้นในบรรทัดสุดท้ายของแต่ละข้อความค้นหา (ส่วนแทรก) สิ่งที่ไม่ชัดเจนสำหรับฉันคือสาเหตุที่ 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>

ในกรณีนี้,

AssociObjectId 72057594098679808 สอดคล้องกับ member_activity, PK_member_activity

AssociObjectId 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_idเป็นรหัสต่างประเทศในmemberตาราง
  • member_activity.activity_idเป็นรหัสต่างประเทศในactivityตาราง
  • follow.member_idเป็นรหัสต่างประเทศในmemberตาราง
  • follow.follower_idเป็นรหัสต่างประเทศในmemberตาราง

อัปเดต 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's อย่างจริงใจเพื่อช่วยเพิ่มเติม
ฌอน Gallardy

@ SeanGallardy ขอบคุณ ฉันได้แก้ไขเพื่อรวมเอาท์พุท 1222 ในกรณีที่ฉันตีความผิดและฉันได้เพิ่มรายละเอียดเพิ่มเติมเกี่ยวกับสิ่งที่ SP กำลังทำอยู่ สิ่งนี้ช่วยได้ไหม?
Leland Richardson

2
@SeanGallardy ส่วนหนึ่งของแผนแบบสอบถามที่รักษามุมมองการจัดทำดัชนีทำงานภายในที่SERIALIZABLE(มีบิตมากกว่านั้น แต่นี่คือความคิดเห็นที่ไม่ใช่คำตอบ :)
Paul White 9

@ PaulWhite ขอบคุณสำหรับความเข้าใจฉันไม่ทราบว่า! ทำการทดสอบอย่างรวดเร็วฉันสามารถรับโหมดการล็อคแบบอนุกรมได้ด้วยมุมมองที่มีการจัดทำดัชนีระหว่างการแทรกในขั้นตอนการจัดเก็บของคุณ (RangeI-N, RangeS-S, RangeS-U) ดูเหมือนว่าการหยุดชะงักเกิดขึ้นจากโหมดล็อคที่เข้ากันไม่ได้ซึ่งจะกระทบกันระหว่างการแทรกในขั้นตอนการจัดเก็บของคุณเมื่ออยู่ในขอบเขตล็อค (ตัวอย่างเช่นในพื้นที่ที่อยู่ในช่วงล็อค) ทั้งเวลาและการชนกันของข้อมูลอินพุตฉันคิดว่า
ฌอน Gallardy

คำถาม: ถ้าฉันเพิ่มคำแนะนำ HOLDLOCK ในคำสั่ง SELECT การป้องกันจะเกิดขึ้นเมื่อมีการแทรกหรือไม่
Leland Richardson

คำตอบ:


5

ความขัดแย้งจะลดลงไปnetwork_activityเป็นมุมมองที่จัดทำดัชนีซึ่งจะต้องได้รับการดูแลรักษา (ภายใน) ทั่วทั้งงบ DML นี่เป็นสาเหตุที่ SP1 ต้องการล็อคIX_follow-member_id_includesดัชนีเนื่องจากอาจถูกใช้โดยมุมมอง (ดูเหมือนจะเป็นดัชนีครอบคลุมสำหรับมุมมอง)

สองตัวเลือกที่เป็นไปได้:

  1. พิจารณาปล่อยดัชนีแบบคลัสเตอร์บนมุมมองเพื่อไม่ให้เป็นมุมมองแบบดัชนี ประโยชน์ของการมีค่าบำรุงรักษาเกินดุลหรือไม่? คุณเลือกจากมันบ่อยเพียงพอหรือมีการเพิ่มประสิทธิภาพการทำดัชนีหรือไม่? หากคุณเรียกใช้ procs เหล่านี้ค่อนข้างบ่อยค่าใช้จ่ายอาจสูงกว่าประโยชน์หรือไม่

  2. หากประโยชน์ของการมีการจัดทำดัชนีมุมมองนั้นมีค่ามากกว่าต้นทุนให้พิจารณาการแยกการดำเนินการ DML กับตารางฐานของมุมมองนั้น สิ่งนี้สามารถทำได้ผ่านการใช้ Application Locks (ดูsp_getapplockและsp_releaseapplock ) ล็อคแอปพลิเคชันช่วยให้คุณสร้างล็อครอบแนวคิดโดยพลการ ความหมายคุณสามารถกำหนด@Resourceเป็น "network_activity" ใน Procs ที่เก็บไว้ซึ่งจะบังคับให้รอการเปิดของพวกเขา แต่ละ proc จะเป็นไปตามโครงสร้างเดียวกัน:

    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ค่า ดูเหมือนจะไม่ได้นำไปใช้กับสถานการณ์เฉพาะนี้ แต่ฉันได้เห็นมันใช้แบบนั้นและมันค่อนข้างมีประโยชน์โดยเฉพาะอย่างยิ่งในระบบหลายผู้เช่าที่คุณต้องการ จำกัด กระบวนการในการเธรดเดียวต่อลูกค้าแต่ละราย แต่ ยังคงมีหลายเธรดข้ามลูกค้า
โซโลมอน Rutzky

ฉันต้องการอัปเดตและบอกว่าสิ่งนี้ได้ผลในสภาพแวดล้อมการผลิตของเรา :)
Leland Richardson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.