error en la base de datos_configuraciones_scoped

9

Estoy tratando de insertar el conjunto de resultados de:

SELECT * FROM sys.database_scoped_configurations

en una tabla temporal, porque quiero verificar la configuración de todas las bases de datos en mi servidor. Entonces escribí 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

Pero entonces solo habrá una fila por base de datos, no las cuatro filas que espero de ejecutar una selección simple en cada base de datos.

Sé que hay mejores formas de codificar esto que usando sp_MSForEachDB, y probé varias. Pero todavía obtengo solo una fila por base de datos. He intentado esto en SQL Server 2016 RTM y SP1

¿Es esto un error con SQL Server 2016 o estoy haciendo algo mal?

Henrik Staun Poulsen
fuente
el error se ha solucionado, al menos en Microsoft SQL Server 2017 (RTM-CU15-GDR)
Henrik Staun Poulsen

Respuestas:

8

¿Es esto un error con SQL Server 2016?

Si. Definitivamente este no es el comportamiento correcto. Lo informé aquí y se solucionó en SQL Server 2016 SP2 CU9 .

Como dice Mikael Eriksson en los comentarios sys.database_scoped_configurationsy sys.dm_exec_sessionsse implementan como vistas en el formato

SELECT ...  
FROM OpenRowset(TABLE xxxx)  

Sin embargo, al comparar los dos planes a continuación, hay una diferencia obvia.

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 );

ingrese la descripción de la imagen aquí

La salida del indicador de rastreo 8619 para ambas consultas muestra

Aplicar regla: EnforceHPandAccCard - x0-> Spool or Top (x0)

SQL Server aparentemente no puede determinar que la fuente del TVF no sea también el objetivo de inserción, por lo que requiere protección de Halloween.

En el caso de las sesiones, esto se implementó como un carrete que captura todas las filas primero. En el database_scoped_configurationsmediante la adición de un TOP 1al plan. El uso de TOPpara la protección de Halloween se trata en este artículo . El artículo también menciona una marca de rastreo indocumentada para forzar un carrete en lugar de TOPque funcione como se esperaba.

DECLARE @database_scoped_configurations TABLE(x INT);

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

Un problema obvio con el uso en TOP 1lugar de un carrete es que limitará arbitrariamente el número de filas insertadas. Por lo tanto, esto solo sería válido si el número de filas devueltas por la función fuera <= 1.

El memo inicial se ve así

ingrese la descripción de la imagen aquí

Compare esto con la nota inicial para la consulta 2

ingrese la descripción de la imagen aquí

Si entiendo correctamente lo anterior, creo que el primer TVF puede devolver un máximo de una fila y, por lo tanto, aplica una optimización incorrecta. El valor máximo para la segunda consulta se establece en 1.34078E+154( 2^512).

No tengo idea de dónde se deriva este recuento de filas máximo. ¿Quizás los metadatos proporcionados por el autor del DMV? También es extraño que la TOP(50)solución no se vuelva a escribir TOP(1)porque TOP(50)no evitaría que ocurriera el problema de Halloween (aunque evitaría que continuara indefinidamente)

Martin Smith
fuente
6

Por favor deja de usar sp_MSForEachDB. No es compatible, no está documentado y tiene errores, lo que puede ser el problema aquí. Mi reemplazo muestra el mismo problema aquí, pero en general es más seguro de usar.

Para cosas como esta, prefiero generar SQL dinámico que pasar un solo comando a un procedimiento para ejecutar varias veces (incluso mi procedimiento, en el que confío mucho más), de esta manera simplemente puedo imprimir los comandos en lugar de ejecutarlos, y asegúrese de que todos hagan lo que dicen.

Partiendo de la observación de que el código subyacente a la vista del sistema implementa a TOP (1), podemos intentar de esta manera:

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;

Tenga en cuenta que no uso USEaquí, sino que prefijo la sysvista de catálogo con el nombre de la base de datos.

Por qué la vista funciona de manera mágica, no lo sé; No sé si obtendrá una buena respuesta aquí, ya que es probable que requiera comentarios de Microsoft (o cualquier persona con acceso al código fuente o dispuesto a iniciar un depurador).

Aaron Bertrand
fuente
ese fue el primero de los varios métodos que probé, pero no pensé que podría usar ese sproc en el ejemplo.
Henrik Staun Poulsen
6

¡Gracias por informar este problema!

De hecho, esto es un error en la forma en que el Optimizador de consultas genera un plan para la sys.database_scoped_configurationsvista de catálogo. Abordaremos esto en una de las próximas actualizaciones de SQL Server 2016 y en Azure SQL Database.

Como solución alternativa, puede agregar una TOPcláusula en la SELECTparte de su inserto para obtener el plan correcto, por ejemplo:

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

Estoy de acuerdo en que esto es muy extraño y un error potencial, pero agregar un TOP (50), por ejemplo, a su selección en realidad devuelve todas las filas, por lo que eso al menos lo pondrá en marcha. El resultado parece provenir de una función de valor de tabla del sistema ([DB_SCOPED_CONFIG]), por lo que realmente no puedo decir qué está pasando.

Estaré atento a este hilo para ver si las personas 'más inteligentes' saben POR QUÉ esto está sucediendo.

Scott Hodgin
fuente
¿Obtiene solo la fila MAXDOP para cada base de datos?
Dan Guzman
@DanGuzman: sí La selección por sí sola funciona bien (sin todas las cosas de foreach, solo en una sola base de datos). Es cuando agrega el Insertar en que produce el comportamiento extraño
Scott Hodgin