当使用sp_MSForEachDB遍历数据库时,IF语句不跳过TempDB


8

[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'

这是我得到的错误:

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

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

Answers:


4

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

为什么以下脚本给我一个与tempdb有关的错误?

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

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

得到以下错误:

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

以及以下内容:

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

得到以下错误:

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

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

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

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

这次您得到以下错误:

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

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

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

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

总结一下:
首先,请考虑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

其次,执行查询批处理时,SQL Server需要执行几个步骤:

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

重要的是要了解这些是独立的步骤,并且在继续进行下一步之前可能会产生错误(因此在继续进行下一步之前取消进一步的处理)。在这种情况下,错误发生在第2步-编译-如上面第二到最后一个示例(第一个以开头IF (1 = 0))中所证明的那样。该IF (1 = 0)防止了的代码内BEGIN...END从不断运行块,仍然出现但该错误。因此,由于实际尝试运行这两个ALTER语句而不会发生该错误。

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


我的问题是-为什么是“if”语句允许tempdb中的ID(即2)被发送到if语句块,如果它不符合的量大的情况比4
GaganLamba

1
@GaganLamba我理解您的问题,并非常明确地回答了它。你运行我的例子了吗?正如我在答案中指出的那样,这是编译时而不是运行时错误。因此,此时该IF语句或任何其他SQL语句(包括两个ALTERs)都没有执行。该IF声明是不是允许tempdb中的ID被发送到什么是内BEGIN/ END块,并且代码甚至没有运行ALTER在这一点上发言。由于SQL Server 执行SQL 之前正在验证SQL,因此引发了该错误。我在最后添加了一个摘要部分。
所罗门·鲁茨基

3

消息5058,级别16,状态1,第5行无法在数据库“ tempdb”中设置选项“恢复”。

它完成了应做的工作。但是我无法想到错误的原因。

首先,没有必要使用ms_foreachdb其未记录的文档,而且很糟糕,您可以使用简单的游标进行循环。关于错误,您正在尝试更改所有数据库的恢复模型,including tempdb但是无法更改tempdb的恢复模型,也无法对其执行任何备份操作,这就是收到此错误消息的原因。Microsoft不允许这样做。请阅读有关可在tempdb上执行的操作的更多信息


1
我的观点是,ms_foreachdb没有记录,我不应该使用它。我也了解,不应备份tempdb或更改恢复选项。但是,为什么SQL Server试图更改TEMPDB的选项,我的问题仍然没有答案。TEMPDB的ID为2,甚至没有返回。
GaganLamba 2015年

1
@GaganLamba SQL Server实际上并未尝试更改TEMPDB的选项。这些ALTER语句只是被验证而不是执行。我在回答中提供了详细信息。
所罗门·鲁兹基

3

除了您不能更改tempdb的恢复选项外,您不需要为正在执行的操作循环

通过按CTRL+ 在SSMS中运行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'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.