Tengo 3 tablas "grandes" que se unen en un par de columnas (ambas int
).
- Table1 tiene ~ 200 millones de filas
- Table2 tiene ~ 1.5 millones de filas
- Table3 tiene ~ 6 millones de filas
Cada tabla tiene un índice agrupado en Key1
, Key2
y luego una columna más. Key1
tiene baja cardinalidad y es muy sesgada. Siempre se hace referencia en la WHERE
cláusula. Key2
nunca se menciona en la WHERE
cláusula Cada unión es de muchos a muchos.
El problema es con la estimación de cardinalidad. La estimación de salida de cada combinación se hace más pequeña en lugar de más grande . Esto da como resultado estimaciones finales de cientos bajos cuando el resultado real llega a millones.
¿Hay alguna forma de que yo pueda dar pistas al CE para hacer mejores estimaciones?
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;
Soluciones que he probado:
- Crear estadísticas de varias columnas en
Key1
,Key2
- Crear toneladas de estadísticas filtradas en
Key1
(Esto ayuda bastante, pero termino con miles de estadísticas creadas por el usuario en la base de datos).
Plan de ejecución enmascarado (perdón por el mal enmascaramiento)
En el caso que estoy viendo, el resultado tiene 9 millones de filas. El nuevo CE estima 180 filas; el legado CE estima 6100 filas.
Aquí hay un ejemplo reproducible:
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;
fuente
make_parallel
función de Adam se acostumbra a ayudar a mitigar el problema. Voy a echar un vistazomany
. Parece una curita bastante asquerosa.Las estadísticas de SQL Server solo contienen un histograma para la columna inicial del objeto de estadísticas. Por lo tanto, puede crear estadísticas filtradas que proporcionen un histograma de valores para
Key2
, pero solo entre filas conKey1 = 1
. La creación de estas estadísticas filtradas en cada tabla corrige las estimaciones y conduce al comportamiento que espera para la consulta de prueba: cada nueva unión no afecta la estimación de cardinalidad final (confirmada en SQL 2016 SP1 y SQL 2017).Sin estas estadísticas filtradas, SQL Server adoptará un enfoque más heurístico para estimar la cardinalidad de su unión. El siguiente documento contiene buenas descripciones de alto nivel de algunas de las heurísticas que utiliza SQL Server: Optimización de sus planes de consulta con el Estimador de cardinalidad de SQL Server 2014 .
Por ejemplo, agregar la
USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')
sugerencia a su consulta cambiará la heurística de contención de la unión para asumir cierta correlación (en lugar de independencia) entre elKey1
predicado y elKey2
predicado de unión, lo que puede ser beneficioso para su consulta. Para la consulta de prueba final, esta sugerencia aumenta la estimación de cardinalidad de1,175
a7,551
, pero todavía está bastante por debajo de la20,000
estimación de fila correcta producida con las estadísticas filtradas.Otro enfoque que hemos usado en situaciones similares es extraer el subconjunto relevante de los datos en tablas #temp. Especialmente ahora que las versiones más nuevas de SQL Server ya no escriben ansiosamente tablas #temp en el disco , hemos tenido buenos resultados con este enfoque. Su descripción de su unión de muchos a muchos implica que cada tabla de #temp individual en su caso sería relativamente pequeña (o al menos más pequeña que el conjunto de resultados final), por lo que vale la pena probar este enfoque.
fuente
Key1
valor en cada tabla. Ahora tenemos miles de ellos.Un alcance No hay una base real aparte de intentarlo.
fuente