Script para eliminar todas as conexões com um banco de dados (mais de RESTRICTED_USER ROLLBACK)

239

Eu tenho um banco de dados de desenvolvimento que é reimplantado frequentemente de um projeto de banco de dados do Visual Studio (por meio de uma compilação automática do TFS).

Às vezes, quando executo minha compilação, recebo este erro:

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.  

Eu tentei isso:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

mas ainda não consigo descartar o banco de dados. (Meu palpite é que a maioria dos desenvolvedores tem dboacesso.)

Posso executar manualmente SP_WHOe começar a eliminar conexões, mas preciso de uma maneira automática de fazer isso na criação automática. (Embora desta vez minha conexão seja a única no db que estou tentando interromper.)

Existe um script que pode eliminar meu banco de dados, independentemente de quem está conectado?

Vaccano
fonte

Respostas:

642

Atualizada

Para MS SQL Server 2012 e 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
fonte
25
Esta é a melhor resposta dos dois; evita colocar o banco de dados offline e a resposta aceita nem sempre funciona (às vezes, não é possível reverter tudo).
Mark Henderson
3
Uma resposta tão agradável para agregar killtodas as declarações. Eu usaria um cursor para matar cada processo, o que obviamente não é eficiente. A técnica usada nesta resposta é brilhante.
Saeed Neamati
Eu concordo com o Mark. Esse método deve ser a resposta aceita, pois é significativamente mais elegante e menos impactante para os bancos de dados.
Austin S.
3
bom e rápido. O único problema poderia ser SPID sistema para que u pode adicionar ONDE dbid = db_id ( 'my_db') e spid> 50
Saurabh Sinha
1
@FrenkyB Você precisa alterar o contexto do banco de dados antes de executar o script. Por exemplo: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

Correntes
fonte
9
Curiosamente, foi USE masteressa a chave. Eu estava tentando soltar o db enquanto conectado a ele (Duh!). Obrigado!
Vaccano
9
Se você usar, SET OFFLINEprecisará excluir manualmente os arquivos db.
mattalxndr
5
Não alter database YourDatabaseName set SINGLE_USER with rollback immediateseria melhor? Se você defini-lo OFFLINEcomo (como afirma @mattalxndr), os arquivos serão deixados no disco, mas com a SINGLE_USERsua conexão, o único será deixado e drop database YourDatabaseNameos arquivos ainda serão removidos.
12111 Keith
1
@ Keith no script, você não está conectado ao banco de dados, portanto não é "sua conexão", mas outros que serão deixados. Imediatamente após o set offline, você pode emitir set onlinepara evitar o problema de arquivos restantes (sim, há uma possibilidade de condição de corrida).
Ivan_pozdeev 8/08/2014
2
Obrigado! Não percebi que alguma guia com a instrução sql no SQL Management Studio, executada anteriormente neste banco de dados, estava fazendo com que meu banco de dados fosse relatado em uso. Use o mestre e vá, fez tudo funcionar!
eythort
26

Você pode obter o script que o SSMS fornece, fazendo o seguinte:

  1. Clique com o botão direito do mouse em um banco de dados no SSMS e escolha excluir
  2. Na caixa de diálogo, marque a caixa de seleção "Fechar conexões existentes".
  3. Clique no botão Script na parte superior da caixa de diálogo.

O script será mais ou menos assim:

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [YourDatabaseName]
GO
Pourya
fonte
3
Eu não recomendo tomar o banco de dados no modo de usuário único para qualquer um dos usuários, pois isso pode causar a perda da conexão atual com algum usuário do aplicativo e problemas desnecessários para encontrar esses usuários e matar o mesmo ou algumas vezes você precisa reiniciar o sql server se as conexões com o db forem tão frequente
Saurabh Sinha
7

Pouco conhecido: a instrução GO sql pode levar um número inteiro pelo número de vezes para repetir o comando anterior.

Então, se você:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Então:

USE [DATABASENAME]
GO 2000

