Dropping SQL Server database: handling concurrent connections - sql-server

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.

Related

Monitor when Database is created and receive an email

I created a trigger at the level of the server to control when a db is created.
I have this script that was working fine on SQL 2014, now we moved to SQL 2017, the script is working but I receive lot of emails
CREATE TRIGGER [ddl_trig_database]
ON ALL SERVER
FOR ALTER_DATABASE
AS
DECLARE #results NVARCHAR(max)
DECLARE #subjectText NVARCHAR(max)
DECLARE #databaseName NVARCHAR(255)
SET #subjectText = 'NEW DATABASE Created on ' + ##SERVERNAME + ' by ' + SUSER_SNAME()
SET #results = (SELECT EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','nvarchar(max)'))
SET #databaseName = (SELECT EVENTDATA().value('(/EVENT_INSTANCE/DatabaseName)[1]', 'VARCHAR(255)'))
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'EmailProfile',
#recipients = 'test#domain.com',
#body = #results,
#subject = #subjectText,
#exclude_query_output = 1 --Suppress 'Mail Queued' message
GO
I receive for example in different emails each of these lines:
ALTER DATABASE [testNewDB] SET DELAYED_DURABILITY = DISABLED
ALTER DATABASE [testNewDB] SET RECOVERY FULL
ALTER DATABASE [testNewDB] SET READ_WRITE
ALTER DATABASE [testNewDB] SET READ_COMMITTED_SNAPSHOT OFF
There are more so I believe the trigger is sending the info for each configuration parameter of the new db created, any idea how to receive only the info of the new DB created without all the rest?
You can replace ALTER_DATABASE with CREATE_DATABASE, but this will not catch a restore event because a restore does not generate a DLL event.
CREATE TRIGGER [ddl_trig_database]
ON ALL SERVER
FOR CREATE_DATABASE
AS
The following article covers a solution that will work around the missing DDL event:
DDL triggers enable us to audit DDL changes but there are a few
missing events, design decisions and installation complications. This
post explains and provides a full solution that includes auditing for
database restores (there is no DDL event for this) and an incremental
self install, which keeps the whole server audit configured for DDL
auditing.
https://www.sqlservercentral.com/forums/topic/sql-2008-ddl-auditing-a-full-self-installingupdating-solution-for-whole-server
The solution in the article for RESTORE events involves a job that runs to check for new databases:
SQL 2008 Audit RESTORE DATABASE
SQL Agent job which runs (in less than 1 second) every 1 minute to
copy new restore database auditing information from
msdb.dbo.restorehistory to dbadata.dbo.ServerAudit. If it finds that a
database restore has happened but has not been audited it
automatically runs the “Setup DDL Audit” job because there is a
possibility that the restored database is not configured for DDL
auditing as expected.

Is there a mode/profile/permission that automatically rollbacks all requests submitted by a user?

At work, we have production databases on which developers have read permission. When developers have to fix something in the database, they must test their scripts in a copy of the production databases and then ask the DBA team to execute it in production.
Sometimes however, the data that must be fixed is not in the test databases. Developers then ask for a new copy of production databases, and this can take a lot of time.
Of course, we could grant them update permission and ask them to use BEGIN TRANSACTION / ROLLBACK, but it is too risky. Nobody wants that, not even the developers.
My question: is it possible to create a profile on SQL Server - or grant special permission - that would allow to execute update and delete commands but would always, no matter what the developer wrote, rollback after a GO or after the last command issued in a session?
This would be really helpful to test scripts before sending them to production.
You could create a sproc and give EXEC access to devs on that sproc only, SOLUTION #1 - SPROCS. This is probably the most elegant solution as you want them to have a simple way to run their query and also want to control their perms on the production environment. Example to execute a command would be: EXEC [dbo].[usp_rollback_query] 'master', 'INSERT INTO table1 SELECT * FROM table2
SOLUTION #1
USE [DATABASENAME]
GO
ALTER PROC dbo.usp_rollback_query
(
#db VARCHAR(128),
#query NVARCHAR(max)
)
AS
BEGIN
DECLARE #main_query NVARCHAR(max) = 'USE [' + #db + ']
' + #query;
BEGIN TRAN
EXEC sp_executesql #main_query;
ROLLBACK TRAN
END
If you can afford to have snapshot created and dropped each time, SOLUTION #2 - DB SNAPSHOTS is the best way to go about it. It's super fast, the only two drawbacks are that you need to kick people off the DB before you can restore and it will restore all changes made since the snapshot was created.
SOLUTION #2
-- CREATE SNAPSHOT
CREATE DATABASE [DATABASENAME_SS1]
ON
(
NAME = DATABASENAME,
FILENAME = 'your\path\DATABASENAME_SS1.ss'
) AS SNAPSHOT OF [DATABASENAME];
GO
-- let devs run whatever they want
-- CLOSE CONNECTIONS
USE [master];
GO
ALTER DATABASE [DATABASENAME]
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
GO
-- RETORE DB
RESTORE DATABASE [DATABASENAME]
FROM DATABASE_SNAPSHOT = 'DATABASENAME_SS1';
GO
-- CLEANUP SNAPSHOT COPY
DROP DATABASE [DATABASENAME_SS1];
I don't think ROLLBACK on each query is a good idea or a good design but if you have to go that route, you would need to use triggers. The limitation with triggers is that a DATABASE or SERVER level trigger can only be for DDL and not DML. Creating triggers on each TABLE object that you think is being altered is doable, however, the drawback here is that you need to know which tables are being modified and even then it's quite messy. Regardless please look at SOLUTION #3 - TABLE TRIGGERS below. To make this better you could create a role and check if the user is part of that role, then rollback.
SOLUTION #3
USE DATABASENAME
GO
ALTER TRIGGER dbo.tr_rollback_devs
ON dbo.table_name
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF SYSTEM_USER IN ('dev1', 'dev2')
ROLLBACK
END
GO

