Script para eliminar todas las conexiones a una base de datos (más de RESTRICTED_USER ROLLBACK)

239

Tengo una base de datos de desarrollo que se vuelve a implementar con frecuencia desde un proyecto de base de datos de Visual Studio (a través de una compilación automática TFS).

A veces, cuando ejecuto mi compilación, aparece este error:

ALTER DATABASE failed because a lock could not be placed on database 'MyDB'. Try again later.  
ALTER DATABASE statement failed.  
Cannot drop database "MyDB" because it is currently in use.  

Intenté esto:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

pero todavía no puedo soltar la base de datos. (Supongo que la mayoría de los desarrolladores tienen dboacceso).

Puedo ejecutar manualmente SP_WHOy comenzar a eliminar conexiones, pero necesito una forma automática de hacerlo en la compilación automática. (Aunque esta vez mi conexión es la única en la base de datos que estoy tratando de eliminar).

¿Existe un script que pueda eliminar mi base de datos independientemente de quién esté conectado?

Vaccano
fuente

Respuestas:

642

Actualizado

Para MS SQL Server 2012 y superior

USE [master];

DECLARE @kill varchar(8000) = '';  
SELECT @kill = @kill + 'kill ' + CONVERT(varchar(5), session_id) + ';'  
FROM sys.dm_exec_sessions
WHERE database_id  = db_id('MyDB')

EXEC(@kill);

Para MS SQL Server 2000, 2005, 2008

USE master;

DECLARE @kill varchar(8000); SET @kill = '';  
SELECT @kill = @kill + 'kill ' + CONVERT(varchar(5), spid) + ';'  
FROM master..sysprocesses  
WHERE dbid = db_id('MyDB')

EXEC(@kill); 
AlexK
fuente
25
Esta es la mejor respuesta de las dos; evita desconectar la base de datos y la respuesta aceptada no siempre funciona (a veces no puede revertir todo).
Mark Henderson
3
Es una buena respuesta para agregar killdeclaraciones todas juntas. Usaría un cursor para matar cada proceso, que por supuesto no es eficiente en absoluto. La técnica utilizada en esta respuesta es brillante.
Saeed Neamati
Estoy de acuerdo con Mark Este método debería ser la respuesta aceptada, ya que es significativamente más elegante y menos impactante para las bases de datos.
Austin S.
3
bueno y rápido. El único problema podría ser el spid del sistema, por lo que puede agregar WHERE dbid = db_id ('My_db') y spid> 50
Saurabh Sinha
1
@FrenkyB Debe cambiar el contexto de la base de datos antes de ejecutar el script. Por ejemplo:USE [Master]
AlexK
133
USE master
GO
ALTER DATABASE database_name
SET OFFLINE WITH ROLLBACK IMMEDIATE
GO

Ref: http://msdn.microsoft.com/en-us/library/bb522682%28v=sql.105%29.aspx

Cadenas
fuente
99
Por extraño que parezca, USE masteresa era la clave. Estaba tratando de soltar el db mientras estaba conectado a él (¡Duh!). ¡Gracias!
Vaccano
99
Si lo usa SET OFFLINE, debe eliminar manualmente los archivos db.
mattalxndr
55
¿No alter database YourDatabaseName set SINGLE_USER with rollback immediatesería mejor? Si lo establece en OFFLINE(como dice @mattalxndr) los archivos se dejarán en el disco, pero con SINGLE_USERsu conexión se dejará como el único y drop database YourDatabaseNameaún se eliminarán los archivos.
Keith
1
@Keith en el script, no está conectado a la base de datos, por lo que no es "su conexión", sino alguna otra que quedará. Inmediatamente después del set offline, puede emitir set onlinepara evitar el problema de los archivos sobrantes (sí, existe la posibilidad de una condición de carrera).
ivan_pozdeev
2
¡Gracias! No me di cuenta de que alguna pestaña con la instrucción sql en SQL Management Studio, ejecutada anteriormente en esta base de datos, estaba causando que mi base de datos se informara en uso. Usa master, y listo, ¡todo funcionó!
eythort
26

Puede obtener el script que SSMS proporciona haciendo lo siguiente:

  1. Haga clic derecho en una base de datos en SSMS y elija eliminar
  2. En el cuadro de diálogo, marque la casilla de verificación "Cerrar conexiones existentes".
  3. Haga clic en el botón Script en la parte superior del cuadro de diálogo.

