Every night, I backup my production server and push the backup to my dev server. My dev server then has a job that runs which first checks if the backup file exits, if so check if the database exists in dev and if so drop the database, then restore from file. This all works fine, unless the file is not yet complete due to slow transfer, etc. If the file is not completely downloaded when the job runs then the first step sees it exists and drops the database. The next step tries to restore and of course fails. The next day when the job runs I would expect that when it checks if the database exists, it would see that it does not and shouldn't attempt to drop it and just restore. However, what's happening is the job is unable to drop the database and just fails at that point. This requires manual intervention to get the database restored, which is my problem. I'm less concerned with the fact of having no database existing on the server for a day (in theory) as I can tweak the schedule further to restore sooner. What I am concerned with is why is the IF statement not working to check if the database exists and attempts to drop a database regardless? Here's the T-SQL code that I am using:
DECLARE #output INT
DECLARE #SqlPath varchar(500) = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\Backup\PROD-01_prod_backup.bak'
EXECUTE master.dbo.xp_fileexist #SqlPath, #output OUT
IF #output = 1
BEGIN
IF (EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = '[PROD-01]')))
BEGIN
ALTER DATABASE [PROD-01] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE [PROD-01]
END
RESTORE DATABASE [PROD-01] FROM DISK = #SqlPath
END
I'm not sure how this is happening, as I am unable to reproduce, but a TRY / CATCH block is an ideal solution for this case:
SET XACT_ABORT ON
GO
DECLARE #output INT
DECLARE #SqlPath varchar(500) = 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\Backup\PROD-01_prod_backup.bak'
EXECUTE master.dbo.xp_fileexist #SqlPath, #output OUT
IF #output = 1
BEGIN
IF (EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE (name = 'PROD-01')))
BEGIN TRY
ALTER DATABASE [PROD-01] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE [PROD-01]
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE();
END CATCH
RESTORE DATABASE [PROD-01] FROM DISK = #SqlPath
END
If I make a change to MyTable on MyDatabase, I want it to delete the contents of TestTable on TestDatabase on MYLINKEDSERVER and reinsert everything from scratch. To do this, I use a trigger on MyTable. I know this is inefficient but the number of records is less than 10.
In case the linked server is down, I want it to still commit the changes locally to MyTable and just send an email alert stating that the linkedserver could not be updated. I am failing to get the local transaction to commit when the linkedserver is unavailable... I've tried messing with XACT_ABORT but I just get different errors.
What am I doing wrong?
CREATE TRIGGER trig_updatelinkedserver ON MyDatabase.dbo.MyTable
FOR INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE #linked_server SYSNAME = 'MYLINKEDSERVER'
, #tablename SYSNAME --name of the table calling the trigger so we can send error details in alert eamil
SELECT #tablename = OBJECT_NAME(parent_object_id)
FROM sys.objects
WHERE sys.objects.name = OBJECT_NAME(##PROCID)
BEGIN TRY
--If linkedserver fails to connect, we do not want the remaining code in the block to run
--but we do want the original transaction that triggered this to complete.
EXEC sp_testlinkedserver #servername = #linked_server
DELETE FROM MYLINKEDSERVER.TestDatabase.dbo.TestTable
INSERT INTO MYLINKEDSERVER.TestDatabase.dbo.TestTable
SELECT *
FROM MyDatabase.dbo.MyTable
END TRY
BEGIN CATCH
DECLARE #subj VARCHAR(1000) = 'TRIGGER FAILURE: ' + #tablename + ': Could not locate linkedserver ' + #linked_server
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'foo#bar.com'
, #subject = #subj
, #body = ''
, #body_format = 'HTML'
, #profile_name = 'MyEmailProfile'
END CATCH
END
Error handling in tsql is complicated and inconsistent. What are you doing wrong? Making assumptions. Here is what Erland says in his lengthy discussion about the topic:
What is important to understand about triggers is that they are part of the command that fired the trigger, and in a trigger you are always in a transaction, even if you did not use BEGIN TRANSACTION. Sometimes I see people in SQL Server forums ask if they can write a trigger that does not roll back the command that fired the trigger if the trigger fails. The answer is that there is no way that you can do this reliably, so you better not even try. If you have this type of requirement, you should probably not use a trigger at all, but use some other solution.
So take that last sentence to heart. Note that the link takes you into the middle of the discussion. And there are links at the end of the page that continue to related topics - one of which is about linked servers.
In the following TSQL code I can use my local variable in first few lines and then I cannot use it again. Why am I not able to use it in the last line of my code ?
Where does its scope end?
DECLARE ##CurrentDB varchar(50);
SET ##CurrentDB = 'MyDBNAME';
-- Find Data & Log Fiel locations
SELECT DB_NAME(database_id) AS DatabaseName, name AS LogicalFileName, physical_name AS PhysicalFileName, size/(128*1024) [GB]
FROM sys.master_files AS mf
WHERE DB_NAME(database_id) = ##CurrentDB
-- Detach DB
USE
GO
ALTER DATABASE SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
EXEC master.dbo.sp_detach_db #dbname = ##Cur
GO
Here is the error:
Any time you pass SQL Server a GO command, that ends the context in which the variable exists and it is no longer accessible by anything after that point in the T-SQL code. "Global" variables as such do not exist in SQL Server, but there are ways around it, generally by implementing a global variable table (either temporary or permanent).
You can get the general idea from this blog post that sets up a permanent table to track global variables.
As a workaround, you can use a Global Temp Table:
Declare #CurrentDB varchar(50)
SET #CurrentDB = 'MyDBNAME'
Create Table ##CurrentDB (Name varchar(50))
Insert Into ##CurrentDB Values (#CurrentDB)
GO
-- ...
GO
Declare #CurrentDB varchar(50)
Select Top 1 #CurrentDB = Name From ##CurrentDB
Select #CurrentDB
This should work even if you are using different databases in each part of your script.
Why use a global variable or temp table at all? This cries out to me to be a user defined stored procedure.
Here are the business rules.
1 - You basically want to get the location and size of a database you want to detach.
2 - Want to set database to single user mode.
3 - You want to detach the database. Just remember the files will be hanging around afterwards.
I created it in the MSDB database but you can put it in your own toolbox database.
I did not check to see if the database is really in use only mode. - TODO list
Just check the mode in the sys.databases table. If the ALTER, fails do not try the detach. Just notify the user to find the spids and kill them.
http://technet.microsoft.com/en-us/library/ms178534.aspx
4 - I did not put any error handling in. - TODO list
Last but not least, this solution could be prone to SQL injection, do not give the world access.
In short, the stored procedure below does just what you want.
--
-- Create a user stored procedure
--
-- Start in msdb
use msdb
go
-- drop existing
if object_id('my_detach_process') > 0
drop procedure my_detach_process
go
-- create new
create procedure my_detach_process(#dbname sysname)
as
-- Show the data
SELECT
DB_NAME(mf.database_id) AS DatabaseName,
mf.name AS LogicalName,
mf.physical_name AS PhysicalName, mf.size as SizeMb
FROM sys.master_files AS mf
WHERE DB_NAME(database_id) = #dbname;
-- Set to single user
DECLARE #sqlstmt1 nvarchar(512) = '';
SET #sqlstmt1 = 'ALTER DATABASE [' + #dbname + '] SET SINGLE_USER WITH ROLLBACK IMMEDIATE';
EXEC sp_executesql #sqlstmt1;
-- Detach
DECLARE #sqlstmt2 nvarchar(512) = '';
SET #sqlstmt2 = 'USE [master]; EXEC master.dbo.sp_detach_db #dbname = ' + #dbname;
EXEC sp_executesql #sqlstmt2;
GO
--
-- Sample call
--
-- Choose master
use master
go
-- Create toy db
create database toy;
go
-- Call the sp
exec msdb.dbo.my_detach_process #dbname = 'Toy'
Sample output from sample call.
During my integration tests, I try to drop database using:
USE master
ALTER DATABASE TestXyz SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE TestXyz
However, quite often (given the number of tests) one of the application background processes manages to get between SET SINGLE_USER and DROP DATABASE, which makes it single user of the database and breaks the DROP.
I can not use RESTRICTED_USER, as the application currently has db_owner permission (due to a large amount of legacy code, some of which requires it, so it will not be changed just for the tests).
I can not use OFFLINE as it does not delete database files from the disk.
How would you solve this problem?
OK plan b... iterate a drop of connections and rename the DB to get it away from the applications domain. Then drop it. To handle iterating through connections a try catch on the rename will hopefully allow it to run until it is able to drop the connection. Example code below creates a DB TestDB; renames it to testdb2 in the while loop before dropping it after the loop has succeeded.
-- Setup a scratch Db for testing
create database testdb
go
use testdb
while exists (select name from sys.databases where name = 'testdb')
Begin
DECLARE #DbName nvarchar(50) SET #DbName = N'testdb'
DECLARE #EXECSQL varchar(max) SET #EXECSQL = ''
SELECT #EXECSQL = #EXECSQL + 'Kill ' + Convert(varchar, SPId) + ';'
FROM MASTER..SysProcesses
WHERE DBId = DB_ID(#DbName) AND SPId <> ##SPId
EXEC(#EXECSQL)
Begin try
EXEC sp_renamedb 'testdb', 'testdb2'
end try
Begin Catch
print 'failed to rename'
End Catch
end
drop database testdb2
Try this once:
Stop application services and run your query.
Stop application services and restart SQL Server Services and then run your query.
I have finally solved it using the following approach:
ALTER LOGIN MyAppUser DISABLE
ALTER DATABASE TestXyz SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE TestXyz
ALTER LOGIN MyAppUser ENABLE
Since I can use different login for test database management process, this allows me to block application from accessing the DB. (The reason for SINGLE_USER here is just to kick already connected users. I haven't checked if ALTER LOGIN already does that, but I assume it does not).
Alternative option is to delete MyAppUser from the database before dropping it, however I thought about it only now and do not have code for it.
I want to rename a database, but keep getting the error that 'couldn't get exclusive lock' on the database, which implies there is some connection(s) still active.
How can I kill all the connections to the database so that I can rename it?
The reason that the approach that Adam suggested won't work is that during the time that you are looping over the active connections new one can be established, and you'll miss those. You could instead use the following approach which does not have this drawback:
-- set your current connection to use master otherwise you might get an error
use master
ALTER DATABASE YourDatabase SET SINGLE_USER WITH ROLLBACK IMMEDIATE
--do you stuff here
ALTER DATABASE YourDatabase SET MULTI_USER
Script to accomplish this, replace 'DB_NAME' with the database to kill all connections to:
USE master
GO
SET NOCOUNT ON
DECLARE #DBName varchar(50)
DECLARE #spidstr varchar(8000)
DECLARE #ConnKilled smallint
SET #ConnKilled=0
SET #spidstr = ''
Set #DBName = 'DB_NAME'
IF db_id(#DBName) < 4
BEGIN
PRINT 'Connections to system databases cannot be killed'
RETURN
END
SELECT #spidstr=coalesce(#spidstr,',' )+'kill '+convert(varchar, spid)+ '; '
FROM master..sysprocesses WHERE dbid=db_id(#DBName)
IF LEN(#spidstr) > 0
BEGIN
EXEC(#spidstr)
SELECT #ConnKilled = COUNT(1)
FROM master..sysprocesses WHERE dbid=db_id(#DBName)
END
Kill it, and kill it with fire:
USE master
go
DECLARE #dbname sysname
SET #dbname = 'yourdbname'
DECLARE #spid int
SELECT #spid = min(spid) from master.dbo.sysprocesses where dbid = db_id(#dbname)
WHILE #spid IS NOT NULL
BEGIN
EXECUTE ('KILL ' + #spid)
SELECT #spid = min(spid) from master.dbo.sysprocesses where dbid = db_id(#dbname) AND spid > #spid
END
Using SQL Management Studio Express:
In the Object Explorer tree drill down under Management to "Activity Monitor" (if you cannot find it there then right click on the database server and select "Activity Monitor"). Opening the Activity Monitor, you can view all process info. You should be able to find the locks for the database you're interested in and kill those locks, which will also kill the connection.
You should be able to rename after that.
I've always used:
ALTER DATABASE DB_NAME SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
SP_RENAMEDB 'DB_NAME','DB_NAME_NEW'
Go
ALTER DATABASE DB_NAME_NEW SET MULTI_USER -- set back to multi user
GO
ALTER DATABASE [Test]
SET OFFLINE WITH ROLLBACK IMMEDIATE
ALTER DATABASE [Test]
SET ONLINE
Take offline takes a while and sometimes I experience some problems with that..
Most solid way in my opinion:
Detach
Right click DB -> Tasks -> Detach...
check "Drop Connections"
Ok
Reattach
Right click Databases -> Attach..
Add... -> select your database, and change the Attach As column to your desired database name.
Ok
Select 'Kill '+ CAST(p.spid AS VARCHAR)KillCommand into #temp
from master.dbo.sysprocesses p (nolock)
join master..sysdatabases d (nolock) on p.dbid = d.dbid
Where d.[name] = 'your db name'
Declare #query nvarchar(max)
--Select * from #temp
Select #query =STUFF((
select ' ' + KillCommand from #temp
FOR XML PATH('')),1,1,'')
Execute sp_executesql #query
Drop table #temp
use the 'master' database and run this query, it will kill all the active connections from your database.
I usually run into that error when I am trying to restore a database I usually just go to the top of the tree in Management Studio and right click and restart the database server (because it's on a development machine, this might not be ideal in production). This is close all database connections.
In MS SQL Server Management Studio on the object explorer, right click on the database. In the context menu that follows select 'Tasks -> Take Offline'
Another "kill it with fire" approach is to just restart the MSSQLSERVER service.
I like to do stuff from the commandline. Pasting this exactly into CMD will do it:
NET STOP MSSQLSERVER & NET START MSSQLSERVER
Or open "services.msc" and find "SQL Server (MSSQLSERVER)" and right-click, select "restart".
This will "for sure, for sure" kill ALL connections to ALL databases running on that instance.
(I like this better than many approaches that change and change back the configuration on the server/database)
Here's how to reliably this sort of thing in MS SQL Server Management Studio 2008 (may work for other versions too):
In the Object Explorer Tree, right click the root database server (with the green arrow), then click activity monitor.
Open the processes tab in the activity monitor, select the 'databases' drop down menu, and filter by the database you want.
Right click the DB in Object Explorer and start a 'Tasks -> Take Offline' task. Leave this running in the background while you...
Safely shut down whatever you can.
Kill all remaining processes from the process tab.
Bring the DB back online.
Rename the DB.
Bring your service back online and point it to the new DB.
The option working for me in this scenario is as follows:
Start the "Detach" operation on the database in question. This wil open a window (in SQL 2005) displaying the active connections that prevents actions on the DB.
Kill the active connections, cancel the detach-operation.
The database should now be available for restoring.
Try this:
ALTER DATABASE [DATABASE_NAME]
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE
Right click on the database name, click on Property to get property window, Open the Options tab and change the "Restrict Access" property from Multi User to Single User. When you hit on OK button, it will prompt you to closes all open connection, select "Yes" and you are set to rename the database....
These didn't work for me (SQL2008 Enterprise), I also couldn't see any running processes or users connected to the DB. Restarting the server (Right click on Sql Server in Management Studio and pick Restart) allowed me to restore the DB.
I'm using SQL Server 2008 R2, my DB was already set for single user and there was a connection that restricted any action on the database. Thus the recommended SQLMenace's solution responded with error. Here is one that worked in my case.
I use sp_who to get list of all process in database. This is better because you may want to review which process to kill.
declare #proc table(
SPID bigint,
Status nvarchar(255),
Login nvarchar(255),
HostName nvarchar(255),
BlkBy nvarchar(255),
DBName nvarchar(255),
Command nvarchar(MAX),
CPUTime bigint,
DiskIO bigint,
LastBatch nvarchar(255),
ProgramName nvarchar(255),
SPID2 bigint,
REQUESTID bigint
)
insert into #proc
exec sp_who2
select *, KillCommand = concat('kill ', SPID, ';')
from #proc
Result
You can use command in KillCommand column to kill the process you want to.
SPID KillCommand
26 kill 26;
27 kill 27;
28 kill 28;
You can Use SP_Who command and kill all process that use your database and then rename your database.