Estou lutando contra o NOLOCK no meu ambiente atual. Um argumento que ouvi é que a sobrecarga do bloqueio diminui a velocidade de uma consulta. Então, criei um teste para ver o quanto essa sobrecarga pode ser.
Descobri que o NOLOCK realmente diminui a velocidade da digitalização.
No começo, fiquei encantado, mas agora estou confuso. Meu teste é inválido de alguma forma? O NOLOCK não deveria realmente permitir uma verificação um pouco mais rápida? O que está acontecendo aqui?
Aqui está o meu 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())
O que eu tentei que não funcionou:
- Executando em servidores diferentes (mesmos resultados, os servidores eram 2016-SP1 e 2016-SP2, ambos silenciosos)
- Executando no dbfiddle.uk em versões diferentes (barulhento, mas provavelmente os mesmos resultados)
- DEFINIR NÍVEL DE ISOLAMENTO em vez de dicas (mesmos resultados)
- Desativando a escalação de bloqueio na tabela (mesmos resultados)
- Examinando o tempo real de execução da verificação no plano de consulta real (mesmos resultados)
- Dica de recompilação (mesmos resultados)
- Grupo de arquivos somente leitura (mesmos resultados)
A exploração mais promissora vem da remoção da variável do lixo e do uso de uma consulta sem resultados. Inicialmente, isso mostrou o NOLOCK como um pouco mais rápido, mas quando mostrei a demonstração para o meu chefe, o NOLOCK voltou a ser mais lento.
O que há no NOLOCK que retarda uma varredura com atribuição de variável?
fonte
Respostas:
NOTA: este pode não ser o tipo de resposta que você está procurando. Mas talvez seja útil para outros respondentes em potencial, na medida em que fornece pistas sobre onde começar a procurar
Quando executo essas consultas no rastreamento ETW (usando PerfView), obtenho os seguintes resultados:
Então a diferença é 51ms . Isso está bastante morto com a sua diferença (~ 50ms). Meus números são um pouco mais altos em geral devido à sobrecarga de amostragem do criador de perfil.
Encontrando a diferença
Aqui está uma comparação lado a lado, mostrando que a diferença de 51ms está no
FetchNextRow
método em sqlmin.dll:A seleção simples está à esquerda em 332 ms, enquanto a versão nolock está à direita em 383 ( 51ms a mais). Você também pode ver que os dois caminhos de código diferem desta maneira:
Avião
SELECT
sqlmin!RowsetNewSS::FetchNextRow
chamadassqlmin!IndexDataSetSession::GetNextRowValuesInternal
Usando
NOLOCK
sqlmin!RowsetNewSS::FetchNextRow
chamadassqlmin!DatasetSession::GetNextRowValuesNoLock
que chama tantosqlmin!IndexDataSetSession::GetNextRowValuesInternal
oukernel32!TlsGetValue
Isso mostra que há alguma ramificação no
FetchNextRow
método com base no nível de isolamento / dica nolock.Por que o
NOLOCK
ramo leva mais tempo?Na verdade, o ramo nolock gasta menos tempo
GetNextRowValuesInternal
acessando (25ms a menos). Mas o código diretamenteGetNextRowValuesNoLock
(sem incluir os métodos que chama de AKA de "Exc") é executado por 63ms - que é a maior parte da diferença (63 - 25 = 38ms de aumento líquido no tempo da CPU).Então, quais são os outros 13ms (51ms no total - 38ms até agora) de despesas gerais
FetchNextRow
?Envio de interface
Eu pensei que isso era mais uma curiosidade do que qualquer coisa, mas a versão nolock parece incorrer em alguma sobrecarga de despacho de interface chamando o método da API do Windows
kernel32!TlsGetValue
viakernel32!TlsGetValueStub
- um total de 17ms. A seleção simples parece não passar pela interface, portanto nunca atinge o stub e gasta apenas 6msTlsGetValue
(uma diferença de 11ms ). Você pode ver isso acima na primeira captura de tela.Provavelmente eu deveria executar esse rastreamento novamente com mais iterações da consulta, acho que existem algumas pequenas coisas, como interrupções de hardware, que não foram detectadas pela taxa de amostra de 1ms do PerfView
Fora desse método, notei outra pequena diferença que faz com que a versão nolock seja mais lenta:
Liberando bloqueios
O ramo nolock parece executar o
sqlmin!RowsetNewSS::ReleaseRows
método de forma mais agressiva , que você pode ver nesta captura de tela:A planície de seleção está no topo, em 12ms, enquanto a versão nolock está no fundo a 26 ms ( 14ms mais). Você também pode ver na coluna "Quando" que o código foi executado com mais frequência durante a amostra. Este pode ser um detalhe de implementação do nolock, mas parece introduzir um pouco de sobrecarga em pequenas amostras.
Existem muitas outras pequenas diferenças, mas essas são as grandes partes.
fonte