El guión se verá más o menos así:

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [YourDatabaseName]
GO
Pourya
fuente
3
No recomendaré tomar databse en modo de usuario único para ninguno de los usuarios, ya que esto puede causar que pierda la conexión actual con algún usuario de la aplicación y problemas innecesarios para encontrar a esos usuarios y matarlos, o algunas veces debe reiniciar el servidor sql si las conexiones a db son tan frecuente
Saurabh Sinha
7

Poco conocido: la instrucción GO sql puede tomar un número entero para la cantidad de veces que se repite el comando anterior.

Así que si usted:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Luego:

USE [DATABASENAME]
GO 2000

Esto repetirá el comando USE 2000 veces, forzará un punto muerto en todas las demás conexiones y tomará posesión de la conexión única. (Otorgando a su ventana de consulta acceso exclusivo para hacer lo que desee).

Sodoshi
fuente
2
GO no es un comando TSQL, sino un comando especial que solo son reconocidos por las utilidades sqlcmd y osql y SSMS.
dados
4

Según mi experiencia, el uso de SINGLE_USER ayuda la mayoría de las veces, sin embargo, uno debe tener cuidado: he experimentado ocasiones en las que entre el momento en que inicio el comando SINGLE_USER y el momento en que finaliza ... aparentemente, otro 'usuario' había recibido el Acceso SINGLE_USER, no yo. Si eso sucede, te espera un trabajo difícil tratando de recuperar el acceso a la base de datos (en mi caso, se trataba de un servicio específico que se ejecutaba para un software con bases de datos SQL que se apoderó del acceso SINGLE_USER antes que yo). Lo que creo que debería ser la forma más confiable (no puedo responder por ello, pero es lo que probaré en los próximos días), es en realidad:
- detener los servicios que pueden interferir con su acceso (si hay alguno)
- use el script 'kill' anterior para cerrar todas las conexiones
- establezca la base de datos en single_user inmediatamente después de eso
- luego realice la restauración

Sacha
fuente
Si el comando SINGLE_USER está en el mismo lote que su comando de restauración (con secuencia de comandos), ¡no está separado por una instrucción GO! - entonces ningún otro proceso puede obtener acceso de usuario único, en mi experiencia. Sin embargo, me atraparon esta noche porque mi trabajo programado todas las noches de configurar un solo usuario; restaurar; configurar un multiusuario explotó. otro proceso tenía acceso exclusivo a mi archivo bak (smh) y, por lo tanto, la restauración falló, seguido por el error SET MULTI_USER ... lo que significa que cuando me llamaron en medio de la noche para limpiar la sangre, alguien más tuvo acceso SINGLE_USER y tuvo que ser asesinado.
Ross Presser
3

El script sumamente eficiente de Matthew actualizado para usar el DMV dm_exec_sessions, reemplazando la tabla del sistema sysprocesses en desuso:

USE [master];
GO

DECLARE @Kill VARCHAR(8000) = '';

SELECT
    @Kill = @Kill + 'kill ' + CONVERT(VARCHAR(5), session_id) + ';'
FROM
    sys.dm_exec_sessions
WHERE
    database_id = DB_ID('<YourDB>');

EXEC sys.sp_executesql @Kill;

Alternativa al uso del ciclo WHILE (si desea procesar cualquier otra operación por ejecución):

USE [master];
GO

DECLARE @DatabaseID SMALLINT = DB_ID(N'<YourDB>');    
DECLARE @SQL NVARCHAR(10);

WHILE EXISTS ( SELECT
                1
               FROM
                sys.dm_exec_sessions
               WHERE
                database_id = @DatabaseID )    
    BEGIN;
        SET @SQL = (
                    SELECT TOP 1
                        N'kill ' + CAST(session_id AS NVARCHAR(5)) + ';'
                    FROM
                        sys.dm_exec_sessions
                    WHERE
                        database_id = @DatabaseID
                   );
        EXEC sys.sp_executesql @SQL;
    END;
Chris Bates
fuente
2

La respuesta aceptada tiene el inconveniente de que no tiene en cuenta que una base de datos puede bloquearse mediante una conexión que está ejecutando una consulta que involucra tablas en una base de datos distinta de la que está conectada.

Este puede ser el caso si la instancia del servidor tiene más de una base de datos y la consulta directa o indirecta (por ejemplo, a través de sinónimos) usa tablas en más de una base de datos, etc.

Por lo tanto, creo que a veces es mejor usar syslockinfo para encontrar las conexiones para matar.

Por lo tanto, mi sugerencia sería utilizar la siguiente variación de la respuesta aceptada de AlexK:

