Скрипт для уничтожения всех соединений с базой данных (больше чем RESTRICTED_USER ROLLBACK)


239

У меня есть база данных для разработки, которая часто переустанавливается из проекта базы данных Visual Studio (через TFS Auto Build).

Иногда, когда я запускаю свою сборку, я получаю эту ошибку:

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.  

Я попробовал это:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

но я все еще не могу удалить базу данных. (Я предполагаю, что большинство разработчиков имеют dboдоступ.)

Я могу вручную запускать SP_WHOи запускать уничтожение соединений, но мне нужен автоматический способ сделать это в автоматической сборке. (Хотя на этот раз мое соединение - единственное в БД, которое я пытаюсь разорвать.)

Есть ли сценарий, который может отбросить мою базу данных независимо от того, кто подключен?

Ответы:


642

обновленный

Для MS SQL Server 2012 и выше

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

Для 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
Это лучший ответ из двух; избегает отключения базы данных, а принятый ответ не всегда работает (иногда он не может откатить все назад).
Марк Хендерсон

3
Такой хороший ответ для агрегирования killзаявлений всех вместе. Я бы использовал курсор для уничтожения каждого процесса, что, конечно, неэффективно. Техника, использованная в этом ответе, блестящая.
Саид Нямати

Я согласен с Марком. Этот метод должен быть принятым ответом, поскольку он значительно более элегантен и менее эффективен для баз данных.
Остин С.

3
хороший и быстрый. Единственной проблемой может быть системный спид, поэтому вы можете добавить ГДЕ dbid = db_id ('My_db') и spid> 50
Саурабх Синха

1
@FrenkyB Вам нужно изменить контекст базы данных перед запуском скрипта. Например:USE [Master]
AlexK

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

Ссылка: http://msdn.microsoft.com/en-us/library/bb522682%28v=sql.105%29.aspx


9
Как ни странно, это USE masterбыл ключ. Я пытался сбросить БД при подключении к нему (Дух!). Спасибо!
Ваккано

9
Если вы используете, SET OFFLINEвы должны вручную удалить файлы БД.
mattalxndr

5
Не alter database YourDatabaseName set SINGLE_USER with rollback immediateбудет лучше? Если вы установите его OFFLINE(как утверждает @mattalxndr), файлы будут оставлены на диске, но с SINGLE_USERвашим подключением останется единственным, и drop database YourDatabaseNameвсе равно удалит файлы.
Кит

1
@ Не забывайте, что в сценарии вы не подключены к БД, поэтому останется не «ваше соединение», а какое-то другое. Сразу после этого set offlineвы можете выполнить set onlineэту проблему, чтобы избежать проблемы с оставшимися файлами (да, существует возможность состояния гонки).
ivan_pozdeev

2
Спасибо! Я не осознавал, что какая-то вкладка с оператором sql в SQL Management Studio, ранее выполненная для этой базы данных, приводила к тому, что мой db был объявлен в использовании. Используй мастер, и иди, все заработало!
Eythort

26

Вы можете получить скрипт, который предоставляет SSMS, выполнив следующие действия:

  1. Щелкните правой кнопкой мыши базу данных в SSMS и выберите «Удалить».
  2. В диалоговом окне установите флажок «Закрыть существующие подключения».
  3. Нажмите кнопку «Сценарий» в верхней части диалогового окна.

Сценарий будет выглядеть примерно так:

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

3
Я не буду рекомендовать использование базы данных в однопользовательском режиме для любого из пользователей, так как это может привести к потере текущего соединения с каким-либо пользователем приложения и ненужным проблемам с поиском этих пользователей и их уничтожению, или иногда вам придется перезапускать сервер SQL, если соединения с БД так часто.
Саурабх Синха

7

Малоизвестно: оператор GO sql может принимать целое число для повторения предыдущей команды.

Так что если вы:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Затем:

USE [DATABASENAME]
GO 2000

Это будет повторять команду USE 2000 раз, вызывать взаимоблокировку всех других соединений и переходить во владение одним соединением. (Предоставление вашему окну запроса единственного доступа к действиям, которые вы хотите.)


2
GO - это не команда TSQL, а специальная команда, распознаваемая только утилитами sqlcmd и osql и SSMS.
без костей

4

По моему опыту, использование SINGLE_USER в большинстве случаев помогает, однако, следует быть осторожным: у меня были случаи, когда между временем, когда я запускаю команду SINGLE_USER, и временем, когда она закончена ... очевидно, другой «пользователь» получил SINGLE_USER доступ, а не я. Если это произойдет, вам придется выполнить сложную работу, пытаясь вернуть доступ к базе данных (в моем случае это была специальная служба, работающая для программного обеспечения с базами данных SQL, которая получила доступ SINGLE_USER раньше, чем я). То, что я думаю, должно быть самым надежным способом (не могу ручаться за это, но это то, что я проверю в ближайшие дни), на самом деле:
- остановите службы, которые могут помешать вашему доступу (если они есть)
- используйте скрипт «kill» выше, чтобы закрыть все соединения
- сразу после этого установите базу данных в single_user
- затем выполните восстановление


Если команда SINGLE_USER находится в том же пакете, что и ваша (сценарий) команда восстановления - не разделяется оператором GO! - тогда, по моему опыту, ни один другой процесс не может получить однопользовательский доступ. Тем не менее, я был пойман сегодня вечером, потому что моя ночная запланированная работа set-single-user, restore, set-multi-user взорвалась. другой процесс имел монопольный доступ к моему bak-файлу (smh), поэтому восстановление не удалось, после чего произошел сбой SET MULTI_USER ... то есть, когда мне позвонили посреди ночи, чтобы очистить кровь, кто-то другой имел доступ SINGLE_USER и должен был быть убит.
Росс Прессер

3

В высшей степени эффективный сценарий Мэтью обновлен для использования DMV dm_exec_sessions, заменив устаревшую системную таблицу 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;

Альтернативно использование цикла WHILE (если вы хотите обрабатывать любые другие операции за выполнение):

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

Принятый ответ имеет недостаток, заключающийся в том, что он не принимает во внимание, что база данных может быть заблокирована соединением, которое выполняет запрос, который включает таблицы в базе данных, отличной от той, к которой подключен.

Это может быть в том случае, если экземпляр сервера имеет более одной базы данных и запрос прямо или косвенно (например, через синонимы) использует таблицы в более чем одной базе данных и т. Д.

Поэтому я считаю, что иногда лучше использовать syslockinfo для поиска соединений, которые нужно уничтожить.

Поэтому я предложил бы использовать приведенный ниже вариант принятого ответа от 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);

ЭТОТ! Хотя я лично использую sys.dm_tran_locksтаблицу, syslockinfoкоторая помечена как устаревшая, вы также можете исключить ваш текущий @@ SPID на всякий случай.
Дероби

1

Вы должны быть осторожны с исключениями во время процессов убийства. Таким образом, вы можете использовать этот скрипт:

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 написал отличный ответ . Я просто хочу добавить свои два цента. Приведенный ниже код полностью основан на ответе @ AlexK, разница в том, что вы можете указать пользователя и время с момента последнего выполнения пакета (обратите внимание, что код использует sys.dm_exec_sessions вместо 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)

В этом примере будет уничтожен только процесс пользователя usrDBTest, последний пакет которого был выполнен более 1 часа назад.


1

Вы можете использовать Курсор так:

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

Я написал об этом в своем блоге здесь: 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

Я успешно протестировал с простым кодом ниже

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

2
У меня были проблемы с тем, set SINGLE_USERкогда уже было единственное активное соединение.
ivan_pozdeev
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.