Instruction IF ne sautant pas TempDB lors d'une boucle via des bases de données avec sp_MSForEachDB

8

[SQL Server 2012 SP2 EE]

Pourquoi le script suivant me donne-t-il une erreur concernant 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'

Voici l'erreur que j'obtiens:

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

Il fait le travail qu'il est censé faire. Mais je ne peux pas penser à une raison de l'erreur. Je sais que le databaseID de tempdb est 2, alors au moins il ne devrait pas essayer de définir l'option pour tempdb.

GaganLamba
la source

Réponses:

4

NOTE AUX LECTEURS: Veuillez lire l'intégralité de la question, y compris l'exemple de code (c'est-à-dire pas seulement le titre). Cette question n'est pas sur la meilleure façon de parcourir les bases de données, ni sur la raison pour laquelle [tempdb] reçoit cette erreur. L'OP essaie déjà d'éviter d'exécuter les ALTERinstructions sur toutes les bases de données système (enfin, les 4 visibles) et demande pourquoi l'instruction IF qui devrait être sautée [tempdb] ne semble pas la sauter.

Pourquoi le script suivant me donne-t-il une erreur concernant tempdb?

La raison en est que l' IFinstruction n'affecte que ce qui se passe lorsque le code s'exécute réellement, mais SQL Server doit toujours analyser et compiler le lot avant de l'exécuter. Les erreurs d'analyse sont celles liées à la syntaxe, telles que la vérification que les instructions SQL sont correctement formées et que les variables ont été correctement déclarées:

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

obtient l'erreur suivante:

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

Et ce qui suit:

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

obtient l'erreur suivante:

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

Si le lot est analysé avec succès, il est compilé, auquel moment des éléments tels que les autorisations sont vérifiés et d'autres vérifications sont effectuées.

-- 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;

Le SQL ci-dessus est correctement formé afin que le batch soit correctement analysé. Essayez maintenant d'exécuter le SQL ci-dessus en appuyant sur F5 ou Control-E ou le ! Bouton Exécuter , etc.

Cette fois, vous obtenez l'erreur suivante:

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

même si le IF (1 = 0)garantit que le code ne s'exécutera jamais. Cela signifie que vous rencontrez une erreur de compilation. Vous pouvez contourner ces types d'erreurs en déplaçant le code incriminé dans un sous-processus via un EXECappel.

Exécutez ce qui suit et cela se terminera avec succès car ce qui se trouve à l'intérieur de EXEC()n'est pas analysé ou compilé tant que cette instruction n'est pas exécutée au moment de l'exécution.

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

Pour résumer:
Tout d'abord, considérez que sp_MSForEachDBparcourt les bases de données et exécute votre SQL passé après avoir remplacé le ?par le nom de la base de données actuelle. Ainsi, lorsque le curseur à l'intérieur de sp_MSForEachDBarrive à [tempdb], il effectue effectivement les opérations suivantes:

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

Deuxièmement, SQL Server effectue plusieurs étapes lorsque vous exécutez un lot de requêtes:

  1. Analyser
  2. Compiler
  3. Exécution réelle

Il est important de comprendre que ce sont des étapes distinctes et peuvent générer des erreurs avant de passer à l'étape suivante (et donc annuler tout traitement supplémentaire avant de passer à l'étape suivante). Dans le cas ici, l'erreur se produit à l'étape 2 - Compilation - comme prouvé dans le deuxième au dernier exemple ci-dessus (le premier pour commencer IF (1 = 0)). Le IF (1 = 0)empêche le code à l'intérieur du BEGIN...ENDbloc de s'exécuter, mais l'erreur persiste. Par conséquent, l'erreur ne se produit pas en raison d'une tentative réelle d'exécuter les deux ALTERinstructions.

La raison pour laquelle l'habillage des ALTERinstructions à l'intérieur de la EXEC()fonction fonctionne parce que SQL Server n'analysera pas et ne compilera pas ce qui se trouve à l'intérieur de EXEC()jusqu'à ce que le EXEC()soit réellement exécuté. À ce moment - là, la IF ( (select database_id from sys.databases where name = ''?'') > 4)déclaration sera autorisé à exécuter car le lot ne manquera pas lors de la compilation et la rendre à l' exécution, et la IFdéclaration sera sauter [tempdb]et la « option « récupération » ne peut pas être défini dans la base de données « tempdb » » erreur n'arrivera pas.

Solomon Rutzky
la source
Ma question est la suivante: pourquoi l'instruction "if" permet-elle d'envoyer l'ID de tempdb (c'est-à-dire 2) au bloc d'instructions if s'il ne remplit pas la condition d'être supérieur à 4.
GaganLamba
1
@GaganLamba Je comprends votre question et y ai répondu très précisément. Avez-vous exécuté mes exemples? Comme je l'ai indiqué dans la réponse, il s'agit d'une erreur au moment de la compilation et non au moment de l'exécution. Par conséquent, ni l' IFinstruction ni aucune autre instruction SQL (y compris les deux ALTERs) ne sont exécutées à ce stade. L' IFinstruction ne permet pas d'envoyer l'ID de tempdb à ce qui se trouve à l'intérieur du bloc BEGIN/ END, et le code n'exécute même pas les ALTERinstructions à ce stade. L'erreur est levée par SQL Server car il valide le SQL avant de l' exécuter. J'ai ajouté une section récapitulative à la fin.
Solomon Rutzky
3

Msg 5058, niveau 16, état 1, ligne 5 L'option 'RECOVERY' ne peut pas être définie dans la base de données 'tempdb'.

Il fait le travail qu'il est censé faire. Mais je ne peux pas penser à une raison de l'erreur.

Tout d'abord, il n'est pas nécessaire d'utiliser ms_foreachdbson non documenté et vous pouvez boucler en utilisant un simple curseur. En ce qui concerne l'erreur, vous essayez de modifier le modèle de récupération de toutes les bases de données, including tempdbmais vous ne pouvez pas modifier le modèle de récupération de tempdb et vous ne pouvez effectuer aucune opération de sauvegarde dessus, c'est pourquoi vous avez reçu ce message d'erreur. Cela n'est pas autorisé par Microsoft. Veuillez en savoir plus sur les opérations que vous pouvez effectuer sur tempdb

Shanky
la source
1
Je comprends que ms_foreachdb n'est pas documenté et que je ne devrais pas l'utiliser. Je comprends également que tempdb n'est pas censé être sauvegardé ou changer d'option de récupération. Mais ma question est toujours sans réponse quant à la raison pour laquelle le serveur SQL essaie de changer l'option pour TEMPDB? L'ID de TEMPDB, qui est 2, n'a même pas été retourné.
GaganLamba
1
@GaganLamba SQL Server n'essaie pas réellement de changer l'option pour TEMPDB. Les ALTERdéclarations sont simplement en cours de validation , pas exécutées. Je donne des détails dans ma réponse .
Solomon Rutzky
3

Mis à part le fait que vous ne pouvez pas modifier l'option de récupération pour tempdb, vous n'avez pas besoin d'une boucle pour ce que vous faites:

Exécuter dans SSMS en appuyant sur 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
la source