สคริปต์เพื่อฆ่าการเชื่อมต่อทั้งหมดไปยังฐานข้อมูล (มากกว่า 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และเริ่มต้นการเชื่อมต่อด้วยตนเองได้แต่ฉันต้องการวิธีอัตโนมัติในการสร้างอัตโนมัติ (แม้ว่าครั้งนี้การเชื่อมต่อของฉันจะเป็นเพียงหนึ่งเดียวบนฐานข้อมูลที่ฉันพยายามจะวาง)

มีสคริปต์ที่สามารถลบฐานข้อมูลของฉันได้หรือไม่

คำตอบ:


642

Updated

สำหรับ 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
นี่คือคำตอบที่ดีกว่าของทั้งสอง; หลีกเลี่ยงการออฟไลน์ฐานข้อมูลและคำตอบที่ยอมรับไม่ได้ผลเสมอไป (บางครั้งก็ไม่สามารถย้อนกลับได้ทุกอย่าง)
Mark Henderson

3
ช่างเป็นคำตอบที่ดีสำหรับการรวบรวมkillข้อความทั้งหมดเข้าด้วยกัน ฉันจะใช้เคอร์เซอร์เพื่อฆ่าแต่ละกระบวนการซึ่งแน่นอนว่าไม่มีประสิทธิภาพเลย เทคนิคที่ใช้ในคำตอบนี้ยอดเยี่ยม
Saeed Neamati

ฉันเห็นด้วยกับมาร์ค วิธีนี้ควรเป็นคำตอบที่ได้รับการยอมรับเนื่องจากมีความสวยงามและมีผลกระทบต่อฐานข้อมูลน้อยกว่า
Austin S.

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

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


9
ผิดปกติพอมันเป็นUSE masterกุญแจสำคัญ ฉันพยายามที่จะวางฐานข้อมูลในขณะที่เชื่อมต่อกับมัน (Duh!) ขอบคุณ!
Vaccano

9
หากคุณใช้SET OFFLINEคุณต้องลบไฟล์ db ด้วยตนเอง
mattalxndr

5
จะalter database YourDatabaseName set SINGLE_USER with rollback immediateดีกว่าไหม หากคุณตั้งค่าเป็นOFFLINE(เป็น @mattalxndr ฯ ) ไฟล์จะถูกทิ้งไว้ในดิสก์ แต่ด้วยSINGLE_USERการเชื่อมต่อของคุณจะถูกทิ้งเป็นไฟล์เดียวและdrop database YourDatabaseNameจะยังคงลบไฟล์
Keith

1
@Keith ในสคริปต์คุณไม่ได้เชื่อมต่อกับ DB ดังนั้นจึงไม่ใช่ "การเชื่อมต่อของคุณ" แต่มีบางอย่างที่จะถูกทิ้งไว้ ทันทีหลังจากset offlineนั้นคุณสามารถออกset onlineเพื่อหลีกเลี่ยงปัญหาไฟล์ที่เหลือ (ใช่มีความเป็นไปได้ของสภาพการแข่งขัน)
ivan_pozdeev

2
ขอบคุณ! ฉันไม่ทราบว่าบางแท็บที่มีคำสั่ง sql ใน SQL Management Studio ดำเนินการก่อนหน้านี้ในฐานข้อมูลนี้ทำให้ฐานข้อมูลของฉันถูกใช้งาน ใช้หลักและไปทำให้ทุกอย่างทำงาน!
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
ฉันจะไม่แนะนำให้ใช้ databse ในโหมดผู้ใช้คนเดียวสำหรับผู้ใช้ใด ๆ เนื่องจากอาจทำให้คุณขาดการเชื่อมต่อกับผู้ใช้แอพพลิเคชั่นบางคนและปัญหาที่ไม่จำเป็นในการค้นหาผู้ใช้และฆ่าเหมือนกันหรือบางครั้ง บ่อยมาก
Saurabh Sinha

7

ไม่ค่อยมีใครรู้จัก: คำสั่ง GO sql สามารถใช้จำนวนเต็มสำหรับจำนวนครั้งในการทำซ้ำคำสั่งก่อนหน้า

ดังนั้นถ้าคุณ:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

แล้ว:

USE [DATABASENAME]
GO 2000

สิ่งนี้จะทำซ้ำคำสั่ง USE 2000 ครั้งบังคับให้หยุดชะงักในการเชื่อมต่ออื่น ๆ ทั้งหมดและเป็นเจ้าของการเชื่อมต่อเดียว (ให้หน้าต่างแบบสอบถามของคุณเข้าถึงได้อย่างเดียวตามที่คุณต้องการ)


2
GO ไม่ใช่คำสั่ง TSQL แต่เป็นคำสั่งพิเศษที่รู้จักโดยยูทิลิตี้ sqlcmd และ osql และ SSMS เท่านั้น
diceless

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 และจะต้องถูกฆ่า
Ross Presser

3

สคริปต์ที่มีประสิทธิภาพสูงสุดของ Matthew ได้รับการอัพเดตเพื่อใช้ dm_exec_sessions DMV แทนที่ตารางระบบ 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 ปัจจุบันของคุณในกรณีที่
Deroby

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

คุณสามารถใช้Cursorเช่นนั้น:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.