Pencegahan kebuntuan MERGE


9

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 HOLDLOCKada 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

indeks mencari rencana eksekusi

dengan pola penguncian berikut

indeks mencari pola penguncian

yaitu IXkunci pada objek diikuti oleh kunci lebih rinci.

Namun, kadang-kadang, rencana eksekusi permintaan berbeda

rencana pelaksanaan pemindaian tabel

(bentuk rencana ini dapat dipaksa dengan menambahkan INDEX(0)petunjuk) dan pola pengunciannya

pola penguncian pindai tabel

Xkunci pemberitahuan ditempatkan pada objek setelah IXditempatkan.

Karena dua IXkompatibel, tetapi dua Xtidak, hal yang terjadi di bawah konkurensi adalah

jalan buntu

grafik kebuntuan

jalan buntu !

Dan di sini bagian pertama dari pertanyaan muncul. Apakah menempatkan Xkunci pada objek setelah IXmemenuhi syarat? Bukankah itu bug?

Status dokumentasi :

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 Xkunci pada objek setelah IXterlihat 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 TABLOCKpola penguncian di tempat menjadi

menggabungkan pola kunci tablock holdlock

dan dengan TABLOCKXpola penguncian adalah

menggabungkan pola kunci tablockx holdlock

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 PAGLOCKdan ROWLOCKmembuat kunci lebih granular dan mengurangi pertikaian. Keduanya tidak memiliki efek ( Xpada objek masih diamati segera setelah IX).

Upaya terakhir saya adalah memaksa bentuk rencana eksekusi "baik" dengan penguncian granular yang baik dengan menambahkan FORCESEEKpetunjuk

MERGE INTO [Cache] WITH (HOLDLOCK, FORCESEEK(IX_Cache(ItemKey))) T

dan itu berhasil.

Dan di sini muncul pertanyaan kedua . Mungkinkah itu FORCESEEKakan diabaikan dan pola penguncian yang buruk akan digunakan? (Seperti yang saya sebutkan, PAGLOCKdan ROWLOCKtampaknya diabaikan).


Menambahkan UPDLOCKtidak berpengaruh ( Xpada objek yang masih dapat diamati setelah IX).

Membuat IX_Cacheindeks 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 @itemKeydeklarasi variabel dari varchar (200) ke nvarchar (200) , rencana eksekusi menjadi

indeks mencari rencana eksekusi dengan nvarchar

melihat bahwa mencari digunakan, TETAPI pola penguncian dalam kasus ini lagi menunjukkan Xkunci 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 Xkunci pada objek setelah IXmasih terbuka. Dan jika memenuhi syarat, adakah yang bisa dilakukan seseorang untuk mencegah penguncian objek?


Permintaan terkait di feedback.azure.com
i-one

Jawaban:


9

Apakah penempatan IXdiikuti oleh Xobjek yang memenuhi syarat? Apakah ini bug atau tidak?

Ini terlihat agak aneh, tetapi ini valid. Pada saat IXdiambil, niatnya mungkin untuk mengambil Xkunci 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 untuk ISdan Skunci 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 Xkunci di tingkat objek. Dalam hal ini, mesin mungkin dapat mendeteksi lebih awal bahwa suatu Xkunci tidak dapat dihindarkan diperlukan jika metode aksesnya adalah pemindaian tumpukan, dan karenanya menghindari pengambilan IXkunci.

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 IXmungkin 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.

Mungkinkah itu FORCESEEKakan diabaikan dan pola penguncian yang buruk akan digunakan?

Tidak. FORCESEEKItu 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).

... deklarasi variabel dari varchar(200)ke nvarchar(200)...

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]....

Pemahaman saya [...] adalah bahwa penguncian adalah situasional dalam tingkat yang besar, dan bentuk rencana eksekusi tertentu tidak menyiratkan pola penguncian tertentu.

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:

CREATE UNIQUE CLUSTERED INDEX IX_Cache ON [Cache] ([ItemKey]);

Ini mungkin memiliki keuntungan penggunaan kembali ruang yang penting, serta memberikan solusi yang baik untuk masalah kebuntuan saat ini.

MERGEjuga sedikit tidak biasa untuk dilihat dalam lingkungan konkurensi tinggi. Agak kontra-intuitif, sering kali lebih efisien untuk melakukan pernyataan terpisah INSERTdan UPDATE, misalnya:

DECLARE
    @itemKey varchar(200) = 'Item_0F3C43A6A6A14255B2EA977EA730EDF2',
    @fileName nvarchar(255) = 'File_0F3C43A6A6A14255B2EA977EA730EDF2.dat';

BEGIN TRANSACTION;

    DECLARE @expires datetime2(2) = DATEADD(MINUTE, 10, SYSDATETIME());

    UPDATE TOP (1) dbo.Cache WITH (SERIALIZABLE, UPDLOCK)
    SET [FileName] = @fileName,
        Expires = @expires
    OUTPUT Deleted.[FileName]
    WHERE
        ItemKey = @itemKey;

    IF @@ROWCOUNT = 0
        INSERT dbo.Cache
            (ItemKey, [FileName], Expires)
        VALUES
            (@itemKey, @fileName, @expires);

COMMIT TRANSACTION;

Perhatikan bagaimana pencarian RID tidak lagi diperlukan:

Rencana eksekusi

Jika Anda dapat menjamin keberadaan indeks unik pada ItemKey(seperti dalam pertanyaan) yang berlebihan TOP (1)dalam UPDATEdapat dihapus, memberikan rencana yang lebih sederhana:

Pembaruan yang disederhanakan

Keduanya INSERTdan UPDATErencana memenuhi syarat untuk rencana sepele dalam kedua kasus. MERGEselalu 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 .

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.