J'ai une très grande requête qui doit être exécutée sur plusieurs bases de données et les résultats annexés à une table TEMP et sont retournés.
La syntaxe de base ressemble à ceci:
INSERT INTO #tmpTable (Id, ...)
SELECT T1.Id, ...
FROM Server.Database.dbo.Table1 as T1
INNER JOIN #tmpIds as T ON T1.Id = T.Id
INNER JOIN Server.Database.dbo.Table2 as T2 ON T1.Id = T2.Id
INNER JOIN Server.Database.dbo.Table3 as T3 ON T2.Id = T3.Id
LEFT OUTER JOIN Server.Database.dbo.Table4 as T4 ON T3.Id = T4.Id
LEFT OUTER JOIN Server.Database.dbo.Table5 as T5 ON T4.Id = T5.Id
LEFT OUTER JOIN Server.Database.dbo.Table6 as T6 ON T5.Id = T6.Id
La requête fonctionne rapidement si elle est exécutée localement sur les serveurs individuels, mais il faut une longue période pour exécuter s'il est exécuté à partir d'un serveur lié en utilisant les noms en 4 parties telles que ci-dessus.
Le problème semble être il s'agit d'interroger le serveur lié à l'ensemble de résultats non filtrés, puis de l'adhérer au #tmpIds
Tableau sur le serveur local par la suite, ce qui rend la requête prenne très longtemps à courir.
Si j'accepte les identifiants pour filtrer le résultat défini sur le serveur lié, tel que
SELECT T1.Id, ...
FROM Server.Database.dbo.Table1 as T1
-- INNER JOIN #tmpIds as T ON T1.Id = T.Id
INNER JOIN Server.Database.dbo.Table2 as T2 ON T1.Id = T2.Id
INNER JOIN Server.Database.dbo.Table3 as T3 ON T2.Id = T3.Id
LEFT OUTER JOIN Server.Database.dbo.Table4 as T4 ON T3.Id = T4.Id
LEFT OUTER JOIN Server.Database.dbo.Table5 as T5 ON T4.Id = T5.Id
LEFT OUTER JOIN Server.Database.dbo.Table6 as T6 ON T5.Id = T6.Id
WHERE T1.Id IN (1, 2, 3)
il court rapidement en quelques secondes.
Existe-t-il un moyen d'exécuter cette requête afin qu'elle filtre l'ensemble de résultats de la requête du serveur lié par le #tmpId
Table d'abord, avant de retourner le résultat défini sur le serveur local?
Certaines choses à noter
La requête est très grande et complexe, et Dynamic SQL n'est pas une option viable pour cela due au cauchemar de maintenance qui cause.
Je serais ouvert aux suggestions sur la manière d'utiliser SQL dynamique pour quelque chose d'autre, telle que l'exécution d'une procédure stockée ou de l'UDF s'il existe un moyen de le faire sur un serveur lié (essayé quelques méthodes différentes telles que sp_executeSQL
, OPENROWSET
, et OPENQUERY
, mais ceux-ci ont tous échoué).
Les transactions distribuées sont désactivées, de sorte que ce qui suit ne fonctionne pas
INSERT INTO #table
EXEC Server.Database.dbo.StoredProcedure @ids
Le problème de performance a eu à voir avec le LEFT OUTER JOIN
les tables. Si je les ai changés vers INNER JOIN
, ou si j'excluais leurs données des colonnes SELECT
, la requête a fonctionné bien.
Ce que j'ai fini par faire était de créer un View
sur le serveur lié contenant toutes les données que je voulais, puis je vous joignez simplement à celui du serveur principal avec le #tmpIds
tableau.
Je ne pensais pas que cela fonctionnerait depuis que je pensais tout rejoindre et la retirant jusqu'au deuxième serveur avant de filtrer était la même chose que ce que je faisais maintenant et conduirait au même problème de performance, mais surprenant que cela ne semble pas être le cas.
CREATE VIEW MyView
AS
SELECT T1.Id, T2.ColA, ...
FROM Table1 as T1
INNER JOIN Table2 as T2 ON T1.Id = T2.Id
INNER JOIN Table3 as T3 ON T2.Id = T3.Id
LEFT OUTER JOIN Table4 as T4 ON T3.Id = T4.Id
LEFT OUTER JOIN Table5 as T5 ON T4.Id = T5.Id
LEFT OUTER JOIN Table6 as T6 ON T5.Id = T6.Id
GO
et
INSERT INTO #tmpTable (Id, ...)
SELECT T1.Id, T1.ColA, ...
FROM Server.Database.dbo.MyView as T1
INNER JOIN #tmpIds as T ON T1.Id = T.Id
Toutes les colonnes jointes ont été correctement indexées, cependant, selon cette réponse
Même s'il peut y avoir une indexation sur des tables sur le serveur distant, SQL peut ne pas être en mesure de les profiter lorsqu'il peut créer un plan de requête local qui prend des avantages de l'indexation.
Et celui-ci
Laissez le serveur lié faire autant que possible.
[.____] Il est impossible pour SQL Server d'optimiser une requête sur un serveur lié, même un autre serveur SQL
je suppose donc que le plan de requête utilisé pour la requête n'utilisait pas les indices définis et SQL Server générait un plan de requête médiocre pour le LEFT OUTER JOIN
les tables.
Avez-vous essayé l'indemnité de la requête de la force? Il oblige le compilateur à garder l'ordre des jointures comme indiqué dans la requête lors de l'optimisation.
SELECT T1.Id, ...
FROM Server.Database.dbo.Table1 as T1
INNER JOIN #tmpIds as T ON T1.Id = T.Id
INNER JOIN Server.Database.dbo.Table2 as T2 ON T1.Id = T2.Id
INNER JOIN Server.Database.dbo.Table3 as T3 ON T2.Id = T3.Id
LEFT OUTER JOIN Server.Database.dbo.Table4 as T4 ON T3.Id = T4.Id
LEFT OUTER JOIN Server.Database.dbo.Table5 as T5 ON T4.Id = T5.Id
LEFT OUTER JOIN Server.Database.dbo.Table6 as T6 ON T5.Id = T6.Id
OPTION (FORCE ORDER)
Edit : Étant donné que la commande de force n'a pas fonctionné, avez-vous pensé à faire quelque chose comme ceci:
WHERE T1.Id IN (SELECT Id FROM #tmpIds)
2e édition : Un autre essaie, celui-ci est un peu complexe cependant.
Pouvez-vous faire quelque chose comme ça:
Sur le serveur distant crée une table "temporaire" permanente
CREATE TABLE tmpTable1 (Id INT)
Ensuite (toujours sur le serveur distant) Créez une vue
CREATE VIEW queryView AS
SELECT Table1.*
FROM Table1
JOIN tmpTable1
ON Table1.Id = tmpTable1.Id
Puis dans votre processus sur votre instance "home"
DELETE FROM Server.Database.dbo.tmpTable1
INSERT INTO Server.Database.dbo.tmpTable1 VALUES
SELECT * FROM #tmpIds
Puis dans votre requête se joindre à Server.Database.dbo.queryView
Je recommande d'écrire une fonction définie par l'utilisateur sur chaque serveur lié, qui obtient toutes les données nécessaires d'eux, puis interroger la fonction via openquery, comme ceci:
INSERT INTO #tmpTable (Id, ...)
SELECT T1.Id, ...
FROM OPENQUERY([Server], 'SELECT * FROM Database.dbo.UdfGetData()')
INNER JOIN #tmpIds as T ...
De cette façon, toutes les données dont vous avez besoin d'être importées sont traitées dans le serveur lié, et vous obtenez seulement les résultats par opequery.
Si vous interrogez chaque table comme ceci:
... INNER JOIN Server.Database.dbo.Table2 ...
Vous obtenez toutes les données de chaque table du serveur lié au serveur local et de le charger dans la mémoire. Ensuite, les jointures sont effectuées dans le serveur local, probablement (je devine) sans tous les index. Donc, vous importez plus de données que vous avez besoin, et aussi les jointures sont plus lents parce que le manque d'indices.
J'ai eu cette question il y a quelque temps, et en utilisant OPENQUERY j'ai pu réduire le temps d'exécution de mon processus d'environ deux jours (hehehe, en fait, personne n'a remarqué jusqu'à ce que le serveur devienne plus lent) à dix minutes.
L'inconvénient de cette méthode est que vous devez concaténer les paramètres dans la chaîne de requête de openquery. Pour devenir plus, je suggère la prochaine:
CREATE TABLE #TempT (
a INT NOT NULL,
b ...
);
DECLARE @query VARCHAR(MAX);
SET @query
= 'SELECT a, b, ... FROM OPENQUERY([Server], SELECT * FROM Database.dbo.UdfGetData(' + @p1 + ',' + @p2 ')';
-- @p1 and @p2 are the parameters, but you will need to format them according to the datatype: DATETIME, VARCHAR, etc
INSERT INTO #TempT EXEC (@query);
INSERT INTO #tmpTable (Id, ...)
SELECT T1.Id, ...
FROM #TempT
INNER JOIN #tmpIds as T ...
Je sais que ce n'est pas assez, et certaines personnes dire qu'il est un " combinaison de mauvaises idées contre nature " , mais il fait le travail XD