INSERT masif yang memblokir SELECT

14

Saya memiliki masalah dengan sejumlah besar INSERT yang memblokir operasi SELECT saya.

Skema

Saya punya tabel seperti ini:

CREATE TABLE [InverterData](
    [InverterID] [bigint] NOT NULL,
    [TimeStamp] [datetime] NOT NULL,    
    [ValueA] [decimal](18, 2) NULL,
    [ValueB] [decimal](18, 2) NULL
    CONSTRAINT [PrimaryKey_e149e28f-5754-4229-be01-65fafeebce16] PRIMARY KEY CLUSTERED 
    (
        [TimeStamp] DESC,
        [InverterID] ASC
    ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF
    , IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON)
)

Saya juga memiliki prosedur pembantu kecil ini, yang memungkinkan saya untuk memasukkan atau memperbarui (pembaruan saat konflik) dengan perintah MERGE:

CREATE PROCEDURE [InsertOrUpdateInverterData]
    @InverterID bigint, @TimeStamp datetime
    , @ValueA decimal(18,2), @ValueB decimal(18,2)
AS
BEGIN
    MERGE [InverterData] AS TARGET
        USING (VALUES (@InverterID, @TimeStamp, @ValueA, @ValueB))
        AS SOURCE ([InverterID], [TimeStamp], [ValueA], [ValueB])
        ON TARGET.[InverterID] = @InverterID AND TARGET.[TimeStamp] = @TimeStamp
    WHEN MATCHED THEN
        UPDATE
        SET [ValueA] = SOURCE.[ValueA], [ValueB] = SOURCE.[ValueB]              
    WHEN NOT MATCHED THEN
        INSERT ([InverterID], [TimeStamp], [ValueA], [ValueB]) 
        VALUES (SOURCE.[InverterID], SOURCE.[TimeStamp], SOURCE.[ValueA], SOURCE.[ValueB]);
END

Pemakaian

Saya sekarang telah menjalankan instance layanan pada beberapa server yang melakukan pembaruan besar-besaran dengan memanggil [InsertOrUpdateInverterData]prosedur dengan cepat.

Ada juga situs web yang melakukan kueri SELECT di atas [InverterData]meja.

Masalah

Jika saya melakukan SELECT kueri di atas [InverterData]meja, mereka diproses dalam rentang waktu yang berbeda, tergantung pada penggunaan INSERT dari layanan saya. Jika saya menjeda semua instance layanan SELECT sangat cepat, jika instance melakukan insert cepat, SELECT menjadi sangat lambat atau bahkan membatalkan waktu tunggu.

Mencoba

Saya sudah melakukan beberapa SELECT di atas [sys.dm_tran_locks]meja untuk menemukan proses penguncian, seperti ini

SELECT
tl.request_session_id,
wt.blocking_session_id,
OBJECT_NAME(p.OBJECT_ID) BlockedObjectName,
h1.TEXT AS RequestingText,
h2.TEXT AS BlockingText,
tl.request_mode

FROM sys.dm_tran_locks AS tl

INNER JOIN sys.dm_os_waiting_tasks AS wt ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.partitions AS p ON p.hobt_id = tl.resource_associated_entity_id
INNER JOIN sys.dm_exec_connections ec1 ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections ec2 ON ec2.session_id = wt.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

Ini hasilnya:

masukkan deskripsi gambar di sini

S = Dibagikan. Sesi holding diberikan akses bersama ke sumber daya.

Pertanyaan

Mengapa SELECT diblokir oleh [InsertOrUpdateInverterData]prosedur yang hanya menggunakan perintah MERGE?

Apakah saya harus menggunakan semacam transaksi dengan mode isolasi yang ditentukan di dalam [InsertOrUpdateInverterData]?

Pembaruan 1 (terkait dengan pertanyaan dari @Paul)

Berdasarkan pelaporan internal MS-SQL server tentang [InsertOrUpdateInverterData]statistik berikut:

  • Waktu CPU Rata-rata: 0.12 ms
  • Proses Baca Rata-rata: 5.76 per / s
  • Rata-rata proses Menulis: 0,4 per / s

Berdasarkan ini sepertinya perintah MERGE sebagian besar sibuk dengan operasi pembacaan yang akan mengunci tabel! (?)

Pembaruan 2 (terkait dengan pertanyaan dari @Paul)

The [InverterData]tabel seperti yang berikut statistik penyimpanan:

  • Ruang data: 26.901,86 MB
  • Jumlah baris: 131.827.749
  • Dipartisi: benar
  • Jumlah partisi: 62

