Ich kämpfe in meiner aktuellen Umgebung gegen NOLOCK. Ein Argument, das ich gehört habe, ist, dass der Aufwand für das Sperren eine Abfrage verlangsamt. Also habe ich einen Test entwickelt, um zu sehen, wie hoch dieser Overhead sein könnte.
Ich habe festgestellt, dass NOLOCK meinen Scan tatsächlich verlangsamt.
Anfangs war ich begeistert, aber jetzt bin ich nur noch verwirrt. Ist mein Test irgendwie ungültig? Sollte NOLOCK nicht tatsächlich einen etwas schnelleren Scan zulassen? Was passiert hier?
Hier ist mein Skript:
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())
Was ich versucht habe, hat nicht funktioniert:
- Läuft auf verschiedenen Servern (gleiche Ergebnisse, Server waren 2016-SP1 und 2016-SP2, beide leise)
- Laufen auf dbfiddle.uk auf verschiedenen Versionen (laut, aber wahrscheinlich die gleichen Ergebnisse)
- SET ISOLATION LEVEL anstelle von Hinweisen (gleiche Ergebnisse)
- Deaktivieren der Sperreneskalation auf dem Tisch (gleiche Ergebnisse)
- Untersuchen der tatsächlichen Ausführungszeit des Scans im tatsächlichen Abfrageplan (gleiche Ergebnisse)
- Hinweis neu kompilieren (gleiche Ergebnisse)
- Schreibgeschützte Dateigruppe (gleiche Ergebnisse)
Die vielversprechendste Untersuchung ergibt sich aus dem Entfernen der Papierkorbvariablen und der Verwendung einer Abfrage ohne Ergebnisse. Anfangs zeigte dies, dass NOLOCK etwas schneller war, aber als ich meinem Chef die Demo zeigte, war NOLOCK wieder langsamer.
Was ist mit NOLOCK, das einen Scan mit variabler Zuweisung verlangsamt?
Antworten:
HINWEIS: Dies ist möglicherweise nicht die Art von Antwort, nach der Sie suchen. Aber vielleicht ist es für andere potenzielle Antwortende hilfreich, Hinweise zu geben, wo sie anfangen sollen zu suchen
Wenn ich diese Abfragen unter ETW-Ablaufverfolgung (mit PerfView) ausführe, erhalte ich die folgenden Ergebnisse:
Der Unterschied beträgt also 51 ms . Dies ist ziemlich tot mit Ihrem Unterschied (~ 50ms). Meine Zahlen sind aufgrund des Overheads der Profiler-Stichproben insgesamt etwas höher.
Den Unterschied finden
Hier ist ein Vergleich nebeneinander, der zeigt, dass der Unterschied von 51 ms in der
FetchNextRow
Methode in sqlmin.dll liegt:Die einfache Auswahl befindet sich bei 332 ms links, während die Nolock-Version bei 383 ms ( 51 ms länger) rechts liegt . Sie können auch sehen, dass sich die beiden Codepfade folgendermaßen unterscheiden:
Einfach
SELECT
sqlmin!RowsetNewSS::FetchNextRow
Anrufesqlmin!IndexDataSetSession::GetNextRowValuesInternal
Verwenden von
NOLOCK
sqlmin!RowsetNewSS::FetchNextRow
Anrufesqlmin!DatasetSession::GetNextRowValuesNoLock
was entweder anruftsqlmin!IndexDataSetSession::GetNextRowValuesInternal
oderkernel32!TlsGetValue
Dies zeigt, dass die
FetchNextRow
Methode basierend auf der Isolationsstufe / dem Nolock-Hinweis eine gewisse Verzweigung aufweist.Warum dauert die
NOLOCK
Filiale länger?Der Nolock-Zweig verbringt tatsächlich weniger Zeit mit Anrufen
GetNextRowValuesInternal
(25 ms weniger). Der Code direkt inGetNextRowValuesNoLock
(ohne Methoden, die AKA als "Exc" -Spalte bezeichnen) wird jedoch für 63 ms ausgeführt - was den größten Teil des Unterschieds ausmacht (63 - 25 = 38 ms Nettozunahme der CPU-Zeit).Wie hoch sind die anderen 13 ms (insgesamt 51 ms - bisher 38 ms) des Overheads
FetchNextRow
?Schnittstellenversand
Ich dachte, dies sei mehr eine Kuriosität als alles andere, aber die Nolock-Version scheint einen gewissen Aufwand für den Schnittstellenversand zu verursachen, wenn die Windows-API-Methode
kernel32!TlsGetValue
überkernel32!TlsGetValueStub
- insgesamt 17 ms - aufgerufen wird. Die einfache Auswahl scheint nicht über die Schnittstelle zu gehen, trifft also nie den Stub und verbringt nur 6 msTlsGetValue
(ein Unterschied von 11 ms ). Sie können dies oben im ersten Screenshot sehen.Ich sollte diesen Trace wahrscheinlich mit mehr Iterationen der Abfrage erneut ausführen. Ich denke, es gibt einige kleine Dinge, wie Hardware-Interrupts, die von der 1-ms-Abtastrate von PerfView nicht erfasst wurden
Außerhalb dieser Methode habe ich einen weiteren kleinen Unterschied festgestellt, der dazu führt, dass die Nolock-Version langsamer ausgeführt wird:
Schlösser lösen
Der Nolock-Zweig scheint die
sqlmin!RowsetNewSS::ReleaseRows
Methode aggressiver auszuführen , was Sie in diesem Screenshot sehen können:Die einfache Auswahl befindet sich oben bei 12 ms, während die Nolock-Version unten bei 26 ms ( 14 ms länger) liegt. Sie können auch in der Spalte "Wann" sehen, dass der Code während des Beispiels häufiger ausgeführt wurde. Dies mag ein Implementierungsdetail von nolock sein, aber es scheint für kleine Stichproben einen erheblichen Overhead zu bedeuten.
Es gibt viele andere kleine Unterschiede, aber das sind die großen Stücke.
quelle