Di salah satu database kami, kami memiliki tabel yang secara intensif diakses secara bersamaan oleh banyak utas. Utas memperbarui atau menyisipkan baris MERGE
. Ada juga utas yang kadang-kadang menghapus baris, sehingga data tabel sangat fluktuatif. Utas yang melakukan upert terkadang mengalami kebuntuan. Masalahnya terlihat mirip dengan yang dijelaskan dalam pertanyaan ini . Perbedaannya adalah, dalam kasus kami, masing-masing utas melakukan pembaruan atau memasukkan tepat satu baris .
Setup yang disederhanakan mengikuti. Tabel ini tumpukan dengan dua indeks nonclustered unik berakhir
CREATE TABLE [Cache]
(
[UID] uniqueidentifier NOT NULL CONSTRAINT DF_Cache_UID DEFAULT (newid()),
[ItemKey] varchar(200) NOT NULL,
[FileName] nvarchar(255) NOT NULL,
[Expires] datetime2(2) NOT NULL,
CONSTRAINT [PK_Cache] PRIMARY KEY NONCLUSTERED ([UID])
)
GO
CREATE UNIQUE INDEX IX_Cache ON [Cache] ([ItemKey]);
GO
dan permintaan tipikal adalah
DECLARE
@itemKey varchar(200) = 'Item_0F3C43A6A6A14255B2EA977EA730EDF2',
@fileName nvarchar(255) = 'File_0F3C43A6A6A14255B2EA977EA730EDF2.dat';
MERGE INTO [Cache] WITH (HOLDLOCK) T
USING (
VALUES (@itemKey, @fileName, dateadd(minute, 10, sysdatetime()))
) S(ItemKey, FileName, Expires)
ON T.ItemKey = S.ItemKey
WHEN MATCHED THEN
UPDATE
SET
T.FileName = S.FileName,
T.Expires = S.Expires
WHEN NOT MATCHED THEN
INSERT (ItemKey, FileName, Expires)
VALUES (S.ItemKey, S.FileName, S.Expires)
OUTPUT deleted.FileName;
yaitu pencocokan terjadi dengan kunci indeks unik. Petunjuk HOLDLOCK
ada di sini, karena konkurensi (seperti yang disarankan di sini ).
Saya melakukan penyelidikan kecil dan berikut ini adalah apa yang saya temukan.
Dalam sebagian besar kasus, rencana eksekusi kueri adalah
dengan pola penguncian berikut
yaitu IX
kunci pada objek diikuti oleh kunci lebih rinci.
Namun, kadang-kadang, rencana eksekusi permintaan berbeda
(bentuk rencana ini dapat dipaksa dengan menambahkan INDEX(0)
petunjuk) dan pola pengunciannya
X
kunci pemberitahuan ditempatkan pada objek setelah IX
ditempatkan.
Karena dua IX
kompatibel, tetapi dua X
tidak, hal yang terjadi di bawah konkurensi adalah
jalan buntu !
Dan di sini bagian pertama dari pertanyaan muncul. Apakah menempatkan X
kunci pada objek setelah IX
memenuhi syarat? Bukankah itu bug?
Kunci inten dinamai kunci maksud karena mereka diperoleh sebelum kunci pada level yang lebih rendah, dan oleh karena itu sinyal maksud untuk menempatkan kunci pada level yang lebih rendah .
dan juga
IX berarti niat untuk memperbarui hanya beberapa baris daripada semuanya
jadi, menempatkan X
kunci pada objek setelah IX
terlihat SANGAT mencurigakan bagi saya.
Pertama saya mencoba untuk mencegah kebuntuan dengan mencoba menambahkan petunjuk penguncian tabel
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCK) T
dan
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCKX) T
dengan TABLOCK
pola penguncian di tempat menjadi
dan dengan TABLOCKX
pola penguncian adalah
karena dua SIX
(dan juga dua X
) tidak kompatibel ini mencegah kebuntuan secara efektif, tetapi, sayangnya, mencegah konkurensi juga (yang tidak diinginkan).
Upaya saya berikutnya adalah menambah PAGLOCK
dan ROWLOCK
membuat kunci lebih granular dan mengurangi pertikaian. Keduanya tidak memiliki efek ( X
pada objek masih diamati segera setelah IX
).
Upaya terakhir saya adalah memaksa bentuk rencana eksekusi "baik" dengan penguncian granular yang baik dengan menambahkan FORCESEEK
petunjuk
MERGE INTO [Cache] WITH (HOLDLOCK, FORCESEEK(IX_Cache(ItemKey))) T
dan itu berhasil.
Dan di sini muncul pertanyaan kedua . Mungkinkah itu FORCESEEK
akan diabaikan dan pola penguncian yang buruk akan digunakan? (Seperti yang saya sebutkan, PAGLOCK
dan ROWLOCK
tampaknya diabaikan).
Menambahkan UPDLOCK
tidak berpengaruh ( X
pada objek yang masih dapat diamati setelah IX
).
Membuat IX_Cache
indeks berkerumun, seperti yang diantisipasi, berhasil. Itu mengarah ke rencana dengan Clustered Index Seek dan penguncian granular. Selain itu saya mencoba memaksa Scan Indeks Clustered yang ditampilkan penguncian granular juga.
Namun. Pengamatan tambahan. Di pengaturan awal bahkan dengan FORCESEEK(IX_Cache(ItemKey)))
di tempat, jika seseorang mengubah @itemKey
deklarasi variabel dari varchar (200) ke nvarchar (200) , rencana eksekusi menjadi
melihat bahwa mencari digunakan, TETAPI pola penguncian dalam kasus ini lagi menunjukkan X
kunci ditempatkan pada objek setelah IX
.
Jadi, tampaknya mencari paksa tidak selalu menjamin kunci granular (dan deadlock tidak ada karenanya). Saya tidak yakin, bahwa memiliki pengelompokan indeks jaminan granular. Atau apakah itu?
Pemahaman saya (koreksi saya jika saya salah) adalah bahwa penguncian adalah situasional dalam tingkat yang besar, dan bentuk rencana eksekusi tertentu tidak menyiratkan pola penguncian tertentu.
Pertanyaan tentang kelayakan menempatkan X
kunci pada objek setelah IX
masih terbuka. Dan jika memenuhi syarat, adakah yang bisa dilakukan seseorang untuk mencegah penguncian objek?
sumber
Jawaban:
Ini terlihat agak aneh, tetapi ini valid. Pada saat
IX
diambil, niatnya mungkin untuk mengambilX
kunci di tingkat yang lebih rendah. Tidak ada yang mengatakan bahwa kunci semacam itu harus diambil. Lagipula, mungkin tidak ada apa pun untuk dikunci di tingkat bawah; mesin tidak bisa tahu itu sebelumnya. Selain itu, mungkin ada optimisasi sehingga kunci tingkat rendah dapat dilewati (contoh untukIS
danS
kunci dapat dilihat di sini ).Lebih khusus untuk skenario saat ini, memang benar bahwa kunci-rentang kunci tidak tersedia untuk tumpukan, jadi satu-satunya alternatif adalah
X
kunci di tingkat objek. Dalam hal ini, mesin mungkin dapat mendeteksi lebih awal bahwa suatuX
kunci tidak dapat dihindarkan diperlukan jika metode aksesnya adalah pemindaian tumpukan, dan karenanya menghindari pengambilanIX
kunci.Di sisi lain, penguncian itu rumit, dan kunci niat terkadang diambil karena alasan internal yang tidak selalu terkait dengan niat untuk mengambil kunci tingkat bawah. Mengambil
IX
mungkin merupakan cara yang paling tidak invasif untuk memberikan perlindungan yang diperlukan untuk beberapa kasus tepi yang tidak jelas. Untuk jenis pertimbangan serupa, lihat Kunci Bersama yang dikeluarkan pada IsolationLevel.ReadUncommitted .Jadi, situasi saat ini sangat disayangkan untuk skenario kebuntuan Anda, dan mungkin pada prinsipnya dapat dihindari, tetapi itu tidak selalu sama dengan menjadi 'bug'. Anda dapat melaporkan masalah melalui saluran dukungan normal Anda, atau di Microsoft Connect, jika Anda membutuhkan jawaban yang pasti untuk ini.
Tidak.
FORCESEEK
Itu kurang dari petunjuk dan lebih banyak arahan. Jika pengoptimal tidak dapat menemukan rencana yang menghormati 'petunjuk', itu akan menghasilkan kesalahan.Memaksa indeks adalah cara untuk memastikan bahwa kunci rentang kunci dapat diambil. Bersama dengan kunci pembaruan yang diambil secara alami saat memproses metode akses untuk baris yang diubah, ini memberikan cukup jaminan untuk menghindari masalah konkurensi dalam skenario Anda.
Jika skema tabel tidak berubah (misalnya menambahkan indeks baru), petunjuknya juga cukup untuk menghindari kebuntuan kueri ini dengan sendirinya. Masih ada kemungkinan kebuntuan siklik dengan kueri lain yang mungkin mengakses heap sebelum indeks nonclustered (seperti pembaruan ke kunci dari indeks nonclustered).
Ini melanggar jaminan bahwa satu baris akan terpengaruh, sehingga Eager Table Spool diperkenalkan untuk perlindungan Halloween. Sebagai solusi lebih lanjut untuk ini, buat jaminan eksplisit dengan
MERGE TOP (1) INTO [Cache]...
.Tentu saja ada lebih banyak hal yang terjadi dalam rencana eksekusi. Anda dapat memaksa bentuk rencana tertentu dengan misalnya panduan rencana, tetapi mesin mungkin masih memutuskan untuk mengambil kunci yang berbeda saat runtime. Peluangnya cukup rendah jika Anda memasukkan
TOP (1)
elemen di atas.Komentar umum
Agak tidak lazim melihat heap table digunakan dengan cara ini. Anda harus mempertimbangkan manfaat mengubahnya menjadi tabel berkerumun, mungkin menggunakan indeks yang disarankan Dan Guzman dalam komentar:
Ini mungkin memiliki keuntungan penggunaan kembali ruang yang penting, serta memberikan solusi yang baik untuk masalah kebuntuan saat ini.
MERGE
juga sedikit tidak biasa untuk dilihat dalam lingkungan konkurensi tinggi. Agak kontra-intuitif, sering kali lebih efisien untuk melakukan pernyataan terpisahINSERT
danUPDATE
, misalnya:Perhatikan bagaimana pencarian RID tidak lagi diperlukan:
Jika Anda dapat menjamin keberadaan indeks unik pada
ItemKey
(seperti dalam pertanyaan) yang berlebihanTOP (1)
dalamUPDATE
dapat dihapus, memberikan rencana yang lebih sederhana:Keduanya
INSERT
danUPDATE
rencana memenuhi syarat untuk rencana sepele dalam kedua kasus.MERGE
selalu memerlukan optimasi berbasis biaya penuh.Lihat tanya jawab terkait SQL Server 2014 masalah input bersamaan untuk pola yang benar untuk digunakan, dan informasi lebih lanjut tentang
MERGE
.Deadlock tidak selalu bisa dicegah. Mereka dapat dikurangi seminimal mungkin dengan pengkodean dan desain yang cermat, tetapi aplikasi harus selalu siap untuk menangani kebuntuan aneh dengan anggun (misalnya, periksa kembali kondisi lalu coba lagi).
Jika Anda memiliki kontrol penuh atas proses yang mengakses objek yang dimaksud, Anda mungkin juga mempertimbangkan menggunakan kunci aplikasi untuk membuat serial akses ke elemen individual, seperti yang dijelaskan dalam SQL Server Concurrent Sisipan dan Hapus .
sumber