O SQL Server armazena em cache o resultado de uma função com valor de tabela com várias instruções?

22

Uma função com valor de tabela com várias instruções retorna seu resultado em uma variável de tabela.

Esses resultados são sempre reutilizados ou a função é sempre totalmente avaliada toda vez que é chamada?

Paul White diz que a GoFundMonica
fonte

Respostas:

23

Os resultados de uma função com valor de tabela de múltiplas instruções (msTVF) nunca são armazenados em cache ou reutilizados entre instruções (ou conexões), mas existem algumas maneiras pelas quais um resultado de msTVF pode ser reutilizado na mesma instrução. Nessa medida, um msTVF não é necessariamente repovoado cada vez que é chamado.

Exemplo msTVF

Este (deliberadamente ineficiente) msTVF retorna um intervalo especificado de números inteiros, com um registro de data e hora em cada linha:

IF OBJECT_ID(N'dbo.IntegerRange', 'TF') IS NOT NULL
    DROP FUNCTION dbo.IntegerRange;
GO
CREATE FUNCTION dbo.IntegerRange (@From integer, @To integer)
RETURNS @T table 
(
    n integer PRIMARY KEY, 
    ts datetime DEFAULT CURRENT_TIMESTAMP
)
WITH SCHEMABINDING
AS
BEGIN
    WHILE @From <= @To
    BEGIN
        INSERT @T (n)
        VALUES (@From);

        SET @From = @From + 1;
    END;
    RETURN;
END;

Variável de tabela estática

Se todos os parâmetros para a chamada de função forem constantes (ou constantes de tempo de execução), o plano de execução preencherá o resultado da variável da tabela uma vez. O restante do plano pode acessar a variável da tabela várias vezes. A natureza estática da variável da tabela pode ser reconhecida no plano de execução. Por exemplo:

SELECT
    IR.n,
    IR.ts 
FROM dbo.IntegerRange(1, 5) AS IR
ORDER BY
    IR.n;

Retorna um resultado semelhante a:

Resultado simples

O plano de execução é:

Plano de execução simples

O operador Sequence primeiro chama o operador Table Valued Function, que preenche a variável da tabela (observe que este operador não retorna linhas). Em seguida, a Sequência chama sua segunda entrada, que retorna o conteúdo da variável da tabela (usando uma Varredura de Índice em Cluster neste caso).

A ideia de que o plano está usando um resultado de variável de tabela 'estática' é o operador Função com valor de tabela abaixo de uma sequência - a variável de tabela precisa ser totalmente preenchida uma vez antes que o restante do plano possa continuar.

Vários acessos

Para mostrar o resultado da variável de tabela sendo acessado mais de uma vez, usaremos uma segunda tabela com linhas numeradas de 1 a 5:

IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
    DROP TABLE dbo.T;

CREATE TABLE dbo.T (i integer NOT NULL);

INSERT dbo.T (i) 
VALUES (1), (2), (3), (4), (5);

E uma nova consulta que une essa tabela à nossa função (isso também pode ser escrito como um APPLY):

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
JOIN dbo.IntegerRange(1, 5) AS IR
    ON IR.n = T.i;

O resultado é:

Resultado da junção

O plano de execução:

Aderir ao plano

Como antes, a Sequência preenche o resultado da variável de tabela msTVF primeiro. Em seguida, loops aninhados são usados ​​para associar cada linha da tabela Ta uma linha do resultado do msTVF. Como a definição da função incluiu um índice útil na variável da tabela, uma busca por índice pode ser usada.

O ponto principal é que, quando os parâmetros para o msTVF forem constantes (incluindo variáveis ​​e parâmetros) ou tratados como constantes de tempo de execução para a instrução pelo mecanismo de execução, o plano apresentará dois operadores separados para o resultado da variável da tabela msTVF: um para preencher o tabela; outro para acessar os resultados, possivelmente acessando a tabela várias vezes e possivelmente fazendo uso dos índices declarados na definição da função.

