รวมคำสั่ง deadlocking ตัวเอง


22

ฉันมีขั้นตอนดังต่อไปนี้ (SQL Server 2008 R2):

create procedure usp_SaveCompanyUserData
    @companyId bigint,
    @userId bigint,
    @dataTable tt_CoUserdata readonly
as
begin

    set nocount, xact_abort on;

    merge CompanyUser with (holdlock) as r
    using (
        select 
            @companyId as CompanyId, 
            @userId as UserId, 
            MyKey, 
            MyValue
        from @dataTable) as newData
    on r.CompanyId = newData.CompanyId
        and r.UserId = newData.UserId
        and r.MyKey = newData.MyKey
    when not matched then
        insert (CompanyId, UserId, MyKey, MyValue) values
        (@companyId, @userId, newData.MyKey, newData.MyValue);

end;

CompanyId, UserId, MyKey สร้างคีย์ผสมสำหรับตารางเป้าหมาย CompanyId เป็น foreign key ไปยังตารางหลัก CompanyId asc, UserId ascนอกจากนี้ยังมีดัชนีที่ไม่ใช่คลัสเตอร์บน

มันถูกเรียกจากหลาย ๆ กระทู้และฉันได้รับการหยุดชะงักอย่างต่อเนื่องระหว่างกระบวนการต่าง ๆ ที่เรียกใช้คำสั่งเดียวกันนี้ ฉันเข้าใจว่าจำเป็นต้องใช้ "with (holdlock)" เพื่อป้องกันข้อผิดพลาดของการแทรก / อัปเดตการแข่งขัน

ฉันคิดว่าสองกระทู้ที่แตกต่างกันคือการล็อคแถว (หรือหน้า) ในคำสั่งที่แตกต่างกันเมื่อพวกเขากำลังตรวจสอบข้อ จำกัด และทำให้เป็น deadlock

นี่เป็นข้อสมมติฐานที่ถูกต้องหรือไม่?

เป็นวิธีที่ดีที่สุดในการแก้ไขสถานการณ์นี้ (เช่นไม่มีการหยุดชะงักผลกระทบขั้นต่ำในการทำงานแบบมัลติเธรด) คืออะไร?

รูปภาพแผนแบบสอบถาม (หากคุณดูภาพในแท็บใหม่จะสามารถอ่านได้ขออภัยขนาดเล็ก)

  • มีมากที่สุด 28 แถวอยู่ใน @datatable
  • ฉันย้อนกลับไปดูรหัสแล้วและฉันไม่สามารถเห็นธุรกรรมใด ๆ ที่เราเริ่มทำธุรกรรมได้ที่นี่
  • คีย์ต่างประเทศถูกตั้งค่าเป็นเรียงซ้อนเมื่อลบเท่านั้นและไม่มีการลบออกจากตารางหลัก

คำตอบ:


12

โอเคหลังจากมองทุกอย่างไปสองสามครั้งฉันคิดว่าสมมติฐานพื้นฐานของคุณถูกต้อง สิ่งที่อาจเกิดขึ้นที่นี่ก็คือ:

  1. ส่วน MATCH ของ MERGE จะตรวจสอบดัชนีสำหรับการจับคู่อ่านล็อกแถว / หน้าเหล่านั้นเมื่อมันไป

  2. เมื่อมีแถวที่ไม่มีการจับคู่มันจะพยายามแทรกดัชนีแถวใหม่ก่อนเพื่อที่จะขอล็อกการเขียนแถว / หน้า ...

แต่หากผู้ใช้รายอื่นได้เข้าสู่ขั้นตอนที่ 1 ในแถว / หน้าเดียวกันผู้ใช้คนแรกจะถูกบล็อกจากการอัปเดตและ ...

หากผู้ใช้คนที่สองต้องแทรกในหน้าเดียวกันแสดงว่าพวกเขาอยู่ในการหยุดชะงัก

AFAIK มีเพียงวิธีเดียว (ง่าย) ที่จะแน่ใจได้ 100% ว่าคุณไม่สามารถหยุดชะงักได้ด้วยขั้นตอนนี้และนั่นคือการเพิ่มคำใบ้ TABLOCKX ให้กับ MERGE แต่นั่นอาจส่งผลเสียต่อประสิทธิภาพการทำงาน

เป็นไปได้ว่าการเพิ่มคำแนะนำ TABLOCK แทนจะเพียงพอที่จะแก้ปัญหาโดยไม่ต้องมีผลกระทบต่อประสิทธิภาพการทำงานของคุณ

สุดท้ายคุณสามารถลองเพิ่ม PAGLOCK, XLOCK หรือทั้ง PAGLOCK และ XLOCK อีกครั้งที่อาจทำงานและประสิทธิภาพอาจไม่น่ากลัวเกินไป คุณจะต้องลองดู


คุณคิดว่าระดับการแยกสแน๊ปช็อต (การกำหนดเวอร์ชันแถว) อาจมีประโยชน์หรือไม่
Mikael Eriksson

