web-dev-qa-db-fra.com

Comment puis-je exporter les données du magasin de requêtes?

J'ai Query Store (QS) en cours d'exécution sur une instance SQL 2017. Actuellement sur RTM, avec RTM CU13 actuellement en test, à appliquer sur prod dans la fenêtre de patch du mois prochain.

Bien que la plupart des requêtes et des rapports retournent rapidement des résultats avec peu ou pas d'impact, tout ce que j'essaie de regarder autour des attentes est problématique. L'utilisation du processeur passe de 20 à 80% et y reste pendant quelques minutes jusqu'à ce que je le tue. Il s'agit d'un système de production 24/7, donc si je veux vraiment regarder les attentes QS, je vais devoir le faire ailleurs.

La base de données est de 150 Go avec 1000 Mo d'espace pour QS. J'ai un bac à sable avec 10 Go d'espace, donc si je pouvais sortir les données QS, je pourrais y jouer avec.

J'ai regardé autour de moi et je ne trouve pas comment faire ça. Le meilleur que j'ai trouvé est celui-ci sql.sasquatch 2016 post avec une réponse de 2016 par Erin Stellato

À l'heure actuelle, il n'y a pas d'option pour exporter et/ou importer des données du magasin de requêtes, mais il existe un élément Connect pour voter: https://connect.Microsoft.com/SQLServer/feedback/details/2620017/export -query-store-tables-distinct-from-the-database-tables

Remarque: le lien va vers une redirection "Microsoft Connect a été retiré". Il semble que le lien réel devrait être https://feedback.Azure.com/forums/908035-sql-server/suggestions/32901670-export -query-store-tables-séparément-des-données

En regardant Microsoft, je trouve que presque tout ce que vous pouvez utiliser pour accéder aux données est une vue, une procédure stockée ou un rapport. Je ne vois pas un moyen d'extraire simplement toutes les choses QS de la base de données.

Exemples de requêtes directes, utilisez les vues Exemple de Kendra Little J'ai joué avec l'idée de faire juste un Select * à partir des vues et en exportant les résultats vers mon bac à sable. Mais comme je n'ai trouvé personne en parler, je ne suis pas sûr que ce soit une bonne idée.

En relation

De plus Je voudrais pouvoir conserver les résultats du magasin de requêtes pré-CU13 pour les utiliser comme référence pour comparer le post CU13.

Modifier après la première réponse et les modifier Récent modifier la réponse par jadarnel27 ajoute de bonnes informations, mais je m'en fiche À propos de l'interface utilisateur, je souhaite pouvoir interroger les données sans altérer la base de données ni affecter les performances. Comme objectif secondaire, j'aimerais pouvoir archiver les données QS, afin de pouvoir revoir les performances précédentes (c'est-à-dire avant et mettre à niveau, mais après la suppression des anciennes données QS)

6
James Jenkins

Tout d'abord, vous pouvez obtenir des performances acceptables avec les requêtes directement par rapport aux vues de catalogue du magasin de requêtes en mettant à jour les statistiques, en ajoutant des conseils de requête avec des guides de plan ou en modifiant le niveau de compatibilité de la base de données/CE. Voir les réponses de Forrest et Marian ici:

recherche de magasin de requêtes sans fin


Si vous êtes sur SP1 ou supérieur, l'approche la plus simple serait d'utiliser DBCC CLONEDATABASE - qui inclut les statistiques, les données du magasin de requêtes et les objets de schéma - mais aucun des données réels des tables.


Sinon, pour l'exportation, une approche serait une simple SELECT...INTO des vues du magasin de requêtes à la base de données "sandbox". Ce sont les vues pertinentes .

L'approche de base serait la suivante:

SELECT * INTO Sandbox.dbo.query_store_runtime_stats FROM sys.query_store_runtime_stats;
SELECT * INTO Sandbox.dbo.query_store_runtime_stats_interval FROM sys.query_store_runtime_stats_interval;
SELECT * INTO Sandbox.dbo.query_store_plan FROM sys.query_store_plan;
SELECT * INTO Sandbox.dbo.query_store_query FROM sys.query_store_query;
SELECT * INTO Sandbox.dbo.query_store_query_text FROM sys.query_store_query_text;
SELECT * INTO Sandbox.dbo.query_store_wait_stats FROM sys.query_store_wait_stats;

