Các nguyên nhân chính của bế tắc và chúng có thể được ngăn chặn?


55

Gần đây, một trong những ứng dụng ASP.NET của chúng tôi hiển thị lỗi khóa chết cơ sở dữ liệu và tôi được yêu cầu kiểm tra và sửa lỗi. Tôi quản lý để tìm ra nguyên nhân của sự bế tắc là một thủ tục được lưu trữ đang cập nhật nghiêm ngặt một bảng trong một con trỏ.

Đây là lần đầu tiên tôi thấy lỗi này và không biết cách theo dõi và khắc phục nó một cách hiệu quả. Tôi đã thử tất cả các cách có thể mà tôi biết, và cuối cùng thấy rằng bảng đang được cập nhật không có khóa chính! may mắn thay, đó là một cột danh tính.

Sau đó tôi đã tìm thấy nhà phát triển đã viết kịch bản cơ sở dữ liệu để triển khai sai. Tôi đã thêm một khóa chính và vấn đề đã được giải quyết.

Tôi cảm thấy hạnh phúc và quay lại dự án của mình, và thực hiện một số nghiên cứu để tìm ra lý do cho sự bế tắc đó ...

Rõ ràng, đó là một điều kiện chờ đợi vòng tròn gây ra bế tắc. Các cập nhật dường như mất nhiều thời gian hơn mà không có khóa chính so với khóa chính.

Tôi biết đó không phải là một kết luận được xác định rõ ràng, đó là lý do tại sao tôi đăng bài ở đây ...

  • Là khóa chính bị thiếu là vấn đề?
  • Có bất kỳ điều kiện nào khác gây ra bế tắc ngoài (loại trừ lẫn nhau, giữ và chờ đợi, không có quyền ưu tiên và chờ đợi thông tư)?
  • Làm thế nào để tôi ngăn chặn và theo dõi các bế tắc?

2
Đa số IME (tất cả?) Của những bế tắc tôi từng thấy xảy ra do chờ đợi vòng tròn (chủ yếu là do sử dụng quá nhiều kích hoạt).
Sathyajith Bhat

Thông tư là một trong những điều kiện cần thiết của sự bế tắc. Bạn có thể tránh mọi bế tắc nếu tất cả các phiên của bạn có được các khóa theo cùng một thứ tự.
Peter G.

Câu trả lời:


38

theo dõi bế tắc là dễ dàng hơn trong hai:

Theo mặc định, các khóa chết không được ghi trong nhật ký lỗi. Bạn có thể khiến SQL viết các khóa chết vào nhật ký lỗi với các cờ theo dõi 1204 và 3605.

Viết thông tin khóa chết vào nhật ký lỗi Máy chủ SQL: DBCC TRACEON (-1, 1204, 3605)

Tắt nó đi: DBCC TRACEOFF (-1, 1204, 3605)

Xem "Khắc phục sự cố Bế tắc" để biết thảo luận về cờ theo dõi 1204 và đầu ra bạn sẽ nhận được khi bật. https://msdn.microsoft.com/en-us/l Library / ms178104.aspx

Phòng ngừa khó khăn hơn, về cơ bản bạn phải chú ý những điều sau:

Mã khối 1 khóa tài nguyên A, sau đó tài nguyên B, theo thứ tự đó.

Mã khối 2 khóa tài nguyên B, sau đó tài nguyên A, theo thứ tự đó.

Đây là điều kiện cổ điển có thể xảy ra bế tắc, nếu khóa cả hai tài nguyên không phải là nguyên tử, Mã khối 1 có thể khóa A và bị xóa trước, sau đó Mã khối 2 khóa B trước khi A lấy lại thời gian xử lý. Bây giờ bạn có bế tắc.

Để ngăn chặn tình trạng này, bạn có thể làm một cái gì đó như sau

Mã khối A (mã psuedo)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

Mã khối B (mã giả)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