SQL Server Account Issue

I am currently using SQL Server 2005 and I try to restore a database from couple of months ago but when I restore it when I try to create a account it tells me that the account already exists but you cannot see it under security. I tried dropping that account but it tells me that the account does not exist to drop...But when I try to create it, it tells me that it exists... It is driving me insane. I completely removed that account from the server and all the linked servers and all the databases and I tried to re-create it again it works on all database except one that it keeps on saying it already exists. Any ideas what it could be or what else I should be checking?
You are having orphaned user accounts in database. It is very common issue when you copy/move your database from one server to another server. You will need to remove them from database user Or more better option is to create Server Login and map that login with that orphaned database user.
While back I wrote about it in my blog with some demo script, you may want to look at it. Or just look at this MSDN link for use of SP which can be used to manager orphaned users.
As a DBA, I get this problem all the time. And the GUI is not much of a help. Your SQL server has the accounts you want, the db has the accounts you want, but they don't auto-connect on restore.
The REAL way to fix this is to STOP-USING-INDIVIDUAL-ACCOUNTS. Only provide access to DBs on the basis of being a member of an active directory group. Then you can give access issues to the AD-group. BTW: While individual accounts don't auto-reconnect on a restore, group-access does.
Here is some code I use to address the same issue.
create procedure [dbo].[proc_FarSyncLogins] #TargetDB nvarchar(128)= '' as
begin
SET NOCOUNT ON
declare #cmd varchar(1000)
begin try
drop table master.dbo.NeededUsers
end try
begin catch
print 'could not: drop table master.dbo.NeededUsers'
end catch
set #cmd='select name collate Latin1_General_CI_AS as name,is_disabled
into master.dbo.NeededUsers
from ['+#TargetDB+'].sys.sql_logins'
--print #cmd
exec(#cmd)
print 'logins that need to be enabled'
select * from master.dbo.NeededUsers
declare #UserName nvarchar(128)
declare SyncUsers1 cursor fast_forward for SELECT Name FROM master.dbo.NeededUsers
OPEN SyncUsers1
FETCH NEXT FROM SyncUsers1 INTO #UserName
WHILE ##FETCH_STATUS = 0
BEGIN
set #cmd = ''
if #TargetDB <> ''
begin
set #cmd=#cmd+'Use ['+#TargetDB+']; '+char(10)
end
begin try
SET #cmd = #cmd+'ALTER LOGIN ['+#UserName+'] ENABLE; '+char(10)
SET #cmd = #cmd+'exec sp_change_users_login #Action=''Auto_Fix'', #UserNamePattern ='''+#UserName+'''; '+char(10)
PRINT #cmd
EXEC(#cmd)
end try
begin catch
Print 'Failed :'+#cmd
end catch
FETCH NEXT FROM SyncUsers1 INTO #UserName
END
CLOSE SyncUsers1
DEALLOCATE SyncUsers1
end
Then just
exec master.dbo.proc_FarSyncLogins '<yourDBName>';
It IS determinately HACKY, you will want to read the output carefully to verify you meant to do all those actions. But most of the time you just want to restore everything that you had before.
The account exists in the database and at the server level.
So you need to drop the account from the database itself but not from the server as it deosn't exist there.
have you tried...
use {yourdatabasename}
go
EXEC sp_dropuser '{username}'

How long should SET READ_COMMITTED_SNAPSHOT ON take?

How long should it take to run
ALTER DATABASE [MySite] SET READ_COMMITTED_SNAPSHOT ON
I just ran it and it's taken 10 minutes.
How can I check if it is applied?
You can check the status of the READ_COMMITTED_SNAPSHOT setting using the sys.databases view. Check the value of the is_read_committed_snapshot_on column. Already asked and answered.
As for the duration, Books Online states that there can't be any other connections to the database when this takes place, but it doesn't require single-user mode. So you may be blocked by other active connections. Run sp_who (or sp_who2) to see what else is connected to that database.
Try this:
ALTER DATABASE generic SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE
OK (I am the original questioner) so it turns out this whole time I didn't even have the darn thing enabled.
Here's the ultimate code to run to enable snapshot mode and make sure it is enabled.
SELECT is_read_committed_snapshot_on, snapshot_isolation_state_desc,snapshot_isolation_state FROM sys.databases WHERE name='shipperdb'
ALTER DATABASE shipperdb SET allow_snapshot_isolation ON
ALTER DATABASE shipperdb SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE shipperdb SET read_committed_snapshot ON
ALTER DATABASE shipperdb SET MULTI_USER
SELECT is_read_committed_snapshot_on, snapshot_isolation_state_desc,snapshot_isolation_state FROM sys.databases WHERE name='shipperdb'
This works even with connections active (presumably you're fine with them getting kicked out).
You can see the before and after state and this should run almost immediately.
IMPORTANT:
The option READ_COMMITTED_SNAPSHOT above corresponds to IsolationLevel.ReadCommitted in .NET
The option ALLOW_SNAPSHOT_ISOLATION above corresponds to IsolationLevel.Snapshot in .NET
Great article about different versioning
.NET Tips:
Looks like Isolationlevel.ReadCommitted is allowed in code even if not enabled by the database. No warning is thrown. So do yourself a favor and be sure it is turned on before you assume it is for 3 years like I did!!!
If you're using C# you probably want the ReadCommitted IsolationLevel and not Snapshot - unless you are doing writes in this transaction.
READ COMMITTED SNAPSHOT does optimistic reads and pessimistic writes. In contrast, SNAPSHOT does optimistic reads and optimistic writes. (from here)
bool snapshotEnabled = true;
using (var t = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted
}))
{
using (var shipDB = new ShipperDBDataContext())
{
}
}
In additional you may get an error about being 'unable to promote' a transaction. Search for 'promotion' in Introducing System.Transactions in the .NET Framework 2.0.
Unless you're doing something special like connecting to an external database (or second database) then something as simple as creating a new DataContext can cause this. I had a cache that 'spun up' its own datacontext at initialization and this was trying to escalate the transaction to a full distributed one.
The solution was simple :
using (var tran = new TransactionScope(TransactionScopeOption.Suppress))
{
using (var shipDB = new ShipperDBDataContext())
{
// initialize cache
}
}
See also Deadlocked article by #CodingHorror
Try this code:
if(charindex('Microsoft SQL Server 2005',##version) > 0)
begin
declare #sql varchar(8000)
select #sql = '
ALTER DATABASE ' + DB_NAME() + ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE ;
ALTER DATABASE ' + DB_NAME() + ' SET READ_COMMITTED_SNAPSHOT ON;
ALTER DATABASE ' + DB_NAME() + ' SET MULTI_USER;'
Exec(#sql)
end
I tried the command:
ALTER DATABASE MyDB SET READ_COMMITTED_SNAPSHOT ON
GO
against a dev box but the it took 10+ minutes and so I killed it.
I then found this:
https://willwarren.com/2015/10/12/sql-server-read-committed-snapshot/
and used his code block (which took about 1:26 to run):
USE master
GO
/**
* Cut off live connections
* This will roll back any open transactions after 30 seconds and
* restricts access to the DB to logins with sysadmin, dbcreator or
* db_owner roles
*/
ALTER DATABASE MyDB SET RESTRICTED_USER WITH ROLLBACK AFTER 30 SECONDS
GO
-- Enable RCSI for MyDB
ALTER DATABASE MyDB SET READ_COMMITTED_SNAPSHOT ON
GO
-- Allow connections to be established once again
ALTER DATABASE MyDB SET MULTI_USER
GO
-- Check the status afterwards to make sure it worked
SELECT is_read_committed_snapshot_on
FROM sys.databases
WHERE [name] = 'MyDB '
Try use master database before altering current database.
USE Master
GO
ALTER DATABASE [YourDatabase] SET READ_COMMITTED_SNAPSHOT ON
GO
I didn't take a second for me when i changed my DB to single user
All you need to do is this:
ALTER DATABASE xyz SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;
No need to put the database into single user mode.
You will rollback uncommitted transactions though.
Try Shut off the other SQL services so that only the SQL server service is running.
Mine ran for 5 minutes then I cancelled it because it was obvious nothing was happening. Its a brand new server so there are no other users connected. I shut off the SQL Reporting Services and then ran it again.. took less than a second to complete.
With "ROLLBACK IMMEDIATE" it took about 20-30 seconds on my db which is 300GB.
ALTER DATABASE DBNAME SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE

How do you kill all current connections to a SQL Server 2005 database?

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.

Resources