Estou tentando inserir o conjunto de resultados de:
SELECT * FROM sys.database_scoped_configurations
em uma tabela temporária, porque quero verificar as configurações de todos os bancos de dados em meu servidor. Então eu escrevi este código:
DROP TABLE IF EXISTS #h
CREATE TABLE #h(dbname sysname, configuration_id INT, name sysname, value SQL_VARIANT, value_for_secondary SQL_VARIANT)
EXEC sys.sp_MSforeachdb 'USE ?; insert into #h(dbname, configuration_id, name, value,value_for_secondary) SELECT ''?'' as dbname, * FROM sys.database_scoped_configurations D'
SELECT * FROM #h H
Mas então haverá apenas uma linha por banco de dados, não as quatro linhas que espero executar uma seleção simples em cada banco de dados.
Sei que existem maneiras melhores de codificar isso do que usar sp_MSForEachDB e tentei várias. Mas ainda recebo apenas uma linha por banco de dados. Eu tentei isso no SQL Server 2016 RTM e no SP1
Isso é um bug do SQL Server 2016 ou estou fazendo algo errado?
Sim. Definitivamente, este não é um comportamento correto. Eu relatei aqui e está corrigido no SQL Server 2016 SP2 CU9 .
Como diz Mikael Eriksson nos comentários
sys.database_scoped_configurations
esys.dm_exec_sessions
são implementadas como views no formatoNo entanto, comparando os dois planos abaixo, há uma diferença óbvia.
A saída do sinalizador de rastreamento 8619 para ambas as consultas mostra
Aparentemente, o SQL Server não é capaz de verificar se a origem do TVF também não é o destino de inserção, portanto, requer proteção do Dia das Bruxas.
No caso das sessões, isso foi implementado como um spool que captura todas as linhas primeiro. No
database_scoped_configurations
adicionando umTOP 1
ao plano. O uso deTOP
para proteção de Halloween é discutido neste artigo . O artigo também menciona um sinalizador de rastreamento não documentado para forçar um spool em vez deTOP
funcionar conforme o esperado.Um problema óbvio com o uso
TOP 1
em vez de um spool é que ele limitará arbitrariamente o número de linhas inseridas. Portanto, isso só seria válido se o número de linhas retornado pela função fosse <=1.O memorando inicial é assim
Compare isso com o memorando inicial para a consulta 2
Se bem entendi o que foi dito acima, ele pensa que o primeiro TVF pode retornar no máximo uma linha e, portanto, aplica uma otimização incorreta. O máximo para a segunda consulta é definido como
1.34078E+154
(2^512
).Não tenho ideia de onde essa contagem máxima de linhas é derivada. Talvez metadados fornecidos pelo autor do DMV? Também é estranho que a
TOP(50)
solução alternativa não seja reescritaTOP(1)
porqueTOP(50)
não impediria que o problema do Halloween ocorresse (embora o impedisse de continuar indefinidamente)Por favor, pare de usar
sp_MSForEachDB
. Não é suportado, não é documentado e tem bugs - o que pode ser o problema aqui. Minha substituição demonstra o mesmo problema aqui, mas em geral é uma coisa mais segura de usar.Para coisas como essa, prefiro gerar SQL dinâmico do que entregar um único comando para um procedimento executar várias vezes (até mesmo meu procedimento, no qual confio muito mais), dessa forma posso simplesmente imprimir os comandos em vez de executá-los e certifique-se de que todos farão o que dizem.
Tomando emprestado a observação de que o código subjacente à visão do sistema implementa um
TOP (1)
, podemos tentar desta maneira:Observe que não uso
USE
aqui, mas sim prefixo asys
visualização do catálogo com o nome do banco de dados.Por que a visão funciona de maneira mágica, não sei; Não sei se você obterá uma boa resposta aqui, pois provavelmente requer comentários da Microsoft (ou de qualquer pessoa com acesso ao código-fonte ou disposto a iniciar um depurador).
Obrigado por comunicar este problema!
Este é realmente um bug na forma como o Query Optimizer gera um plano para a
sys.database_scoped_configurations
visualização do catálogo. Abordaremos isso em uma das próximas atualizações do SQL Server 2016 e no Banco de Dados SQL do Azure.Como solução alternativa, você pode adicionar uma
TOP
cláusula naSELECT
parte de sua inserção para obter o plano correto, por exemplo:Concordo que isso é muito estranho e um bug em potencial, mas adicionar um TOP(50), por exemplo, ao seu select realmente retorna todas as linhas, de modo que pelo menos o ajudaria. O resultado parece vir de uma função de valor de tabela do sistema ([DB_SCOPED_CONFIG]), então não posso dizer o que está acontecendo.
Estarei de olho neste tópico para ver se as pessoas 'mais inteligentes' sabem POR QUE isso está acontecendo.