Bế tắc khi cập nhật các hàng khác nhau với chỉ mục không được phân cụm


13

Tôi đang giải quyết vấn đề bế tắc trong khi tôi nhận thấy hành vi khóa là khác nhau khi tôi sử dụng chỉ mục được phân cụm và không phân cụm trên trường id. Vấn đề bế tắc dường như được giải quyết nếu chỉ số clusty hoặc khóa chính được áp dụng cho trường id.

Tôi có các giao dịch khác nhau thực hiện một hoặc nhiều cập nhật cho các hàng khác nhau, ví dụ: giao dịch A sẽ chỉ cập nhật hàng có ID = a, tx B sẽ chỉ chạm vào hàng có ID = b, v.v.

Và tôi đã hiểu rằng nếu không có chỉ mục, bản cập nhật sẽ có được khóa cập nhật cho tất cả các hàng và chuyển sang khóa độc quyền khi cần thiết, điều này cuối cùng sẽ dẫn đến bế tắc. Nhưng tôi không tìm hiểu tại sao với chỉ số không được phân cụm, bế tắc vẫn còn đó (mặc dù tỷ lệ trúng dường như bị giảm)

Bảng dữ liệu:

CREATE TABLE [dbo].[user](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [userName] [nvarchar](255) NULL,
    [name] [nvarchar](255) NULL,
    [phone] [nvarchar](255) NULL,
    [password] [nvarchar](255) NULL,
    [ip] [nvarchar](30) NULL,
    [email] [nvarchar](255) NULL,
    [pubDate] [datetime] NULL,
    [todoOrder] [text] NULL
)

Dấu vết bế tắc

deadlock-list
deadlock victim=process4152ca8
process-list
process id=process4152ca8 taskpriority=0 logused=0 waitresource=RID: 5:1:388:29 waittime=3308 ownerId=252354 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.947 XDES=0xb0bf180 lockMode=U schedulerid=3 kpid=11392 status=suspended spid=57 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.953 lastbatchcompleted=2014-04-11T00:15:30.950 lastattention=1900-01-01T00:00:00.950 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252354 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=62 sqlhandle=0x0200000062f45209ccf17a0e76c2389eb409d7d970b0f89e00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(2)<c/>@owner int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@owner
process id=process4153468 taskpriority=0 logused=4652 waitresource=KEY: 5:72057594042187776 (3fc56173665b) waittime=3303 ownerId=252344 transactionname=user_transaction lasttranstarted=2014-04-11T00:15:30.920 XDES=0x4184b78 lockMode=U schedulerid=3 kpid=7272 status=suspended spid=58 sbid=0 ecid=0 priority=0 trancount=2 lastbatchstarted=2014-04-11T00:15:30.960 lastbatchcompleted=2014-04-11T00:15:30.960 lastattention=1900-01-01T00:00:00.960 clientapp=.Net SqlClient Data Provider hostname=BOOD-PC hostpid=9272 loginname=getodo_sql isolationlevel=read committed (2) xactid=252344 currentdb=5 lockTimeout=4294967295 clientoption1=671088672 clientoption2=128056
executionStack
frame procname=adhoc line=1 stmtstart=60 sqlhandle=0x02000000d4616f250747930a4cd34716b610a8113cb92fbc00000000000000000000000000000000
update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
frame procname=unknown line=1 sqlhandle=0x00000000000000000000000000000000000000000000000000000000000000000000000000000000
unknown
inputbuf
(@para0 nvarchar(61)<c/>@uid int)update [user] WITH (ROWLOCK) set [todoOrder]=@para0 where id=@uid
resource-list
ridlock fileid=1 pageid=388 dbid=5 objectname=SQL2012_707688_webows.dbo.user id=lock3f7af780 mode=X associatedObjectId=72057594042122240
owner-list
owner id=process4153468 mode=X
waiter-list
waiter id=process4152ca8 mode=U requestType=wait
keylock hobtid=72057594042187776 dbid=5 objectname=SQL2012_707688_webows.dbo.user indexname=10 id=lock3f7ad700 mode=U associatedObjectId=72057594042187776
owner-list
owner id=process4152ca8 mode=U
waiter-list
waiter id=process4153468 mode=U requestType=wait

Ngoài ra một phát hiện thú vị và có thể liên quan là chỉ số phân cụm và không phân cụm dường như có các hành vi khóa khác nhau

Khi sử dụng chỉ mục được nhóm, có một khóa độc quyền trên khóa cũng như khóa độc quyền trên RID khi cập nhật, dự kiến; trong khi có hai khóa độc quyền trên hai RID khác nhau nếu sử dụng chỉ mục không phân cụm, điều này làm tôi bối rối.

Sẽ hữu ích nếu bất cứ ai có thể giải thích tại sao về điều này quá.

Kiểm tra SQL:

use SQL2012_707688_webows;
begin transaction;
update [user] with (rowlock) set todoOrder='{1}' where id = 63501
exec sp_lock;
commit;

