IF 语句在使用 sp_MSForEachDB 循环数据库时不跳过 TempDB

Gag*_*mba 8 sql-server sql-server-2012 tempdb

[SQL Server 2012 SP2 EE]

为什么以下脚本给我一个与 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'
Run Code Online (Sandbox Code Playgroud)

这是我得到的错误:

  Msg 5058, Level 16, State 1, Line 5
  Option 'RECOVERY' cannot be set in database 'tempdb'.
Run Code Online (Sandbox Code Playgroud)

它完成了它应该做的工作。但我想不出错误的原因。我知道 tempdb 的 databaseID 是 2,那么至少它不应该尝试为 tempdb 设置选项。

Sol*_*zky 4

读者注意事项:请阅读整个问题,包括示例代码(即不仅仅是标题)。这个问题不是关于如何最好地循环数据库,也不是关于为什么 [tempdb] 收到此错误。OP 已经尝试避免ALTER在所有系统数据库(好吧,4 个可见数据库)上执行语句,并询问为什么应该跳过 [tempdb] 的 IF 语句似乎没有跳过它。

为什么以下脚本给出与 tempdb 相关的错误?

原因是该IF语句仅影响代码实际运行时发生的情况,但 SQL Server 在执行之前仍然必须解析和编译批处理。解析错误是与语法相关的错误,例如确保 SQL 语句格式正确并且变量已正确声明:

-- parse the following by hitting Control-F5 or clicking the check mark in SSMS
SELECT @Bob;
Run Code Online (Sandbox Code Playgroud)

收到以下错误:

Msg 137, Level 15, State 2, Line 2
Must declare the scalar variable "@Bob".
Run Code Online (Sandbox Code Playgroud)

以及以下内容:

-- parse the following by hitting Control-F5 or clicking the check mark in SSMS
CREATE TABLE b
Run Code Online (Sandbox Code Playgroud)

收到以下错误:

Msg 102, Level 15, State 1, Line 1
Incorrect syntax near 'b'.
Run Code Online (Sandbox Code Playgroud)

如果批处理解析成功,则会对其进行编译,此时会检查权限等内容并执行其他一些检查。

-- 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;
Run Code Online (Sandbox Code Playgroud)

上面的 SQL 格式正确,因此批处理解析成功。现在尝试通过按F5Control-E!来执行上述 SQL。执行按钮等

这次你会得到以下错误:

Msg 5058, Level 16, State 1, Line 4
Option 'RECOVERY' cannot be set in database 'tempdb'.
Run Code Online (Sandbox Code Playgroud)

即使IF (1 = 0)确保代码永远不会运行。这意味着您遇到了编译错误。您可以通过调用将有问题的代码移动到子进程中来解决这些类型的错误EXEC

执行以下命令,它将成功完成,因为EXEC()在运行时执行该语句之前,不会解析或编译 内部的内容。

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

总结一下:
首先,考虑循环数据库并在将 替换为当前数据库名称sp_MSForEachDB后执行传入的 SQL 。?因此,当 中的光标sp_MSForEachDB到达时[tempdb],它实际上执行以下操作:

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
Run Code Online (Sandbox Code Playgroud)

其次,执行批处理查询时,SQL Server 会执行以下几个步骤:

  1. 解析
  2. 编译
  3. 实际执行

重要的是要了解这些是单独的步骤,并且可能会在继续下一步之前生成错误(因此在继续下一步之前取消进一步的处理)。在本例中,错误发生在步骤 2(编译)中,如上面倒数第二个示例(以 开头的第一个示例IF (1 = 0))所示。这IF (1 = 0)会阻止块内的代码BEGIN...END运行,但错误仍然发生。因此,由于实际尝试运行这两个语句,不会发生错误ALTER

ALTER将语句包装在函数内部的原因是因为 SQL Server 在实际运行之前EXEC()不会解析和编译内部的内容。此时,该语句将被允许运行,因为批处理在编译期间不会失败,并将使其执行,并且该语句跳过并出现“Option 'RECOVERY'无法在数据库'tempdb'中设置”错误不会发生。EXEC()EXEC()IF ( (select database_id from sys.databases where name = ''?'') > 4)IF[tempdb]