Giải quyết bế tắc từ 2 bảng chỉ liên quan qua chế độ xem được lập chỉ mục


16

Tôi có một tình huống tôi đang gặp bế tắc và tôi nghĩ rằng tôi đã thu hẹp được thủ phạm, nhưng tôi không chắc mình có thể làm gì để khắc phục nó.

Đây là trên một môi trường sản xuất chạy SQL Server 2008 R2.

Để cung cấp cho bạn một cái nhìn hơi đơn giản về tình huống:


Tôi có 3 bảng như được định nghĩa dưới đây:

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,
    ...
)

Các member_activitybảng có một hợp chất Primary Key định nghĩa là member_id, activity_id, bởi vì tôi chỉ có bao giờ cần phải tìm kiếm dữ liệu trên bảng mà theo cách đó.

Tôi cũng có một chỉ mục không bao gồm trên follow:

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

Ngoài ra, tôi có chế độ xem liên kết Schema network_activityđược xác định như sau:

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

Mà cũng có một chỉ mục cụm duy nhất:

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

Bây giờ, tôi có hai thủ tục lưu trữ bế tắc. Họ trải qua quá trình sau đây:

-- 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 (...)

Cả hai thủ tục này đều chạy trong cách ly READ CAMEDED. Tôi đã quản lý để truy vấn đầu ra 1222 sự kiện mở rộng và đã diễn giải những điều sau đây liên quan đến các bế tắc:

SP1 đang chờ RangeS-Skhóa khóa trên IX_follow_member_id_includeschỉ mục trong khi SP2 giữ khóa (X) xung đột

SP2 đang chờ Skhóa chế độ PK_member_activity trong khi SP1 giữ khóa (X) xung đột

Sự bế tắc dường như đang xảy ra ở dòng cuối cùng của mỗi truy vấn (phần chèn). Điều không rõ ràng với tôi là tại sao SP1 lại muốn khóa trên IX_follow-member_id_includeschỉ mục. Liên kết duy nhất, với tôi, dường như là từ quan điểm được lập chỉ mục này, đó là lý do tại sao tôi đưa nó vào.

Điều gì sẽ là cách tốt nhất để tôi ngăn chặn những bế tắc này xảy ra? Bất kì sự trợ giúp nào đều được đánh giá cao. Tôi không có nhiều kinh nghiệm trong việc giải quyết các vấn đề bế tắc.

Xin vui lòng cho tôi biết nếu có thêm thông tin tôi có thể cung cấp có thể giúp đỡ!

Cảm ơn trước.


Chỉnh sửa 1: Thêm một số thông tin cho mỗi yêu cầu.

Đây là đầu ra 1222 từ bế tắc này:

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

Trong trường hợp này,

AssociObjectId 72057594098679808 tương ứng với member_activity, PK_member_activity

AssociObjectId 72057594104905728 tương ứng với follow, IX_follow_member_id_includes

Ngoài ra, đây là một hình ảnh chính xác hơn về những gì SP1 và SP2 đang làm

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

còn 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)

Chỉnh sửa 2: Sau khi đọc lại các bình luận, tôi nghĩ tôi cũng sẽ thêm một số thông tin về các cột nào là khóa ngoại ...

  • member_activity.member_idlà một khóa ngoại đối với một memberbảng
  • member_activity.activity_idlà một khóa ngoại cho activitybảng
  • follow.member_idlà một khóa ngoại đối với một memberbảng
  • follow.follower_idlà một khóa ngoại đối với một memberbảng

Cập nhật 1:

Tôi đã thực hiện một vài thay đổi mà tôi nghĩ có thể giúp ngăn chặn sự bế tắc, không có may mắn.

Những thay đổi tôi đã thực hiện như sau:

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

và với 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

Với hai thay đổi này, tôi dường như vẫn đang gặp bế tắc.

Nếu có bất cứ điều gì khác tôi có thể cung cấp, xin vui lòng cho tôi biết. Cảm ơn.


đọc cam kết không có khóa phạm vi khóa, chỉ có tuần tự hóa. Nếu bế tắc thực tế hiển thị đã đọc cam kết (2) thì gues của tôi là bạn đang truy cập để thay đổi khóa ngoại sẽ được chuyển thành tuần tự hóa dưới vỏ bọc (mặc dù vẫn nói đã đọc cam kết). Chúng tôi thực sự cần toàn bộ ddl và sp để giúp thêm.
Sean nói Hủy bỏ Sara Chipps

@SeanGallardy, cảm ơn. Tôi đã chỉnh sửa để bao gồm đầu ra 1222 trong trường hợp tôi hiểu sai và tôi đã thêm chi tiết về những gì SP đang làm. Không giúp đỡ à?
Leland Richardson

2
@SeanGallardy Một phần của kế hoạch truy vấn duy trì chế độ xem được lập chỉ mục chạy bên trong SERIALIZABLE(có nhiều hơn một chút so với điều đó, nhưng đây là một nhận xét không phải là một câu trả lời :)
Paul White Rebstate Monica

