J'ai des tables qui sont partitionnées en fonction d'une colonne INT
.
Je vois des requêtes qui utilisent $Partition
Fonction Pour comparer le numéro de partition au lieu de comparer les données de champ réelles.
Par exemple, au lieu de dire:
select *
from T1
inner join T2 on T2.SnapshotKey = T1.SnapshotKey
ils ont été écrits comme ci-dessous:
select *
from T1
inner join T2 on $Partition.PF_Name(T2.SnapshotKey) = $Partition.PF_Name(T1.SnapshotKey)
où PF_Name
est le nom de la fonction de partition.
Je vois des commentaires sur ces questions que cela a été fait pour améliorer les performances, et lorsque j'exécute les deux requêtes, je vois différents dans le temps d'exécution et le plan d'exécution différent. Je ne sais pas comment ces deux requêtes sont différentes.
Ceci est une vraie requête:
-- this takes about 9 seconds
select sum(PrinBal)
from fpc
inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703
-- this takes about 5 seconds
select sum(PrinBal)
from fpc
inner join gl on $Partition.SnapshotKeyPF(gl.SnapshotKey) = $Partition.SnapshotKeyPF(fpc.SnapshotKey)
where fpc.SnapshotKey = 201703
Et ci-dessous est le plan d'exécution de la requête réelle:
Désolé, je ne peux pas télécharger même un plan d'exécution désinfecté car les téléchargements sont surveillés par notre réseau et il pourrait s'agir d'une violation de la politique.
La question est la suivante: pourquoi les plans d'exécution sont différents et pourquoi la deuxième requête est plus rapide.
J'apprécie si quelqu'un peut partager une idée à ce sujet. Juste intéressé à savoir pourquoi ils sont différents. Plus rapide est mieux cependant.
Ceci se produit sur SQL Server 2014. Si je suis identique sur SQL Server 2012, le résultat sera différent et la première requête fonctionnera plus rapidement!
Pourquoi les plans d'exécution sont différents
select sum(PrinBal)
from fpc
inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703
L'optimiseur sait:
gl.SnapshotKey = fpc.SnapshotKey
; etfpc.SnapshotKey = 201703
donc, il peut en déduire:
gl.SnapshotKey = 201703
Comme si vous aviez écrit:
select sum(PrinBal)
from fpc
inner join gl on gl.SnapshotKey = fpc.SnapshotKey
where fpc.SnapshotKey = 201703
and gl.SnapshotKey = 201703
La valeur littérale 201703 peut également être utilisée par l'optimiseur pour déterminer l'ID de partition. Avec les deux SnapshotKey
prédicats (une donnée, une inféré) Cela signifie que l'optimiseur connaît l'ID de partition pour les deux tables.
Aller plus loin, avec une valeur littérale (201703) pour SnapshotKey
maintenant disponible sur les deux tables, le prédicat de jointure:
gl.SnapshotKey = fpc.SnapshotKey
sIMPLIFIES À:
201703 = 201703
; ou simplementtrue
Ce qui signifie qu'il n'y a pas de joint prédicat du tout. Le résultat est une jointure de croix logique. Exprimer le plan d'exécution final en utilisant la syntaxe T-SQL disponible la plus proche, c'est comme si vous l'avez écrit:
SELECT
CASE
WHEN SUM(Q1.c) = 0 THEN NULL
ELSE SUM(Q1.s)
END
FROM
(
SELECT c = COUNT_BIG(*), s = SUM(GL.PrinBal)
FROM dbo.gl AS GL
WHERE GL.SnapshotKey = 201703
AND $PARTITION.PF(GL.SnapshotKey) = $PARTITION.PF(201703)
) AS Q1
CROSS JOIN
(
SELECT Dummy = 1
FROM dbo.fpc AS FPC
WHERE FPC.SnapshotKey = 201703
AND $PARTITION.PF(FPC.SnapshotKey) = $PARTITION.PF(201703)
) AS Q2;
select sum(PrinBal)
from fpc
inner join gl on $Partition.PF(gl.SnapshotKey) = $Partition.PF(fpc.SnapshotKey)
where fpc.SnapshotKey = 201703
L'optimiseur ne peut plus déduire rien de gl.SnapshotKey
, les simplifications et les transformations faites pour la première requête ne sont plus possibles.
En effet, sauf s'il est vrai que chaque partition ne détient qu'un seul SnapshotKey
, la réécriture n'est pas garantie pour produire les mêmes résultats.
Encore une fois, exprimant le plan d'exécution produit à l'aide de la syntaxe T-SQL disponible la plus proche:
SELECT
CASE
WHEN SUM(Q2.c) = 0 THEN NULL
ELSE SUM(Q2.s)
END
FROM
(
SELECT
Q1.PtnID,
c = COUNT_BIG(*),
s = SUM(Q1.PrinBal)
FROM
(
SELECT GL.PrinBal, PtnID = $PARTITION.PF(GL.SnapshotKey)
FROM dbo.gl AS GL
) AS Q1
GROUP BY
Q1.PtnID
) AS Q2
CROSS APPLY
(
SELECT Dummy = 1
FROM dbo.fpc AS FPC
WHERE
$PARTITION.PF(FPC.SnapshotKey) = Q2.PtnID
AND FPC.SnapshotKey = 201703
) AS Q3;
Cette fois, il n'y a pas de jointure de croix logique. Au lieu de cela, il y a une jointure corrélée (une application) sur l'ID de partition.
pourquoi la deuxième requête est plus rapide.
C'est difficile à évaluer à partir des informations données. En utilisant des données et des tableaux simulés en fonction des requêtes et de la planification de l'image fournie, j'ai trouvé la première requête surperformé la seconde dans chaque cas.
La même requête exprimée à l'aide d'une syntaxe différente peut souvent produire un plan d'exécution différent, simplement parce que l'optimiseur a commencé à partir d'un point différent et exploré des options dans un ordre différent avant de trouver un plan d'exécution approprié. La recherche de plan n'est pas exhaustive et toutes les transformations logiques éventuelles sont disponibles, le résultat final risque d'être différent. Comme indiqué ci-dessus, les deux requêtes n'expriment pas nécessairement la même exigence de toute façon (au moins étant donné les informations à la disposition de l'optimiseur).
Sur une note distincte, sachez que la mise en œuvre initiale de la colonne de colonne dans SQL Server 2012 (et à une moindre mesure, 2014) présente de nombreuses limitations, notamment sur le côté de l'optimisation des choses. Vous irez probablement mieux, et plus cohérent, des résultats en mettant à niveau une version plus récente (idéalement le tout dernier). Cela est particulièrement vrai si vous allez utiliser partitionnement.
Je ne vous recommanderai certainement pas que vous soyez dans l'habitude de réécrire les jointures en utilisant $PARTITION
, sauf en tant que dernier recours, et avec une parfaite compréhension de ce que vous faites.
C'est à peu près tout ce que je peux dire sans pouvoir voir le schéma ou le détail de plan.