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?