Parâmetros correlacionados e parâmetros não constantes

Para destacar as diferenças quando parâmetros correlacionados (referências externas) ou parâmetros de função não constantes são usados, alteraremos o conteúdo da tabela Tpara que a função tenha muito mais trabalho a fazer:

TRUNCATE TABLE dbo.T;

INSERT dbo.T (i) 
VALUES (50001), (50002), (50003), (50004), (50005);

A consulta modificada a seguir agora usa uma referência externa para a tabela Tem um dos parâmetros de função:

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;

Essa consulta leva cerca de 8 segundos para retornar resultados como:

Resultado correlato

Observe a diferença de horário entre as linhas na coluna ts. A WHEREcláusula limita o resultado final para uma saída de tamanho sensato, mas a função ineficiente ainda leva um tempo para preencher a variável da tabela com 50.000 linhas ímpares (dependendo do valor correlacionado da itabela T).

O plano de execução é:

Plano de execução correlacionado

Observe a falta de um operador Sequence. Agora, existe um único operador Table Valued Function que preenche a variável da tabela e retorna suas linhas em cada iteração da junção de loops aninhados.

Para ficar claro: com apenas 5 linhas na tabela T, o operador Função com valor de tabela é executado 5 vezes. Ele gera 50.001 linhas na primeira iteração, 50.002 na segunda ... e assim por diante. A variável de tabela é 'descartada' (truncada) entre iterações, portanto, cada uma das cinco chamadas é uma população completa. É por isso que é tão lento e cada linha leva aproximadamente o mesmo tempo para aparecer no resultado.

Notas laterais:

Naturalmente, o cenário acima é deliberadamente artificial para mostrar como o desempenho pode ser ruim quando o msTVF preenche muitas linhas em cada iteração.

Uma implementação sensata do código acima definiria ambos os parâmetros msTVF para ie removeria a WHEREcláusula redundante . A variável de tabela ainda seria truncada e preenchida novamente em cada iteração, mas apenas com uma linha de cada vez.

Também poderíamos buscar os ivalores mínimo e máximo Te armazená-los em variáveis ​​em uma etapa anterior. Chamar a função com variáveis ​​em vez de parâmetros correlacionados permitiria que o padrão de variável da tabela 'estático' fosse usado como observado anteriormente.

Armazenamento em cache para parâmetros correlatos inalterados

Voltando a abordar a pergunta original mais uma vez, onde o padrão estático de Sequência não pode ser usado, o SQL Server pode evitar truncar e repovoar a variável de tabela msTVF se nenhum dos parâmetros correlatos tiver sido alterado desde a iteração anterior de uma junção de loop aninhada.

Para demonstrar isso, substituiremos o conteúdo de Tcinco valores idênticos i :

TRUNCATE TABLE dbo.T;

INSERT dbo.T (i) 
VALUES (50005), (50005), (50005), (50005), (50005);

A consulta com um parâmetro correlacionado novamente:

SELECT T.i,
       IR.n,
       IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;

Desta vez, os resultados aparecem em cerca de 1,5 segundos :

Resultados de linha idênticos

Observe os carimbos de data e hora idênticos em cada linha. O resultado armazenado em cache na variável de tabela é reutilizado para iterações subseqüentes em que o valor correlacionado ié inalterado. Reutilizar o resultado é muito mais rápido do que inserir 50.005 linhas por vez.

O plano de execução é muito semelhante ao anterior:

Planejar linhas idênticas

A principal diferença é nos reais rebinds e rebobina reais propriedades do valor de tabela operador Função:

Propriedades do operador

Quando os parâmetros correlatos não são alterados, o SQL Server pode reproduzir (retroceder) os resultados atuais na variável da tabela. Quando a correlação é alterada, o SQL Server deve truncar e preencher novamente a variável da tabela (religar). A única religação acontece na primeira iteração; as quatro iterações subseqüentes são todas rebobinadas, pois o valor de T.ié inalterado.

Paul White diz que a GoFundMonica
fonte