Berikut adalah set hasil sp_WhoIsActive lengkap (allmost) :

SELECT perintah

  • dh jj: mm: ss.mss: 00 00: 01: 01.930
  • session_id: 73
  • wait_info: (12629ms) LCK_M_S
  • CPU: 198
  • blocking_session_id: 146
  • berbunyi: 99.368
  • menulis: 0
  • status: ditangguhkan
  • open_tran_count: 0

[InsertOrUpdateInverterData]Perintah pemblokiran

  • dh jj: mm: ss.mss: 00 00: 00: 00.330
  • session_id: 146
  • wait_info: NULL
  • CPU: 3.972
  • blocking_session_id: NULL
  • berbunyi: 376,95
  • menulis: 126
  • status: tidur
  • open_tran_count: 1
Steffen Mangold
sumber
The ([TimeStamp] DESC, [InverterID] ASC)terlihat seperti pilihan yang aneh untuk indeks berkerumun. Maksudku DESCbagian itu.
ypercubeᵀᴹ
Saya mengerti maksud Anda: Indeks Clustered DESC memasukkan data akan memaksa tabel membangun kembali agak menambahkan sampai akhir ... anjing kinerja; akan mengunci meja sementara pembangunan kembali terjadi ... ya. oleh Jove, kamu memilikinya. Struktur menyebabkan penguncian lebih dari penguncian.
Alocyte

Jawaban:

12

Pertama, meskipun sedikit tidak terkait dengan pertanyaan utama, Anda MERGE pernyataan berpotensi berisiko kesalahan karena kondisi balapan . Masalahnya, singkatnya, adalah mungkin untuk beberapa utas konkuren untuk menyimpulkan bahwa baris target tidak ada, menghasilkan upaya bertabrakan untuk memasukkan. Akar penyebabnya adalah bahwa tidak mungkin untuk mengambil kunci bersama atau memperbarui pada baris yang tidak ada. Solusinya adalah menambahkan petunjuk:

MERGE [dbo].[InverterData] WITH (SERIALIZABLE) AS [TARGET]

The tingkat isolasi serializable petunjuk memastikan kisaran utama dimana baris akan dikunci. Anda memiliki indeks unik untuk mendukung penguncian rentang, jadi petunjuk ini tidak akan memiliki efek buruk pada penguncian, Anda hanya akan mendapatkan perlindungan terhadap kondisi lomba potensial ini.

Pertanyaan utama

Mengapa demikian? SELECTs diblokir oleh prosedur [InsertOrUpdateInverterData] yang hanya menggunakan MERGEperintah?

Di bawah standar penguncian baca komitmen terisolasi, kunci (S) bersama diambil saat membaca data, dan biasanya (meskipun tidak selalu) dilepaskan segera setelah pembacaan selesai. Beberapa kunci bersama ditahan di akhir pernyataan.

SEBUAH MERGE memodifikasi pernyataan data, sehingga akan memperoleh S atau memperbarui (U) kunci ketika mencari data untuk perubahan, yang dikonversi ke eksklusif (X) kunci sebelum melakukan modifikasi yang sebenarnya. Baik kunci U dan X harus ditahan hingga akhir transaksi.

Ini benar di bawah semua level isolasi kecuali isolasi snapshot (SI) optimis ' tidak - dikacaukan dengan versi yang dilakukan, juga dikenal sebagai read berkomitmen snapshot isolation (RCSI).

Tidak ada dalam pertanyaan Anda yang menunjukkan sesi menunggu kunci S diblokir oleh sesi memegang kunci U. Kunci-kunci ini kompatibel . Setiap pemblokiran hampir pasti disebabkan oleh pemblokiran pada kunci X yang ditahan. Ini bisa agak sulit untuk ditangkap ketika sejumlah besar kunci jangka pendek diambil, dikonversi, dan dirilis dalam interval waktu singkat.

Itu open_tran_count: 1 pada InsertOrUpdateInverterData layak diselidiki. Meskipun perintah belum berjalan sangat lama, Anda harus memeriksa bahwa Anda tidak memiliki transaksi yang berisi (dalam aplikasi atau prosedur tersimpan tingkat yang lebih tinggi) yang terlalu panjang. Praktik terbaik adalah menjaga transaksi sesingkat mungkin. Ini mungkin bukan apa-apa, tetapi Anda harus memeriksa.

Solusi potensial

