Các chuyên gia cơ sở dữ liệu thường xuyên phải đối mặt với các vấn đề về hiệu suất cơ sở dữ liệu như lập chỉ mục không đúng và mã được viết kém trong các phiên bản SQL sản xuất. Giả sử bạn đã cập nhật một giao dịch và SQL Server đã báo cáo thông báo bế tắc sau. Đối với các DBA mới bắt đầu, điều này có thể là một cú sốc.
Trong bài viết này, chúng ta sẽ khám phá các bế tắc của SQL Server và các cách tốt nhất để tránh chúng.
SQL Server deadlock là gì?
SQL Server là một cơ sở dữ liệu có tính giao dịch cao. Ví dụ:giả sử bạn đang hỗ trợ cơ sở dữ liệu cho một cổng mua sắm trực tuyến, nơi bạn nhận được đơn đặt hàng mới từ khách hàng suốt ngày đêm. Nhiều người dùng có thể thực hiện cùng một hoạt động cùng một lúc. Trong trường hợp này, cơ sở dữ liệu của bạn phải tuân theo các thuộc tính Nguyên tử, Nhất quán, Cô lập, Độ bền (ACID) để nhất quán, đáng tin cậy và bảo vệ tính toàn vẹn của dữ liệu.
Hình ảnh bên dưới mô tả các thuộc tính ACID trong cơ sở dữ liệu quan hệ.
Để tuân theo các thuộc tính ACID, SQL Server sử dụng cơ chế khóa, ràng buộc và ghi nhật ký ghi trước. Các loại khóa khác nhau bao gồm:khóa độc quyền (X), khóa chia sẻ (S), khóa cập nhật (U), khóa ý định (I), khóa lược đồ (SCH) và khóa cập nhật hàng loạt (BU). Các khóa này có thể được lấy ở cấp độ khóa, bảng, hàng, trang và cơ sở dữ liệu.
Giả sử bạn có hai người dùng, John và Peter được kết nối với cơ sở dữ liệu khách hàng.
- John muốn cập nhật hồ sơ cho khách hàng có [customerid] 1.
- Đồng thời, Peter muốn truy xuất giá trị cho khách hàng có [customerid] 1.
Trong trường hợp này, SQL Server sử dụng các khóa sau cho cả John và Peter.
Khóa dành cho John
- Nó có một khóa dành riêng cho mục đích (IX) trên bảng khách hàng và trang có chứa bản ghi.
- Cần thêm một khóa (X) độc quyền trên hàng mà John muốn cập nhật. Nó ngăn không cho bất kỳ người dùng nào khác sửa đổi dữ liệu hàng cho đến khi quy trình A giải phóng khóa của nó.
Ổ khóa cho Peter
- Nó có được một khóa mục đích được chia sẻ (IS) trên bảng khách hàng và trang chứa bản ghi theo mệnh đề where.
- Nó cố gắng sử dụng một khóa dùng chung để đọc hàng. Hàng này đã có khóa dành riêng cho John.
Trong trường hợp này, Peter cần đợi cho đến khi John hoàn thành công việc của mình và giải phóng khóa độc quyền. Tình huống này được gọi là chặn.
Bây giờ, giả sử trong một tình huống khác, John và Peter có các ổ khóa sau.
- John có một khóa độc quyền trên bảng khách hàng cho id khách hàng 1.
- Peter có một khóa riêng trên bảng đơn đặt hàng cho id khách hàng 1.
- John yêu cầu một khóa riêng trên bảng đơn đặt hàng để hoàn tất giao dịch của mình. Peter đã có một khóa riêng trên bảng đơn đặt hàng.
- Peter yêu cầu một khóa riêng trên bàn khách hàng để hoàn tất giao dịch của mình. John đã có một chiếc khóa độc quyền trên bàn khách hàng.
Trong trường hợp này, cả hai giao dịch đều không thể tiếp tục vì mỗi giao dịch yêu cầu một tài nguyên do giao dịch kia nắm giữ. Tình huống này được gọi là bế tắc Máy chủ SQL.
Cơ chế giám sát bế tắc của SQL Server
SQL Server giám sát các tình huống bế tắc theo định kỳ bằng cách sử dụng chuỗi giám sát bế tắc. Điều này kiểm tra các quy trình liên quan đến một deadlock và xác định xem một phiên có trở thành nạn nhân của deadlock hay không. Nó sử dụng một cơ chế bên trong để xác định quá trình bế tắc của nạn nhân. Theo mặc định, giao dịch có ít tài nguyên nhất được yêu cầu để khôi phục được coi là nạn nhân.
SQL Server giết phiên nạn nhân để một phiên khác có thể có được khóa cần thiết để hoàn thành giao dịch của nó. Theo mặc định, SQL Server kiểm tra tình hình bế tắc 5 giây một lần bằng trình theo dõi bế tắc. Nếu nó phát hiện thấy bế tắc, nó có thể giảm tần suất từ 5 giây xuống 100 mili giây tùy thuộc vào sự cố xảy ra. Nó một lần nữa đặt lại chuỗi giám sát thành 5 giây nếu không xảy ra tình trạng tắc nghẽn thường xuyên.
Sau khi Máy chủ SQL giết một quy trình là nạn nhân của bế tắc, bạn sẽ nhận được thông báo sau. Trong phiên này, quy trình ID 69 là một nạn nhân của bế tắc.
Tác động của việc sử dụng các câu lệnh ưu tiên bế tắc của SQL Server
Theo mặc định, SQL Server đánh dấu giao dịch có lần khôi phục ít tốn kém nhất là nạn nhân của bế tắc. Người dùng có thể đặt mức độ ưu tiên của deadlock trong một giao dịch bằng cách sử dụng câu lệnh DEADLOCK_PRIORITY.
SET DEADLOCK_PRIORITY
Nó sử dụng các đối số sau:
- Thấp:Tương đương với mức ưu tiên bế tắc -5
- Bình thường:Đây là mức ưu tiên bế tắc mặc định 0
- Cao:Đây là mức ưu tiên bế tắc cao nhất 5.
Chúng tôi cũng có thể đặt các giá trị số cho mức độ ưu tiên của deadlock từ -10 đến 10 (tổng số 21 giá trị).
Hãy xem một vài ví dụ về các câu lệnh ưu tiên bế tắc.
Ví dụ 1:
Phiên 1 với mức ưu tiên bế tắc:Bình thường (0)> Phiên 2 với mức ưu tiên bế tắc:Thấp (-5)
Nạn nhân bế tắc: Phiên 2
Ví dụ 2:
Phiên 1 với mức độ ưu tiên của deadlock:Bình thường (0)
Nạn nhân bế tắc: Phiên 1
Ví dụ 3
Phiên 1 với mức độ ưu tiên của deadlock:-3> Phiên 2 với mức độ ưu tiên của deadlock:-7
Ví dụ 4:
Phiên 1 với mức độ ưu tiên của deadlock:-5
Nạn nhân bế tắc: Phiên 1
Đồ thị deadlock là một biểu diễn trực quan của các tiến trình deadlock, các ổ khóa của chúng và nạn nhân của deadlock. Chúng tôi có thể kích hoạt các cờ theo dõi 1204 và 1222 để nắm bắt thông tin chi tiết về bế tắc ở định dạng đồ họa và XML. Chúng ta có thể sử dụng sự kiện mở rộng system_health mặc định để lấy thông tin chi tiết về bế tắc. Một cách nhanh chóng và dễ dàng để giải thích bế tắc là thông qua biểu đồ bế tắc. Hãy mô phỏng một điều kiện bế tắc và xem biểu đồ bế tắc tương ứng của nó.
Đối với phần trình diễn này, chúng tôi đã tạo bảng Khách hàng và Đơn hàng và chèn một vài bản ghi mẫu.
Sau đó, chúng tôi đã mở một cửa sổ truy vấn mới và bật cờ theo dõi trên toàn cầu.
Dấu vết DBCC (1222, -1)
Khi chúng tôi bật cờ theo dõi bế tắc, chúng tôi bắt đầu hai phiên và thực hiện truy vấn theo thứ tự dưới đây:
Trong ví dụ này, SQL Server chọn một nạn nhân bế tắc (ID phiên 65) và kết thúc giao dịch. Hãy tìm nạp biểu đồ bế tắc từ phiên sự kiện mở rộng system_health.
Truy vấn này cung cấp cho chúng tôi một XML bế tắc yêu cầu một DBA có kinh nghiệm để diễn giải thông tin.
Chúng tôi lưu XML bế tắc này bằng cách sử dụng phần mở rộng .XDL và khi chúng tôi mở tệp XDL trong SSMS, chúng tôi nhận được biểu đồ bế tắc được hiển thị bên dưới.
Biểu đồ bế tắc này cung cấp thông tin sau:
Nó đại diện cho một nạn nhân của bế tắc bằng cách gạch bỏ hình bầu dục trong biểu đồ bế tắc.
Bạn có thể nắm bắt thông tin bế tắc của SQL Server theo những cách sau:
1) Bế tắc tra cứu dấu trang
Tra cứu dấu trang là một bế tắc thường thấy trong SQL Server. Nó xảy ra do xung đột giữa câu lệnh select và câu lệnh DML (chèn, cập nhật và xóa). Thông thường, SQL Server chọn câu lệnh select làm nạn nhân của deadlock vì nó không gây ra thay đổi dữ liệu và quá trình khôi phục diễn ra nhanh chóng. Để tránh tra cứu dấu trang, bạn có thể sử dụng chỉ mục bao trùm. Bạn cũng có thể sử dụng gợi ý truy vấn NOLOCK trong các câu lệnh select, nhưng nó đọc dữ liệu không được cam kết.
2) Bế tắc quét phạm vi
Đôi khi, chúng tôi sử dụng mức cô lập SERIALIZABLE ở cấp máy chủ hoặc cấp phiên. Đây là mức cô lập hạn chế để kiểm soát đồng thời và có thể tạo khóa quét phạm vi thay vì khóa cấp trang hoặc hàng. Ở cấp độ cô lập SERIALIZABLE, người dùng không thể đọc dữ liệu nếu nó được sửa đổi nhưng đang chờ được cam kết trong một giao dịch. Tương tự, nếu một giao dịch đọc dữ liệu, thì một giao dịch khác không thể sửa đổi nó. Nó cung cấp sự đồng thời thấp nhất, vì vậy chúng ta nên sử dụng mức cách ly này trong các yêu cầu ứng dụng cụ thể.
3) Bế tắc ràng buộc xếp tầng
SQL Server sử dụng mối quan hệ cha-con giữa các bảng bằng cách sử dụng các ràng buộc khóa ngoại. Trong trường hợp này, nếu chúng tôi cập nhật hoặc xóa một bản ghi khỏi bảng mẹ, thì cần phải có các khóa cần thiết trên bảng con để ngăn các bản ghi mồ côi. Để loại bỏ những bế tắc này, bạn phải luôn sửa đổi dữ liệu trong bảng con trước tiên là dữ liệu mẹ. Bạn cũng có thể làm việc trực tiếp với bảng mẹ bằng cách sử dụng các tùy chọn XÓA CASCADE hoặc CẬP NHẬT CASCADE. Bạn cũng nên tạo các chỉ mục thích hợp trên các cột khóa ngoại.
4) Bế tắc song song truy vấn nội bộ
Khi người dùng gửi truy vấn tới công cụ truy vấn SQL, trình tối ưu hóa truy vấn sẽ xây dựng một kế hoạch thực thi được tối ưu hóa. Nó có thể thực hiện truy vấn theo thứ tự nối tiếp hoặc song song tùy thuộc vào chi phí truy vấn, mức độ song song tối đa (MAXDOP) và ngưỡng chi phí cho song song.
Trong chế độ song song, SQL Server chỉ định nhiều luồng. Đôi khi đối với một truy vấn lớn ở chế độ song song, các luồng này bắt đầu chặn lẫn nhau. Cuối cùng, nó chuyển thành bế tắc. Trong trường hợp này, bạn cần xem lại kế hoạch thực thi và MAXDOP và ngưỡng chi phí của mình cho các cấu hình song song. Bạn cũng có thể chỉ định MAXDOP ở cấp phiên để khắc phục tình huống bế tắc.
5) Đảo ngược thứ tự đối tượng bế tắc
Trong loại bế tắc này, nhiều giao dịch truy cập các đối tượng theo một thứ tự khác nhau trong T-SQL. Điều này gây ra sự chặn giữa các tài nguyên cho mỗi phiên và chuyển nó thành bế tắc. Bạn luôn muốn truy cập các đối tượng theo một thứ tự hợp lý để không dẫn đến tình trạng bế tắc.
Deadlocks là một cơ chế tự nhiên trong SQL Server để tránh phiên giữ khóa và chờ các tài nguyên khác. Bạn nên nắm bắt các truy vấn bế tắc và tối ưu hóa chúng để chúng không xung đột với nhau. Điều quan trọng là phải nắm bắt khóa trong một khoảng thời gian ngắn và giải phóng nó để các truy vấn khác có thể sử dụng nó một cách hiệu quả.
SQL Server deadlock xảy ra và trong khi SQL Server xử lý nội bộ các tình huống deadlock, bạn nên cố gắng giảm thiểu chúng bất cứ khi nào có thể. Một số cách tốt nhất để loại bỏ bế tắc là tạo chỉ mục, áp dụng các thay đổi mã ứng dụng hoặc kiểm tra cẩn thận các tài nguyên trong biểu đồ bế tắc. Để biết thêm mẹo về cách tránh bế tắc SQL, hãy xem bài đăng của chúng tôi:Tránh bế tắc SQL bằng điều chỉnh truy vấn. Máy chủ SQL bế tắc bằng cách sử dụng đồ thị bế tắc
CREATE TABLE Customer
(ID INT IDENTITY(1,1), CustomerName VARCHAR(20))
GO
CREATE TABLE Orders
(OrderID INT IDENTITY(1,1), ProductName VARCHAR(50))
GO
INSERT INTO Customer(CustomerName) VALUES ('Rajendra')
Go 100
S INSERT INTO Orders(ProductName) VALUES ('Laptop')
Go 100
SELECT XEvent.query('(event/data/value/deadlock)[1]') AS DeadlockGraph
FROM (
SELECT XEvent.query('.') AS XEvent
FROM (
SELECT CAST(target_data AS XML) AS TargetData
FROM sys.dm_xe_session_targets st
INNER JOIN sys.dm_xe_sessions s
ON s.address = st.event_session_address
WHERE s.NAME = ‘system_health’
AND st.target_name = ‘ring_buffer’
) AS Data
CROSS APPLY TargetData.nodes('RingBufferTarget/event[@name="xml_deadlock_report"]
') AS XEventData(XEvent)
) AS source;
5 loại bế tắc trong SQL Server
Các cách hữu ích để tránh và giảm thiểu các bế tắc của SQL Server
Cân nhắc về bế tắc của SQL Server