Script untuk mematikan semua koneksi ke database (Lebih dari RESTRICTED_USER ROLLBACK)

239

Saya memiliki database pengembangan yang sering digunakan kembali dari proyek Database Visual Studio (melalui TFS Auto Build).

Terkadang ketika saya menjalankan build saya, saya mendapatkan kesalahan ini:

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.  

Saya mencoba ini:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

tapi saya masih tidak bisa drop database. (Dugaan saya adalah bahwa sebagian besar pengembang memiliki dboakses.)

Saya dapat secara manual menjalankan SP_WHOdan mulai mematikan koneksi, tetapi saya perlu cara otomatis untuk melakukan ini di auto build. (Meskipun kali ini koneksi saya adalah satu-satunya di db yang saya coba hilangkan.)

Apakah ada skrip yang dapat menjatuhkan basis data saya terlepas dari siapa yang terhubung?

Gunung berapi
sumber

Jawaban:

642

Diperbarui

Untuk MS SQL Server 2012 dan di atasnya

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

Untuk 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
sumber
25
Ini adalah jawaban yang lebih baik dari keduanya; menghindari mengambil offline database dan jawaban yang diterima tidak selalu bekerja (kadang-kadang tidak bisa mengembalikan semuanya).
Mark Henderson
3
Jawaban yang bagus untuk menggabungkan killsemua pernyataan. Saya akan menggunakan kursor untuk membunuh setiap proses, yang tentu saja tidak efisien sama sekali. Teknik yang digunakan dalam jawaban ini sangat brilian.
Saeed Neamati
Saya setuju dengan Mark. Metode ini harus menjadi jawaban yang diterima karena secara signifikan lebih elegan dan tidak terlalu berdampak pada database.
Austin S.
3
bagus dan cepat. Satu-satunya masalah adalah sistem spid sehingga Anda dapat menambahkan WHERE dbid = db_id ('My_db') dan spid> 50
Saurabh Sinha
1
@FrenkyB Anda perlu mengubah konteks basis data sebelum menjalankan skrip. Misalnya: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

Rantai
sumber
9
Anehnya, USE masteritulah kuncinya. Saya mencoba untuk menjatuhkan db saat terhubung ke sana (Duh!). Terima kasih!
Vaccano
9
Jika Anda menggunakan, SET OFFLINEAnda harus menghapus file db secara manual.
mattalxndr
5
Tidak alter database YourDatabaseName set SINGLE_USER with rollback immediateakan lebih baik? Jika Anda mengaturnya ke OFFLINE(sebagai status @mattalxndr) file akan dibiarkan di disk, tetapi dengan SINGLE_USERkoneksi Anda akan dibiarkan sebagai satu-satunya, dan drop database YourDatabaseNamemasih akan menghapus file.
Keith
1
@Keith dalam skrip, Anda tidak terhubung ke DB, jadi itu bukan "koneksi Anda" tetapi beberapa lainnya yang akan ditinggalkan. Segera setelah itu set offline, Anda dapat mengeluarkan set onlineuntuk menghindari masalah file sisa (ya, ada kemungkinan kondisi ras).
ivan_pozdeev
2
Terima kasih! Saya tidak menyadari bahwa beberapa tab dengan pernyataan sql di SQL Management Studio, dieksekusi sebelumnya pada database ini, menyebabkan db saya dilaporkan sedang digunakan. Gunakan master, dan pergi, buat semuanya bekerja!
eythort
26

Anda bisa mendapatkan skrip yang disediakan SSMS dengan melakukan hal berikut:

  1. Klik kanan pada database di SSMS dan pilih delete
  2. Dalam dialog, centang kotak centang untuk "Tutup koneksi yang ada."
  3. Klik tombol Script di bagian atas dialog.

Script akan terlihat seperti ini:

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [YourDatabaseName]
GO
Pourya
sumber
3
Saya tidak akan merekomendasikan mengambil databse dalam mode pengguna tunggal untuk setiap pengguna karena ini dapat menyebabkan Anda kehilangan koneksi saat ini ke beberapa pengguna aplikasi dan masalah yang tidak perlu untuk menemukan pengguna tersebut dan membunuh yang sama atau beberapa kali Anda harus me-restart server sql jika koneksi ke db sedang sangat sering.
Saurabh Sinha
7

Sedikit diketahui: pernyataan GO sql dapat mengambil bilangan bulat untuk berapa kali mengulangi perintah sebelumnya.

Jadi jika kamu:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Kemudian:

USE [DATABASENAME]
GO 2000

