Je me bats contre NOLOCK dans mon environnement actuel. Un argument que j'ai entendu est que la surcharge du verrouillage ralentit une requête. J'ai donc conçu un test pour voir à quel point ces frais généraux pourraient être.
J'ai découvert que NOLOCK ralentit réellement mon scan.
Au début, j'étais ravi, mais maintenant je suis juste confus. Mon test est-il invalide d'une manière ou d'une autre? NOLOCK ne devrait-il pas permettre un scan légèrement plus rapide? Qu'est-ce qu'il se passe ici?
Voici mon script:
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())
Ce que j'ai essayé n'a pas fonctionné:
- Fonctionnant sur différents serveurs (mêmes résultats, les serveurs étaient 2016-SP1 et 2016-SP2, tous deux silencieux)
- Fonctionnant sur dbfiddle.uk sur différentes versions (bruyant, mais probablement les mêmes résultats)
- DÉFINIR LE NIVEAU D'ISOLEMENT au lieu des indices (mêmes résultats)
- Désactiver l'escalade des verrous sur la table (mêmes résultats)
- Examen du temps d'exécution réel de l'analyse dans le plan de requête réel (mêmes résultats)
- Astuce de recompilation (mêmes résultats)
- Groupe de fichiers en lecture seule (mêmes résultats)
L'exploration la plus prometteuse vient de la suppression de la variable corbeille et de l'utilisation d'une requête sans résultat. Au début, cela montrait NOLOCK comme légèrement plus rapide, mais quand j'ai montré la démo à mon patron, NOLOCK était redevenu plus lent.
Qu'est-ce que NOLOCK ralentit un scan avec affectation de variable?
la source
Réponses:
REMARQUE: ce n'est peut-être pas le type de réponse que vous recherchez. Mais il sera peut-être utile à d’autres répondeurs potentiels de fournir des indices sur les points de départ
Lorsque j'exécute ces requêtes sous le traçage ETW (à l'aide de PerfView), j'obtiens les résultats suivants:
La différence est donc de 51 ms . C'est assez mort avec votre différence (~ 50 ms). Mes chiffres sont globalement légèrement plus élevés en raison des frais généraux d'échantillonnage du profileur.
Trouver la différence
Voici une comparaison côte à côte montrant que la différence de 51 ms est dans la
FetchNextRow
méthode dans sqlmin.dll:La sélection simple est à gauche à 332 ms, tandis que la version nolock est à droite à 383 ( 51 ms de plus). Vous pouvez également voir que les deux chemins de code diffèrent de cette manière:
Plaine
SELECT
sqlmin!RowsetNewSS::FetchNextRow
appelssqlmin!IndexDataSetSession::GetNextRowValuesInternal
En utilisant
NOLOCK
sqlmin!RowsetNewSS::FetchNextRow
appelssqlmin!DatasetSession::GetNextRowValuesNoLock
qui appelle soitsqlmin!IndexDataSetSession::GetNextRowValuesInternal
oukernel32!TlsGetValue
Cela montre qu'il y a une ramification dans la
FetchNextRow
méthode basée sur l'indice de niveau d'isolement / nolock.Pourquoi la
NOLOCK
succursale prend-elle plus de temps?La branche nolock passe en fait moins de temps à appeler
GetNextRowValuesInternal
(25 ms de moins). Mais le code directementGetNextRowValuesNoLock
(sans inclure les méthodes qu'il appelle AKA la colonne "Exc") fonctionne pendant 63 ms - ce qui représente la majorité de la différence (63 - 25 = augmentation nette de 38 ms du temps CPU).Alors, quels sont les 13 ms restants (51 ms au total - 38 ms comptabilisés jusqu'à présent)
FetchNextRow
?Envoi de l'interface
Je pensais que c'était plus une curiosité qu'autre chose, mais la version nolock semble entraîner des frais de répartition d'interface en appelant la méthode API Windows
kernel32!TlsGetValue
viakernel32!TlsGetValueStub
- un total de 17 ms. La sélection simple semble ne pas passer par l'interface, donc elle ne frappe jamais le talon et ne passe que 6 msTlsGetValue
(une différence de 11 ms ). Vous pouvez le voir ci-dessus dans la première capture d'écran.Je devrais probablement réexécuter cette trace avec plus d'itérations de la requête, je pense qu'il y a quelques petites choses, comme les interruptions matérielles, qui n'ont pas été détectées par le taux d'échantillonnage de 1 ms de PerfView
En dehors de cette méthode, j'ai remarqué une autre petite différence qui ralentit la version nolock:
Déverrouillage des verrous
La branche nolock semble exécuter la
sqlmin!RowsetNewSS::ReleaseRows
méthode de manière plus agressive , que vous pouvez voir dans cette capture d'écran:La sélection simple est en haut, à 12 ms, tandis que la version nolock est en bas à 26 ms ( 14 ms de plus). Vous pouvez également voir dans la colonne "Quand" que le code a été exécuté plus fréquemment au cours de l'exemple. Cela peut être un détail d'implémentation de nolock, mais il semble introduire un peu de surcharge pour les petits échantillons.
Il y a beaucoup d'autres petites différences, mais ce sont les gros morceaux.
la source