Seperti yang disarankan Kin dalam komentar, Anda bisa mengaktifkan level isolasi versi baris (RCSI atau SI) pada database ini. RCSI adalah yang paling sering digunakan, karena biasanya tidak memerlukan banyak perubahan aplikasi. Setelah diaktifkan, level isolasi komitmen baca default menggunakan versi baris alih-alih mengambil kunci S untuk dibaca, sehingga pemblokiran SX dikurangi atau dihilangkan. Beberapa operasi (mis. Pemeriksaan kunci asing) masih memperoleh kunci S di bawah RCSI.

Sadarilah bahwa versi baris mengkonsumsi ruang tempdb, secara umum proporsional dengan tingkat aktivitas perubahan dan lamanya transaksi. Anda perlu menguji implementasi Anda secara menyeluruh di bawah beban untuk memahami dan merencanakan dampak RCSI (atau SI) dalam kasus Anda.

Jika Anda ingin melokalisasi penggunaan versi Anda, daripada mengaktifkannya untuk seluruh beban kerja, SI mungkin masih menjadi pilihan yang lebih baik. Dengan menggunakan SI untuk transaksi baca, Anda akan menghindari pertentangan antara pembaca dan penulis, dengan biaya pembaca melihat versi baris sebelum setiap modifikasi bersamaan dimulai (lebih tepatnya, operasi baca di bawah SI akan selalu melihat status komitmen dari baris pada saat transaksi SI dimulai). Ada sedikit manfaat atau tidak sama sekali untuk menggunakan SI untuk transaksi penulisan, karena kunci tulis masih akan diambil, dan Anda harus menangani setiap konflik tulis. Kecuali jika itu yang Anda inginkan :)

Catatan: Tidak seperti RCSI (yang pernah diaktifkan berlaku untuk semua transaksi yang berjalan dengan komitmen baca), SI harus secara eksplisit diminta menggunakan SET TRANSACTION ISOLATION SNAPSHOT;.

Perilaku halus yang bergantung pada pembaca yang memblokir penulis (termasuk dalam kode pemicu!) Menjadikan pengujian ini penting. Lihat seri artikel saya yang ditautkan dan Buku Daring untuk detailnya. Jika Anda memutuskan untuk RCSI, pastikan untuk meninjau Modifikasi Data di bawah Baca Komitmen Snapshot Khusus.

Akhirnya, Anda harus memastikan instance Anda ditambal ke SQL Server 2008 Paket Layanan 4.

Paul White 9
sumber
0

Dengan rendah hati, saya tidak akan menggunakan penggabungan. Saya akan menggunakan IF Exists (UPDATE) ELSE (INSERT) - Anda memiliki kunci berkerumun dengan dua kolom yang Anda gunakan untuk mengidentifikasi baris sehingga ini adalah tes yang mudah.

Anda menyebutkan sisipan MASSIVE dan belum melakukan 1 dengan 1 ... memikirkan batching data dalam tabel pementasan dan menggunakan POWER OVERWHELMING SQL data mengatur daya untuk melakukan lebih dari 1 pembaruan / masukkan sekaligus? Seperti memiliki pengujian rutin untuk konten dalam tabel pementasan, dan meraih 10.000 teratas sekaligus, bukan 1 sekaligus ...

Saya akan melakukan sesuatu seperti ini di pembaruan saya

DECLARE @Set TABLE (StagingKey, ID,DATE)
INSERT INTO @Set
UPDATE Staging 
SET InProgress = 1
OUTPUT StagingKey, Staging.ID, Staging.Date
WHERE InProgress = 0
AND StagingID IN (SELECT TOP (100000) StagingKey FROM Staging WHERE inProgress = 0 ORDER BY StagingKey ASC ) --FIFO

DECLARE @Temp 
INSERT INTO @TEMP 
UPDATE [DEST] SET Value = Staging.Value [whatever]
OUTPUT INSERTED.ID, DATE [row identifiers]
FROM [DEST] 
JOIN [STAGING]
JOIN [@SET]; 
INSERT INTO @TEMP 
INSERT [DEST] 
SELECT
OUTPUT INSERT.ID, DATE [row identifiers] 
FROM [STAGING] 
JOIN [@SET] 
LEFT JOIN [DEST]

UPDATE Staging
SET inProgress = NULL
FROM Staging 
JOIN @set
ON @Set.Key = Staging.Key
JOIN @temp
ON @temp.id = @set.ID
AND @temp.date = @set.Date

Anda mungkin dapat menjalankan beberapa pekerjaan untuk membuka kumpulan pembaruan, dan Anda akan membutuhkan pekerjaan terpisah yang menjalankan penghapusan menetes

while exists (inProgress is null) 
delete top (100) from staging where inProgress is null 

untuk membersihkan meja pementasan.

Alosit
sumber