อาจจะ. หรืออาจเปลี่ยนข้อยกเว้นการหยุดชะงักเป็นข้อยกเว้นพร้อมกัน
RBarryYoung

2
การระบุคำใบ้ TABLOCK บนตารางที่เป็นเป้าหมายของคำสั่ง INSERT จะมีผลเช่นเดียวกับการระบุคำใบ้ TABLOCKX (ที่มา: msdn.microsoft.com/en-us/library/bb510625.aspx )
tuespetre

31

จะไม่มีปัญหาหากตัวแปร table มีค่าเพียงค่าเดียวเท่านั้น มีหลายแถวมีความเป็นไปได้ใหม่สำหรับการหยุดชะงัก สมมติว่ากระบวนการที่เกิดขึ้นพร้อมกันสองตัว (A & B) ทำงานกับตัวแปรตารางที่มี (1, 2) และ (2, 1) สำหรับ บริษัท เดียวกัน

กระบวนการ A อ่านปลายทางไม่พบแถวและใส่ค่า '1' มันถือล็อคแถวพิเศษกับค่า '1' กระบวนการ B อ่านปลายทางไม่พบแถวและใส่ค่า '2' มันถือล็อคแถวพิเศษกับค่า '2'

ตอนนี้โปรเซส A ต้องการประมวลผลแถวที่ 2 และโปรเซส B จำเป็นต้องดำเนินการกับแถวที่ 1 กระบวนการทั้งสองไม่สามารถดำเนินการได้เนื่องจากต้องใช้การล็อกที่เข้ากันไม่ได้กับการล็อคแบบเอกสิทธิ์เฉพาะบุคคลที่ถือโดยกระบวนการอื่น

เพื่อหลีกเลี่ยงการติดตายกับแถวหลายแถวที่จะต้องมีการประมวลผล (และตารางการเข้าถึง) ในลำดับเดียวกันทุกครั้ง ตัวแปรตารางในแผนการดำเนินการที่แสดงในคำถามคือฮีปดังนั้นแถวไม่มีลำดับที่แท้จริง (มีแนวโน้มที่จะอ่านตามลำดับการแทรกแม้ว่าจะไม่รับประกัน)

แผนปัจจุบัน

การขาดลำดับการประมวลผลแถวที่สอดคล้องจะนำไปสู่โอกาสการหยุดชะงักโดยตรง ข้อพิจารณาที่สองคือการขาดการรับประกันความเป็นเอกลักษณ์ที่สำคัญหมายความว่า Table Spool จำเป็นต่อการป้องกันฮาโลวีนที่ถูกต้อง สปูลเป็นสปูลกระตือรือร้นซึ่งหมายความว่าทุกแถวจะถูกเขียนไปยังโต๊ะทำงานtempdbก่อนที่จะอ่านและเล่นซ้ำสำหรับตัวดำเนินการแทรก

นิยามใหม่TYPEของตัวแปร table เพื่อรวมคลัสเตอร์PRIMARY KEY:

DROP TYPE dbo.CoUserData;

CREATE TYPE dbo.CoUserData
AS TABLE
(
    MyKey   integer NOT NULL PRIMARY KEY CLUSTERED,
    MyValue integer NOT NULL
);

แผนการดำเนินการในขณะนี้แสดงการสแกนของดัชนีคลัสเตอร์และการรับประกันความเป็นเอกลักษณ์หมายความว่าเครื่องมือเพิ่มประสิทธิภาพสามารถลบ Table Spool ได้อย่างปลอดภัย:

ด้วยรหัสหลัก

ในการทดสอบด้วยMERGEคำสั่ง5,000 ซ้ำของคำสั่งบน 128 เธรดจะไม่มีการหยุดชะงักเกิดขึ้นกับตัวแปรตารางคลัสเตอร์ ฉันควรจะเน้นว่านี่เป็นเพียงการสังเกตเท่านั้น ตัวแปรตารางคลัสเตอร์ยังสามารถสร้างเทคนิคในแถวของคำสั่งที่หลากหลาย แต่โอกาสของคำสั่งที่สอดคล้องกันนั้นได้รับการปรับปรุงอย่างมาก ลักษณะการทำงานที่สังเกตจะต้องมีการ retested สำหรับการปรับปรุงสะสมเซอร์วิสแพ็คหรือ SQL Server รุ่นใหม่ทุกครั้ง

ในกรณีที่นิยามตัวแปรตารางไม่สามารถเปลี่ยนแปลงได้มีทางเลือกอื่น:

MERGE dbo.CompanyUser AS R
USING 
    (SELECT DISTINCT MyKey, MyValue FROM @DataTable) AS NewData ON
    R.CompanyId = @CompanyID
    AND R.UserID = @UserID
    AND R.MyKey = NewData.MyKey
WHEN NOT MATCHED THEN 
    INSERT 
        (CompanyID, UserID, MyKey, MyValue) 
    VALUES
        (@CompanyID, @UserID, NewData.MyKey, NewData.MyValue)
OPTION (ORDER GROUP);

