Jalan buntu saat memperbarui baris yang berbeda dengan indeks non-cluster

13

Saya memecahkan masalah jalan buntu sementara saya melihat perilaku kunci berbeda ketika saya menggunakan indeks clustered dan non-clustered di bidang id. Masalah kebuntuan tampaknya dipecahkan jika indeks atau kunci utama clusted diterapkan ke bidang id.

Saya memiliki transaksi berbeda yang melakukan satu pembaruan atau lebih pada baris yang berbeda, mis. Transaksi A hanya akan memperbarui baris dengan ID = a, tx B hanya akan menyentuh baris dengan ID = b dll.

Dan saya mengerti bahwa tanpa indeks, pembaruan akan mendapatkan kunci pembaruan untuk semua baris dan rahasia ke kunci eksklusif bila perlu, yang pada akhirnya akan menyebabkan kebuntuan. Tapi saya gagal mencari tahu mengapa dengan indeks non-cluster, jalan buntu masih ada (meskipun hit rate tampaknya turun)

Tabel data:

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
)

Jalan buntu

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

Juga temuan terkait yang menarik dan mungkin adalah bahwa indeks clustered dan non-clustered tampaknya memiliki perilaku kunci yang berbeda

Saat menggunakan indeks berkerumun, ada kunci eksklusif pada kunci serta kunci eksklusif pada RID ketika melakukan pembaruan, yang diharapkan; sementara ada dua kunci eksklusif pada dua RID yang berbeda jika indeks non-cluster digunakan, yang membingungkan saya.

Akan sangat membantu jika ada yang bisa menjelaskan mengapa ini juga.

Uji SQL:

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

Dengan id sebagai Indeks Clustered:

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

Dengan id sebagai Indeks Non-Clustered

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: Detail kebuntuan tanpa indeks apa pun
Katakanlah saya memiliki dua tx A dan B, masing-masing dengan dua pernyataan pembaruan, baris yang berbeda tentu saja
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} dan {4} akan memiliki kemungkinan kebuntuan, sejak

pada {1}, kunci U diminta untuk baris 63502 karena perlu melakukan pemindaian tabel, dan kunci X mungkin ditahan pada baris 63501 karena cocok dengan kondisi.

pada {4}, kunci U diminta untuk baris 63501, dan kunci X sudah tahan untuk 63502

jadi kami memiliki txA memegang 63501 dan menunggu 63502 sementara txB memegang 63502 menunggu 63501, yang merupakan jalan buntu

EDIT2: Ternyata bug pada test case saya membuat situasi berbeda di sini Maaf untuk kebingungan tetapi bug membuat situasi perbedaan, dan tampaknya menyebabkan kebuntuan pada akhirnya.

Karena analisis Paul benar-benar membantu saya dalam kasus ini, maka saya akan menerimanya sebagai jawaban.

Karena bug pada test case saya, dua transaksi txA dan txB dapat memperbarui baris yang sama, seperti di bawah ini:

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} dan {3} akan mengalami kebuntuan saat:

txA meminta kunci U pada kunci sambil menahan kunci X pada RID (karena pembaruan {1}) txB meminta kunci U pada kunci sementara memegang kunci U pada kunci

Bood
sumber
1
Saya tidak tahu mengapa transaksi perlu memperbarui baris yang sama dua kali.
ypercubeᵀᴹ
@ ypercube Poin bagus, itu sesuatu yang harus saya tingkatkan. Tetapi dalam hal ini saya hanya ingin memiliki pemahaman yang lebih baik tentang perilaku kunci
Bood
@ ypercube setelah lebih banyak pemikiran saya pikir itu mungkin bahwa aplikasi dengan logika kompleks perlu memperbarui baris yang sama dua kali dalam tx yang sama, bisa jadi kolom yang berbeda misalnya
Bood

Jawaban:

16

... mengapa dengan indeks berkerumun, jalan buntu masih ada (meskipun hit rate tampaknya turun)

Pertanyaannya tidak jelas (misalnya berapa banyak pembaruan dan idnilai-nilai mana dalam setiap transaksi) tetapi satu skenario deadlock yang jelas muncul dengan beberapa pembaruan baris tunggal dalam satu transaksi, di mana ada tumpang tindih [id]nilai, dan id adalah diperbarui dalam [id]urutan berbeda :

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

