Skrypt zabijający wszystkie połączenia z bazą danych (Więcej niż RESTRICTED_USER ROLLBACK)

239

Mam programistyczną bazę danych, która często wdraża się ponownie z projektu Visual Studio Database (poprzez TFS Auto Build).

Czasami po uruchomieniu kompilacji pojawia się ten błąd:

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.  

Próbowałem tego:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

ale nadal nie mogę upuścić bazy danych. (Domyślam się, że większość programistów ma dbodostęp.)

Mogę ręcznie uruchomić SP_WHOi zacząć zabijać połączenia, ale potrzebuję automatycznego sposobu, aby to zrobić w automatycznej kompilacji. (Chociaż tym razem moje połączenie jest jedyne w bazie danych, którą próbuję usunąć).

Czy istnieje skrypt, który może upuścić moją bazę danych niezależnie od tego, kto jest podłączony?

Vaccano
źródło

Odpowiedzi:

642

Zaktualizowano

Dla MS SQL Server 2012 i nowszych

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

Dla 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
źródło
25
To lepsza odpowiedź na oba; unika przełączania bazy danych w tryb offline, a zaakceptowana odpowiedź nie zawsze działa (czasami nie jest w stanie przywrócić wszystkiego do tyłu).
Mark Henderson
3
Taka ładna odpowiedź na agregowanie killinstrukcji razem. Użyłbym kursora, aby zabić każdy proces, co oczywiście nie jest wcale wydajne. Technika zastosowana w tej odpowiedzi jest genialna.
Saeed Neamati,
Zgadzam się z Markiem. Ta metoda powinna być przyjętą odpowiedzią, ponieważ jest znacznie bardziej elegancka i ma mniejszy wpływ na bazy danych.
Austin S.
3
dobry i szybki. Jedynym problemem może być spid systemowy, więc możesz dodać GDZIE dbid = db_id ('My_db') i spid> 50
Saurabh Sinha
1
@FrenkyB Przed uruchomieniem skryptu musisz zmienić kontekst bazy danych. Na przykład: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

Więzy
źródło
9
Dziwne, że USE masterto był klucz. Próbowałem usunąć db podczas połączenia z nim (Duh!). Dzięki!
Vaccano
9
Jeśli używasz SET OFFLINE, musisz ręcznie usunąć pliki db.
mattalxndr
5
Nie alter database YourDatabaseName set SINGLE_USER with rollback immediatebyłoby lepiej? Jeśli ustawisz na OFFLINE(jak stwierdza @mattalxndr), pliki pozostaną na dysku, ale przy SINGLE_USERtwoim połączeniu pozostanie jako jedyny i drop database YourDatabaseNamenadal usunie pliki.
Keith,
1
@Keith w skrypcie, nie jesteś podłączony do bazy danych, więc nie będzie to „twoje połączenie”, ale pozostała część. Natychmiast po tym set offlinemożesz wydać, set onlineaby uniknąć problemu z pozostałymi plikami (tak, istnieje możliwość wystąpienia wyścigu).
ivan_pozdeev
2
Dzięki! Nie zdawałem sobie sprawy, że jakaś karta z instrukcją SQL w SQL Management Studio, wykonana wcześniej w tej bazie danych, powodowała, że ​​moja db była zgłaszana w użyciu. Użyj mistrza i idź, wszystko działa!
eythort
26

Możesz uzyskać skrypt dostarczany przez SSMS, wykonując następujące czynności:

  1. Kliknij prawym przyciskiem myszy bazę danych w SSMS i wybierz opcję usuń
  2. W oknie dialogowym zaznacz pole wyboru „Zamknij istniejące połączenia”.
  3. Kliknij przycisk Skrypt u góry okna dialogowego.

Skrypt będzie wyglądał mniej więcej tak:

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [YourDatabaseName]
GO
Pourya
źródło
3
Nie będę zalecał przenoszenia bazy danych w trybie pojedynczego użytkownika dla żadnego z użytkowników, ponieważ może to spowodować utratę bieżącego połączenia z pewnym użytkownikiem aplikacji i niepotrzebne kłopoty ze znalezieniem tych użytkowników i zabicia tego samego lub czasami musisz zrestartować serwer SQL, jeśli połączenia z db są tak często.
Saurabh Sinha,
7

Mało znane: instrukcja GO sql może przyjmować liczbę całkowitą powtarzającą poprzednie polecenie.

Więc jeśli ty:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Następnie:

USE [DATABASENAME]
GO 2000

