Eu tenho 3 tabelas "grandes" que se juntam a um par de colunas (ambas int
).
- A tabela 1 possui ~ 200 milhões de linhas
- A tabela 2 possui ~ 1,5 milhão de linhas
- A tabela 3 possui ~ 6 milhões de linhas
Cada tabela tem um índice de cluster no Key1
, Key2
e em seguida mais uma coluna. Key1
tem baixa cardinalidade e é muito assimétrica. É sempre referenciado na WHERE
cláusula. Key2
nunca é mencionado na WHERE
cláusula. Cada associação é muitos-para-muitos.
O problema está na estimativa da cardinalidade. A estimativa de saída de cada junção fica menor em vez de maior . Isso resulta em estimativas finais de centenas baixas, quando o resultado real está na casa dos milhões.
Existe alguma maneira de eu induzir o CE a fazer melhores estimativas?
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;
Soluções que tentei:
- Criando estatísticas de várias colunas em
Key1
,Key2
- Criando toneladas de estatísticas filtradas
Key1
(Isso ajuda bastante, mas eu acabo com milhares de estatísticas criadas pelo usuário no banco de dados).
Plano de execução mascarado (desculpe-me pelo mau mascaramento)
No caso que estou vendo, o resultado tem 9 milhões de linhas. O novo CE estima 180 linhas; o CE herdado estima 6100 linhas.
Aqui está um exemplo reproduzível:
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;
fonte
make_parallel
função de Adam é usada para ajudar a mitigar o problema. Vou dar uma olhadamany
. Parece um band-aid bastante nojento.As estatísticas do SQL Server contêm apenas um histograma para a coluna principal do objeto de estatísticas. Portanto, você pode criar estatísticas filtradas que fornecem um histograma de valores para
Key2
, mas apenas entre as linhas comKey1 = 1
. A criação dessas estatísticas filtradas em cada tabela corrige as estimativas e leva ao comportamento esperado para a consulta de teste: cada nova associação não afeta a estimativa final de cardinalidade (confirmada no SQL 2016 SP1 e SQL 2017).Sem essas estatísticas filtradas, o SQL Server adotará uma abordagem mais heurística para estimar a cardinalidade da sua associação. O whitepaper a seguir contém boas descrições de alto nível de algumas das heurísticas que o SQL Server usa: Otimizando seus planos de consulta com o estimador de cardinalidade do SQL Server 2014 .
Por exemplo, adicionar a
USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')
dica à sua consulta alterará a heurística de contenção de junção para assumir alguma correlação (em vez de independência) entre oKey1
predicado e oKey2
predicado de junção, o que pode ser benéfico para sua consulta. Para a consulta de teste final, essa dica aumenta a estimativa de cardinalidade de1,175
para7,551
, mas ainda é um pouco tímida da correta20,000
estimativa de linha produzida com as estatísticas filtradas.Outra abordagem que usamos em situações semelhantes é extrair o subconjunto relevante dos dados em #temp tables. Especialmente agora que as versões mais recentes do SQL Server não gravam mais #temp tabelas em disco , tivemos bons resultados com essa abordagem. Sua descrição de sua associação muitos-para-muitos implica que cada tabela #temp individual no seu caso seria relativamente pequena (ou pelo menos menor que o conjunto de resultados final); portanto, vale a pena tentar essa abordagem.
fonte
Key1
valor em cada tabela. Agora temos milhares deles.Um alcance. Nenhuma base real além de tentar.
fonte