Urutan deadlock: T1 (u2), T2 (u1), T1 (u1) tunggu , T2 (u2) tunggu .

Urutan kebuntuan ini dapat dihindari dengan memperbarui secara ketat dalam urutan id dalam setiap transaksi (memperoleh kunci dalam urutan yang sama di jalur yang sama).

Saat menggunakan indeks berkerumun, ada kunci eksklusif pada kunci serta kunci eksklusif pada RID ketika melakukan pembaruan, yang diharapkan; sementara ada dua kunci eksklusif pada dua RID yang berbeda jika indeks non-cluster digunakan, yang membingungkan saya.

Dengan indeks berkerumun unik aktif id, kunci eksklusif diambil pada kunci pengelompokan untuk melindungi penulisan ke data in-row. RIDKunci eksklusif terpisah diperlukan untuk melindungi penulisan ke textkolom LOB , yang disimpan pada halaman data terpisah secara default.

Ketika tabel adalah tumpukan dengan hanya indeks nonclustered aktif id, dua hal terjadi. Pertama, satu RIDkunci eksklusif berkaitan dengan data tumpukan in-line, dan yang lainnya adalah kunci pada data LOB seperti sebelumnya. Efek kedua adalah bahwa rencana eksekusi yang lebih kompleks diperlukan.

Dengan indeks berkerumun dan pembaruan predikat kesetaraan nilai tunggal yang sederhana, prosesor kueri dapat menerapkan optimisasi yang melakukan pembaruan (baca dan tulis) dalam satu operator, menggunakan jalur tunggal:

Pembaruan operator tunggal

Baris terletak dan diperbarui dalam operasi pencarian tunggal, hanya membutuhkan kunci eksklusif (tidak diperlukan kunci pembaruan). Contoh penguncian urutan menggunakan tabel sampel Anda:

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

Dengan hanya indeks nonclustered, optimasi yang sama tidak dapat diterapkan karena kita perlu membaca dari satu struktur b-tree dan menulis yang lain. Paket multi-jalur memiliki fase baca dan tulis yang terpisah:

Pembaruan multi-iterator

Ini memperoleh kunci pembaruan saat membaca, mengkonversi ke kunci eksklusif jika baris memenuhi syarat. Contoh urutan kunci dengan skema yang diberikan:

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 

Perhatikan bahwa data LOB dibaca dan ditulis di iterator Pembaruan Tabel. Rencana yang lebih kompleks dan banyak jalur baca dan tulis meningkatkan kemungkinan jalan buntu.

Akhirnya, saya tidak bisa membantu tetapi memperhatikan tipe data yang digunakan dalam definisi tabel. Anda sebaiknya tidak menggunakan texttipe data yang sudah tidak digunakan lagi untuk pekerjaan baru; alternatifnya, jika Anda benar-benar membutuhkan kemampuan untuk menyimpan hingga 2GB data di kolom ini, adalah varchar(max). Satu perbedaan penting antara textdan varchar(max)adalah bahwa textdata disimpan secara off-row secara default, sementara varchar(max)menyimpan in-row secara default.

Gunakan tipe Unicode hanya jika Anda memerlukan fleksibilitas itu (mis. Sulit untuk melihat mengapa alamat IP akan membutuhkan Unicode). Juga, pilih batas panjang yang sesuai untuk atribut Anda - 255 di mana-mana tampaknya tidak mungkin benar.

Bacaan tambahan:
Kebuntuan dan livelock pola umum
seri pemecahan masalah kebuntuan Bart Duncan

Menelusuri kunci dapat dilakukan dengan berbagai cara. SQL Server Express dengan Layanan Lanjut (hanya 2014 & 2012 SP1 dan seterusnya ) berisi alat Profiler , yang merupakan cara yang didukung untuk melihat rincian akuisisi dan rilis kunci.

Paul White 9
sumber
Jawaban yang sangat bagus. Bagaimana cara Anda mengeluarkan log / jejak yang memiliki pesan "mendapatkan ... kunci" dan "melepaskan referensi kunci"?
Sanjiv Jivan