Spowoduje to powtórzenie polecenia USE 2000 razy, wymuszenie zakleszczenia na wszystkich innych połączeniach i przejęcie na własność pojedynczego połączenia. (Dając dostęp do okna zapytania, możesz robić to, co chcesz.)

Sodoshi
źródło
2
GO nie jest poleceniem TSQL, ale specjalnym poleceniem rozpoznawanym tylko przez narzędzia sqlcmd i osql oraz SSMS.
kości
4

Z mojego doświadczenia wynika, że ​​używanie SINGLE_USER pomaga w większości przypadków, jednak należy zachować ostrożność: zdarzały mi się sytuacje, w których od momentu uruchomienia polecenia SINGLE_USER do momentu jego zakończenia ... najwyraźniej inny „użytkownik” otrzymał Dostęp SINGLE_USER, nie ja. Jeśli tak się stanie, czeka cię ciężka praca, próbująca odzyskać dostęp do bazy danych (w moim przypadku była to konkretna usługa działająca dla oprogramowania z bazami SQL, które wcześniej uzyskało dostęp do SINGLE_USER). To, co moim zdaniem powinno być najbardziej niezawodnym sposobem (nie mogę za to ręczyć, ale to właśnie przetestuję w nadchodzących dniach), to w rzeczywistości:
- zatrzymaj usługi, które mogą zakłócać twój dostęp (jeśli takie istnieją)
- użyj powyższego skryptu „kill”, aby zamknąć wszystkie połączenia
- natychmiast po tym ustaw bazę danych na single_user
- następnie przywróć

Sacha
źródło
Jeśli polecenie SINGLE_USER jest w tej samej partii, co polecenie (skrypty) przywracania - nie rozdzielone instrukcją GO! - z mojego doświadczenia żaden inny proces nie może uzyskać dostępu dla pojedynczego użytkownika. Jednak zostałem dziś przyłapany, ponieważ moja nocna zaplanowana praca set-single-user; restore; set-multi-user wysadziła się w powietrze. inny proces miał wyłączny dostęp do mojego pliku BAK (smh) i dlatego przywrócenie nie powiodło się, a następnie niepowodzenie SET MULTI_USER ... co oznacza, że ​​kiedy w środku nocy zostałem wezwany do oczyszczenia krwi, ktoś inny miał dostęp do SINGLE_USER i musiał zostać zabity.
Ross Presser,
3

Niezwykle wydajny skrypt Matthew został zaktualizowany, aby używać DMV dm_exec_sessions, zastępując przestarzałą tabelę systemową 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;

Alternatywnie przy użyciu pętli WHILE (jeśli chcesz przetwarzać inne operacje na wykonanie):

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
źródło
2

Przyjęta odpowiedź ma tę wadę, że nie bierze pod uwagę, że bazę danych można zablokować przez połączenie, które wykonuje zapytanie obejmujące tabele w bazie danych innej niż ta, z którą jest połączone.

Może się tak zdarzyć, jeśli instancja serwera ma więcej niż jedną bazę danych, a zapytanie bezpośrednio lub pośrednio (na przykład przez synonimy) korzysta z tabel w więcej niż jednej bazie danych itp.

Dlatego uważam, że czasem lepiej jest użyć syslockinfo do znalezienia połączeń do zabicia.

Sugeruję zatem użycie poniższej odmiany zaakceptowanej odpowiedzi od 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
źródło
TO! Chociaż osobiście używam sys.dm_tran_lockstabeli jako syslockinfooznaczonej jako przestarzała, możesz również na wszelki wypadek wykluczyć bieżący identyfikator @@ SPID.
deroby
1

Powinieneś uważać na wyjątki podczas procesów zabijania. Możesz więc użyć tego skryptu:

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
źródło
1

@AlexK napisał świetną odpowiedź . Chcę tylko dodać moje dwa centy. Poniższy kod jest całkowicie oparty na odpowiedzi @ AlexK, różnica polega na tym, że możesz określić użytkownika i czas od ostatniego uruchomienia partii (zwróć uwagę, że kod używa sys.dm_exec_sessions zamiast 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)

W tym przykładzie zabity zostanie tylko proces użytkownika usrDBTest, którego ostatnia partia została wykonana ponad godzinę temu.

Cantoni
źródło
1

Możesz użyć takiego kursora :

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

Napisałem o tym na moim blogu tutaj: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Cursor

Filip Holub
źródło
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
źródło
-1

Testowałem pomyślnie z prostym kodem poniżej

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
Binh Truong
źródło
2
Miałem problemy, set SINGLE_USERkiedy było już jedno aktywne połączenie.
ivan_pozdeev