La bonne chose à propos de cette approche est que:

  • vous n'obtiendrez que les données dont vous avez besoin (1000 Mo)
  • vous pouvez ajouter des index pour prendre en charge vos requêtes de génération de rapports, car il s'agit de tables réelles
  • ils n'auront pas le comportement inhabituel d'analyse de la mémoire qui conduit à de mauvaises performances par rapport aux vues réelles (encore une fois, car ce sont de véritables tables)
    • Remarque: le SELECT...INTO les requêtes ne devraient pas augmenter le CPU comme les requêtes de rapports du magasin de requêtes intégré, car elles n'auront pas les jointures problématiques qui provoquent un accès répété aux TVF en mémoire
  • vous pouvez conserver différentes versions des données (pour différents niveaux CU, etc.) en modifiant les noms des tables ou en ajoutant une colonne aux tables qui indique les données et/ou la version de SQL Server qui a été utilisée pour cette importation

Le "con" de cette approche est que vous ne pouvez pas utiliser l'interface utilisateur du magasin de requêtes. Une solution de contournement serait d'utiliser le profileur ou des événements étendus pour capturer les requêtes en cours d'exécution par l'interface utilisateur pour les rapports spécifiques dont vous avez besoin. Vous pouvez même effectuer cette capture dans un environnement non prod, car les requêtes doivent être les mêmes.


Attention: c'est potentiellement une très mauvaise idée. Il y a une raison pour laquelle vous ne pouvez normalement pas écrire dans ces tables. Un merci spécial à Forrest pour avoir mentionné la possibilité pour moi.

Si vous vraiment voulez pouvoir utiliser l'interface utilisateur, vous pouvez réellement charger les tables de base du magasin de requêtes avec des données tout en connexion via le DAC . Voici ce qui a fonctionné pour moi.

Rappel: vous devez utiliser une connexion DAC pour ce faire, sinon vous obtiendrez des erreurs liées au sys.plan_persist_* tables inexistantes

USE [master];
GO
CREATE DATABASE [Sandbox];
GO

USE [YourSourceDatabaseWithTheQueryStoreInfo];
GO

BEGIN TRANSACTION;

INSERT INTO Sandbox.sys.plan_persist_runtime_stats SELECT * FROM sys.plan_persist_runtime_stats;
INSERT INTO Sandbox.sys.plan_persist_runtime_stats_interval SELECT * FROM sys.plan_persist_runtime_stats_interval;
INSERT INTO Sandbox.sys.plan_persist_plan SELECT * FROM sys.plan_persist_plan;
INSERT INTO Sandbox.sys.plan_persist_query SELECT * FROM sys.plan_persist_query;
INSERT INTO Sandbox.sys.plan_persist_query_text SELECT * FROM sys.plan_persist_query_text;
INSERT INTO Sandbox.sys.plan_persist_wait_stats SELECT * FROM sys.plan_persist_wait_stats;
INSERT INTO Sandbox.sys.plan_persist_context_settings SELECT * FROM sys.plan_persist_context_settings

COMMIT TRANSACTION;
GO

USE [master];
GO
ALTER DATABASE [Sandbox] SET QUERY_STORE = ON (OPERATION_MODE = READ_ONLY);

Remarque: si vous utilisez SQL Server 2016, vous devrez supprimer la ligne concernant les statistiques d'attente - cette vue de catalogue n'a pas été ajoutée avant SQL Server 2017

Après cela, j'ai pu utiliser l'interface utilisateur du magasin de requêtes dans SSMS pour afficher des informations sur les requêtes de la base de données source. Soigné!

Il est important de charger les données dans la base de données Sandbox avec Query Store off, puis d'activer Query Store en mode lecture seule. Sinon, QS s'est retrouvé dans un état d'erreur, et cela a été écrit dans le journal des erreurs SQL Server:

Erreur: 12434, gravité: 20, état: 56.
Le magasin de requêtes dans la base de données Sandbox n'est pas valide, probablement en raison d'une incohérence de schéma ou de catalogue.

query store in the "error" state

J'ai également remarqué que cela ne fonctionne pas s'il y a des tables OLTP (Hekaton) en mémoire dans la base de données source. Quoi que je fasse, le magasin de requêtes se retrouve dans l'état "Erreur" avec ce message dans le journal des erreurs:

Erreur: 5571, gravité: 16, état: 2.
Erreur FILESTREAM interne: échec d'accès à la table de récupération de place.