Ini akan mengulangi perintah USE 2000 kali, memaksakan kebuntuan pada semua koneksi lain, dan mengambil kepemilikan koneksi tunggal. (Memberi Anda akses tunggal jendela permintaan untuk melakukan yang Anda inginkan.)

Sodoshi
sumber
2
GO bukan perintah TSQL tetapi perintah khusus yang hanya dikenali oleh utilitas sqlcmd dan osql dan SSMS.
dicintai
4

Untuk pengalaman saya, menggunakan SINGLE_USER membantu sebagian besar waktu, bagaimanapun, kita harus berhati-hati: Saya pernah mengalami saat-saat di mana antara saat saya memulai perintah SINGLE_USER dan waktu selesai ... tampaknya 'pengguna' lain telah mendapatkan SINGLE_USER akses, bukan saya. Jika itu terjadi, Anda berada dalam pekerjaan berat yang berusaha mendapatkan akses kembali ke basis data (dalam kasus saya, itu adalah layanan khusus yang berjalan untuk perangkat lunak dengan database SQL yang mendapatkan akses SINGLE_USER sebelum saya melakukannya). Apa yang saya pikir harus menjadi cara yang paling dapat diandalkan (tidak dapat menjaminnya, tetapi ini adalah apa yang akan saya uji di masa mendatang), sebenarnya:
- menghentikan layanan yang dapat mengganggu akses Anda (jika ada)
- gunakan skrip 'bunuh' di atas untuk menutup semua koneksi
- atur database ke single_user segera setelah itu
- lalu lakukan restore

Sacha
sumber
Jika perintah SINGLE_USER berada di batch yang sama dengan perintah restore (scripted) Anda - tidak dipisahkan oleh pernyataan GO! - maka tidak ada proses lain yang dapat mengambil akses pengguna tunggal, menurut pengalaman saya. Namun, saya tertangkap malam ini karena pekerjaan terjadwal malam saya dari pengguna-tunggal; mengembalikan; set-multi-pengguna meledak. proses lain memiliki akses file eksklusif ke file bak saya (smh) dan oleh karena itu pemulihan gagal, diikuti oleh SET MULTI_USER gagal ... artinya ketika saya dipanggil di tengah malam untuk membersihkan darah, orang lain memiliki akses SINGLE_USER dan harus dibunuh.
Ross Presser
3

Skrip Matthew yang sangat efisien diperbarui untuk menggunakan DMV dm_exec_sessions, menggantikan tabel sistem sysprocess yang sudah usang:

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;

Alternatif menggunakan loop WHILE (jika Anda ingin memproses operasi lain per eksekusi):

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
sumber
2

Jawaban yang diterima memiliki kelemahan yang tidak mempertimbangkan bahwa database dapat dikunci oleh koneksi yang mengeksekusi kueri yang melibatkan tabel dalam database selain yang terhubung ke.

Ini dapat menjadi kasus jika instance server memiliki lebih dari satu database dan permintaan secara langsung atau tidak langsung (misalnya melalui sinonim) menggunakan tabel di lebih dari satu database dll.

Karena itu saya menemukan bahwa kadang-kadang lebih baik menggunakan syslockinfo untuk menemukan koneksi untuk mematikan.

Karenanya saran saya adalah menggunakan variasi jawaban yang diterima dari AlexK di bawah ini:

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
sumber
INI! Meskipun saya pribadi menggunakan sys.dm_tran_lockstabel sebagaimana syslockinfoditandai sudah usang, Anda juga mungkin ingin mengecualikan @@ SPID Anda untuk berjaga-jaga.
deroby
1

Anda harus berhati-hati tentang pengecualian selama proses pembunuhan. Jadi, Anda dapat menggunakan skrip ini:

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
sumber
1

@AlexK menulis jawaban yang bagus . Saya hanya ingin menambahkan dua sen saya. Kode di bawah ini sepenuhnya didasarkan pada jawaban @ AlexK, perbedaannya adalah Anda dapat menentukan pengguna dan waktu sejak batch terakhir dieksekusi (perhatikan bahwa kode menggunakan sys.dm_exec_sessions alih-alih 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)

Dalam contoh ini hanya proses pengguna usrDBTest yang batch terakhir dieksekusi lebih dari 1 jam yang lalu akan dibunuh.

kantoni
sumber
1

Anda dapat menggunakan kursor seperti itu:

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

Saya menulis tentang itu di blog saya di sini: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Cursor

Filip Holub
sumber
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
sumber
-1

Saya telah berhasil menguji dengan kode sederhana di bawah ini

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
Binh Truong
sumber
2
Saya memiliki masalah dengan set SINGLE_USERketika sudah ada satu koneksi aktif.
ivan_pozdeev