Tập lệnh hủy tất cả các kết nối đến cơ sở dữ liệu (Hơn RESTRICTED_USER ROLLBACK)


239

Tôi có một cơ sở dữ liệu phát triển triển khai lại thường xuyên từ dự án Cơ sở dữ liệu Visual Studio (thông qua TFS Auto Build).

Đôi khi khi tôi chạy bản dựng, tôi gặp lỗi này:

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.  

Tôi đã thử điều này:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

nhưng tôi vẫn không thể bỏ cơ sở dữ liệu. (Tôi đoán là hầu hết các nhà phát triển đều có dboquyền truy cập.)

Tôi có thể tự chạy SP_WHOvà bắt đầu tắt các kết nối, nhưng tôi cần một cách tự động để thực hiện việc này trong quá trình xây dựng tự động. (Mặc dù lần này kết nối của tôi là người duy nhất trên db tôi đang cố gắng thả.)

Có một kịch bản có thể bỏ cơ sở dữ liệu của tôi bất kể ai được kết nối?

Câu trả lời:


639

Đã cập nhật

Dành cho MS SQL Server 2012 trở lên

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

Đối với 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
Đây là câu trả lời tốt hơn của hai người; tránh lấy cơ sở dữ liệu ngoại tuyến và câu trả lời được chấp nhận không phải lúc nào cũng hoạt động (đôi khi nó không thể khôi phục mọi thứ trở lại).
Đánh dấu Henderson

3
Thật là một câu trả lời hay cho việc tổng hợp killtất cả các câu lại với nhau. Tôi sẽ sử dụng một con trỏ để giết từng tiến trình, tất nhiên là không hiệu quả. Kỹ thuật được sử dụng trong câu trả lời này là tuyệt vời.
Saeed Neamati

Tôi đồng ý với Mark. Phương pháp này nên là câu trả lời được chấp nhận vì nó thanh lịch hơn và ít ảnh hưởng hơn đến cơ sở dữ liệu.
Austin S.

3
tốt một và nhanh chóng. Vấn đề duy nhất có thể là hệ thống spid vì vậy bạn có thể thêm WHERE dbid = db_id ('My_db') và spid> 50
Saurabh Sinha

1
@FrenkyB Bạn cần thay đổi bối cảnh cơ sở dữ liệu trước khi chạy tập lệnh. Ví dụ:USE [Master]
AlexK

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

Tham chiếu: http://msdn.microsoft.com/en-us/l Library / bb522682% 28v = sql.105% 29.aspx


9
Thật kỳ lạ, USE masterđó là chìa khóa. Tôi đã cố gắng bỏ db trong khi kết nối với nó (Duh!). Cảm ơn!
Núi lửa

9
Nếu bạn sử dụng, SET OFFLINEbạn phải xóa thủ công các tệp db.
mattalxndr

5
Sẽ không alter database YourDatabaseName set SINGLE_USER with rollback immediatetốt hơn sao? Nếu bạn đặt nó thành OFFLINE(như trạng thái @mattalxndr), các tệp sẽ được để lại trên đĩa, nhưng với SINGLE_USERkết nối của bạn sẽ chỉ còn lại là duy nhất và drop database YourDatabaseNamesẽ vẫn xóa các tệp.
Keith

1
@Keith trong tập lệnh, bạn không kết nối với DB, vì vậy đó không phải là "kết nối của bạn" mà là một số khác sẽ bị bỏ lại. Ngay sau đó set offline, bạn có thể phát hành set onlineđể tránh sự cố tệp còn sót lại (vâng, có khả năng điều kiện cuộc đua).
ivan_pozdeev

2
Cảm ơn! Tôi đã không nhận ra rằng một số tab có câu lệnh sql trong SQL Management Studio, được thực thi trước đó trên cơ sở dữ liệu này, đã khiến db của tôi được báo cáo khi sử dụng. Sử dụng chủ, và đi, làm cho mọi thứ hoạt động!
Eythort

26

Bạn có thể lấy tập lệnh mà SSMS cung cấp bằng cách thực hiện như sau:

  1. Nhấp chuột phải vào cơ sở dữ liệu trong SSMS và chọn xóa
  2. Trong hộp thoại, chọn hộp kiểm cho "Đóng kết nối hiện có."
  3. Nhấp vào nút Script ở đầu hộp thoại.

Kịch bản sẽ trông giống như thế này:

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

3
Tôi sẽ không khuyên bạn nên sử dụng databse ở chế độ một người dùng cho bất kỳ người dùng nào vì điều này có thể khiến bạn mất kết nối hiện tại với một số người dùng ứng dụng và gặp rắc rối khi tìm những người dùng đó và giết một số lần hoặc bạn phải khởi động lại máy chủ sql nếu kết nối với db quá thường xuyên
Saurabh Sinha

7

Ít ai biết: câu lệnh GO sql có thể lấy một số nguyên cho số lần lặp lại lệnh trước đó.

Vì vậy, nếu bạn:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Sau đó:

USE [DATABASENAME]
GO 2000