USE [master];

DECLARE @kill varchar(8000) = '';  
SELECT @kill = @kill + 'kill ' + CONVERT(varchar(5), req_spid) + ';'  
FROM master.dbo.syslockinfo
WHERE rsc_type = 2
AND rsc_dbid  = db_id('MyDB')

EXEC(@kill);
Tono suave
fuente
¡ESTA! Aunque yo personalmente uso la sys.dm_tran_lockstabla como syslockinfoestá marcada como obsoleta, también, puede querer excluir su @@ SPID actual por si acaso.
deroby
1

Debe tener cuidado con las excepciones durante los procesos de eliminación. Entonces puedes usar este script:

USE master;
GO
 DECLARE @kill varchar(max) = '';
 SELECT @kill = @kill + 'BEGIN TRY KILL ' + CONVERT(varchar(5), spid) + ';' + ' END TRY BEGIN CATCH END CATCH ;' FROM master..sysprocesses 
EXEC (@kill)
Shahriar Khazaei
fuente
1

@AlexK escribió una gran respuesta . Solo quiero agregar mis dos centavos. El siguiente código se basa completamente en la respuesta de @ AlexK, la diferencia es que puede especificar el usuario y el tiempo desde que se ejecutó el último lote (tenga en cuenta que el código usa sys.dm_exec_sessions en lugar de master..sysprocess):

DECLARE @kill varchar(8000);
set @kill =''
select @kill = @kill + 'kill ' +  CONVERT(varchar(5), session_id) + ';' from sys.dm_exec_sessions 
where login_name = 'usrDBTest'
and datediff(hh,login_time,getdate()) > 1
--and session_id in (311,266)    
exec(@kill)

En este ejemplo, solo se eliminará el proceso del usuario usrDBTest que realizó el último lote hace más de 1 hora.

Cantoni
fuente
1

Puedes usar el cursor así:

USE master
GO

DECLARE @SQL AS VARCHAR(255)
DECLARE @SPID AS SMALLINT
DECLARE @Database AS VARCHAR(500)
SET @Database = 'AdventureWorks2016CTP3'

DECLARE Murderer CURSOR FOR
SELECT spid FROM sys.sysprocesses WHERE DB_NAME(dbid) = @Database

OPEN Murderer

FETCH NEXT FROM Murderer INTO @SPID
WHILE @@FETCH_STATUS = 0

    BEGIN
    SET @SQL = 'Kill ' + CAST(@SPID AS VARCHAR(10)) + ';'
    EXEC (@SQL)
    PRINT  ' Process ' + CAST(@SPID AS VARCHAR(10)) +' has been killed'
    FETCH NEXT FROM Murderer INTO @SPID
    END 

CLOSE Murderer
DEALLOCATE Murderer

Escribí sobre eso en mi blog aquí: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Cursor

Filip Holub
fuente
0
SELECT
    spid,
    sp.[status],
    loginame [Login],
    hostname, 
    blocked BlkBy,
    sd.name DBName, 
    cmd Command,
    cpu CPUTime,
    memusage Memory,
    physical_io DiskIO,
    lastwaittype LastWaitType,
    [program_name] ProgramName,
    last_batch LastBatch,
    login_time LoginTime,
    'kill ' + CAST(spid as varchar(10)) as 'Kill Command'
FROM master.dbo.sysprocesses sp 
JOIN master.dbo.sysdatabases sd ON sp.dbid = sd.dbid
WHERE sd.name NOT IN ('master', 'model', 'msdb') 
--AND sd.name = 'db_name' 
--AND hostname like 'hostname1%' 
--AND loginame like 'username1%'
ORDER BY spid

/* If a service connects continously. You can automatically execute kill process then run your script:
DECLARE @sqlcommand nvarchar (500)
SELECT @sqlcommand = 'kill ' + CAST(spid as varchar(10))
FROM master.dbo.sysprocesses sp 
JOIN master.dbo.sysdatabases sd ON sp.dbid = sd.dbid
WHERE sd.name NOT IN ('master', 'model', 'msdb') 
--AND sd.name = 'db_name' 
--AND hostname like 'hostname1%' 
--AND loginame like 'username1%'
--SELECT @sqlcommand
EXEC sp_executesql @sqlcommand
*/
Emrah Saglam
fuente
-1

He probado con éxito con el código simple a continuación

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
Binh Truong
fuente
2
Tuve problemas con set SINGLE_USERcuando ya había una sola conexión activa.
ivan_pozdeev