@PaulWhite Cảm ơn bạn đã hiểu biết, tôi không biết điều đó! Thực hiện một thử nghiệm nhanh, tôi chắc chắn có thể có được các chế độ khóa tuần tự hóa với chế độ xem được lập chỉ mục trong quá trình chèn trong các thủ tục được lưu trữ của bạn (RangeI-N, RangeS-S, RangeS-U). Có vẻ như sự bế tắc đang xảy ra từ các chế độ khóa không tương thích đánh vào đúng thời điểm với nhau trong khi chèn trong quy trình được lưu trữ của bạn khi chúng nằm trong ranh giới khóa (ví dụ: trong khu vực được giữ bởi khóa phạm vi). Cả thời gian và xung đột dữ liệu đầu vào tôi sẽ nghĩ.
Sean nói Hủy bỏ Sara Chipps

Câu hỏi: Nếu tôi đã thêm một gợi ý HOLDLOCK trên các câu lệnh CHỌN, điều đó có ngăn khóa xảy ra khi chèn không?
Leland Richardson

Câu trả lời:


4

Xung đột có nghĩa network_activitylà một Chế độ xem được lập chỉ mục cần được duy trì (bên trong) trên các báo cáo DML. Đó rất có thể là lý do tại sao SP1 muốn có một khóa trênIX_follow-member_id_includes chỉ mục vì nó có thể được sử dụng bởi Chế độ xem (có vẻ là chỉ mục bao trùm cho Chế độ xem).

Hai tùy chọn có thể:

  1. Xem xét bỏ Chỉ mục được nhóm trên Chế độ xem để nó không còn là Chế độ xem được lập chỉ mục. Liệu lợi ích của việc có nó lớn hơn chi phí bảo trì? Bạn có chọn từ nó đủ thường xuyên hoặc là hiệu suất đạt được khi nó được lập chỉ mục xứng đáng? Nếu bạn chạy các procs này khá thường xuyên, thì có lẽ chi phí cao hơn lợi ích?

  2. Nếu lợi ích của việc Chế độ xem được lập chỉ mục sẽ lớn hơn chi phí, thì hãy xem xét cách ly các hoạt động DML với các bảng cơ sở của Chế độ xem đó. Điều này có thể được thực hiện thông qua việc sử dụng Khóa ứng dụng (xem sp_getapplocksp_releaseapplock ). Khóa ứng dụng cho phép bạn tạo các khóa xung quanh các khái niệm tùy ý. Có nghĩa là, bạn có thể định nghĩa @Resourcelà "network_activity" trong cả hai Procs được lưu trữ của bạn, điều này sẽ buộc họ phải chờ đến lượt. Mỗi Proc sẽ theo cùng một cấu trúc:

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

    Bạn cần quản lý lỗi / ROLLBACKchính mình (như đã nêu trong tài liệu MSDN được liên kết), vì vậy hãy đưa vào thông thường TRY...CATCH. Nhưng, điều này không cho phép bạn quản lý tình huống.
    Xin lưu ý: sp_getapplock / sp_releaseapplocknên được sử dụng một cách tiết kiệm; Khóa ứng dụng chắc chắn có thể rất tiện dụng (chẳng hạn như trong trường hợp như thế này) nhưng chúng chỉ nên được sử dụng khi thực sự cần thiết.


Cảm ơn đã giúp đỡ. Tôi sẽ đọc thêm một chút về tùy chọn # 2 và xem điều đó có hiệu quả với chúng tôi không. Chế độ xem được đọc từ khá nhiều và chỉ mục được nhóm là một trợ giúp khá lớn ... vì vậy tôi chưa muốn xóa nó. Tôi sẽ quay lại bản cập nhật một khi tôi cho nó một shot.
Leland Richardson

Tôi nghĩ rằng sử dụng sp_getapplock sẽ hoạt động. Tôi chưa thể thử nó trong môi trường sản xuất của chúng tôi, nhưng tôi muốn chắc chắn rằng bạn đã nhận được tiền thưởng trước khi nó hết hạn. Tôi sẽ cập nhật ở đây khi tôi có thể xác nhận nó hoạt động!
Leland Richardson

Cảm ơn. Một điều thú vị về Khóa ứng dụng là bạn có thể thay đổi mức độ chi tiết của việc ghép nối trong một cái gì đó giống như member_idvào @Resourcegiá trị. Điều đó dường như không áp dụng cho tình huống cụ thể này nhưng tôi đã thấy nó được sử dụng như thế và nó khá tiện dụng, đặc biệt là trong một hệ thống nhiều người thuê nơi bạn muốn giới hạn quy trình cho một luồng trên mỗi khách hàng, nhưng vẫn có nó được đa luồng trên toàn khách hàng.
Solomon Rutzky

Tôi muốn đưa ra một bản cập nhật và nói rằng điều này đã kết thúc hoạt động trong môi trường sản xuất của chúng tôi. :)
Leland Richardson
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.