La instrucción IF no omite TempDB al recorrer las bases de datos con sp_MSForEachDB

8

[SQL Server 2012 SP2 EE]

¿Por qué el siguiente script me da un error relacionado con 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'

Aquí está el error que recibo:

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

Hace el trabajo que se supone que debe hacer. Pero no puedo pensar en una razón para el error. Sé que el ID de base de datos de tempdb es 2, entonces al menos no debería intentar establecer la opción para tempdb.

GaganLamba
fuente

Respuestas:

4

NOTA PARA LOS LECTORES: Lea la pregunta completa, incluido el código de ejemplo (es decir, no solo el título). Esta pregunta no se trata de cómo recorrer mejor las bases de datos, ni se trata de por qué [tempdb] recibe este error. El OP ya está tratando de evitar ejecutar las ALTERdeclaraciones en todas las bases de datos del sistema (bueno, las 4 visibles) y pregunta por qué la declaración IF que debería omitirse [tempdb] no parece omitirla.

¿Por qué el siguiente script me da un error relacionado con tempdb?

La razón es que la IFdeclaración solo afecta lo que sucede cuando el código se está ejecutando, pero SQL Server todavía tiene que analizar y compilar el lote antes de ejecutarlo. Los errores de análisis son los relacionados con la sintaxis, como asegurarse de que las instrucciones SQL estén formadas correctamente y que las variables se hayan declarado correctamente:

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

obtiene el siguiente error:

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

Y lo siguiente:

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

obtiene el siguiente error:

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

Si el lote se analiza con éxito, se compila, en cuyo momento se verifican los permisos y se realizan otras verificaciones.

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

El SQL anterior está formado correctamente para que el lote se analice correctamente. ¡Ahora intente ejecutar el SQL anterior presionando F5 o Control-E o el ! Botón de ejecución , etc.

Esta vez obtienes el siguiente error:

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

aunque IF (1 = 0)asegura que el código nunca se ejecutará. Esto significa que se encuentra con un error de compilación. Puede sortear estos tipos de errores moviendo el código ofensivo a un subproceso a través de una EXECllamada.

Ejecute lo siguiente y se completará con éxito ya que lo que está dentro del EXEC()archivo no se analiza ni se compila hasta que esa instrucción se ejecuta en tiempo de ejecución.

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

Para resumir:
Primero, considere que sp_MSForEachDBrecorre las bases de datos y ejecuta su SQL pasado después de reemplazarlo ?con el nombre de la base de datos actual. Entonces, cuando el cursor dentro de sp_MSForEachDBllega [tempdb], efectivamente hace lo siguiente:

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, hay varios pasos que SQL Server toma cuando ejecuta un lote de consulta:

  1. Analizar gramaticalmente
  2. Compilar
  3. Ejecución real

Es importante comprender que estos son pasos separados y pueden generar errores antes de proceder al siguiente paso (y, por lo tanto, cancelar el procesamiento adicional antes de pasar al siguiente paso). En el caso aquí, el error ocurre en el Paso 2 - Compilar - como se demostró en el segundo al último ejemplo anterior (el primero para comenzar IF (1 = 0)). Esto IF (1 = 0)evita que el código dentro del BEGIN...ENDbloque se ejecute, pero aún se produce el error. Por lo tanto, el error no ocurre debido a un intento real de ejecutar las dos ALTERdeclaraciones.

La razón por la que funciona el encapsulado de las ALTERdeclaraciones dentro de la EXEC()función es porque SQL Server no analizará ni compilará lo que está dentro de la función EXEC()hasta EXEC()que realmente se esté ejecutando. En ese punto, IF ( (select database_id from sys.databases where name = ''?'') > 4)se permitirá que la instrucción se ejecute, ya que el lote no fallará durante la compilación y llegará a la ejecución, y la IFinstrucción se saltará [tempdb]y la "Opción 'RECUPERACIÓN' no se puede establecer en la base de datos 'tempdb'" error no ocurrirá.

Solomon Rutzky
fuente
Mi pregunta es: ¿por qué la instrucción "if" permite que la ID de tempdb (es decir, 2) se envíe al bloque de instrucciones if si no cumple la condición de ser mayor que 4.
GaganLamba
1
@GaganLamba Entiendo tu pregunta y la respondí muy específicamente. ¿Ejecutó mis ejemplos? Como dije en la respuesta, este es un error en tiempo de compilación, no en tiempo de ejecución. Por lo tanto, ni la IFinstrucción ni ninguna otra instrucción SQL (incluidas las dos ALTERs) se están ejecutando en este momento. La IFdeclaración no permite que la ID de tempdb se envíe a lo que está dentro del bloque BEGIN/ END, y el código ni siquiera ejecuta las ALTERdeclaraciones en este momento. El servidor SQL arroja el error al validar el SQL antes de ejecutarlo. Agregué una sección de resumen al final.
Solomon Rutzky
3

Msg 5058, Nivel 16, Estado 1, Línea 5 La opción 'RECUPERACIÓN' no se puede establecer en la base de datos 'tempdb'.

Hace el trabajo que se supone que debe hacer. Pero no puedo pensar en una razón para el error.

En primer lugar, no hay necesidad de usar ms_foreachdbsu indocumentado y es muy malo que puedas usar un cursor simple. Con respecto al error, está intentando cambiar el modelo de recuperación de todas las bases de datos, including tempdbpero no puede cambiar el modelo de recuperación de tempdb ni puede realizar ninguna operación de copia de seguridad en él, es por eso que recibió este mensaje de error. Esto no está permitido por Microsoft. Lea más sobre las operaciones que puede hacer en tempdb

Shanky
fuente
1
Entiendo que ms_foreachdb no está documentado y no debería usarlo. También entiendo que se supone que tempdb no debe tener una copia de seguridad o cambiar la opción de recuperación. Pero mi pregunta sigue sin respuesta en cuanto a por qué el servidor SQL está tratando de cambiar la opción para TEMPDB. El ID de TEMPDB, que es 2, ni siquiera fue devuelto.
GaganLamba
1
@GaganLamba SQL Server en realidad no está intentando cambiar la opción para TEMPDB. Las ALTERdeclaraciones simplemente se validan , no se ejecutan. Proporciono detalles en mi respuesta .
Solomon Rutzky
3

Además del hecho de que no puede cambiar la opción de recuperación para tempdb, no necesita un bucle para lo que está haciendo:

Ejecute en SSMS presionando 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
fuente