Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

A atribuição de parâmetros de entrada de procedimento armazenado a variáveis ​​locais ajuda a otimizar a consulta?


Não vou tentar explicar todos os detalhes do sniffing de parâmetros, mas resumindo, não, não sempre ajudar (e pode atrapalhar).

Imagine uma tabela (T) com uma chave primária e uma coluna Data indexada (A), na tabela há 1.000 linhas, 400 têm o mesmo valor de A (digamos hoje 20130122), as 600 linhas restantes são os próximos 600 dias , portanto, apenas 1 registro por data.

Esta consulta:
SELECT *
FROM T
WHERE A = '20130122';

Produzirá um plano de execução diferente para:
SELECT *
FROM T
WHERE A = '20130123';

Como as estatísticas indicarão que para as primeiras 400 de 1.000 linhas serão retornadas, o otimizador deve reconhecer que uma verificação de tabela será mais eficiente do que uma pesquisa de marcador, enquanto a segunda produzirá apenas 1 linha, portanto, uma pesquisa de marcador será muito mais mais eficiente.

Agora, de volta à sua pergunta, se fizermos disso um procedimento:
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param

Então corra
EXECUTE dbo.GetFromT '20130122'; --400 rows

O plano de consulta com a verificação de tabela será usado, se na primeira vez que você o executar você usar '20130123' como parâmetro, ele armazenará o plano de pesquisa de favoritos. Até que o procedimento seja recompilado, o plano permanecerá o mesmo. Fazendo algo assim:
CREATE PROCEDURE dbo.GetFromT @Param VARCHAR(5)
AS
    DECLARE @Param2 VARCHAR(5) = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2

Então isso é executado:
EXECUTE dbo.GetFromT '20130122';

Embora o procedimento seja compilado de uma só vez, ele não flui corretamente, então o plano de consulta criado na primeira compilação não tem ideia de que @Param2 se tornará o mesmo que @param, então o otimizador (sem conhecimento de quantas linhas expect) assumirá que 300 serão retornados (30%), pois considerará uma verificação de tabela mais eficiente do que uma pesquisa de favoritos. Se você executasse o mesmo procedimento com '20130123' como parâmetro, ele produziria o mesmo plano (independentemente de qual parâmetro foi invocado pela primeira vez) porque as estatísticas não podem ser usadas para um valor desconhecido. Portanto, executar este procedimento para '20130122' seria mais eficiente, mas para todos os outros valores seria menos eficiente do que sem parâmetros locais (assumindo que o procedimento sem parâmetros locais foi invocado pela primeira vez com qualquer coisa menos '20130122')

Algumas consultas para demonstrar para que você possa visualizar os planos de execução por conta própria

Criar esquema e dados de amostra
CREATE TABLE T (ID INT IDENTITY(1, 1) PRIMARY KEY, A DATE NOT NULL, B INT,C INT, D INT, E INT);

CREATE NONCLUSTERED INDEX IX_T ON T (A);

INSERT T (A, B, C, D, E)
SELECT  TOP 400 CAST('20130122' AS DATE), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   type = 'P'
UNION ALL
SELECT TOP 600 DATEADD(DAY, number, CAST('20130122' AS DATE)), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   Type = 'P';
GO
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
GO
CREATE PROCEDURE dbo.GetFromT2 @Param DATE
AS
    DECLARE @Param2 DATE = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
GO

Executar procedimentos (mostrando o plano de execução real):
EXECUTE GetFromT '20130122';
EXECUTE GetFromT '20130123';
EXECUTE GetFromT2 '20130122';
EXECUTE GetFromT2 '20130123';
GO
EXECUTE SP_RECOMPILE GetFromT;
EXECUTE SP_RECOMPILE GetFromT2;
GO
EXECUTE GetFromT '20130123';
EXECUTE GetFromT '20130122';
EXECUTE GetFromT2 '20130123';
EXECUTE GetFromT2 '20130122';

Você verá que a primeira vez que GetFromT é compilado, ele usa uma verificação de tabela e retém isso quando executado com o parâmetro '20130122', GetFromT2 também usa uma varredura de tabela e mantém o plano para '20130122'.

Após os procedimentos terem sido definidos para recompilação e executados novamente (observe em uma ordem diferente), GetFromT usa um loop de favoritos e mantém o plano para '20130122', apesar de ter considerado anteriormente que uma verificação de tabela é um plano mais apropriado. GetFromT2 não é afetado pelo pedido e tem o mesmo plano de antes da recompliação.

Então, em resumo, depende da distribuição de seus dados e seus índices, sua frequência de recompilação e um pouco de sorte se um procedimento se beneficiará do uso de variáveis ​​locais. Certamente não sempre ajuda.

Espero ter esclarecido o efeito do uso de parâmetros locais, planos de execução e compilação de procedimentos armazenados. Se eu falhei completamente ou perdi um ponto-chave, uma explicação muito mais detalhada pode ser encontrada aqui:

http://www.sommarskog.se/query-plan-mysteries.html