Saya berjuang melawan NOLOCK di lingkungan saya saat ini. Salah satu argumen yang saya dengar adalah bahwa overhead penguncian memperlambat kueri. Jadi, saya membuat tes untuk melihat seberapa besar biaya overhead ini.
Saya menemukan bahwa NOLOCK sebenarnya memperlambat pemindaian saya.
Awalnya saya senang, tapi sekarang saya bingung. Apakah pengujian saya salah? Bukankah seharusnya NOLOCK memungkinkan pemindaian yang sedikit lebih cepat? Apa yang sedang terjadi disini?
Ini skrip saya:
USE TestDB
GO
--Create a five-million row table
DROP TABLE IF EXISTS dbo.JustAnotherTable
GO
CREATE TABLE dbo.JustAnotherTable (
ID INT IDENTITY PRIMARY KEY,
notID CHAR(5) NOT NULL )
INSERT dbo.JustAnotherTable
SELECT TOP 5000000 'datas'
FROM sys.all_objects a1
CROSS JOIN sys.all_objects a2
CROSS JOIN sys.all_objects a3
/********************************************/
-----Testing. Run each multiple times--------
/********************************************/
--How fast is a plain select? (I get about 587ms)
DECLARE @trash CHAR(5), @dt DATETIME = SYSDATETIME()
SELECT @trash = notID --trash variable prevents any slowdown from returning data to SSMS
FROM dbo.JustAnotherTable
ORDER BY ID
OPTION (MAXDOP 1)
SELECT DATEDIFF(MILLISECOND,@dt,SYSDATETIME())
----------------------------------------------
--Now how fast is it with NOLOCK? About 640ms for me
DECLARE @trash CHAR(5), @dt DATETIME = SYSDATETIME()
SELECT @trash = notID
FROM dbo.JustAnotherTable (NOLOCK)
ORDER BY ID --would be an allocation order scan without this, breaking the comparison
OPTION (MAXDOP 1)
SELECT DATEDIFF(MILLISECOND,@dt,SYSDATETIME())
Apa yang saya coba tidak berhasil:
- Berjalan di server yang berbeda (hasil yang sama, server 2016-SP1 dan 2016-SP2, keduanya sunyi)
- Berjalan di dbfiddle.uk pada versi yang berbeda (hasil berisik, tetapi mungkin sama)
- SET TINGKAT ISOLASI alih-alih petunjuk (hasil yang sama)
- Mematikan eskalasi kunci di atas meja (hasil yang sama)
- Memeriksa waktu pelaksanaan aktual pemindaian dalam rencana kueri aktual (hasil yang sama)
- Kompilasi ulang petunjuk (hasil yang sama)
- Baca hanya filegroup (hasil yang sama)
Eksplorasi yang paling menjanjikan datang dari menghapus variabel sampah dan menggunakan kueri tanpa hasil. Awalnya ini menunjukkan NOLOCK sedikit lebih cepat, tetapi ketika saya menunjukkan demo kepada bos saya, NOLOCK kembali menjadi lebih lambat.
Ada apa dengan NOLOCK yang memperlambat pemindaian dengan penugasan variabel?
sumber
Jawaban:
CATATAN: ini mungkin bukan jenis jawaban yang Anda cari. Tetapi mungkin akan bermanfaat bagi penjawab potensial lainnya sejauh memberikan petunjuk di mana harus mulai mencari
Ketika saya menjalankan kueri ini di bawah pelacakan ETW (menggunakan PerfView), saya mendapatkan hasil berikut:
Jadi perbedaannya adalah 51 ms . Ini cukup mati dengan perbedaan Anda (~ 50 ms). Angka saya secara keseluruhan sedikit lebih tinggi karena overhead pengambilan sampel profiler.
Menemukan perbedaannya
Berikut perbandingan berdampingan yang menunjukkan bahwa perbedaan 51ms dalam
FetchNextRow
metode di sqlmin.dll:Pilih polos di sebelah kiri di 332 ms, sedangkan versi nolock di sebelah kanan di 383 ( lebih lama 51ms ). Anda juga dapat melihat bahwa dua jalur kode berbeda dengan cara ini:
Polos
SELECT
sqlmin!RowsetNewSS::FetchNextRow
panggilansqlmin!IndexDataSetSession::GetNextRowValuesInternal
Menggunakan
NOLOCK
sqlmin!RowsetNewSS::FetchNextRow
panggilansqlmin!DatasetSession::GetNextRowValuesNoLock
yang memanggil baiksqlmin!IndexDataSetSession::GetNextRowValuesInternal
ataukernel32!TlsGetValue
Ini menunjukkan bahwa ada beberapa percabangan dalam
FetchNextRow
metode berdasarkan tingkat isolasi / petunjuk nolock.Mengapa
NOLOCK
cabang membutuhkan waktu lebih lama?Cabang nolock sebenarnya menghabiskan lebih sedikit waktu untuk memanggil
GetNextRowValuesInternal
(kurang dari 25 ms). Tetapi kode langsung diGetNextRowValuesNoLock
(tidak termasuk metode yang disebut AKA kolom "Exc") berjalan untuk 63ms - yang merupakan mayoritas perbedaan (63 - 25 = 38ms kenaikan bersih dalam waktu CPU).Jadi apa yang 13ms lainnya (total 51ms - 38ms dicatat sejauh ini) dari overhead
FetchNextRow
?Pengiriman antarmuka
Saya pikir ini lebih merupakan keingintahuan daripada apa pun, tetapi versi nolock tampaknya mengeluarkan beberapa biaya pengiriman antarmuka dengan memanggil ke dalam metode Windows API
kernel32!TlsGetValue
melaluikernel32!TlsGetValueStub
- total 17ms. Pilih polos tampaknya tidak melalui antarmuka, sehingga tidak pernah menyentuh tulisan rintisan, dan hanya menghabiskan 6msTlsGetValue
(perbedaan 11ms ). Anda dapat melihat ini di atas pada tangkapan layar pertama.Saya mungkin harus menjalankan jejak ini lagi dengan lebih banyak iterasi kueri, saya pikir ada beberapa hal kecil, seperti gangguan perangkat keras, yang tidak diambil oleh laju sampel 1ms dari PerfView
Di luar metode itu, saya perhatikan perbedaan kecil lain yang menyebabkan versi nolock berjalan lebih lambat:
Melepaskan Kunci
Cabang nolock tampaknya menjalankan
sqlmin!RowsetNewSS::ReleaseRows
metode dengan lebih agresif , yang dapat Anda lihat di tangkapan layar ini:Pilih polos di atas, di 12ms, sedangkan versi nolock di bawah di 26ms ( lebih lama 14ms ). Anda juga dapat melihat di kolom "Kapan" bahwa kode dieksekusi lebih sering selama sampel. Ini mungkin merupakan detail implementasi nolock, tetapi tampaknya memperkenalkan sedikit overhead untuk sampel kecil.
Ada banyak perbedaan kecil lainnya, tetapi itu adalah potongan besar.
sumber