bug em database_scoped_configurations

9

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 no 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 haverá apenas uma linha por banco de dados, não as quatro linhas que eu espero executar uma seleção simples em cada banco de dados.

Eu sei que existem maneiras melhores de codificar isso do que usar sp_MSForEachDB, e eu 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 erro do SQL Server 2016 ou estou fazendo algo errado?

Henrik Staun Poulsen
fonte
bug foi corrigido, pelo menos em Microsoft SQL Server 2017 (RTM-15 UM-RDA)
Henrik Staun Poulsen

Respostas:

8

Isso é um erro do SQL Server 2016?

Sim. Definitivamente, este não é um comportamento correto. Eu relatei aqui e foi corrigido no SQL Server 2016 SP2 CU9 .

Como Mikael Eriksson diz nos comentários sys.database_scoped_configurationse sys.dm_exec_sessionssão implementados como visualizações no formato

SELECT ...  
FROM OpenRowset(TABLE xxxx)  

No entanto, comparando os dois planos abaixo, há uma diferença óbvia.

DBCC TRACEON(3604);

DECLARE @database_scoped_configurations TABLE(x INT);

INSERT INTO @database_scoped_configurations
SELECT configuration_id
FROM   sys.database_scoped_configurations
OPTION (QUERYTRACEON 8608, QUERYTRACEON 8615, QUERYTRACEON 8619, QUERYTRACEON 8620 );


DECLARE @dm_exec_sessions TABLE(x INT);

INSERT INTO @dm_exec_sessions
SELECT session_id
FROM   sys.dm_exec_sessions
OPTION (QUERYTRACEON 8608, QUERYTRACEON 8615, QUERYTRACEON 8619, QUERYTRACEON 8620 );

insira a descrição da imagem aqui

A saída do sinalizador de rastreamento 8619 para ambas as consultas mostra

Aplicar regra: EnforceHPandAccCard - x0-> spool ou superior (x0)

Aparentemente, o SQL Server não é capaz de verificar se a fonte do TVF também não é o destino da inserção, portanto, requer proteção de Halloween.

No caso das sessões, isso foi implementado como um spool que captura todas as linhas primeiro. No database_scoped_configurationsadicionando um TOP 1ao plano. O uso da TOPproteçã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 de TOPfuncionar conforme o esperado.

DECLARE @database_scoped_configurations TABLE(x INT);

INSERT INTO @database_scoped_configurations
SELECT configuration_id
FROM   sys.database_scoped_configurations
OPTION (QUERYTRACEON 8692)

Um problema óbvio com o uso em TOP 1vez de um spool é que ele arbitrariamente limitará o número de linhas inseridas. Portanto, isso só seria válido se o número de linhas retornadas pela função fosse <= 1.

A nota inicial se parece com isso

insira a descrição da imagem aqui

Compare isso com o memorando inicial da consulta 2

insira a descrição da imagem aqui

Se entendi o que foi dito corretamente, ele acha 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 está definido como 1.34078E+154( 2^512).

Não faço ideia de onde esse número máximo de linhas é derivado. Talvez os metadados fornecidos pelo autor do DMV? Também é estranho que a TOP(50)solução alternativa não seja reescrita TOP(1)porque TOP(50)não impediria a ocorrência do problema do Dia das Bruxas (mas impediria que continuasse indefinidamente)

Martin Smith
fonte
6

Por favor, pare de usar sp_MSForEachDB. É sem suporte, sem documentos e com erros - 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 a um procedimento para executar várias vezes (mesmo o 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.

Partindo da observação de que o código subjacente à visualização do sistema implementa a TOP (1), podemos tentar desta maneira:

DROP TABLE IF EXISTS #h;

CREATE TABLE #h(dbname sysname, configuration_id INT, name sysname, 
  value SQL_VARIANT,  value_for_secondary SQL_VARIANT);

DECLARE @sql nvarchar(max) = N'', @base nvarchar(max) = N'insert into #h
  (dbname, configuration_id, name, value,value_for_secondary)  SELECT TOP ($c$) 
  $db$ as dbname, * FROM $qdb$.sys.database_scoped_configurations;';

SELECT @sql += REPLACE(REPLACE(REPLACE(@base, N'$qdb$', QUOTENAME(name)), 
  N'$db$', CHAR(39) + name + CHAR(39)), N'$c$', RTRIM(COUNT(*) OVER()))
FROM sys.databases WHERE state = 0;

PRINT @sql;
EXEC sys.sp_executesql @sql;
SELECT * FROM #h;

Observe que eu não uso USEaqui, mas prefixe a sysexibição do catálogo com o nome do banco de dados.

Por que a visão funciona de maneiras mágicas, eu não sei; Não sei se você receberá uma boa resposta aqui, pois provavelmente requer comentários da Microsoft (ou de qualquer pessoa com acesso ao código-fonte ou que esteja disposto a acionar um depurador).

Aaron Bertrand
fonte
esse foi o primeiro dos vários métodos que tentei, mas não achei que pudesse usar esse sproc no exemplo.
Henrik Staun Poulsen
6

Obrigado por comunicar este problema!

Este é realmente um bug na maneira como o Query Optimizer gera um plano para a sys.database_scoped_configurationsexibiçã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 TOPcláusula na SELECTparte de sua inserção para obter o plano correto, por exemplo:

DECLARE @database_scoped_configurations TABLE(x INT); 
INSERT INTO @database_scoped_configurations 
SELECT **TOP 100** configuration_id 
FROM sys.database_scoped_configurations 
Panagiotis Antonopoulos
fonte
3

Concordo que isso é muito estranho e um bug em potencial, mas a adição de um TOP (50), por exemplo, ao seu select realmente retorna todas as linhas, de modo que pelo menos o ajudaria. O resultado parece estar vindo de uma função de valor de tabela do sistema ([DB_SCOPED_CONFIG]), então não consigo realmente dizer o que está acontecendo.

Eu vou ficar de olho nesse tópico para ver se as pessoas mais 'inteligentes' sabem por que isso está acontecendo.

Scott Hodgin
fonte
Você obtém apenas a linha MAXDOP para cada banco de dados?
Dan Guzman
@ DanGuzman - sim O select por si só funciona muito bem (sem todo o material foreach, apenas em um único banco de dados). É quando você adicionar o Insert em que produz o comportamento estranho
Scott Hodgin