không quên mở khóa A và B khi thực hiện với chúng

điều này sẽ ngăn chặn sự bế tắc giữa khối mã A và khối mã B

Từ góc độ cơ sở dữ liệu, tôi không chắc chắn về cách ngăn chặn tình huống này, vì các khóa được xử lý bởi chính cơ sở dữ liệu, tức là khóa hàng / bảng khi cập nhật dữ liệu. Nơi tôi đã thấy hầu hết các vấn đề xảy ra là nơi bạn nhìn thấy bạn, bên trong một con trỏ. Con trỏ nổi tiếng là không hiệu quả, tránh chúng nếu có thể.


Ý của bạn là khóa tài nguyên A trước tài nguyên B trong Mã khối B? Như đã viết, điều này sẽ gây ra bế tắc .. như chính bạn đã đề cập trong các ý kiến ​​trước đây. Càng nhiều càng tốt, bạn luôn muốn khóa tài nguyên theo cùng một thứ tự, ngay cả khi bạn cần truy vấn giả ngay từ đầu để đảm bảo thứ tự khóa đó.
Gerard ONeill

23

Các bài viết yêu thích của tôi để đọc và tìm hiểu về các bế tắc là: Nói chuyện đơn giản - Theo dõi các bế tắcSQL Server Central - Sử dụng Profiler để giải quyết các bế tắc . Họ sẽ cung cấp cho bạn các mẫu và lời khuyên về cách xử lý tình huống hút.

Nói tóm lại, để giải quyết vấn đề hiện tại, tôi sẽ làm cho các giao dịch liên quan ngắn hơn, loại bỏ phần không cần thiết ra khỏi chúng, quan tâm đến thứ tự sử dụng của các đối tượng, xem mức độ cô lập thực sự cần thiết, không đọc không cần thiết dữ liệu...

Nhưng tốt hơn đọc các bài báo, họ sẽ đẹp hơn trong lời khuyên.


16

Đôi khi một bế tắc có thể được giải quyết bằng cách thêm lập chỉ mục, vì nó cho phép cơ sở dữ liệu khóa các bản ghi riêng lẻ thay vì toàn bộ bảng, do đó bạn giảm sự tranh chấp và khả năng mọi thứ bị kẹt.

Ví dụ: trong InnoDB :

Nếu bạn không có chỉ mục phù hợp với câu lệnh của mình và MySQL phải quét toàn bộ bảng để xử lý câu lệnh, mọi hàng của bảng sẽ bị khóa, từ đó chặn tất cả các chèn của người dùng khác vào bảng. Điều quan trọng là tạo các chỉ mục tốt để các truy vấn của bạn không cần quét nhiều hàng một cách không cần thiết.

Một giải pháp phổ biến khác là tắt tính nhất quán trong giao dịch khi không cần thiết hoặc thay đổi mức độ cô lập của bạn , ví dụ, một công việc dài hạn để tính toán thống kê ... nói chung là đủ, bạn không cần số chính xác, như họ đang thay đổi từ dưới bạn. Và nếu mất 30 phút để hoàn thành, bạn không muốn nó dừng tất cả các giao dịch khác trên các bảng đó.

...

Đối với việc theo dõi chúng, nó phụ thuộc vào phần mềm cơ sở dữ liệu bạn đang sử dụng.


Đó là phép lịch sự phổ biến để cung cấp nhận xét khi hạ cấp ... Đây là một câu trả lời hợp lệ, một tuyên bố chọn lọc nâng cấp lên khóa bảng và mất mãi mãi chắc chắn có thể gây ra bế tắc.
BlackICE

1
MS SQLServer cũng có thể đưa ra hành vi khóa bất ngờ nếu các chỉ mục không được nhóm. Nó sẽ âm thầm bỏ qua hướng của bạn để sử dụng khóa cấp hàng và sẽ thực hiện khóa cấp trang. Sau đó, bạn có thể nhận được bế tắc chờ đợi trên trang.
Jay

