Mengapa NOLOCK membuat pemindaian dengan penugasan variabel lebih lambat?

11

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?

Forrest
sumber
Dibutuhkan seseorang dengan akses kode sumber dan profiler untuk memberikan jawaban yang pasti. Tetapi NOLOCK harus melakukan beberapa pekerjaan tambahan untuk memastikan itu tidak memasuki loop tak terbatas di hadapan data yang bermutasi. Dan mungkin ada optimasi yang dinonaktifkan (alias tidak pernah diuji) untuk kueri NOLOCK.
David Browne - Microsoft
1
Tidak ada repro untuk saya di Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) localdb.
Martin Smith

Jawaban:

12

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:

Plain  - 608 ms  
NOLOCK - 659 ms

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 FetchNextRowmetode di sqlmin.dll:

FetchNextRow

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 panggilan
      • sqlmin!IndexDataSetSession::GetNextRowValuesInternal
  • Menggunakan NOLOCK

    • sqlmin!RowsetNewSS::FetchNextRow panggilan
      • sqlmin!DatasetSession::GetNextRowValuesNoLock yang memanggil baik
        • sqlmin!IndexDataSetSession::GetNextRowValuesInternal atau
        • kernel32!TlsGetValue

Ini menunjukkan bahwa ada beberapa percabangan dalam FetchNextRowmetode berdasarkan tingkat isolasi / petunjuk nolock.

Mengapa NOLOCKcabang membutuhkan waktu lebih lama?

Cabang nolock sebenarnya menghabiskan lebih sedikit waktu untuk memanggil GetNextRowValuesInternal(kurang dari 25 ms). Tetapi kode langsung di GetNextRowValuesNoLock(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!TlsGetValuemelalui kernel32!TlsGetValueStub- total 17ms. Pilih polos tampaknya tidak melalui antarmuka, sehingga tidak pernah menyentuh tulisan rintisan, dan hanya menghabiskan 6ms TlsGetValue(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::ReleaseRowsmetode dengan lebih agresif , yang dapat Anda lihat di tangkapan layar ini:

Melepaskan kunci

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.

Josh Darnell
sumber