Với id là Chỉ mục cụm:

spid    dbid    ObjId   IndId   Type    Resource    Mode    Status
53  5   917578307   1   KEY (b1a92fe5eed4)                      X   GRANT
53  5   917578307   1   PAG 1:879                               IX  GRANT
53  5   917578307   1   PAG 1:1928                              IX  GRANT
53  5   917578307   1   RID 1:879:7                             X   GRANT

Với id là Chỉ mục không phân cụm

spid    dbid    ObjId   IndId   Type    Resource    Mode    Status
53  5   917578307   0   PAG 1:879                               IX  GRANT
53  5   917578307   0   PAG 1:1928                              IX  GRANT
53  5   917578307   0   RID 1:879:7                             X   GRANT
53  5   917578307   0   RID 1:1928:18                           X   GRANT

EDIT1: Chi tiết về sự bế tắc không có chỉ số
Nói rằng tôi có hai tx A và B, mỗi câu có hai câu lệnh cập nhật, hàng khác nhau của khóa học
tx A

update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501

tx B

update [user] with (rowlock) set todoOrder='{3}' where id = 63502
update [user] with (rowlock) set todoOrder='{4}' where id = 63502

{1} và {4} sẽ có cơ hội bế tắc, vì

tại {1}, khóa U được yêu cầu cho hàng 63502 vì nó cần thực hiện quét bảng và khóa X có thể được giữ ở hàng 63501 vì nó phù hợp với điều kiện

tại {4}, khóa U được yêu cầu cho hàng 63501 và khóa X đã được giữ cho 63502

vì vậy chúng ta có txA giữ 63501 và chờ 63502 trong khi txB giữ 63502 chờ 63501, đó là một bế tắc

EDIT2: Hóa ra một lỗi trong trường hợp thử nghiệm của tôi tạo ra một tình huống khác biệt ở đây Xin lỗi vì sự nhầm lẫn nhưng lỗi này tạo ra một tình huống khác biệt và cuối cùng dường như gây ra bế tắc.

Vì phân tích của Paul thực sự đã giúp tôi trong trường hợp này nên tôi sẽ chấp nhận đó là câu trả lời.

Do lỗi của trường hợp thử nghiệm của tôi, hai giao dịch txA và txB có thể cập nhật cùng một hàng, như sau:

tx A

update [user] with (rowlock) set todoOrder='{1}' where id = 63501
update [user] with (rowlock) set todoOrder='{2}' where id = 63501

tx B

update [user] with (rowlock) set todoOrder='{3}' where id = 63501

{2} và {3} sẽ có cơ hội bế tắc khi:

txA yêu cầu khóa U trên khóa trong khi giữ khóa X trên RID (do cập nhật {1}) txB yêu cầu khóa U trên RID trong khi giữ khóa U trên khóa


1
Tôi không thể hiểu tại sao một giao dịch cần cập nhật cùng một hàng hai lần.
ypercubeᵀᴹ

@ypercube Điểm hay, đó là điều tôi nên cải thiện. Nhưng trong trường hợp này tôi chỉ muốn hiểu rõ hơn về các hành vi khóa
Bood

@ypercube sau nhiều suy nghĩ hơn Tôi nghĩ rằng có thể một ứng dụng có logic phức tạp cần cập nhật cùng một hàng hai lần trong cùng một tx, chẳng hạn có thể là các cột khác nhau
Bood

Câu trả lời:


16

... tại sao với chỉ số được nhóm, bế tắc vẫn còn đó (mặc dù tỷ lệ trúng dường như bị giảm)

Câu hỏi không chính xác rõ ràng (ví dụ: có bao nhiêu cập nhật và idgiá trị nào trong mỗi giao dịch) nhưng một kịch bản bế tắc rõ ràng xuất hiện với nhiều cập nhật một hàng trong một giao dịch, trong đó có sự chồng chéo của các [id]giá trị và id cập nhật theo [id]thứ tự khác :

[T1]: Update id 2; Update id 1;
[T2]: Update id 1; Update id 2;

Bế tắc trình tự: T1 (u2), T2 (u1), T1 (u1) chờ đợi , T2 (u2) chờ đợi .

Trình tự khóa chết này có thể tránh được bằng cách cập nhật nghiêm ngặt theo thứ tự id trong mỗi giao dịch (có được các khóa theo cùng một thứ tự trên cùng một đường dẫn).

Khi sử dụng chỉ mục được nhóm, có một khóa độc quyền trên khóa cũng như khóa độc quyền trên RID khi cập nhật, dự kiến; trong khi có hai khóa độc quyền trên hai RID khác nhau nếu sử dụng chỉ mục không phân cụm, điều này làm tôi bối rối.

