Skript zum Beenden aller Verbindungen zu einer Datenbank (Mehr als RESTRICTED_USER ROLLBACK)

239

Ich habe eine Entwicklungsdatenbank, die häufig aus einem Visual Studio-Datenbankprojekt (über ein TFS Auto Build) erneut bereitgestellt wird.

Manchmal, wenn ich meinen Build ausführe, wird folgende Fehlermeldung angezeigt:

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.  

Ich habe es versucht:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

aber ich kann die Datenbank immer noch nicht löschen. (Ich vermute, dass die meisten Entwickler dboZugriff haben.)

Ich kann Verbindungen manuell ausführen SP_WHOund beenden, aber ich benötige eine automatische Methode, um dies im automatischen Build zu tun. (Obwohl diesmal meine Verbindung die einzige auf der Datenbank ist, die ich zu trennen versuche.)

Gibt es ein Skript, mit dem meine Datenbank gelöscht werden kann, unabhängig davon, wer verbunden ist?

Vaccano
quelle

Antworten:

639

Aktualisiert

Für MS SQL Server 2012 und höher

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

Für 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
quelle
25
Dies ist die bessere Antwort der beiden; vermeidet, die Datenbank offline zu schalten, und die akzeptierte Antwort funktioniert nicht immer (manchmal kann nicht alles zurückgesetzt werden).
Mark Henderson
3
Eine so schöne Antwort, um killAussagen zusammenzufassen. Ich würde einen Cursor verwenden, um jeden Prozess abzubrechen, was natürlich überhaupt nicht effizient ist. Die in dieser Antwort verwendete Technik ist brillant.
Saeed Neamati
Ich stimme Mark zu. Diese Methode sollte die akzeptierte Antwort sein, da sie wesentlich eleganter und für die Datenbanken weniger wirkungsvoll ist.
Austin S.
3
gut und schnell. Das einzige Problem könnte sein, dass das System spid, also können Sie WHERE dbid = db_id ('My_db') und spid> 50
Saurabh Sinha
1
@FrenkyB Sie müssen den Datenbankkontext ändern, bevor Sie das Skript ausführen. Zum Beispiel: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

Ketten
quelle
9
Seltsamerweise war USE masterdas der Schlüssel. Ich habe versucht, die Datenbank zu löschen, während ich mit ihr verbunden war (Duh!). Vielen Dank!
Vaccano
9
Wenn Sie verwenden SET OFFLINE, müssen Sie die Datenbankdateien manuell löschen.
Mattalxndr
5
Wäre nicht alter database YourDatabaseName set SINGLE_USER with rollback immediatebesser? Wenn Sie es auf OFFLINE(wie @mattalxndr angibt) setzen, bleiben die Dateien auf der Festplatte, aber SINGLE_USERIhre Verbindung bleibt die einzige und drop database YourDatabaseNamedie Dateien werden trotzdem entfernt.
Keith
1
@Keith im Skript sind Sie nicht mit der Datenbank verbunden, daher bleibt nicht "Ihre Verbindung", sondern eine andere übrig. Unmittelbar nach dem set offlinekönnen Sie set onlineProbleme verursachen, um das Problem mit den verbleibenden Dateien zu vermeiden (ja, es besteht die Möglichkeit einer Racebedingung).
ivan_pozdeev
2
Vielen Dank! Ich habe nicht bemerkt, dass eine Registerkarte mit SQL-Anweisung in SQL Management Studio, die zuvor in dieser Datenbank ausgeführt wurde, dazu führte, dass meine Datenbank als verwendet gemeldet wurde. Benutze den Meister und lass alles funktionieren!
Eythort
26

Sie können das von SSMS bereitgestellte Skript wie folgt abrufen:

  1. Klicken Sie mit der rechten Maustaste auf eine Datenbank in SSMS und wählen Sie Löschen
  2. Aktivieren Sie im Dialogfeld das Kontrollkästchen "Vorhandene Verbindungen schließen".
  3. Klicken Sie oben im Dialogfeld auf die Schaltfläche Skript.

Das Skript sieht ungefähr so ​​aus:

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [YourDatabaseName]
GO
Pourya
quelle
3
Ich werde nicht empfehlen, die Datenbank im Einzelbenutzermodus für einen der Benutzer zu verwenden, da dies dazu führen kann, dass Sie die aktuelle Verbindung zu einem Anwendungsbenutzer verlieren und unnötige Probleme haben, diese Benutzer zu finden und zu beenden, oder manchmal müssen Sie den SQL Server neu starten, wenn Verbindungen zu db bestehen so häufig.
Saurabh Sinha
7

Wenig bekannt: Die GO-SQL-Anweisung kann eine Ganzzahl für die Häufigkeit verwenden, mit der der vorherige Befehl wiederholt wird.

Also, wenn Sie:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Dann:

USE [DATABASENAME]
GO 2000

