Instrução IF não ignorando TempDB ao fazer um loop nos bancos de dados com sp_MSForEachDB

8

[SQL Server 2012 SP2 EE]

Por que o script a seguir fornece um erro relacionado ao tempdb?

    exec sp_MSForEachDB '
    IF ( (select database_id from sys.databases where name = ''?'') > 4)
    BEGIN 
    ALTER AUTHORIZATION ON DATABASE::? TO [sa];
    ALTER DATABASE [?] SET RECOVERY SIMPLE;
    END'

Aqui está o erro que recebo:

  Msg 5058, Level 16, State 1, Line 5
  Option 'RECOVERY' cannot be set in database 'tempdb'.

Ele faz o trabalho que deveria fazer. Mas não consigo pensar em uma razão para o erro. Eu sei que databaseID do tempdb é 2, pelo menos ele não deve tentar definir a opção para tempdb.

GaganLamba
fonte

Respostas:

4

NOTA PARA OS LEITORES: Leia a pergunta inteira, incluindo o código de exemplo (ou seja, não apenas o título). Esta pergunta não é sobre como melhor percorrer os bancos de dados, nem sobre por que [tempdb] recebe esse erro. O OP já está tentando evitar executar as ALTERinstruções em todos os bancos de dados do sistema (bem, os 4 visíveis) e está perguntando por que a instrução IF que deveria estar pulando [tempdb] parece não estar pulando.

Por que o script a seguir fornece um erro relacionado ao tempdb?

O motivo é que a IFinstrução afeta apenas o que acontece quando o código está realmente em execução, mas o SQL Server ainda precisa analisar e compilar o lote antes de executá-lo. Os erros de análise são aqueles relacionados à sintaxe, como garantir que as instruções SQL sejam formadas corretamente e que as variáveis ​​tenham sido declaradas corretamente:

-- parse the following by hitting Control-F5 or clicking the check mark in SSMS
SELECT @Bob;

obtém o seguinte erro:

Msg 137, Level 15, State 2, Line 2
Must declare the scalar variable "@Bob".

E o seguinte:

-- parse the following by hitting Control-F5 or clicking the check mark in SSMS
CREATE TABLE b

obtém o seguinte erro:

Msg 102, Level 15, State 1, Line 1
Incorrect syntax near 'b'.

Se o lote for analisado com êxito, ele será compilado. Nesse momento, coisas como permissões são verificadas e outras verificações são executadas.

-- parse the following by hitting Control-F5 or clicking the check mark in SSMS
IF (1 = 0)
BEGIN 
  ALTER AUTHORIZATION ON DATABASE::[tempdb] TO [sa];
  ALTER DATABASE [tempdb] SET RECOVERY SIMPLE;
END;

O SQL acima é formado corretamente para que o lote seja analisado com êxito. Agora tente executar o SQL acima pressionando F5 ou Control-E ou o ! Botão Executar , etc.

Dessa vez, você recebe o seguinte erro:

Msg 5058, Level 16, State 1, Line 4
Option 'RECOVERY' cannot be set in database 'tempdb'.

mesmo que IF (1 = 0)isso garanta que o código nunca seja executado. Isso significa que você está executando um erro de compilação. Você pode contornar esses tipos de erros movendo o código incorreto para um subprocesso por meio de uma EXECchamada.

Execute o seguinte e ele será concluído com êxito, pois o que está dentro dele EXEC()não é analisado ou compilado até que a instrução seja executada em tempo de execução.

