J'ai 3 "grandes" tables qui se rejoignent sur une paire de colonnes (les deux int
s).
- Le tableau 1 compte environ 200 millions de lignes
- Le tableau 2 compte environ 1,5 million de lignes
- Table3 a environ 6 millions de lignes
Chaque table possède un index clusterisé sur Key1
, Key2
puis une autre colonne. Key1
a une faible cardinalité et est très asymétrique. Il est toujours référencé dans la WHERE
clause. Key2
n'est jamais mentionné dans la WHERE
clause. Chaque jointure est plusieurs-à-plusieurs.
Le problème est lié à l'estimation de la cardinalité. L'estimation de sortie de chaque jointure devient plus petite au lieu de plus grande . Il en résulte des estimations finales de centaines faibles lorsque le résultat réel est bien en millions.
Existe-t-il un moyen pour moi d'indiquer au CE de faire de meilleures estimations?
SELECT 1
FROM Table1 t1
JOIN Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
JOIN Table3 t3
ON t1.Key1 = t3.Key1
AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;
Solutions que j'ai essayées:
- Création de statistiques multi-colonnes sur
Key1
,Key2
- Créer des tonnes de statistiques filtrées sur
Key1
(Cela aide beaucoup, mais je me retrouve avec des milliers de statistiques créées par les utilisateurs dans la base de données.)
Plan d'exécution masqué (désolé pour le mauvais masquage)
Dans le cas que je regarde, le résultat a 9 millions de lignes. Le nouveau CE estime 180 lignes; l'héritage CE estime 6100 lignes.
Voici un exemple reproductible:
DROP TABLE IF EXISTS #Table1, #Table2, #Table3;
CREATE TABLE #Table1 (Key1 INT NOT NULL, Key2 INT NOT NULL, T1Key3 INT NOT NULL, CONSTRAINT pk_t1 PRIMARY KEY CLUSTERED (Key1, Key2, T1Key3));
CREATE TABLE #Table2 (Key1 INT NOT NULL, Key2 INT NOT NULL, T2Key3 INT NOT NULL, CONSTRAINT pk_t2 PRIMARY KEY CLUSTERED (Key1, Key2, T2Key3));
CREATE TABLE #Table3 (Key1 INT NOT NULL, Key2 INT NOT NULL, T3Key3 INT NOT NULL, CONSTRAINT pk_t3 PRIMARY KEY CLUSTERED (Key1, Key2, T3Key3));
-- Table1
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2),
DataSize (Key1, NumberOfRows)
AS (SELECT 1, 2000 UNION
SELECT 2, 10000 UNION
SELECT 3, 25000 UNION
SELECT 4, 50000 UNION
SELECT 5, 200000)
INSERT INTO #Table1
SELECT Key1
, Key2 = ROW_NUMBER() OVER (PARTITION BY Key1, T1Key3 ORDER BY Number)
, T1Key3
FROM DataSize
CROSS APPLY (SELECT TOP(NumberOfRows)
Number
, T1Key3 = Number%(Key1*Key1) + 1
FROM Numbers
ORDER BY Number) size;
-- Table2 (same Key1, Key2 values; smaller number of distinct third Key)
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2)
INSERT INTO #Table2
SELECT DISTINCT
Key1
, Key2
, T2Key3
FROM #Table1
CROSS APPLY (SELECT TOP (Key1*10)
T2Key3 = Number
FROM Numbers
ORDER BY Number) size;
-- Table2 (same Key1, Key2 values; smallest number of distinct third Key)
WITH Numbers
AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2)
INSERT INTO #Table3
SELECT DISTINCT
Key1
, Key2
, T3Key3
FROM #Table1
CROSS APPLY (SELECT TOP (Key1)
T3Key3 = Number
FROM Numbers
ORDER BY Number) size;
DROP TABLE IF EXISTS #a;
SELECT col = 1
INTO #a
FROM #Table1 t1
JOIN #Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
WHERE t1.Key1 = 1;
DROP TABLE IF EXISTS #b;
SELECT col = 1
INTO #b
FROM #Table1 t1
JOIN #Table2 t2
ON t1.Key1 = t2.Key1
AND t1.Key2 = t2.Key2
JOIN #Table3 t3
ON t1.Key1 = t3.Key1
AND t1.Key2 = t3.Key2
WHERE t1.Key1 = 1;
la source
make_parallel
fonction d'Adam est utilisée pour aider à atténuer le problème. Je vais voirmany
. On dirait un pansement assez grossier.Les statistiques SQL Server contiennent uniquement un histogramme pour la première colonne de l'objet de statistiques. Par conséquent, vous pouvez créer des statistiques filtrées qui fournissent un histogramme de valeurs pour
Key2
, mais uniquement parmi les lignes avecKey1 = 1
. La création de ces statistiques filtrées sur chaque table corrige les estimations et conduit au comportement que vous attendez de la requête de test: chaque nouvelle jointure n'a pas d'impact sur l'estimation de cardinalité finale (confirmée dans SQL 2016 SP1 et SQL 2017).Sans ces statistiques filtrées, SQL Server adoptera une approche plus heuristique pour estimer la cardinalité de votre jointure. Le livre blanc suivant contient de bonnes descriptions de haut niveau de certaines des heuristiques utilisées par SQL Server: Optimisation de vos plans de requête avec l'estimateur de cardinalité SQL Server 2014 .
Par exemple, l'ajout de l'
USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')
indice à votre requête modifiera l'heuristique de confinement de jointure pour supposer une certaine corrélation (plutôt que l'indépendance) entre leKey1
prédicat et leKey2
prédicat de jointure, ce qui peut être bénéfique pour votre requête. Pour la dernière requête de test, cette indication augmente l'estimation de la cardinalité de1,175
à7,551
, mais reste assez timide par rapport à l'20,000
estimation de ligne correcte produite avec les statistiques filtrées.Une autre approche que nous avons utilisée dans des situations similaires consiste à extraire le sous-ensemble pertinent des données dans des tables #temp. Surtout maintenant que les nouvelles versions de SQL Server n'écrivent plus avec impatience les tables #temp sur le disque , nous avons eu de bons résultats avec cette approche. Votre description de votre jointure plusieurs-à-plusieurs implique que chaque table #temp individuelle dans votre cas serait relativement petite (ou au moins plus petite que l'ensemble de résultats final), donc cette approche peut être utile d'essayer.
la source
Key1
valeur sur chaque table. Nous en avons maintenant des milliers.Une portée. Aucune vraie base autre que d'essayer.
la source