Vous pouvez peut-être contourner cela en ajoutant un groupe de fichiers à mémoire optimisée à la base de données Sandbox, je ne l'ai pas encore essayé.

8
Josh Darnell

En complément du excellente réponse de Josh Darnell j'ai lu toutes les descriptions des vues de données qui sont exportées dans des tableaux. Le code suivant ajoute les clés primaires, les index clusterisés et les clés étrangères comme décrit dans les documents Microsoft. Cela devrait aider avec les requêtes sur les données.

----------------------------------------------------------------
--Add primary key,  clustered indexes and foreign keys
-----------------------------------------------------------

Use Admin
ALTER TABLE query_context_settings ADD CONSTRAINT PK_context_settings_id PRIMARY KEY CLUSTERED (context_settings_id);
ALTER TABLE query_store_plan ADD CONSTRAINT PK_plan_id PRIMARY KEY CLUSTERED (plan_id);
ALTER TABLE query_store_query ADD CONSTRAINT PK_query_id PRIMARY KEY CLUSTERED (query_id);
ALTER TABLE query_store_query_text ADD CONSTRAINT PK_query_text_id PRIMARY KEY CLUSTERED (query_text_id);
-- query_store_runtime_stats -- Has foreign keys but the "primary key - 'runtime_stats_id'" is not unique in run time. Only add for historical data
ALTER TABLE query_store_runtime_stats ADD CONSTRAINT PK_runtime_stats_id PRIMARY KEY CLUSTERED (runtime_stats_id);
ALTER TABLE query_store_runtime_stats_interval ADD CONSTRAINT PK_runtime_stats_interval_id PRIMARY KEY CLUSTERED (runtime_stats_interval_id);
-- query_store_wait_stats the "primary key - 'wait_stats_id'" is not unique in run time. Only add for historical data
ALTER TABLE query_store_wait_stats ADD CONSTRAINT PK_wait_stats_id PRIMARY KEY CLUSTERED (wait_stats_id);

--Create Foreign Keys

ALTER TABLE query_store_plan ADD CONSTRAINT FK_query_id FOREIGN KEY (query_id)     
    REFERENCES query_store_query (query_id)     
    ON DELETE CASCADE    
    ON UPDATE CASCADE    
;    
GO   

ALTER TABLE query_store_query ADD CONSTRAINT FK_query_text_id FOREIGN KEY (query_text_id)     
    REFERENCES query_store_query_text (query_text_id)     
    ON DELETE CASCADE    
    ON UPDATE CASCADE    
;    
GO  

 ALTER TABLE query_store_query ADD CONSTRAINT FK_context_settings_id FOREIGN KEY (context_settings_id)     
    REFERENCES query_context_settings (context_settings_id)     
    ON DELETE CASCADE    
    ON UPDATE CASCADE    
;    
GO  

 ALTER TABLE query_store_runtime_stats ADD CONSTRAINT FK_plan_id FOREIGN KEY (plan_id)     
    REFERENCES query_store_plan (plan_id)     
    ON DELETE CASCADE    
    ON UPDATE CASCADE    
;    
GO  

 ALTER TABLE query_store_runtime_stats ADD CONSTRAINT FK_runtime_stats_interval_id FOREIGN KEY (runtime_stats_interval_id)     
    REFERENCES query_store_runtime_stats_interval (runtime_stats_interval_id)     
    ON DELETE CASCADE    
    ON UPDATE CASCADE    
;    
GO  

 ALTER TABLE query_store_wait_stats ADD CONSTRAINT FK_2_plan_id FOREIGN KEY (plan_id)     
    REFERENCES query_store_plan (plan_id)     
    ON DELETE CASCADE    
    ON UPDATE CASCADE    
;    
GO  

 ALTER TABLE query_store_wait_stats ADD CONSTRAINT FK_2_runtime_stats_interval_id FOREIGN KEY (runtime_stats_interval_id)     
    REFERENCES query_store_runtime_stats_interval (runtime_stats_interval_id)     
    ON DELETE CASCADE    
    ON UPDATE CASCADE    
;    
GO  

--Additional Indexes 
--Improve linking plans to queries  
CREATE NONCLUSTERED INDEX NC_QueryID_with_PlanID ON query_store_plan (query_id ASC) INCLUDE (plan_id) 
GO

--To get summary info easier, add query_id column to tables with only the plan_id
--Add the column
ALTER TABLE query_store_runtime_stats
ADD query_id bigint
Go

