读者注意:请阅读整个问题,包括示例代码(即不只是标题)。这个问题不是关于如何最好地循环数据库,也不是关于[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格式正确,因此批处理成功解析。现在,尝试通过按F5或Control-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需要执行几个步骤:
- 解析
- 编译
- 实际执行
重要的是要了解这些是独立的步骤,并且在继续进行下一步之前可能会产生错误(因此在继续进行下一步之前取消进一步的处理)。在这种情况下,错误发生在第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'”错误不会发生。