bogue dans database_scoped_configurations

9

J'essaie d'insérer le jeu de résultats à partir de:

SELECT * FROM sys.database_scoped_configurations

dans une table temporaire, car je veux vérifier les paramètres de toutes les bases de données sur mon serveur. J'ai donc écrit ce code:

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

Mais alors, il n'y aura qu'une seule ligne par base de données, pas les quatre lignes que j'attends de l'exécution d'une sélection simple dans chaque base de données.

Je sais qu'il existe de meilleures façons de coder cela que d'utiliser sp_MSForEachDB, et j'en ai essayé plusieurs. Mais je n'ai toujours qu'une seule ligne par base de données. J'ai essayé cela sur SQL Server 2016 RTM et SP1

Est-ce un bogue avec SQL Server 2016, ou est-ce que je fais quelque chose de mal?

Henrik Staun Poulsen
la source
bug a été corrigé, au moins dans Microsoft SQL Server 2017 (RTM-CU15-GDR)
Henrik Staun Poulsen

Réponses:

8

Est-ce un bogue avec SQL Server 2016?

Oui. Ce n'est certainement pas un comportement correct. Je l'ai signalé ici et est résolu dans SQL Server 2016 SP2 CU9 .

Comme Mikael Eriksson le dit dans les commentaires sys.database_scoped_configurationset sys.dm_exec_sessionssont mis en œuvre sous forme de vues au format

SELECT ...  
FROM OpenRowset(TABLE xxxx)  

Cependant, en comparant les deux plans ci-dessous, il y a une différence évidente.

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

entrez la description de l'image ici

L'indicateur de trace 8619 affiche la sortie de ces deux requêtes

Appliquer la règle: EnforceHPandAccCard - x0-> Spool ou Top (x0)

SQL Server n'est apparemment pas en mesure de vérifier que la source du TVF n'est pas également la cible d'insertion, il nécessite donc une protection Halloween.

Dans le cas des sessions, cela a été implémenté comme un spool qui capture d'abord toutes les lignes. Dans le database_scoped_configurationsen ajoutant un TOP 1au plan. L'utilisation de TOPpour la protection Halloween est discutée dans cet article . L'article mentionne également un indicateur de trace non documenté pour forcer une bobine plutôt que TOPcela fonctionne comme prévu.

DECLARE @database_scoped_configurations TABLE(x INT);

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

Un problème évident avec l'utilisation TOP 1plutôt qu'avec une bobine est qu'elle limitera arbitrairement le nombre de lignes insérées. Ainsi, cela ne serait valide que si le nombre de lignes renvoyées par la fonction était <= 1.

Le mémo initial ressemble à ceci

entrez la description de l'image ici

Comparez cela avec le mémo initial de la requête 2

entrez la description de l'image ici

Si je comprends bien ce qui précède, il pense que le premier TVF peut renvoyer un maximum d'une ligne et applique donc une optimisation incorrecte. Le Max pour la deuxième requête est défini sur 1.34078E+154( 2^512).

Je n'ai aucune idée d'où ce nombre maximal de lignes est dérivé. Peut-être des métadonnées fournies par l'auteur du DMV? Il est également étrange que la TOP(50)solution de contournement ne soit pas réécrite, TOP(1)car TOP(50)cela n'empêcherait pas le problème d'Halloween de se produire (mais l'empêcherait de continuer indéfiniment)

Martin Smith
la source
6

Veuillez cesser d'utiliser sp_MSForEachDB. Il n'est pas pris en charge, n'est pas documenté et est bogué - ce qui peut être le problème ici. Mon remplaçant présente le même problème ici, mais en général, c'est une chose plus sûre à utiliser.

Pour des choses comme celle-ci, je préfère générer du SQL dynamique plutôt que de transmettre une seule commande à une procédure à exécuter plusieurs fois (même ma procédure, à laquelle j'ai beaucoup plus confiance), de cette façon je peux simplement imprimer les commandes au lieu de les exécuter, et assurez-vous qu'ils vont tous faire ce qu'ils disent.

En empruntant à l'observation que le code sous-jacent à la vue système implémente a TOP (1), nous pouvons essayer de cette façon:

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;

Notez que je n'utilise pas USEici, mais préfixez plutôt la sysvue de catalogue avec le nom de la base de données.

Pourquoi la vue fonctionne de façon magique, je ne sais pas; Je ne sais pas si vous obtiendrez une bonne réponse ici, car cela nécessite probablement des commentaires de Microsoft (ou de toute personne ayant accès au code source ou désireuse de lancer un débogueur).

Aaron Bertrand
la source
c'était la première des nombreuses méthodes que j'ai essayées, mais je ne pensais pas pouvoir utiliser ce sproc dans l'exemple.
Henrik Staun Poulsen
6

Merci d'avoir signalé ce problème!

Il s'agit en effet d'un bogue dans la façon dont l'Optimiseur de requête génère un plan pour la sys.database_scoped_configurationsvue catalogue. Nous allons résoudre ce problème lors de l'une des prochaines mises à jour de SQL Server 2016 et dans Azure SQL Database.

Comme solution de contournement, vous pouvez ajouter une TOPclause de la SELECTpart de votre insert pour obtenir le plan correct, par exemple:

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

Je suis d'accord que c'est très étrange et un bug potentiel, mais l'ajout d'un TOP (50), par exemple, à votre sélection retourne en fait toutes les lignes, ce qui vous permettrait au moins de continuer. Le résultat semble provenir d'une fonction de valeur de table système ([DB_SCOPED_CONFIG]), donc je ne peux pas vraiment dire ce qui se passe.

Je garderai un œil sur ce fil pour voir si les gens «plus intelligents» savent POURQUOI cela se produit.

Scott Hodgin
la source
Obtenez-vous uniquement la ligne MAXDOP pour chaque base de données?
Dan Guzman
@DanGuzman - oui La sélection en elle-même fonctionne très bien (sans tous les éléments foreach, juste sur une seule base de données). C'est lorsque vous ajoutez l'insertion dans qui produit le comportement étrange
Scott Hodgin