Với một chỉ mục cụm duy nhất trên id, một khóa độc quyền được thực hiện trên khóa phân cụm để bảo vệ ghi vào dữ liệu liên tiếp. Một RIDkhóa độc quyền riêng biệt được yêu cầu để bảo vệ ghi vào textcột LOB , được lưu trữ trên một trang dữ liệu riêng theo mặc định.

Khi bảng là một đống chỉ có một chỉ số không bao gồm trên id, có hai điều xảy ra. Đầu tiên, một RIDkhóa độc quyền liên quan đến dữ liệu heap liên tiếp và khóa còn lại là khóa trên dữ liệu LOB như trước đây. Hiệu quả thứ hai là một kế hoạch thực hiện phức tạp hơn được yêu cầu.

Với chỉ mục được nhóm và cập nhật biến vị ngữ bằng một giá trị đơn giản, bộ xử lý truy vấn có thể áp dụng tối ưu hóa thực hiện cập nhật (đọc và ghi) trong một toán tử, sử dụng một đường dẫn duy nhất:

Cập nhật một toán tử

Hàng được định vị và cập nhật trong một hoạt động tìm kiếm duy nhất, chỉ yêu cầu các khóa độc quyền (không cần khóa cập nhật). Một chuỗi khóa ví dụ sử dụng bảng mẫu của bạn:

acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IX lock on PAGE: 6:1:59104 -- INROW
acquiring X lock on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
acquiring IX lock on PAGE: 6:1:59091 -- LOB
acquiring X lock on RID: 6:1:59091:1 -- LOB

releasing lock reference on PAGE: 6:1:59091 -- LOB
releasing lock reference on RID: 6:1:59091:1 -- LOB
releasing lock reference on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
releasing lock reference on PAGE: 6:1:59104 -- INROW

Chỉ với một chỉ mục không bao gồm, tối ưu hóa tương tự có thể được áp dụng bởi vì chúng ta cần đọc từ một cấu trúc cây b và viết một cấu trúc khác. Kế hoạch đa đường có các giai đoạn đọc và viết riêng biệt:

Cập nhật đa vòng lặp

Điều này có được các khóa cập nhật khi đọc, chuyển đổi thành các khóa độc quyền nếu hàng đủ điều kiện. Trình tự khóa ví dụ với lược đồ đã cho:

acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IU lock on PAGE: 6:1:59105 -- NC INDEX
acquiring U lock on KEY: 6:72057594233749504 (61a06abd401c) -- NC INDEX
acquiring IU lock on PAGE: 6:1:59104 -- HEAP
acquiring U lock on RID: 6:1:59104:1 -- HEAP
acquiring IX lock on PAGE: 6:1:59104 -- HEAP convert to X
acquiring X lock on RID: 6:1:59104:1 -- HEAP convert to X
acquiring IU lock on PAGE: 6:1:59091 -- LOB
acquiring U lock on RID: 6:1:59091:1 -- LOB

releasing lock reference on PAGE: 6:1:59091 
releasing lock reference on RID: 6:1:59091:1
releasing lock reference on RID: 6:1:59104:1
releasing lock reference on PAGE: 6:1:59104 
releasing lock on KEY: 6:72057594233749504 (61a06abd401c)
releasing lock on PAGE: 6:1:59105 

Lưu ý dữ liệu LOB được đọc và ghi tại Trình lặp cập nhật bảng. Kế hoạch phức tạp hơn và nhiều đường dẫn đọc và viết làm tăng khả năng bế tắc.

Cuối cùng, tôi không thể không chú ý đến các kiểu dữ liệu được sử dụng trong định nghĩa bảng. Bạn không nên sử dụng textkiểu dữ liệu không dùng nữa cho công việc mới; thay thế, nếu bạn thực sự cần khả năng lưu trữ tới 2GB dữ liệu trong cột này, là varchar(max). Một sự khác biệt quan trọng giữa textvarchar(max)textdữ liệu được lưu trữ ngoài hàng theo mặc định, trong khi varchar(max)lưu trữ theo hàng theo mặc định.

Chỉ sử dụng các loại Unicode nếu bạn cần sự linh hoạt đó (ví dụ khó hiểu tại sao địa chỉ IP lại cần Unicode). Ngoài ra, chọn giới hạn độ dài phù hợp cho các thuộc tính của bạn - 255 ở mọi nơi dường như không chính xác.

Đọc thêm:
Bế tắc và mô hình chung chung
Chuỗi xử lý sự cố bế tắc của Bart Duncan

Khóa truy tìm có thể được thực hiện theo nhiều cách khác nhau. SQL Server Express với các dịch vụ nâng cao (chỉ dành cho 2014 & 2012 SP1 ) chứa công cụ Profiler , đây là một cách được hỗ trợ để xem chi tiết về việc mua và phát hành khóa.


Câu trả lời tuyệt vời. Làm thế nào bạn xuất ra các bản ghi / dấu vết có thông báo "lấy ... khóa" và "phát hành tham chiếu khóa"?
Sanjiv Jivan
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.