J'ai une base de développement qui redéploie fréquemment à partir d'un projet de base de données Visual Studio (via une construction automatique TFS).
Parfois, lorsque je lance ma construction, 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 abandonner la base de données. (Je suppose que la plupart des développeurs ont un accès dbo
.)
Je peux exécuter manuellement SP_WHO
et commencer à supprimer les connexions, mais il me faut 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'essaye de laisser tomber.)
Existe-t-il un script qui peut supprimer ma base de données indépendamment de la personne connectée?
Mise à jour
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);
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
Vous pouvez obtenir le script fourni par SSMS en procédant comme suit:
Le script ressemblera à ceci:
USE [master]
GO
ALTER DATABASE [YourDatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [YourDatabaseName]
GO
Peu connu: l’instruction GO sql peut prendre un nombre entier correspondant au nombre de répétitions de la commande précédente.
Donc si vous:
ALTER DATABASE [DATABASENAME] SET SINGLE_USER
GO
Ensuite:
USE [DATABASENAME]
GO 2000
Cela répète la commande USE 2000 fois, force le blocage sur toutes les autres connexions et devient propriétaire de la connexion unique. (Donner à votre fenêtre de requête le seul accès possible pour faire ce que vous voulez.)
Le script extrêmement efficace de Matthew a été mis à jour pour utiliser le fichier DMV dm_exec_sessions, en remplacement de la table système obsolète 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;
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;
Selon mon expérience, utiliser SINGLE_USER aide la plupart du temps, cependant, il faut être prudent: j'ai eu des occasions dans lesquelles entre le moment où je lance la commande SINGLE_USER et le moment où elle est terminée ... apparemment, un autre "utilisateur" avait 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, il s'agissait d'un service spécifique exécuté pour un logiciel avec des bases de données SQL qui avait auparavant accès à l'accès SINGLE_USER). Ce que je pense devrait être le moyen le plus fiable (je ne peux pas en témoigner, mais c'est ce que je vais tester dans les jours à venir), c'est en réalité:
- arrêtez les services susceptibles d'interférer avec votre accès (le cas échéant)
- utilisez le script 'kill' ci-dessus pour fermer toutes les connexions
- définit la base de données sur single_user immédiatement après
- puis faites la restauration
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 sur mon blog ici: http://www.pigeonsql.com/single-post/2016/12/13/Kill-all-connections-on-DB-by-Cursor
Vous devez faire attention aux exceptions lors des processus de mise à mort. Donc, vous pouvez 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)
La réponse acceptée présente l'inconvénient de ne pas prendre en compte le 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 si 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 variante 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);
@AlexK a écrit un grand 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 une heure depuis l'exécution du dernier lot (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 éliminé.
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
*/