NEWID () na tabela virtual ingressada causa um comportamento indesejado de aplicação cruzada

9

Minha consulta de trabalho real foi uma junção interna, mas este exemplo simples com junção cruzada quase sempre reproduz o problema.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT NEWID() TEST_ID
) BB ( B )

Com minha junção interna, eu tinha muitas linhas para as quais adicionei a cada um GUID usando a função NEWID (), e para cerca de 9 em 10 dessas linhas a multiplicação com a tabela virtual de 2 linhas produziu os resultados esperados, apenas 2 cópias de o mesmo GUID, enquanto 1 em cada 10 produziria resultados diferentes. Isso foi inesperado para dizer o mínimo e me deu muita dificuldade em tentar encontrar esse bug no meu script de geração de dados de teste.

Se você der uma olhada nas consultas a seguir usando as funções getdate e sysdatetime não determinísticas, você não verá isso, de qualquer maneira - sempre vejo o mesmo valor de datetime nas duas linhas de resultados finais.

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT GETDATE() TEST_ID
) BB ( B )

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT SYSDATETIME() TEST_ID
) BB ( B )

Atualmente, estou usando o SQL Server 2008 e minha solução, por enquanto, é carregar minhas linhas com GUIDs em uma variável de tabela antes de terminar meu script de geração de dados aleatórios. Depois de tê-los como valores em uma tabela em oposição à tabela virtual, o problema desaparece.

Eu tenho uma solução alternativa, mas estou procurando maneiras de solucionar essa situação sem tabelas ou variáveis ​​de tabela reais.

Enquanto escrevia isso, tentei sem sucesso as seguintes possibilidades: 1) colocar o newid () em uma tabela virtual aninhada:

SELECT *
FROM (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )
CROSS JOIN (
    SELECT TEST_ID
    FROM (
        SELECT NEWID() TEST_ID
    ) TT
) BB ( B )

2) envolvendo o newid () dentro de uma expressão convertida, como:

SELECT CAST(NEWID() AS VARCHAR(100)) TEST_ID

3) reverter a ordem de aparência das tabelas virtuais na expressão de junção

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS JOIN (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

4) usando aplicação cruzada não correlacionada

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT 1 UNION ALL
    SELECT 2
) AA ( A )

Antes de finalmente postar esta pergunta, agora eu tentei isso com sucesso, parece que uma cruz correlacionada se aplica:

SELECT *
FROM (
    SELECT NEWID() TEST_ID
) BB ( B )
CROSS APPLY (
    SELECT A
    FROM (
        SELECT 1 UNION ALL
        SELECT 2
    ) TT ( A )
    WHERE BB.B IS NOT NULL
) AA ( A )

Alguém tem outra solução alternativa mais elegante e simples? Eu realmente não quero usar aplicação cruzada ou correlação para uma multiplicação de linha simples, se não for necessário.

JM Hicks
fonte

Respostas:

20

Esse comportamento é por design, conforme explicado em detalhes neste relatório de bug do Connect . A resposta mais pertinente da Microsoft é reproduzida abaixo por conveniência (e caso o link morra em algum momento):

Publicado pela Microsoft em 7/7/2008 às 9:27

Fechando o loop. . . Eu discuti essa questão com a equipe de desenvolvimento. E, finalmente, decidimos não mudar o comportamento atual, pelos seguintes motivos:

  1. O otimizador não garante tempo ou número de execuções de funções escalares. Esse é um princípio estabelecido há muito tempo. É a 'margem de manobra' fundamental que permite ao otimizador liberdade suficiente para obter melhorias significativas na execução do plano de consulta.

  2. Esse "comportamento de uma vez por linha" não é um problema novo, embora não seja amplamente discutido. Começamos a ajustar seu comportamento de volta no lançamento do Yukon. Mas é muito difícil definir com precisão, em todos os casos, exatamente o que isso significa! Por exemplo, isso se aplica a linhas intermediárias calculadas 'a caminho' do resultado final? - nesse caso, depende claramente do plano escolhido. Ou se aplica apenas às linhas que aparecerão no resultado concluído? - Há uma recursão desagradável acontecendo aqui, como eu tenho certeza que você concorda!

  3. Como mencionei anteriormente, o padrão é "otimizar o desempenho" - o que é bom para 99% dos casos. O 1% dos casos em que ele pode alterar os resultados é bastante fácil de detectar - funções 'com efeito colateral', como NEWID - e fácil de 'corrigir' (desempenho da negociação, como conseqüência). Esse padrão para "otimizar o desempenho" novamente, é estabelecido há muito tempo e aceito. (Sim, não é a postura escolhida pelos compiladores para linguagens de programação convencionais, mas que seja).

Portanto, nossas recomendações são:

  1. Evite confiar em tempo não garantido e semântica de número de execuções.
  2. Evite usar NEWID () nas expressões da tabela.
  3. Use OPTION para forçar um comportamento específico (desempenho de negociação)

Espero que esta explicação ajude a esclarecer nossas razões para fechar esse bug como "não será corrigido".

As funções GETDATEe SYSDATETIMEsão realmente não determinísticas, mas são tratadas como constantes de tempo de execução para uma consulta específica. Em termos gerais, isso significa que o valor da função é armazenado em cache quando a execução da consulta é iniciada e o resultado reutilizado para todas as referências na consulta.

Nenhuma das 'soluções alternativas' na pergunta é segura; não há garantia de que o comportamento não será alterado na próxima vez que o plano for compilado, quando você aplicar um service pack ou uma atualização cumulativa ... ou por outros motivos.

A única solução segura é usar algum tipo de objeto temporário - uma variável, tabela ou função de múltiplas instruções, por exemplo. Usar uma solução alternativa que parece funcionar hoje com base na observação é uma ótima maneira de experimentar comportamentos inesperados no futuro, geralmente na forma de um alerta de paginação às 3h da manhã de domingo.

Paul White 9
fonte
"Nenhuma das 'soluções alternativas' da pergunta é segura;" idem isso. Quando tentei aplicar um deles à minha consulta de trabalho real, não ajudou em nada.
JM Hicks