Điều này sẽ lặp lại lệnh USE 2000 lần, buộc bế tắc trên tất cả các kết nối khác và sở hữu kết nối duy nhất. (Cung cấp quyền truy cập duy nhất cho cửa sổ truy vấn của bạn để làm như bạn muốn.)


2
GO không phải là một lệnh TSQL mà là một lệnh đặc biệt chỉ được công nhận bởi các tiện ích sqlcmd và osql và SSMS.
vô song

4

Theo kinh nghiệm của tôi, việc sử dụng SINGLE_USER hầu hết mọi lúc, tuy nhiên, người ta nên cẩn thận: Tôi đã trải qua những dịp mà trong thời gian tôi bắt đầu lệnh SINGLE_USER và khi nó kết thúc ... rõ ràng là một 'người dùng' khác đã nhận được SINGLE_USER truy cập, không phải tôi. Nếu điều đó xảy ra, bạn đang trong một công việc khó khăn khi cố gắng lấy lại quyền truy cập vào cơ sở dữ liệu (trong trường hợp của tôi, đó là một dịch vụ cụ thể chạy cho một phần mềm có cơ sở dữ liệu SQL có quyền truy cập SINGLE_USER trước khi tôi làm). Những gì tôi nghĩ nên là cách đáng tin cậy nhất (không thể xác minh cho nó, nhưng nó là những gì tôi sẽ kiểm tra trong những ngày tới), thực sự là:
- Các dịch vụ dừng có thể ảnh hưởng truy cập của bạn (nếu có)
- sử dụng tập lệnh 'kill' ở trên để đóng tất cả các kết nối
- đặt cơ sở dữ liệu thành single_user ngay sau đó
- sau đó thực hiện khôi phục


Nếu lệnh SINGLE_USER nằm trong cùng một đợt với lệnh khôi phục (theo kịch bản) của bạn - không được phân tách bằng câu lệnh GO! - sau đó, không có quá trình nào khác có thể lấy quyền truy cập của một người dùng, theo kinh nghiệm của tôi. Tuy nhiên, tôi đã bị bắt gặp tối nay bởi vì công việc được lên lịch hàng đêm của tôi về người dùng đơn lẻ, khôi phục, thiết lập nhiều người dùng đã nổ tung. một quá trình khác có quyền truy cập tệp độc quyền vào tệp bak của tôi (smh) và do đó quá trình khôi phục không thành công, tiếp theo là SET MULTI_USER không thành công ... có nghĩa là khi tôi được gọi vào giữa đêm để dọn sạch máu, một người khác đã truy cập SINGLE_USER và đã bị giết.
Ross Presser

3

Tập lệnh cực kỳ hiệu quả của Matthew được cập nhật để sử dụng DMV dm_exec_sments, thay thế bảng hệ thống sys Processes không dùng nữa:

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;

Thay thế bằng vòng lặp WHILE (nếu bạn muốn xử lý bất kỳ hoạt động nào khác trên mỗi lần thực hiện):

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

Câu trả lời được chấp nhận có một nhược điểm là nó không xem xét rằng cơ sở dữ liệu có thể bị khóa bởi một kết nối đang thực hiện một truy vấn liên quan đến các bảng trong cơ sở dữ liệu khác với cơ sở dữ liệu được kết nối.

Đây có thể là trường hợp nếu phiên bản máy chủ có nhiều cơ sở dữ liệu và truy vấn trực tiếp hoặc gián tiếp (ví dụ: thông qua từ đồng nghĩa) sử dụng các bảng trong nhiều hơn một cơ sở dữ liệu, v.v.

Do đó, tôi thấy rằng đôi khi tốt hơn là sử dụng syslockinfo để tìm các kết nối để hủy.

Do đó, đề xuất của tôi sẽ là sử dụng biến thể dưới đây của câu trả lời được chấp nhận từ 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);

ĐIỀU NÀY! Mặc dù cá nhân tôi sử dụng sys.dm_tran_locksbảng như đã syslockinfobị đánh dấu lỗi thời, Ngoài ra, bạn có thể muốn loại trừ @@ SPID hiện tại của mình chỉ trong trường hợp.
deroby

1

Bạn nên cẩn thận về các ngoại lệ trong quá trình tiêu diệt. Vì vậy, bạn có thể sử dụng tập lệnh này:

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 đã viết một câu trả lời tuyệt vời . Tôi chỉ muốn thêm hai xu của tôi. Mã dưới đây hoàn toàn dựa trên câu trả lời của @ AlexK, điểm khác biệt là bạn có thể chỉ định người dùng và thời gian kể từ đợt cuối cùng được thực thi (lưu ý rằng mã sử dụng sys.dm_exec_simes thay vì master..sys process):

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)

Trong ví dụ này, chỉ có quá trình người dùng usrDBTest mà đợt cuối cùng được thực hiện hơn 1 giờ trước sẽ bị giết.


1

Bạn có thể sử dụng Con trỏ như thế:

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

Tôi đã viết về điều đó trong blog của tôi ở đây: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Coder


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

Tôi đã thử nghiệm thành công với mã đơn giản dưới đây

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

2
Tôi gặp vấn đề set SINGLE_USERkhi đã có một kết nối hoạt động.
ivan_pozdeev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.