IF (1 = 0)
BEGIN
  EXEC('
    ALTER AUTHORIZATION ON DATABASE::[tempdb] TO [sa];
    ALTER DATABASE [tempdb] SET RECOVERY SIMPLE;
  ');
END;

Para resumir:
Primeiro, considere que sp_MSForEachDBpercorre os bancos de dados e executa o SQL passado após a substituição pelo ?nome atual do banco de dados. Portanto, quando o cursor dentro de sp_MSForEachDBchegar [tempdb], ele efetivamente faz o seguinte:

IF ( (select database_id from sys.databases where name = ''tempdb'') > 4)
BEGIN 
  ALTER AUTHORIZATION ON DATABASE::tempdb TO [sa];
  ALTER DATABASE [tempdb] SET RECOVERY SIMPLE;
END

Segundo, há várias etapas que o SQL Server executa quando você executa um lote de consulta:

  1. Analisar
  2. Compilar
  3. Execução real

É importante entender que essas são etapas separadas e podem gerar erros antes de prosseguir para a próxima etapa (e, portanto, cancelar o processamento adicional antes de prosseguir para a próxima etapa). No caso aqui, o erro está ocorrendo na Etapa 2 - Compilar - conforme comprovado no segundo ao último exemplo acima (o primeiro a começar IF (1 = 0)). Os IF (1 = 0)impede a dentro do código do BEGIN...ENDbloco de sempre correndo, mas o erro ainda ocorrer. Portanto, o erro não está ocorrendo devido a uma tentativa real de executar as duas ALTERinstruções.

O motivo pelo qual agrupar as ALTERinstruções dentro da EXEC()função funciona é porque o SQL Server não analisará e compilará o que está dentro dele EXEC()até que ele EXEC()esteja realmente em execução. Nesse ponto, a IF ( (select database_id from sys.databases where name = ''?'') > 4)instrução poderá ser executada, pois o lote não falhará durante a Compilação e chegará à Execução. A IFinstrução será ignorada [tempdb]e o erro "Opção 'RECUPERAÇÃO' não pode ser definido no banco de dados 'tempdb'" não irá ocorrer.

Solomon Rutzky
fonte
Minha pergunta é - Por que a instrução "if" permite que o ID do tempdb (ou seja, 2) seja enviado ao bloco if, se ele não satisfaz a condição de ser maior que 4.
GaganLamba
11
@GaganLamba Entendo sua pergunta e respondi de maneira muito específica. Você executou meus exemplos? Como afirmei na resposta, este é um erro em tempo de compilação, e não em tempo de execução. Portanto, nem a IFinstrução nem nenhuma outra instrução SQL (incluindo as duas ALTER) estão sendo executadas neste momento. A IFinstrução não está permitindo que o ID do tempdb seja enviado para o que está dentro do bloco BEGIN/ END, e o código nem está executando as ALTERinstruções neste momento. O erro está sendo gerado pelo SQL Server, pois está validando o SQL antes de executá-lo. Eu adicionei uma seção de resumo ao final.
Solomon Rutzky,
3

Msg 5058, nível 16, estado 1, linha 5 A opção 'RECOVERY' não pode ser definida no banco de dados 'tempdb'.

Ele faz o trabalho que deveria fazer. Mas não consigo pensar em uma razão para o erro.

Primeiro de tudo, não há necessidade de usar o ms_foreachdbseu documento não documentado e o quão ruim você pode usar o cursor simples. Em relação ao erro, você está tentando alterar o modelo de recuperação de todos os bancos de dados, including tempdbmas não pode alterar o modelo de recuperação do tempdb, nem executar nenhuma operação de backup, por isso recebeu esta mensagem de erro. Isso não é permitido pela Microsoft. Leia mais sobre as operações que você pode realizar no tempdb

Shanky
fonte
11
Entendo que o ms_foreachdb não está documentado e que não devo usá-lo. Também entendo que o tempdb não deve ter backup ou alterar a opção de recuperação. Mas minha pergunta ainda não foi respondida por que o SQL Server está tentando alterar a opção do TEMPDB? O ID do TEMPDB, que é 2, nem foi retornado.
GaganLamba
11
@GaganLamba O SQL Server não está realmente tentando alterar a opção do TEMPDB. As ALTERinstruções estão apenas sendo validadas , não executadas. Eu forneço detalhes na minha resposta .
Solomon Rutzky,
3

Além do fato de você não poder alterar a opção de recuperação para o tempdb, você não precisa de um loop para o que está fazendo:

Execute no SSMS pressionando CTRL+T

select 'alter authorization on database::' + quotename(name) + ' to [sa];' + char(10) + 'alter database ' + quotename(name) + ' set recovery simple;'
from sys.databases
where database_id > 4
    and state_desc = 'ONLINE'
Kin Shah
fonte