NOLOCK neden değişken atama ile bir tarama işlemini yavaşlatıyor?


11

Mevcut ortamımda NOLOCK ile mücadele ediyorum. Duyduğum bir argüman, kilitleme yükünün bir sorguyu yavaşlatması. Bu yüzden, bu yükün ne kadar olabileceğini görmek için bir test tasarladım.

NOLOCK'un taramamı gerçekten yavaşlattığını keşfettim.

İlk başta çok mutlu oldum, ama şimdi kafam karıştı. Testim bir şekilde geçersiz mi? NOLOCK aslında biraz daha hızlı taramaya izin vermemeli mi? Burada neler oluyor?

İşte benim senaryom:

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())

Ne denedim işe yaramadı:

  • Farklı sunucularda çalışıyor (aynı sonuçlar, sunucular 2016-SP1 ve 2016-SP2 idi, her ikisi de sessizdi)
  • Farklı sürümlerde dbfiddle.uk üzerinde çalışıyor (gürültülü, ancak muhtemelen aynı sonuçlar)
  • İpuçları yerine İZOLASYON DÜZEYİNİ AYARLA (aynı sonuçlar)
  • Masada kilit yükselmesini kapatma (aynı sonuçlar)
  • Gerçek sorgu planında taramanın gerçek yürütme süresinin incelenmesi (aynı sonuçlar)
  • İpucunu yeniden derle (aynı sonuçlar)
  • Salt okunur dosya grubu (aynı sonuçlar)

En umut verici keşif, çöp değişkenini kaldırmak ve sonuçsuz bir sorgu kullanmaktır. Başlangıçta bu NOLOCK'u biraz daha hızlı gösterdi, ancak patronuma demoyu gösterdiğimde NOLOCK daha yavaştı.

Değişken atama ile taramayı yavaşlatan NOLOCK ile ilgili nedir?


Kesin bir cevap vermek için kaynak kodu erişimi olan bir kişi ve bir profil oluşturucu gerekir. Ancak NOLOCK, mutasyon yapan verilerin varlığında sonsuz bir döngüye girmediğinden emin olmak için bazı ek çalışmalar yapmalıdır. Ayrıca NOLOCK sorguları için devre dışı bırakılmış (asla test edilmemiştir) optimizasyonlar olabilir.
David Browne - Microsoft

1
Microsoft SQL Server 2016 (SP1) (KB3182545) - 13.0.4001.0 (X64) localdb'de benim için repro yok.
Martin Smith

Yanıtlar:


12

NOT: Bu, aradığınız cevap türü olmayabilir. Ama belki de nereden bakmaya başlayacağına dair ipuçları sağladığı sürece diğer potansiyel cevaplayıcılara yardımcı olacaktır

ETW izleme (PerfView kullanarak) altında bu sorguları çalıştırdığınızda, aşağıdaki sonuçları elde:

Plain  - 608 ms  
NOLOCK - 659 ms

Yani fark 51 ms . Bu farkınızla oldukça ölüdür (~ 50ms). Profiler örnekleme yükü nedeniyle sayılarım genel olarak biraz daha yüksek.

Farkı bulmak

Burada 51ms farkının sqlmin.dll FetchNextRowyönteminde olduğunu gösteren yan yana karşılaştırma :

FetchNextRow

Düz seçim 332 ms'de solda, nolock sürümü sağda 383'te ( 51 ms daha uzun). İki kod yolunun da bu şekilde farklı olduğunu görebilirsiniz:

  • Sade SELECT

    • sqlmin!RowsetNewSS::FetchNextRow aramalar
      • sqlmin!IndexDataSetSession::GetNextRowValuesInternal
  • kullanma NOLOCK

    • sqlmin!RowsetNewSS::FetchNextRow aramalar
      • sqlmin!DatasetSession::GetNextRowValuesNoLock hangisini çağırır
        • sqlmin!IndexDataSetSession::GetNextRowValuesInternal veya
        • kernel32!TlsGetValue

Bu, FetchNextRowizolasyon seviyesi / nolock ipucuna dayanan yöntemde bazı dallanma olduğunu gösterir .

NOLOCKŞube neden daha uzun sürüyor?

Nolock şubesi aslında daha az zaman harcıyor GetNextRowValuesInternal(25 ms daha az). Ancak doğrudan kod GetNextRowValuesNoLock(AKA "Hariç" sütunu olarak adlandırdığı yöntemleri içermez) 63ms için çalışır - bu farkın çoğunluğu (CPU zamanında 63 - 25 = 38ms net artış).

Peki, diğer 13ms (toplam 51ms - şimdiye kadar 38ms) yükü FetchNextRownedir?

Arayüz dağıtımı

Bunun her şeyden daha fazla bir merak olduğunu düşündüm, ancak nolock sürümü, Windows API yöntemini çağırarak - toplam 17ms kernel32!TlsGetValuearacılığıyla bazı arayüz gönderme yüküne neden görünüyor kernel32!TlsGetValueStub. Düz seçim arabirimden geçmiyor gibi görünüyor, bu yüzden saplamaya asla çarpmıyor ve sadece 6 ms harcıyor (11 ms TlsGetValuefark ). Bunu ilk ekran görüntüsünde görebilirsiniz.

Muhtemelen sorgunun daha fazla yinelemesiyle bu izlemeyi tekrar çalıştırmalıyım, donanım kesintileri gibi PerfMS'in 1 ms'lik örnekleme hızı tarafından alınmayan bazı küçük şeyler olduğunu düşünüyorum.


Bu yöntemin dışında, nolock sürümünün daha yavaş çalışmasına neden olan başka bir küçük fark fark ettim:

Kilitleri Serbest Bırakma

Nolock dalı sqlmin!RowsetNewSS::ReleaseRows, bu ekran görüntüsünde görebileceğiniz yöntemi daha agresif bir şekilde çalıştırıyor gibi görünüyor :

Kilitleri serbest bırakma

Düz seçim 12ms'de üstte, nolock sürümü ise 26ms'de ( 14ms daha uzun) alttadır . Kodun örnekleme sırasında daha sık yürütüldüğünü "Ne zaman" sütununda da görebilirsiniz. Bu, nolock'un bir uygulama detayı olabilir, ancak küçük numuneler için biraz ek yük getirdiği görülmektedir.


Başka küçük farklılıklar da var, ama bunlar büyük parçalar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.