Script per interrompere tutte le connessioni a un database (oltre RESTRICTED_USER ROLLBACK)


239

Ho un database di sviluppo che si ri-distribuisce frequentemente da un progetto di database di Visual Studio (tramite un build automatico TFS).

A volte quando eseguo la mia build ottengo questo errore:

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.  

Ho provato questo:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

ma non riesco ancora a eliminare il database. (La mia ipotesi è che la maggior parte degli sviluppatori abbia dboaccesso.)

Posso eseguire manualmente SP_WHOe iniziare a uccidere le connessioni, ma ho bisogno di un modo automatico per farlo nella compilazione automatica. (Anche se questa volta la mia connessione è l'unica sul db che sto cercando di abbandonare.)

Esiste uno script che può eliminare il mio database indipendentemente da chi è connesso?

Risposte:


642

aggiornato

Per MS SQL Server 2012 e versioni successive

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

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

25
Questa è la risposta migliore delle due; evita di mettere offline il database e la risposta accettata non funziona sempre (a volte non può ripristinare tutto).
Mark Henderson,

3
Che bella risposta per aggregare le killdichiarazioni tutti insieme. Vorrei usare un cursore per uccidere ogni processo, che ovviamente non è affatto efficiente. La tecnica usata in questa risposta è geniale.
Saeed Neamati

Sono d'accordo con Mark. Questo metodo dovrebbe essere la risposta accettata in quanto è significativamente più elegante e meno impattante per i database.
Austin S.

3
buono e veloce. L'unico problema potrebbe essere lo spid di sistema in modo da poter aggiungere WHERE dbid = db_id ('My_db') e spid> 50
Saurabh Sinha

1
@FrenkyB È necessario modificare il contesto del database prima di eseguire lo script. Per es .:USE [Master]
AlexK,

133
USE master
GO
ALTER DATABASE database_name
SET OFFLINE WITH ROLLBACK IMMEDIATE
GO

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


9
Stranamente, USE masterquesta era la chiave. Stavo cercando di rilasciare il db mentre era collegato ad esso (Duh!). Grazie!
Vaccano,

9
Se lo usi SET OFFLINEdevi eliminare manualmente i file db.
mattalxndr,

5
Non alter database YourDatabaseName set SINGLE_USER with rollback immediatesarebbe meglio? Se lo si imposta su OFFLINE(come afferma @mattalxndr) i file verranno lasciati sul disco, ma con SINGLE_USERla connessione verrà lasciato come unico e drop database YourDatabaseNamerimuoveranno comunque i file.
Keith,

1
@Keith nello script, non sei connesso al DB, quindi non sarà "la tua connessione" ma ne rimarrà qualche altra. Immediatamente dopo il set offline, è possibile emettere set onlineper evitare il problema dei file rimanenti (sì, c'è una possibilità di condizioni di gara).
ivan_pozdeev

2
Grazie! Non mi rendevo conto che alcune schede con istruzione sql in SQL Management Studio, eseguite in precedenza su questo database, causavano il mio db da segnalare in uso. Usa master e vai, ha fatto funzionare tutto!
eythort,

26

È possibile ottenere lo script fornito da SSMS nel modo seguente:

  1. Fare clic con il tasto destro del mouse su un database in SSMS e selezionare Elimina
  2. Nella finestra di dialogo, seleziona la casella di controllo "Chiudi connessioni esistenti".
  3. Fai clic sul pulsante Script nella parte superiore della finestra di dialogo.

La sceneggiatura avrà un aspetto simile al seguente:

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [YourDatabaseName]
GO

3
Non consiglierò di prendere databse in modalità utente singolo per nessuno degli utenti in quanto ciò può causare la perdita della connessione corrente a qualche utente dell'applicazione e problemi inutili per trovare quegli utenti e uccidere lo stesso o alcune volte è necessario riavviare il server SQL se le connessioni a db sono così frequente.
Saurabh Sinha,

7

Poco noto: l'istruzione sql GO può richiedere un numero intero per il numero di volte in cui ripetere il comando precedente.

Quindi se tu:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Poi:

USE [DATABASENAME]
GO 2000

Ciò ripeterà il comando USE 2000 volte, imporrà il deadlock su tutte le altre connessioni e diventerà proprietario della singola connessione. (Concedi alla tua finestra di query l'accesso esclusivo per fare ciò che desideri.)


2
GO non è un comando TSQL ma un comando speciale riconosciuto solo dalle utility sqlcmd e osql e SSMS.
diceless

4

Per la mia esperienza, l'uso di SINGLE_USER aiuta la maggior parte delle volte, tuttavia, bisogna fare attenzione: ho avuto occasioni in cui tra il momento in cui ho avviato il comando SINGLE_USER e il tempo in cui è finito ... apparentemente un altro 'utente' aveva ottenuto il Accesso SINGLE_USER, non io. Se ciò accade, stai cercando un duro lavoro cercando di riavere l'accesso al database (nel mio caso, era un servizio specifico in esecuzione per un software con database SQL che ha ottenuto l'accesso SINGLE_USER prima di me). Quello che penso dovrebbe essere il modo più affidabile (non posso garantirlo, ma è quello che testerò nei prossimi giorni), è in realtà:
- interrompere i servizi che potrebbero interferire con il tuo accesso (se ce ne sono)
- usa lo script 'kill' sopra per chiudere tutte le connessioni
- imposta subito il database su single_user
- quindi esegui il ripristino


Se il comando SINGLE_USER si trova nello stesso batch del comando di ripristino (con script), non separato da un'istruzione GO! - quindi nessun altro processo può ottenere l'accesso di un singolo utente, secondo la mia esperienza. Tuttavia, sono stato sorpreso stasera perché il mio lavoro notturno programmato di set-single-user; restore; set-multi-user è saltato in aria. un altro processo ha avuto accesso esclusivo al mio file bak (smh) e quindi il ripristino non è riuscito, seguito dal SET MULTI_USER fallito ... il che significa che quando sono stato chiamato nel cuore della notte per ripulire il sangue, qualcun altro aveva accesso SINGLE_USER e doveva essere ucciso.
Ross Presser,

3

Lo script estremamente efficiente di Matthew aggiornato per utilizzare il DMV dm_exec_sessions, in sostituzione della tabella di sistema sysprocesses obsoleta:

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 utilizzando il ciclo WHILE (se si desidera elaborare altre operazioni per esecuzione):

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;

2

La risposta accettata ha lo svantaggio di non considerare che un database può essere bloccato da una connessione che sta eseguendo una query che coinvolge tabelle in un database diverso da quello a cui è connessa.

Questo può essere il caso se l'istanza del server ha più di un database e la query direttamente o indirettamente (ad esempio tramite sinonimi) utilizza le tabelle in più di un database, ecc.

Trovo quindi che a volte è meglio usare syslockinfo per trovare le connessioni da uccidere.

Il mio suggerimento sarebbe quindi di utilizzare la seguente variazione della risposta accettata da 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);

QUESTO! Anche se uso personalmente la sys.dm_tran_lockstabella come syslockinfoè contrassegnata come obsoleta, Inoltre, potresti voler escludere l'attuale @@ SPID nel caso.
derby il

1

Dovresti stare attento alle eccezioni durante i processi di uccisione. Quindi puoi usare questo 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)

1

@AlexK ha scritto un'ottima risposta . Voglio solo aggiungere i miei due centesimi. Il codice qui sotto è interamente basato sulla risposta di @ AlexK, la differenza è che puoi specificare l'utente e un tempo dall'esecuzione dell'ultimo batch (nota che il codice utilizza sys.dm_exec_sessions invece di 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)

In questo esempio verrà interrotto solo il processo dell'utente usrDBTest che l'ultimo batch è stato eseguito più di 1 ora fa.


1

Puoi usare Cursor in questo modo:

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

Ne ho scritto nel mio blog qui: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Cursor


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
*/

-1

Ho testato con successo con un semplice codice di seguito

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO

2
Ho avuto problemi con set SINGLE_USERquando c'era già una singola connessione attiva.
ivan_pozdeev
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.