Сценарій, щоб знищити всі підключення до бази даних (Більше, ніж RESTRICTED_USER ROLLBACK)


239

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

Іноді під час запуску моєї збірки я отримую цю помилку:

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і починати вбивати з'єднання, але мені потрібен автоматичний спосіб зробити це в автоматичній збірці. (Хоча цього разу мій зв’язок є єдиним на db, який я намагаюся перестати.)

Чи існує сценарій, який може видалити мою базу даних незалежно від того, хто підключений?

Відповіді:


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
хороший і швидкий. Єдиною проблемою може бути система spid, тому ви можете додати WHERE dbid = db_id ('My_db') та spid> 50
Saurabh Sinha

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це було ключовим. Я намагався скинути db під час підключення до нього (Duh!). Дякую!
Ваккано

9
Якщо ви користуєтеся, SET OFFLINEвам потрібно вручну видалити файли db.
mattalxndr

5
Не alter database YourDatabaseName set SINGLE_USER with rollback immediateбуло б краще? Якщо встановити його OFFLINE(як заявляє @mattalxndr), файли залишаться на диску, але при цьому SINGLE_USERваше з'єднання залишиться єдиним і drop database YourDatabaseNameвсе одно видалить файли.
Кіт

1
@Keith у скрипті ви не підключені до БД, тому залишиться не "ваше з'єднання", а якесь інше. Відразу після цього set offline, ви можете видати, set onlineщоб уникнути проблеми з файлами, що залишилися (так, є можливість перегонів).
ivan_pozdeev

2
Дякую! Я не усвідомлював, що деяка вкладка з оператором sql в SQL Management Studio, виконана раніше в цій базі даних, викликала повідомлення про використання моєї db. Користуйся майстром і йди, змусивши все працювати!
Ейхорт

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
Я не рекомендую приймати databse в режимі одиночного користувача для будь-якого з користувачів, оскільки це може призвести до втрати поточного з'єднання з деяким користувачем програми та непотрібних проблем з пошуку цих користувачів та вбити того ж або в деяких випадках вам доведеться перезапустити sql-сервер, якщо з'єднання з db є так часто.
Саурах Сінга

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 знаходиться в тій же партії, що і ваша (сценарій) команда відновлення - не відокремлена оператором GO! - тоді, на мій досвід, жоден інший процес не може захопити доступ одного користувача. Однак мене спіймали сьогодні вночі, тому що мій нічний плановий набір одиночного користувача; відновити; багатокористувацький підірвався. інший процес мав ексклюзивний доступ до мого файлу Bak (smh), і тому відновлення не вдалося, після чого не вдалося встановити SET MULTI_USER ... тобто коли мене викликали посеред ночі, щоб очистити кров, хтось ще мав доступ до SINGLE_USER і треба було вбити.
Росс Прессер

3

Надзвичайно ефективний скрипт Метью оновив для використання DMV dm_exec_sesions, замінивши застарілу системну таблицю системних процесів:

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_sesions замість 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.