Isso repetirá o comando USE 2000 vezes, forçará o impasse em todas as outras conexões e assumirá a propriedade da conexão única. (Dando acesso exclusivo à sua janela de consulta para fazer o que você desejar).

Sodoshi
fonte
2
GO não é um comando TSQL, mas um comando especial reconhecido apenas pelos utilitários sqlcmd e osql e SSMS.
Diceless
4

Para minha experiência, o uso de SINGLE_USER ajuda na maioria das vezes, no entanto, é preciso ter cuidado: experimentei ocasiões em que entre o momento em que inicio o comando SINGLE_USER e o tempo em que ele é concluído ... aparentemente outro 'usuário' havia obtido o Acesso SINGLE_USER, não eu. Se isso acontecer, você terá um trabalho difícil tentando recuperar o acesso ao banco de dados (no meu caso, era um serviço específico em execução para um software com bancos de dados SQL que conseguiu o acesso SINGLE_USER antes de mim). O que eu acho que deve ser a maneira mais confiável (não posso garantir isso, mas é o que testarei nos próximos dias): na verdade, é:
- interrompa os serviços que podem interferir no seu acesso (se houver)
- use o script 'kill' acima para fechar todas as conexões
- defina o banco de dados como usuário único imediatamente depois disso
- faça a restauração

Sacha
fonte
Se o comando SINGLE_USER estiver no mesmo lote do seu comando de restauração (com script) - não separado por uma instrução GO! - então nenhum outro processo pode obter acesso de usuário único, na minha experiência. No entanto, fui pego hoje à noite porque meu trabalho noturno agendado de set-single-user; restore; set-multi-user explodiu. outro processo teve acesso exclusivo a meu arquivo bak (smh) e, portanto, a restauração falhou, seguida pela falha de SET MULTI_USER ... ou seja, quando fui chamado no meio da noite para limpar o sangue, alguém tinha acesso SINGLE_USER e teve que ser morto.
Ross Presser
3

O script extremamente eficiente de Matthew atualizado para usar a DMV dm_exec_sessions, substituindo a tabela do sistema sysprocesses descontinuada:

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 usando o loop WHILE (se você quiser processar outras operações por execução):

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
fonte
2

A resposta aceita tem o inconveniente de não levar em consideração que um banco de dados pode ser bloqueado por uma conexão que está executando uma consulta que envolve tabelas em um banco de dados diferente daquele conectado.

Pode ser esse o caso se a instância do servidor tiver mais de um banco de dados e a consulta direta ou indiretamente (por exemplo, por meio de sinônimos) usar tabelas em mais de um banco de dados, etc.

Portanto, acho que às vezes é melhor usar o syslockinfo para encontrar as conexões a serem eliminadas.

Minha sugestão seria, portanto, usar a variação abaixo da resposta aceita 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);
MellowTone
fonte
ISTO! Embora eu pessoalmente use a sys.dm_tran_lockstabela como syslockinfomarcada como obsoleta, você também pode excluir o @@ SPID atual apenas por precaução.
deroby
1

Você deve ter cuidado com exceções durante os processos de extermínio. Então você pode 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
fonte
1

@AlexK escreveu uma ótima resposta . Eu só quero adicionar meus dois centavos. O código abaixo é inteiramente baseado na resposta de @ AlexK, a diferença é que você pode especificar o usuário e um tempo desde que o último lote foi executado (observe que o código usa sys.dm_exec_sessions em vez 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)

Neste exemplo, apenas o processo do usuário usrDBTest cujo último lote foi executado há mais de uma hora será eliminado.

cantoni
fonte
1

Você pode usar o Cursor assim:

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

Eu escrevi sobre isso no meu blog aqui: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Cursor

Filip Holub
fonte
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
fonte
-1

Eu testei com sucesso com o código simples abaixo

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
Binh Truong
fonte
2
Eu tive problemas com set SINGLE_USERquando já havia uma única conexão ativa.
ivan_pozdeev