7

Chỉ để phát triển trên con trỏ điều. nó thực sự là xấu Nó khóa toàn bộ bảng sau đó xử lý từng hàng một.

Tốt nhất là đi qua các hàng theo kiểu con trỏ bằng vòng lặp while

Trong vòng lặp while, một lựa chọn sẽ được thực hiện cho từng hàng trong vòng lặp và khóa sẽ chỉ xảy ra trên một hàng tại thời điểm đó. Phần còn lại của dữ liệu trong bảng là miễn phí cho truy vấn, do đó làm giảm khả năng bế tắc xảy ra.

Thêm vào đó là nhanh hơn. Làm cho bạn tự hỏi tại sao có con trỏ nào.

Đây là một ví dụ về loại cấu trúc này:

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

Nếu trường ID của bạn thưa thớt, bạn có thể muốn kéo một danh sách ID riêng và lặp lại thông qua đó:

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END

6

Thiếu khóa chính không phải là vấn đề. Ít nhất là bởi chính nó. Đầu tiên, bạn không cần một chính để có các chỉ mục. Thứ hai, ngay cả khi bạn đang thực hiện quét bảng (điều này phải xảy ra nếu truy vấn cụ thể của bạn không sử dụng chỉ mục, khóa bảng sẽ không tự gây ra bế tắc. Quá trình viết sẽ chờ đọc và quá trình đọc sẽ chờ đợi để viết, và tất nhiên đọc sẽ không phải chờ nhau chút nào.

Thêm vào các câu trả lời khác, Mức cô lập giao dịch có vấn đề, bởi vì việc đọc và tuần tự lặp lại là nguyên nhân khiến khóa 'đọc' bị giữ cho đến khi kết thúc giao dịch. Khóa tài nguyên không gây ra bế tắc. Giữ nó khóa. Thao tác ghi luôn giữ tài nguyên của họ bị khóa cho đến khi kết thúc giao dịch.

Chiến lược ngăn chặn khóa yêu thích của tôi là sử dụng các tính năng 'chụp nhanh'. Tính năng Snapshot Snapshot có nghĩa là đọc không sử dụng khóa! Và nếu bạn cần kiểm soát nhiều hơn 'Đã đọc cam kết', có tính năng 'Mức cô lập ảnh chụp nhanh'. Điều này cho phép một giao dịch được tuần tự hóa (sử dụng thuật ngữ MS ở đây) xảy ra trong khi không chặn những người chơi khác.

Cuối cùng, một loại khóa chết có thể được ngăn chặn bằng cách sử dụng khóa cập nhật. Nếu bạn đọc và giữ đọc (GIỮ hoặc sử dụng Đọc lặp lại) và một quy trình khác thực hiện tương tự, thì cả hai đều cố gắng cập nhật các bản ghi giống nhau, bạn sẽ gặp bế tắc. Nhưng nếu cả hai đều yêu cầu khóa cập nhật, quy trình thứ hai sẽ đợi lần đầu tiên, đồng thời cho phép các quy trình khác đọc dữ liệu bằng cách sử dụng khóa chung cho đến khi dữ liệu thực sự được ghi. Điều này tất nhiên sẽ không hoạt động nếu một trong các quy trình vẫn yêu cầu khóa GIỮ được chia sẻ.


-2

Mặc dù các con trỏ chậm trong SQL Server, bạn có thể tránh bế tắc trong một con trỏ bằng cách kéo dữ liệu nguồn cho con trỏ vào bảng Temp và chạy con trỏ trên đó. Điều này giữ cho con trỏ không khóa bảng dữ liệu thực tế và các khóa duy nhất bạn nhận được là cho các cập nhật hoặc chèn được thực hiện bên trong con trỏ chỉ được giữ trong thời gian chèn / cập nhật chứ không phải trong thời gian của con trỏ.

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.