Database
 sql >> Base de Dados >  >> RDS >> Database

Que impacto podem ter diferentes opções de cursor?


Já escrevi várias vezes sobre o uso de cursores e como, na maioria dos casos, é mais eficiente reescrever seus cursores usando lógica baseada em conjunto.

Mas sou realista.

Eu sei que há casos em que os cursores são "necessários" - você precisa chamar outro procedimento armazenado ou enviar um e-mail para cada linha, está fazendo tarefas de manutenção em cada banco de dados ou está executando uma tarefa única que simplesmente não vale a pena investir tempo para converter para set-based.

Como você está (provavelmente) fazendo isso hoje


Independentemente do motivo pelo qual você ainda está usando cursores, você deve pelo menos ter cuidado para não usar as opções padrão bastante caras. A maioria das pessoas inicia seus cursores assim:
DECLARE c CURSOR FOR 
  SELECT whatever FROM ...

Agora, novamente, para tarefas pontuais e pontuais, isso provavelmente é bom. Mas há…

Outras maneiras de fazer isso


Eu queria executar alguns testes usando os padrões e compará-los com diferentes opções de cursor, como LOCAL , ESTÁTICO , READ_ONLY e FAST_FORWARD . (Há uma tonelada de opções, mas essas são as mais comumente usadas, pois são aplicáveis ​​aos tipos mais comuns de operações de cursor que as pessoas usam.) Eu não apenas queria testar a velocidade bruta de algumas combinações diferentes, mas também o impacto no tempdb e na memória, tanto após uma reinicialização do serviço frio quanto com um cache quente.

A consulta que decidi alimentar para o cursor é uma consulta muito simples em sys.objects , no banco de dados de exemplo AdventureWorks2012. Isso retorna 318.500 linhas no meu sistema (um sistema de 2 núcleos muito humilde com 4 GB de RAM):
SELECT c1.[object_id] 
  FROM sys.objects AS c1
  CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2;

Em seguida, envolvi essa consulta em um cursor com várias opções (incluindo os padrões) e executei alguns testes, medindo a memória total do servidor, páginas alocadas para tempdb (de acordo com sys.dm_db_task_space_usage e/ou sys.dm_db_session_space_usage ) e duração total. Também tentei observar a contenção do tempdb usando scripts de Glenn Berry e Robert Davis, mas no meu sistema insignificante não consegui detectar nenhuma contenção. É claro que também estou no SSD e absolutamente nada mais está sendo executado no sistema, portanto, essas podem ser coisas que você deseja adicionar aos seus próprios testes se o tempdb for mais provável de ser um gargalo.

Então, no final, as consultas ficaram mais ou menos assim, com consultas de diagnóstico salpicadas em pontos apropriados:
DECLARE @i INT = 1;
 
DECLARE c CURSOR
-- LOCAL
-- LOCAL STATIC
-- LOCAL FAST_FORWARD
-- LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
  SELECT c1.[object_id] 
    FROM sys.objects AS c1
    CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2
    ORDER BY c1.[object_id];
 
OPEN c;
FETCH c INTO @i;
 
WHILE (@@FETCH_STATUS = 0)
BEGIN
  SET @i += 1; -- meaningless operation
  FETCH c INTO @i;
END
 
CLOSE c;
DEALLOCATE c;

Resultados

    Duração


    Indiscutivelmente a medida mais importante e comum é "quanto tempo demorou?" Bem, levou quase cinco vezes mais tempo para executar um cursor com as opções padrão (ou apenas com LOCAL especificado), em comparação com a especificação de STATIC ou FAST_FORWARD :


    Memória


    Eu também queria medir a memória adicional que o SQL Server solicitaria ao preencher cada tipo de cursor. Então, eu simplesmente reiniciei antes de cada teste de cache frio, medindo o contador de desempenho Total Server Memory (KB) antes e depois de cada teste. A melhor combinação aqui foi LOCAL FAST_FORWARD :


    uso do tempdb


    Esse resultado foi surpreendente para mim. Como a definição de um cursor estático significa que ele copia todo o resultado para tempdb e, na verdade, é expresso em sys.dm_exec_cursors como INSTANTÂNEO , eu esperava que o acerto nas páginas tempdb fosse maior com todas as variantes estáticas do cursor. Este não era o caso; novamente, vemos um acerto de aproximadamente 5X no uso do tempdb com o cursor padrão e aquele com apenas LOCAL Especificadas:

Conclusão


Há anos venho enfatizando que a seguinte opção deve sempre ser especificada para seus cursores:
LOCAL STATIC READ_ONLY FORWARD_ONLY

A partir deste ponto, até que eu tenha a chance de testar outras permutações ou encontrar casos em que não seja a opção mais rápida, recomendarei o seguinte:
LOCAL FAST_FORWARD

(Como um aparte, também fiz testes omitindo o LOCAL opção, e as diferenças foram insignificantes.)

Dito isso, isso não é necessariamente verdade para *todos* os cursores. Nesse caso, estou falando apenas de cursores em que você está apenas lendo dados do cursor, apenas para frente, e não está atualizando os dados subjacentes (pela chave ou usando WHERE CURRENT OF ). Esses são testes para outro dia.