Hợp nhất tuyên bố bế tắc chính nó


22

Tôi có quy trình sau (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 tạo thành khóa tổng hợp cho bảng đích. CompanyId là khóa ngoại đối với bảng cha. Ngoài ra, có một chỉ mục không cụm trên CompanyId asc, UserId asc.

Nó được gọi từ nhiều luồng khác nhau và tôi liên tục gặp bế tắc giữa các quy trình khác nhau gọi cùng một tuyên bố này. Tôi hiểu rằng "với (khóa)" là cần thiết để ngăn chặn lỗi chèn / cập nhật điều kiện cuộc đua.

Tôi giả sử rằng hai luồng khác nhau đang khóa các hàng (hoặc trang) theo các thứ tự khác nhau khi chúng xác thực các ràng buộc và do đó đang bế tắc.

Đây có phải là một giả định chính xác?

Cách tốt nhất để giải quyết tình huống này (nghĩa là không có bế tắc, tác động tối thiểu đến hiệu suất đa luồng) là gì?

Kế hoạch truy vấn hình ảnh (Nếu bạn xem hình ảnh trong một tab mới, nó có thể đọc được. Xin lỗi vì kích thước nhỏ.)

  • Có nhiều nhất là 28 hàng trong @datitable.
  • Tôi đã truy ngược lại mã và tôi không thể thấy bất cứ nơi nào chúng tôi bắt đầu giao dịch ở đây.
  • Khóa ngoại được thiết lập thành tầng chỉ khi xóa và không có thao tác xóa khỏi bảng cha.

Câu trả lời:


12

OK, sau khi xem xét mọi thứ trong một vài lần, tôi nghĩ rằng giả định cơ bản của bạn là chính xác. Điều có lẽ đang diễn ra ở đây là:

  1. Phần MATCH của MERGE kiểm tra chỉ mục cho khớp, đọc khóa các hàng / trang đó khi nó đi.

  2. Khi nó có một hàng mà không có kết quả khớp, nó sẽ cố gắng chèn Index Row mới trước để nó sẽ yêu cầu khóa ghi hàng / trang ...

Nhưng nếu người dùng khác cũng đã bước sang bước 1 trên cùng một hàng / trang, thì người dùng đầu tiên sẽ bị chặn khỏi Cập nhật và ...

Nếu người dùng thứ hai cũng cần chèn vào cùng một trang, thì họ sẽ rơi vào bế tắc.

AFAIK, chỉ có một cách (đơn giản) để chắc chắn 100% rằng bạn không thể gặp bế tắc với quy trình này và đó sẽ là thêm gợi ý TABLOCKX vào MERGE, nhưng điều đó có thể có tác động rất xấu đến hiệu suất.

Thay vào đó, có thể thêm gợi ý TABLOCK sẽ đủ để giải quyết vấn đề mà không phải ảnh hưởng lớn đến hiệu suất của bạn.

Cuối cùng, bạn cũng có thể thử thêm PAGLOCK, XLOCK hoặc cả PAGLOCK và XLOCK. Một lần nữa điều đó có thể làm việc và hiệu suất có thể không quá khủng khiếp. Bạn sẽ phải thử nó để xem.


Bạn có nghĩ mức cô lập ảnh chụp nhanh (phiên bản hàng) có thể hữu ích ở đây không?
Mikael Eriksson

Có lẽ. Hoặc nó có thể biến các ngoại lệ bế tắc thành ngoại lệ đồng thời.
RBarryYoung

2
Chỉ định gợi ý TABLOCK trên bảng là mục tiêu của câu lệnh INSERT có tác dụng tương tự như chỉ định gợi ý TABLOCKX. (Nguồn: msdn.microsoft.com/en-us/l
Library / bb510625.aspx

31

Sẽ không có vấn đề gì nếu biến bảng chỉ giữ một giá trị. Với nhiều hàng, có một khả năng mới cho sự bế tắc. Giả sử hai quy trình đồng thời (A & B) chạy với các biến bảng chứa (1, 2) và (2, 1) cho cùng một công ty.

Quá trình A đọc đích, không tìm thấy hàng và chèn giá trị '1'. Nó giữ một khóa hàng độc quyền trên giá trị '1'. Quá trình B đọc đích, không tìm thấy hàng và chèn giá trị '2'. Nó giữ một khóa hàng độc quyền trên giá trị '2'.

Bây giờ xử lý A cần xử lý hàng 2 và quy trình B cần xử lý hàng 1. Cả hai quá trình đều không thể tiến triển vì nó yêu cầu khóa không tương thích với khóa độc quyền được giữ bởi quy trình khác.

Để tránh các bế tắc có nhiều hàng, các hàng cần được xử lý (và các bảng được truy cập) theo cùng một thứ tự mỗi lần . Biến bảng trong kế hoạch thực hiện được hiển thị trong câu hỏi là một đống, vì vậy các hàng không có thứ tự nội tại (chúng hoàn toàn có thể được đọc theo thứ tự chèn, mặc dù điều này không được đảm bảo):

Kế hoạch hiện có

Việc thiếu thứ tự xử lý hàng nhất quán dẫn trực tiếp đến cơ hội bế tắc. Một xem xét thứ hai là việc thiếu một đảm bảo duy nhất quan trọng có nghĩa là Bộ đệm Bảng là cần thiết để cung cấp Bảo vệ Halloween chính xác. Spool là một spool háo hức, có nghĩa là tất cả các hàng được ghi vào một bàn làm việc tempdb trước khi được đọc lại và phát lại cho toán tử Chèn.

Xác định lại biến TYPEcủa bảng để bao gồm một cụm PRIMARY KEY:

DROP TYPE dbo.CoUserData;

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

Bây giờ kế hoạch thực hiện cho thấy quét chỉ mục được nhóm và đảm bảo tính duy nhất có nghĩa là trình tối ưu hóa có thể loại bỏ Bảng Spool một cách an toàn:

Với khóa chính

Trong các thử nghiệm với 5000 lần lặp của MERGEcâu lệnh trên 128 luồng, không có sự tắc nghẽn nào xảy ra với biến bảng được nhóm. Tôi nên nhấn mạnh rằng điều này chỉ dựa trên cơ sở quan sát; biến bảng được phân cụm cũng có thể ( về mặt kỹ thuật ) tạo ra các hàng của nó theo nhiều thứ tự khác nhau, nhưng cơ hội của một đơn hàng nhất quán được tăng cường rất nhiều. Tất nhiên, hành vi được quan sát sẽ cần phải được kiểm tra lại cho mỗi bản cập nhật tích lũy mới, gói dịch vụ hoặc phiên bản mới của SQL Server.

Trong trường hợp định nghĩa biến bảng không thể thay đổi, có một cách khác:

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);

Điều này cũng đạt được việc loại bỏ ống chỉ (và tính nhất quán theo thứ tự hàng) với chi phí giới thiệu một loại rõ ràng:

Sắp xếp kế hoạch

Kế hoạch này cũng không tạo ra bế tắc khi sử dụng thử nghiệm tương tự. Kịch bản sinh sản dưới đây:

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

Tôi nghĩ rằng SQL_Kiwi cung cấp một phân tích rất tốt. Nếu bạn cần giải quyết vấn đề trong cơ sở dữ liệu, bạn nên làm theo gợi ý của anh ấy. Tất nhiên bạn cần kiểm tra lại rằng nó vẫn hoạt động cho bạn mỗi khi bạn nâng cấp, áp dụng gói dịch vụ hoặc thêm / thay đổi chỉ mục hoặc chế độ xem được lập chỉ mục.

Có ba lựa chọn thay thế khác:

  1. Bạn có thể tuần tự hóa các phần chèn của mình để chúng không va chạm: bạn có thể gọi sp_getapplock khi bắt đầu giao dịch và có được một khóa độc quyền trước khi thực hiện MERGE của bạn. Tất nhiên bạn vẫn cần phải căng thẳng kiểm tra nó.

  2. Bạn có thể có một luồng xử lý tất cả các phần chèn của mình, để máy chủ ứng dụng của bạn xử lý đồng thời.

  3. Bạn có thể tự động thử lại sau khi bế tắc - đây có thể là cách tiếp cận chậm nhất nếu đồng thời cao.

Dù bằng cách nào, chỉ có bạn mới có thể xác định tác động của giải pháp của bạn đến hiệu suất.

Thông thường, chúng tôi không có bế tắc trong hệ thống của mình, mặc dù chúng tôi có rất nhiều tiềm năng để có chúng. Vào năm 2011, chúng tôi đã mắc một lỗi trong một lần triển khai và có một nửa tá các bế tắc xảy ra trong vài giờ, tất cả đều theo cùng một kịch bản. Tôi đã sửa nó sớm và đó là tất cả những bế tắc trong năm.

Chúng tôi chủ yếu sử dụng phương pháp 1 trong hệ thống của chúng tôi. Nó hoạt động thực sự tốt cho chúng tôi.


-1

Một cách tiếp cận khả thi khác - Tôi đã tìm thấy Hợp nhất đôi khi trình bày các vấn đề về khóa và hiệu suất - có thể đáng để chơi với tùy chọn truy vấn Tùy chọn (MaxDop x)

Trong quá khứ mờ mịt và xa xôi, SQL Server có tùy chọn Chèn Cấp độ Khóa hàng - nhưng điều này dường như đã chết một cái chết, tuy nhiên, một cụm PK với một danh tính sẽ làm cho các phần chèn chạy sạch hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.