Script pour tuer toutes les connexions à une base de données (plus de RESTRICTED_USER ROLLBACK)

239

J'ai une base de données de développement qui se redéploie fréquemment à partir d'un projet de base de données Visual Studio (via une construction automatique TFS).

Parfois, lorsque j'exécute ma génération, j'obtiens cette erreur:

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.  

J'ai essayé ceci:

ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK IMMEDIATE

mais je ne peux toujours pas supprimer la base de données. (Je suppose que la plupart des développeurs ontdbo accès.)

Je peux exécuter manuellement SP_WHO et commencer à tuer les connexions, mais j'ai besoin d'un moyen automatique de le faire dans la construction automatique. (Bien que cette fois, ma connexion soit la seule sur la base de données que j'essaie de supprimer.)

Existe-t-il un script qui peut supprimer ma base de données, peu importe qui est connecté?

Vaccano
la source

Réponses:

642

Actualisé

Pour MS SQL Server 2012 et supérieur

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

Pour 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
la source
25
C'est la meilleure réponse des deux; évite de mettre la base de données hors ligne et la réponse acceptée ne fonctionne pas toujours (parfois, elle ne peut pas tout restaurer).
Mark Henderson
3
Une si belle réponse pour agréger des killdéclarations tous ensemble. J'utiliserais un curseur pour tuer chaque processus, ce qui bien sûr n'est pas efficace du tout. La technique utilisée dans cette réponse est brillante.
Saeed Neamati
Je suis d'accord avec Mark. Cette méthode devrait être la réponse acceptée car elle est nettement plus élégante et moins impactante pour les bases de données.
Austin S.
3
bon et rapide. Le seul problème pourrait être le système spid afin que vous puissiez ajouter WHERE dbid = db_id ('My_db') et spid> 50
Saurabh Sinha
1
@FrenkyB Vous devez modifier le contexte de la base de données avant d'exécuter le script. Par exemple:USE [Master]
AlexK
133
USE master
GO
ALTER DATABASE database_name
SET OFFLINE WITH ROLLBACK IMMEDIATE
GO

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

Chaînes
la source
9
Curieusement, c'était USE masterla clé. J'essayais de supprimer la base de données tout en y étant connecté (Duh!). Merci!
Vaccano
9
Si vous utilisez, SET OFFLINEvous devez supprimer manuellement les fichiers db.
mattalxndr
5
Ce ne serait pas alter database YourDatabaseName set SINGLE_USER with rollback immediatemieux? Si vous le définissez sur OFFLINE(comme l'indique @mattalxndr), les fichiers seront laissés sur le disque, mais avec SINGLE_USERvotre connexion restera le seul, et drop database YourDatabaseNamesupprimera toujours les fichiers.
Keith
1
@Keith dans le script, vous n'êtes pas connecté à la base de données, donc ce n'est pas "votre connexion" mais une autre qui restera. Immédiatement après le set offline, vous pouvez émettre set onlinepour éviter le problème des fichiers restants (oui, il existe une possibilité de condition de concurrence).
ivan_pozdeev
2
Merci! Je ne savais pas qu'un onglet avec une instruction sql dans SQL Management Studio, exécuté plus tôt sur cette base de données, faisait que ma base de données était signalée en cours d'utilisation. Utilisez master et c'est parti, tout a fonctionné!
eythort
26

Vous pouvez obtenir le script fourni par SSMS en procédant comme suit:

  1. Faites un clic droit sur une base de données dans SSMS et choisissez supprimer
  2. Dans la boîte de dialogue, cochez la case «Fermer les connexions existantes».
  3. Cliquez sur le bouton Script en haut de la boîte de dialogue.

Le script ressemblera à ceci:

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET  SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [YourDatabaseName]
GO
Pourya
la source
3
Je ne recommanderai pas de prendre la base de données en mode mono-utilisateur pour n'importe lequel des utilisateurs car cela peut vous faire perdre la connexion actuelle à un utilisateur d'application et des problèmes non nécessaires pour trouver ces utilisateurs et les tuer ou parfois vous devez redémarrer le serveur sql si les connexions à db sont si fréquent.
Saurabh Sinha
7

Peu connu: l'instruction GO sql peut prendre un nombre entier pour répéter la commande précédente.

Donc si vous:

ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO

Ensuite:

USE [DATABASENAME]
GO 2000

Cela répétera la commande USE 2000 fois, forcera le blocage sur toutes les autres connexions et deviendra propriétaire de la connexion unique. (Donner à votre fenêtre de requête un accès exclusif pour faire ce que vous voulez.)

Sodoshi
la source
2
GO n'est pas une commande TSQL mais une commande spéciale uniquement reconnue par les utilitaires sqlcmd et osql et SSMS.
2018 sans dic
4

D'après mon expérience, l'utilisation de SINGLE_USER aide la plupart du temps, cependant, il faut être prudent: j'ai vécu des occasions entre le moment où je lance la commande SINGLE_USER et le moment où elle est terminée ... apparemment, un autre `` utilisateur '' avait obtenu le Accès SINGLE_USER, pas moi. Si cela se produit, vous aurez du mal à récupérer l'accès à la base de données (dans mon cas, c'était un service spécifique en cours d'exécution pour un logiciel avec des bases de données SQL qui avait obtenu l'accès SINGLE_USER avant moi). Ce que je pense devrait être le moyen le plus fiable (je ne peux pas le garantir, mais c'est ce que je vais tester dans les jours à venir), c'est en fait:
- arrêter les services qui peuvent interférer avec votre accès (s'il y en a) - définissez la base de données sur single_user immédiatement après cela - puis effectuez la restauration
- utilisez le script 'kill' ci-dessus pour fermer toutes les connexions

Sacha
la source
Si la commande SINGLE_USER est dans le même lot que votre commande de restauration (scriptée) - non séparée par une instruction GO! - alors aucun autre processus ne peut accéder à un seul utilisateur, selon mon expérience. Cependant, j'ai été attrapé ce soir parce que mon travail programmé tous les soirs de set-single-user; restore; set-multi-user a explosé. un autre processus avait un accès exclusif au fichier bak (smh) et donc la restauration a échoué, suivi de l'échec de SET MULTI_USER ... ce qui signifie que lorsque j'ai été appelé au milieu de la nuit pour nettoyer le sang, quelqu'un d'autre avait un accès SINGLE_USER et a dû être tué.
Ross Presser
3

Le script extrêmement efficace de Matthew a été mis à jour pour utiliser le DMV dm_exec_sessions, remplaçant la table système sysprocesses obsolète:

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;

Alternative utilisant la boucle WHILE (si vous souhaitez traiter d'autres opérations par exécution):

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
la source
2

La réponse acceptée présente l'inconvénient de ne pas tenir compte du fait qu'une base de données peut être verrouillée par une connexion qui exécute une requête impliquant des tables dans une base de données autre que celle connectée.

Cela peut être le cas si l'instance de serveur a plusieurs bases de données et que la requête directement ou indirectement (par exemple via des synonymes) utilise des tables dans plusieurs bases de données, etc.

Je trouve donc qu'il est parfois préférable d'utiliser syslockinfo pour trouver les connexions à tuer.

Ma suggestion serait donc d'utiliser la variation ci-dessous de la réponse acceptée d'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);
MellowTone
la source
CE! Bien que j'utilise personnellement le sys.dm_tran_lockstableau comme syslockinfoétant obsolète, vous pouvez également exclure votre @@ SPID actuel au cas où.
dérobie
1

Vous devez faire attention aux exceptions lors des processus de mise à mort. Vous pouvez donc utiliser ce script:

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
la source
1

@AlexK a écrit une excellente réponse . Je veux juste ajouter mes deux cents. Le code ci-dessous est entièrement basé sur la réponse de @ AlexK, la différence est que vous pouvez spécifier l'utilisateur et un temps depuis le dernier batch a été exécuté (notez que le code utilise sys.dm_exec_sessions au lieu de 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)

Dans cet exemple, seul le processus de l'utilisateur usrDBTest dont le dernier lot a été exécuté il y a plus d'une heure sera tué.

cantoni
la source
1

Vous pouvez utiliser le curseur comme ça:

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

J'ai écrit à ce sujet dans mon blog ici: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Cursor

Filip Holub
la source
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
la source
-1

J'ai testé avec succès avec le code simple ci-dessous

USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
Binh Truong
la source
2
J'ai eu des problèmes avec set SINGLE_USERquand il y avait déjà une seule connexion active.
ivan_pozdeev