Dadurch wird der USE-Befehl 2000 Mal wiederholt, ein Deadlock für alle anderen Verbindungen erzwungen und der Besitz der einzelnen Verbindung übernommen. (Geben Sie Ihrem Abfragefenster den alleinigen Zugriff, um zu tun, was Sie möchten.)

Sodoshi
quelle
2
GO ist kein TSQL-Befehl, sondern ein spezieller Befehl, der nur von den Dienstprogrammen sqlcmd und osql sowie von SSMS erkannt wird.
Diceless
4

Meiner Erfahrung nach hilft die Verwendung von SINGLE_USER meistens, aber man sollte vorsichtig sein: Ich habe Gelegenheiten erlebt, in denen zwischen dem Start des Befehls SINGLE_USER und dem Zeitpunkt, zu dem er beendet ist ... anscheinend ein anderer 'Benutzer' die erhalten hat SINGLE_USER Zugriff, nicht ich. In diesem Fall haben Sie es schwer, den Zugriff auf die Datenbank wiederherzustellen (in meinem Fall war es ein bestimmter Dienst, der für eine Software mit SQL-Datenbanken ausgeführt wurde, die vor mir den Zugriff auf SINGLE_USER erhalten hat). Was meiner Meinung nach der zuverlässigste Weg sein sollte (kann nicht dafür bürgen, aber ich werde ihn in den kommenden Tagen testen), ist tatsächlich:
- Stoppen Sie Dienste, die Ihren Zugriff beeinträchtigen können (falls vorhanden)
- Verwenden Sie das obige 'kill'-Skript, um alle Verbindungen zu schließen
- Setzen Sie die Datenbank unmittelbar danach auf single_user
- und führen Sie dann die Wiederherstellung durch

Sacha
quelle
Wenn sich der Befehl SINGLE_USER im selben Stapel befindet wie Ihr (skriptbasierter) Wiederherstellungsbefehl - nicht durch eine GO-Anweisung getrennt! - dann kann meiner Erfahrung nach kein anderer Prozess Einzelbenutzerzugriff erhalten. Ich wurde jedoch heute Abend erwischt, weil mein nächtlicher geplanter Job "Set-Single-User", "Restore" und "Set-Multi-User" explodierte. Ein anderer Prozess hatte exklusiven Dateizugriff auf meine Bak-Datei (smh) und daher schlug die Wiederherstellung fehl, gefolgt vom Fehlschlagen von SET MULTI_USER. Das heißt, als ich mitten in der Nacht angerufen wurde, um das Blut zu bereinigen, hatte jemand anderes SINGLE_USER-Zugriff und musste getötet werden.
Ross Presser
3

Matthews äußerst effizientes Skript wurde aktualisiert, um die DMV dm_exec_sessions zu verwenden, und ersetzt die veraltete Systemtabelle sysprocesses:

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;

Alternative Verwendung der WHILE-Schleife (wenn Sie andere Operationen pro Ausführung verarbeiten möchten):

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

Die akzeptierte Antwort hat den Nachteil, dass nicht berücksichtigt wird, dass eine Datenbank durch eine Verbindung gesperrt werden kann, die eine Abfrage ausführt, die Tabellen in einer anderen als der mit ihr verbundenen Datenbank enthält.

Dies kann der Fall sein, wenn die Serverinstanz mehr als eine Datenbank hat und die Abfrage direkt oder indirekt (z. B. über Synonyme) Tabellen in mehr als einer Datenbank usw. verwendet.

Ich finde daher, dass es manchmal besser ist, syslockinfo zu verwenden, um die Verbindungen zu finden, die getötet werden sollen.

Mein Vorschlag wäre daher, die folgende Variante der akzeptierten Antwort von AlexK zu verwenden:

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
quelle
DIES! Obwohl ich die sys.dm_tran_locksTabelle persönlich als syslockinfoveraltet verwende, möchten Sie möglicherweise auch Ihre aktuelle @@ SPID für alle Fälle ausschließen.
Deroby
1

Sie sollten beim Töten auf Ausnahmen achten. Sie können dieses Skript also verwenden:

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
quelle
1

@AlexK hat eine großartige Antwort geschrieben . Ich möchte nur meine zwei Cent hinzufügen. Der folgende Code basiert vollständig auf der Antwort von @ AlexK. Der Unterschied besteht darin, dass Sie den Benutzer und eine Zeit seit der Ausführung des letzten Stapels angeben können (beachten Sie, dass der Code sys.dm_exec_sessions anstelle von master..sysprocess verwendet):

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 diesem Beispiel wird nur der Prozess des Benutzers usrDBTest beendet, bei dem der letzte Stapel vor mehr als einer Stunde ausgeführt wurde.

cantoni
quelle
1

Sie können den Cursor folgendermaßen verwenden :

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

Ich habe darüber in meinem Blog hier geschrieben: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Cursor

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

Ich habe erfolgreich mit einfachem Code unten getestet

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
Binh Truong
quelle
2
Ich hatte Probleme damit, set SINGLE_USERwenn bereits eine einzige aktive Verbindung bestand.
ivan_pozdeev