Estoy luchando contra NOLOCK en mi entorno actual. Un argumento que he escuchado es que la sobrecarga del bloqueo ralentiza una consulta. Entonces, ideé una prueba para ver cuánto podría ser esta sobrecarga.
Descubrí que NOLOCK en realidad ralentiza mi escaneo.
Al principio estaba encantado, pero ahora estoy confundido. ¿Es mi prueba inválida de alguna manera? ¿No debería NOLOCK realmente permitir un escaneo un poco más rápido? ¿Que esta pasando aqui?
Aquí está mi guión:
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())
Lo que he intentado que no funcionó:
- Se ejecuta en diferentes servidores (los mismos resultados, los servidores fueron 2016-SP1 y 2016-SP2, ambos silenciosos)
- Ejecutando en dbfiddle.uk en diferentes versiones (ruidoso, pero probablemente los mismos resultados)
- ESTABLECER NIVEL DE AISLAMIENTO en lugar de pistas (mismos resultados)
- Desactivar la escalada de bloqueo en la tabla (mismos resultados)
- Examinar el tiempo de ejecución real de la exploración en el plan de consulta real (mismos resultados)
- Sugerencia de recompilación (mismos resultados)
- Grupo de archivos de solo lectura (mismos resultados)
La exploración más prometedora proviene de eliminar la variable papelera y utilizar una consulta sin resultados. Inicialmente, esto mostró a NOLOCK como un poco más rápido, pero cuando le mostré la demostración a mi jefe, NOLOCK volvió a ser más lento.
¿Qué tiene NOLOCK que ralentiza un escaneo con asignación variable?
fuente
Respuestas:
NOTA: este podría no ser el tipo de respuesta que está buscando. Pero quizás sea útil para otros posibles respondedores en cuanto a proporcionar pistas sobre dónde comenzar a buscar
Cuando ejecuto estas consultas bajo el rastreo ETW (usando PerfView), obtengo los siguientes resultados:
Entonces la diferencia es 51ms . Esto es bastante acertado con su diferencia (~ 50ms). Mis números son ligeramente más altos en general debido a la sobrecarga de muestreo del generador de perfiles.
Encontrando la diferencia
Aquí hay una comparación de lado a lado que muestra que la diferencia de 51 ms está en el
FetchNextRow
método en sqlmin.dll:La selección simple está a la izquierda a 332 ms, mientras que la versión nolock está a la derecha a 383 ( 51 ms más). También puede ver que las dos rutas de código difieren de esta manera:
Llanura
SELECT
sqlmin!RowsetNewSS::FetchNextRow
llamadassqlmin!IndexDataSetSession::GetNextRowValuesInternal
Utilizando
NOLOCK
sqlmin!RowsetNewSS::FetchNextRow
llamadassqlmin!DatasetSession::GetNextRowValuesNoLock
que llamasqlmin!IndexDataSetSession::GetNextRowValuesInternal
okernel32!TlsGetValue
Esto muestra que hay algunas ramificaciones en el
FetchNextRow
método basadas en el nivel de aislamiento / sugerencia de nolock.¿Por qué la
NOLOCK
rama tarda más?La rama nolock en realidad pasa menos tiempo llamando
GetNextRowValuesInternal
(25ms menos). Pero el código directamenteGetNextRowValuesNoLock
(sin incluir los métodos que llama AKA la columna "Exc") se ejecuta durante 63 ms, que es la mayoría de la diferencia (63 - 25 = 38 ms de aumento neto en el tiempo de CPU).Entonces, ¿cuáles son los otros 13 ms (51 ms en total - 38 ms representados hasta ahora) de gastos generales
FetchNextRow
?Despacho de interfaz
Pensé que esto era más una curiosidad que otra cosa, pero la versión nolock parece incurrir en una sobrecarga de despacho de interfaz al llamar al método API de Windows a
kernel32!TlsGetValue
través dekernel32!TlsGetValueStub
un total de 17 ms. La selección simple parece no pasar por la interfaz, por lo que nunca llega al apéndice y solo pasa 6 msTlsGetValue
(una diferencia de 11 ms ). Puedes ver esto arriba en la primera captura de pantalla.Probablemente debería ejecutar este seguimiento nuevamente con más iteraciones de la consulta, creo que hay algunas cosas pequeñas, como interrupciones de hardware, que no fueron detectadas por la frecuencia de muestreo de 1 ms de PerfView
Fuera de ese método, noté otra pequeña diferencia que hace que la versión nolock se ejecute más lentamente:
Liberación de cerraduras
La rama nolock parece ejecutar el
sqlmin!RowsetNewSS::ReleaseRows
método de forma más agresiva , lo que puedes ver en esta captura de pantalla:La selección simple está en la parte superior, a 12 ms, mientras que la versión nolock está en la parte inferior a 26 ms ( 14 ms más). También puede ver en la columna "Cuándo" que el código se ejecutó con más frecuencia durante la muestra. Esto puede ser un detalle de implementación de nolock, pero parece introducir bastante sobrecarga para muestras pequeñas.
Hay muchas otras pequeñas diferencias, pero esas son las grandes partes.
fuente