นอกจากนี้ยังสามารถกำจัดสปูล (และความสอดคล้องของคำสั่งแถว) ได้ด้วยค่าใช้จ่ายในการแนะนำการจัดเรียงที่ชัดเจน:

จัดเรียงแผน

แผนนี้ไม่มีการหยุดชะงักโดยใช้การทดสอบเดียวกัน สคริปต์การสืบพันธุ์ด้านล่าง:

CREATE TYPE dbo.CoUserData
AS TABLE
(
    MyKey   integer NOT NULL /* PRIMARY KEY */,
    MyValue integer NOT NULL
);
GO
CREATE TABLE dbo.Company
(
    CompanyID   integer NOT NULL

    CONSTRAINT PK_Company
        PRIMARY KEY (CompanyID)
);
GO
CREATE TABLE dbo.CompanyUser
(
    CompanyID   integer NOT NULL,
    UserID      integer NOT NULL,
    MyKey       integer NOT NULL,
    MyValue     integer NOT NULL

    CONSTRAINT PK_CompanyUser
        PRIMARY KEY CLUSTERED
            (CompanyID, UserID, MyKey),

    FOREIGN KEY (CompanyID)
        REFERENCES dbo.Company (CompanyID),
);
GO
CREATE NONCLUSTERED INDEX nc1
ON dbo.CompanyUser (CompanyID, UserID);
GO
INSERT dbo.Company (CompanyID) VALUES (1);
GO
DECLARE 
    @DataTable AS dbo.CoUserData,
    @CompanyID integer = 1,
    @UserID integer = 1;

INSERT @DataTable
SELECT TOP (10)
    V.MyKey,
    V.MyValue
FROM
(
    VALUES
        (1, 1),
        (2, 2),
        (3, 3),
        (4, 4),
        (5, 5),
        (6, 6),
        (7, 7),
        (8, 8),
        (9, 9)
) AS V (MyKey, MyValue)
ORDER BY NEWID();

BEGIN TRANSACTION;

    -- Test MERGE statement here

ROLLBACK TRANSACTION;

8

ฉันคิดว่า SQL_Kiwi ให้การวิเคราะห์ที่ดีมาก หากคุณต้องการแก้ปัญหาในฐานข้อมูลคุณควรทำตามคำแนะนำของเขา แน่นอนคุณต้องทดสอบอีกครั้งว่ามันยังคงใช้ได้สำหรับคุณทุกครั้งที่คุณอัพเกรดใช้เซอร์วิสแพ็คหรือเพิ่ม / เปลี่ยนดัชนีหรือมุมมองที่จัดทำดัชนี

มีสามทางเลือกอื่น ๆ :

  1. คุณสามารถเรียงลำดับเม็ดมีดของคุณเพื่อไม่ให้ชนกัน: คุณสามารถเรียกใช้ sp_getapplock ที่จุดเริ่มต้นของการทำธุรกรรมของคุณและได้รับการล็อคแบบเอกสิทธิ์เฉพาะบุคคลก่อนที่จะดำเนินการผสาน แน่นอนคุณยังคงต้องทดสอบความเครียด

  2. คุณสามารถมีเธรดเดียวจัดการส่วนแทรกทั้งหมดของคุณเพื่อให้เซิร์ฟเวอร์แอปของคุณจัดการพร้อมกัน

  3. คุณสามารถลองอีกครั้งโดยอัตโนมัติหลังจากการหยุดชะงัก - นี่อาจเป็นวิธีที่ช้าที่สุดหากมีการเกิดพร้อมกันสูง

ทั้งสองวิธีมีเพียงคุณเท่านั้นที่สามารถกำหนดผลกระทบของโซลูชันที่มีต่อประสิทธิภาพ

โดยทั่วไปเราไม่มีการหยุดชะงักในระบบของเราเลยถึงแม้ว่าเราจะมีศักยภาพมากมายในการมีพวกเขา ในปี 2011 เราทำผิดพลาดในการปรับใช้ครั้งเดียวและมีการหยุดชะงักครึ่งโหลเกิดขึ้นในไม่กี่ชั่วโมงทั้งหมดนี้เกิดขึ้นในสถานการณ์เดียวกัน ฉันแก้ไขมันในไม่ช้าและนั่นคือการหยุดชะงักทั้งหมดสำหรับปี

เราส่วนใหญ่ใช้วิธีที่ 1 ในระบบของเรา มันใช้งานได้ดีจริง ๆ สำหรับเรา


-1

อีกวิธีหนึ่งที่เป็นไปได้ - ฉันพบว่ามีบางครั้งที่จะนำเสนอปัญหาการล็อกและประสิทธิภาพ - มันอาจคุ้มค่าที่จะเล่นกับตัวเลือกแบบสอบถาม (MaxDop x)

ในที่มืดและไกลที่ผ่านมา SQL Server มีตัวเลือกแทรกระดับแถวล็อค - แต่ดูเหมือนว่าจะตายตาย แต่ PK คลัสเตอร์ที่มีเอกลักษณ์ควรทำให้แทรกทำงานสะอาด

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