Alterações nas estimativas de predicados que contêm SUBSTRING () no SQL Server 2016?

13

Existe alguma documentação ou pesquisa sobre alterações no SQL Server 2016 de como a cardinalidade é estimada para predicados que contêm SUBSTRING () ou outras funções de seqüência de caracteres?

O motivo pelo qual estou perguntando é que estava olhando para uma consulta cujo desempenho diminuiu no modo de compatibilidade 130 e o motivo estava relacionado a uma alteração na estimativa do número de linhas que correspondem a uma cláusula WHERE que continha uma chamada para SUBSTRING (). Corrigi o problema com uma reescrita de consulta, mas estou me perguntando se alguém conhece alguma documentação sobre alterações nessa área no SQL Server 2016.

O código de demonstração está abaixo. As estimativas são muito próximas neste caso de teste, mas a precisão varia de acordo com os dados.

No caso de teste, no nível compat 120, o SQL Server parece estar usando o histograma para a estimativa, enquanto no nível compat 130 o SQL Server parece estar assumindo que 10% fixos das correspondências da tabela.

CREATE DATABASE MyStringTestDB;
GO
USE MyStringTestDB;
GO
DROP TABLE IF EXISTS dbo.StringTest;
CREATE TABLE dbo.StringTest ( [TheString] varchar(15) );
GO
INSERT INTO dbo.StringTest
VALUES
( 'Y5_CLV' );
INSERT INTO dbo.StringTest
VALUES
( 'Y5_EG3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_NE' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_PQT' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_T2V' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_TT4' );
INSERT INTO dbo.StringTest
VALUES
( 'ZY_ZKK' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_LW6' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_QO3' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_TZ7' );
INSERT INTO dbo.StringTest
VALUES
( 'ZZ_UZZ' );

CREATE CLUSTERED INDEX IX_Clustered ON dbo.StringTest (TheString);

/* 
Uses fixed % for estimate; 1.1 rows estimated in this case.
    Plan for computation:
        CSelCalcFixedFilter (0.1) <----
            Selectivity: 0.1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 130;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/* 
Uses histogram to get estimate of 1
 CSelCalcPointPredsFreqBased <----
      Distinct value calculation:
          CDVCPlanLeaf
              0 Multi-Column Stats, 1 Single-Column Stats, 0 Guesses
      Individual selectivity calculations:
          (none)
    Loaded histogram for column QCOL: [DBA].[dbo].[StringTest].TheString from stats with id 1
*/
ALTER DATABASE MyStringTestDB SET compatibility_level = 120;
GO
SELECT * 
FROM dbo.StringTest 
WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);

/*
-- Simpler rewrite; works fine in both compat levels and gets better estimate.
SELECT * 
FROM dbo.StringTest 
WHERE TheString LIKE 'ZZ[_]%'
OPTION (QUERYTRACEON 2363, QUERYTRACEON 3604);
*/
James L
fonte
1
Não tenho certeza sobre a pergunta em particular, mas se as Y5_EG3seqüências de caracteres são apenas códigos e sempre em maiúsculas, você pode sempre tentar especificar um agrupamento binário - Latin1_General_100_BIN2- o que deve melhorar a velocidade nas operações de filtragem. Basta adicionar COLLATE Latin1_General_100_BIN2à CREATE TABLEdeclaração, logo após o varchar(15). Eu ficaria curioso para ver se isso também afetou a geração / estimativa do plano.
Solomon Rutzky

Respostas:

8

Não conheço nenhuma documentação. Eu examinei isso e fiz algumas observações, no entanto, que são muito longas para um comentário.

A estimativa de 10% nem sempre é uma degradação. Veja o exemplo a seguir.

TRUNCATE TABLE dbo.StringTest

INSERT INTO dbo.StringTest
SELECT TOP (1000000) 'ZZ_' + LEFT(NEWID(), 12)
FROM   master..spt_values v1,
       master..spt_values v2;

e a WHEREcláusula na sua pergunta.

WHERE SUBSTRING(TheString, 1, CHARINDEX('_',TheString) - 1) = 'ZZ'

A tabela contém um milhão de linhas. Todos eles correspondem ao predicado. No nível compat 130, a estimativa de 10% gera uma estimativa de 100.000. Abaixo de 120, as linhas estimadas são 1,03913.

O comportamento 120 usa o histograma, mas apenas para obter o número de linhas distintas. O vetor de densidade no meu caso mostra 1.039131E-06 e isso é multiplicado pela cardinalidade da tabela para obter a contagem estimada de linhas. Na verdade, todos os valores são diferentes, mas todos correspondem ao predicado.

O rastreamento do query_optimizer_estimate_cardinalityevento estendido mostra que, abaixo de 130, existem dois diferentes<StatsCollection Name="CStCollFilter" eventos . O primeiro estima 100.000. O segundo carrega o histograma e usa o CSelCalcPointPredsFreqBased / DistinctCountCalculator para obter a estimativa 1,04. Este segundo resultado parece não utilizado.

O comportamento que você observou não é aplicado de maneira consistente em 130. Adicionei ORDER BY TheString esperando que essa seja uma vitória clara para o estimador 130, pois os 120 lutam com uma concessão de memória para uma linha, mas essa alteração menor foi suficiente para reduzir as linhas estimadas para 1.03913 no caso 130 também.

A adição OPTION (QUERYRULEOFF SelectToFilter)reverte a estimativa que vai para a classificação para 100.000, mas a concessão de memória não aumenta e as estimativas que saem da classificação ainda são baseadas nos valores distintos da tabela.

insira a descrição da imagem aqui

Da mesma forma, ajustar o limite de custo do paralelismo para que a consulta obtenha um plano paralelo foi suficiente no caso 130 para reverter para a estimativa mais baixa. AdicionandoQUERYTRACEON 8757 também causa a estimativa mais baixa. Parece que a estimativa de 10% é retida apenas para planos triviais.

Sua reescrita proposta com

WHERE TheString LIKE 'ZZ[_]%'

Mostra estimativas muito superiores a ambos. A saída para isso é

  CSelCalcTrieBased

      Column: QCOL: [MyStringTestDB].[dbo].[StringTest].TheString

Mostrando que ele usou tentativas . Mais informações sobre isso estão na seção de estatísticas de resumo das cadeias logo acima aqui .

No entanto, não é o mesmo que sua consulta original. Como a primeira instância de _agora é assumida como sempre o terceiro caractere, em vez de ser encontrada dinamicamente.

Se essa suposição estiver codificada na sua consulta original

 WHERE SUBSTRING(TheString, 1, 3) = 'ZZ_'

O método de estimativa muda para CSelCalcHistogramComparison(INTERVAL) e as linhas estimadas se tornam precisas.

É capaz de converter isso em um intervalo

WHERE TheString >=  'ZZ_' AND TheString < ???

e use o histograma para estimar o número de linhas com valores nesse intervalo.

Isso se aplica apenas à estimativa de cardinalidade, no entanto. LIKEé preferível, pois pode usar um intervalo de busca em tempo de execução. SUBSTRING(TheString, 1, 3)ou LEFT(TheString, 3)não pode.

Martin Smith
fonte