--Update it
update query_store_runtime_stats
Set query_store_runtime_stats.query_id = query_store_plan.query_id
from   query_store_plan
Inner Join query_store_runtime_stats ON  query_store_runtime_stats.Plan_id = query_store_plan.Plan_id 

--Add an index
CREATE NONCLUSTERED INDEX NC_QueryID_with_PlanID ON query_store_runtime_stats (query_id ASC) INCLUDE (plan_id) 
GO

--Do the Same to query_store_wait_stats
--Add the column
ALTER TABLE query_store_wait_stats
ADD query_id bigint
Go

--Update it
update query_store_wait_stats
Set query_store_wait_stats.query_id = query_store_plan.query_id
from   query_store_plan
Inner Join query_store_wait_stats ON  query_store_wait_stats.Plan_id = query_store_plan.Plan_id 

--Add an index
CREATE NONCLUSTERED INDEX NC_QueryID_with_PlanID ON query_store_wait_stats (query_id ASC) INCLUDE (plan_id) 
GO

Notez que les deux query_store_runtime_stats & query_store_wait_stats n'ont pas de clés primaires décrites dans les documents Microsoft. Comme il s'agit de données exportées, j'évalue les index clusterisés sur plusieurs statistiques dans l'intervalle le plus courant.

Il est unique uniquement pour les intervalles de statistiques d'exécution passés. Pour l'intervalle actuellement actif, il peut y avoir plusieurs lignes

L'intervalle est un paramètre de configuration interval_length_minutes répertorié comme 'intervalle de collecte de statistiques' dans l'interface graphique des propriétés de la page du magasin de requêtes, pour la base de données.

En utilisant EXEC sp_query_store_flush_db; avant SELECT * INTO ne fonctionne pas compile les multiples lignes de l'intervalle de statistiques d'exécution en cours, en entrées uniques, empêchant ainsi les clés primaires et les index clusterisés sur query_store_runtime_stats & query_store_wait_stats dans lourdement Bases de données OLTP . Dans ce cas, avant d'ajouter les clés primaires, les index clusterisés et les clés étrangères (ci-dessus), supprimez l'intervalle d'exécution le plus récent avec le code ci-dessous.

Dans mon cas, j'ai 30 minutes d'intervalle, donc si je veux toutes les données jusqu'à 6 heures du matin, j'extrais quelques minutes après 6 heures du matin, puis je supprime le 6 heures et plus avec ce qui suit.

------------------
-- Not in the current run time interval
------------------------
-- Because runtime_stats_id is only unique in past time, I think I want to exclude current run time from the data in admin
-- Current solution delete after import to keep the import as simple as possible. 

Use Admin
Declare @Max_runtime_stats_interval_id bigint
Declare @Max_start_time datetimeoffset(7)
--Declare @Max_end_time datetimeoffset(7) -- No added value at this time.

Select @Max_runtime_stats_interval_id =  MAX (query_store_runtime_stats_interval.runtime_stats_interval_id) from dbo.query_store_runtime_stats_interval
Select @Max_start_time = query_store_runtime_stats_interval.start_time from dbo.query_store_runtime_stats_interval where query_store_runtime_stats_interval.runtime_stats_interval_id = @Max_runtime_stats_interval_id
--Select @Max_end_time = query_store_runtime_stats_interval.end_time from dbo.query_store_runtime_stats_interval where query_store_runtime_stats_interval.runtime_stats_interval_id = @Max_runtime_stats_interval_id

Print @Max_runtime_stats_interval_id
Print @Max_start_time
--Print @Max_end_time

Delete from dbo.query_store_runtime_stats where runtime_stats_interval_id = @Max_runtime_stats_interval_id

Delete from dbo.query_store_runtime_stats_interval where runtime_stats_interval_id = @Max_runtime_stats_interval_id

Delete from dbo.query_store_plan  where initial_compile_start_time > @Max_start_time

--This should be ok, but there was not a query that met the exclude criteria in inital test data. 
Delete from dbo.query_store_query where initial_compile_start_time > @Max_start_time

Delete from dbo.query_store_wait_stats where runtime_stats_interval_id = @Max_runtime_stats_interval_id

--dbo.query_store_query_text -- No time fields, we are excluding new quiries not sure if we need to also exclude their text, more work for little added value
--dbo.query_context_settings -- Does not need to be filtered
2
James Jenkins