Shrinklog all user databases in SQL2008 - sql-server

Hi I am using this script in the process of weekly maintenance, suggest best approach/scripts to do shrinklog. Currently am getting an error with the below script
declare #s nvarchar(4000)
set #s= '
if ''?'' not in (''tempdb'',''master'',''model'',''msdb'')
begin
use [?]
Alter database [?] SET Recovery simple
end '
exec sp_msforeachdb #s
set #s= '
if ''?'' not in (''tempdb'',''master'',''model'',''msdb'')
begin
use [?]
Declare #LogFileLogicalName sysname
select #LogFileLogicalName=Name from sys.database_files where Type=1
DBCC Shrinkfile(#LogFileLogicalName,1)
end'
exec sp_msforeachdb #s
Error Description:
ShrinkLog Execute SQL Task Description: Executing the query "declare #s nvarchar(4000) set #s= ' ..." failed with the following error: "Option 'RECOVERY' cannot be set in database 'tempdb'. Cannot shrink log file 2 (DBServices_Log) because total number of logical log files cannot be fewer than 2. DBCC execution completed. If DBCC printed error messages, contact your system administrator.
note: I am avoiding tempdb(all System db) in my script, but error message shows tempdb?

This is probably the worst maintenance script I've seen in the past year. A maintenance script that every week breaks the log chain and makes the database unrecoverable?? OMG. Not to mention that the simple premise of shrinking the log on a maintenance task is wrong. If the database log has grown to a certain size, than that size is needed. Schedule log backups more frequently to prevent this, but don't schedule log shrink operations.

You can avoid the 5058 error by using an exec statement to alter the database recovery model. The following script will set each user database to a simple recovery model. (It uses Jimmy's answer to detect system databases.)
exec sp_msforeachdb 'if exists (select name from sys.databases d where case when d.name in (''master'',''model'',''msdb'',''tempdb'') then 1 else d.is_distributor end = 0
and name = ''?'' and recovery_model_desc != ''SIMPLE'')
begin
declare #previousRecoveryModel varchar(100) = (select recovery_model_desc from sys.databases where name = ''?'');
print ''Changing recovery model for ? from '' + #previousRecoveryModel + '' to SIMPLE.'';
use master;
-- Using exec avoids a compile-time 5058 error about tempdb, which is a branch that will never be executed in this code.
exec (''alter database [?] set recovery simple'');
end';
Then you won't get the compile-time error about tempdb because the exec statement will compile its statement with the database name variable already substituted.

Related

set database to multi_user through management studio

I have had to set my database to single_user mode to allow for a dbcc checkdb repair and now I am unable to get my database back to multi_user. I have tried the following command in a query window against the master database but it hasn't worked as suggested by another Stack overflow post:
USE [master];
GO
ALTER DATABASE mydb SET MULTI_USER WITH ROLLBACK IMMEDIATE;
GO
I get the following error:
Msg 5064, Level 16, State 1, Line 2 Changes to the state or options of
database 'mydb' cannot be made at this time. The database is in
single-user mode, and a user is currently connected to it.
Msg 5069,
Level 16, State 1, Line 2 ALTER DATABASE statement failed.
If I right click on the database and try selecting properties then it errors saying that it is already in use.
Any help would be greatly appreciated.
Try killing the existing connection and setting MULTI_USER in the same batch:
USE master;
GO
DECLARE #sql nvarchar(MAX);
SELECT #sql = STRING_AGG(N'KILL ' + CAST(session_id as nvarchar(5)), ';')
FROM sys.dm_exec_sessions
WHERE database_id = DB_ID(N'mydb');
SET #sql = #sql + N';ALTER DATABASE mydb SET MULTI_USER;';
--PRINT #sql;
EXEC sp_executesql #sql;
GO
To get the session that was tied to the database I ran:
SELECT request_session_id
FROM sys.dm_tran_locks
WHERE resource_database_id = DB_ID('mydb')
This yielded a processid of 55
I then ran:
kill 55
go
And I was the able to use the alter multi_user line that was previously not working
The most likely reason why you're getting this error is that you still have the original session open that you used to set the database to single user. If you execute the above SQL in the same session that you set the database to single user in, it will work.
First try selecting the master database and run the alter command.
If it does not work, There exists some open connections to database and you can check and kill the processes
use master
GO
select
d.name,
d.dbid,
spid,
login_time,
nt_domain,
nt_username,
loginame
from sysprocesses p
inner join sysdatabases d
on p.dbid = d.dbid
where d.name = 'dbname'
GO
kill 52 -- kill the number in spid field
GO
exec sp_dboption 'dbname', 'single user', 'FALSE'
GO

Switching from one database to another within the same script

I would like to know how I can switch from one database to another within the same script. I have a script that reads the header information from a SQL Server .BAK file and loads the information into a test database. Once the information is in the temp table (Test database) I run the following script to get the database name.
This part works fine.
INSERT INTO #HeaderInfo EXEC('RESTORE HEADERONLY
FROM DISK = N''I:\TEST\database.bak''
WITH NOUNLOAD')
DECLARE #databasename varchar(128);
SET #databasename = (SELECT DatabaseName FROM #HeaderInfo);
The problem is when I try to run the following script nothing happens. The new database is never selected and the script is still on the test database.
EXEC ('USE '+ #databasename)
The goal is switch to the new database (USE NewDatabase) so that the other part of my script (DBCC CHECKDB) can run. This script checks the integrity of the database and saves the results to a temp table.
What am I doing wrong?
You can't expect a use statement to work in this fashion using dynamic SQL. Dynamic SQL is run in its own context, so as soon as it has executed, you're back to your original context. This means that you'd have to include your SQL statements in the same dynamic SQL execution, such as:
declare #db sysname = 'tempdb';
exec ('use ' + #db + '; dbcc checkdb;')
You can alternatively use fully qualified names for your DB objects and specify the database name in your dbcc command, even with a variable, as in:
declare #db sysname = 'tempdb';
dbcc checkdb (#db);
You can't do this because Exec scope is limited to dynamic query. When exec ends context is returned to original state. But context changes in Exec itself. So you should do your thing in one big dynamic statement like:
DECLARE #str NVARCHAR(MAX)
SET #str = 'select * from table1
USE DatabaseName
select * from table2'
EXEC (#str)

Can a SSIS package called using xp_cmdshell enlist in a SQL Server transaction?

I have a very basic SSIS package with one data flow task (from an OLE DB Source to a Flat File).
The TransactionOption property is set to Required and I have tried the IsolationLevel option set to ReadCommitted, ReadUncommitted and Serializable.
The package exports all rows from a table [TestTable] to the flat file.
I have the following SQL script (that I'm running in Management Studio for the moment):
BEGIN TRANSACTION
DELETE FROM [dbo].[TestTable]
DECLARE #SsisString VARCHAR(8000)
DECLARE #PackageName VARCHAR(200)
DECLARE #ServerName VARCHAR(100)
DECLARE #ReturnCode INT
SET #PackageName = 'TransactionalTestPackage'
SET #ServerName = 'SERVERNAME'
SET #SsisString = 'dtexec /sq ' + #PackageName + ' /ser ' + #ServerName + ' '
EXEC #ReturnCode = xp_cmdshell #SsisString
SELECT #ReturnCode
--COMMIT TRANSACTION
ROLLBACK TRANSACTION
Note that I'm deleting all the rows from the table before running the package, so in theory the package should export zero rows to the file, but what is actually happening is the package is hanging (I think because of the uncommitted delete on the TestTable). Question is: Does the SSIS package called in this way actually enlist in the transaction started at the top of the SQL block, and if not, can it?
The actions in the xp_cmdshell are going to be outside of the transaction, doesn't matter if it's SSIS or another query. You could just as easily replaced the #ssisstring with 'sqlcmd -S myserver -d mydatabase -Q "SELECT TOP 1 FROM dbo.TestTable"
If you need transactions, do it in SSIS. Put your DELETE statement as an Execute SQL Task. Wire that up to your data flow task. At the package level (right click on the background of the control flow and select properties) change the package's transaction level from Supported to Required. This will start a transaction. Everything contained within it will enlist in the parent transaction unless you explicitly opt out of the transaction by changing the default transaction level from Supported to NotSupported.

Unexpected sp_MSForEachDB behavior

I'm working to enhance my understanding of some system sprocs and I'm very confused by this script I was working on. To exercise my understanding of sp_MSForEachDB I decided to write a script that would truncate the logs of all databases on a server. As such, I came up with the following script:
sp_MSForEachDb 'IF LOWER(rtrim(''?'')) NOT IN ('''', ''master'', ''tempdb'', ''tempdev'', ''model'', ''msdb'')
BEGIN
declare #LogFile nvarchar(max)
USE [?]
SELECT #LogFile = sys.sysaltfiles.name FROM sys.sysdatabases
INNER JOIN sys.sysaltfiles ON sys.sysdatabases.dbid = sys.sysaltfiles.dbid
WHERE (sys.sysaltfiles.fileid = 1) AND (sys.sysdatabases.name = ''?'')
print ''DB: [?], Log: '' + #LogFile
CHECKPOINT
DBCC SHRINKFILE (#LogFile, 1)
END'
It turns out that only sometimes does this successfully truncate the log of a database. On the databases it fails (no error message, just leaves me with an untruncated log file), it consistently/reproducibly fails.
In the print statement, however, it prints EXACTLY what I would expect it to print. However, if I manually just type out the functional part of this script for each database:
USE [Seed]
CHECKPOINT
DBCC SHRINKFILE('Seedlog', 1)
it works 100% of the time.
Why is my sp_MSForEachDB "loop" not working as expected? What am I missing?
It looks like your query is returning the logical name for the data file and not the log file? (fileID=1)

Automating DBCC CHECKDB

I am currently trying to script a job on SQL Server 2005 that will automate the DBCC CHECKDB process. Basically, I am using a cursor to run through and run DBCC CHECKDB on every database on an instance. Sometimes it works, running through every database and logging the errors in a table I have designed for that purpose and sometimes it only runs through a few of the databases and stop. Does anyone have any idea what is going on? I have included the code that I use for the cursor.
DECLARE #DbName varchar(100)
DECLARE
GetDbName CURSOR
LOCAL
FORWARD_ONLY
OPTIMISTIC
FOR
SELECT
name
FROM
sys.databases
ORDER BY
name
OPEN GetDbName
FETCH NEXT FROM GetDbName
INTO #DbName
WHILE (##fetch_status = 0)
BEGIN
print #DbName
INSERT INTO
TempLog
EXEC('DBCC CHECKDB ('+ #DbName +') WITH NO_INFOMSGS, TABLERESULTS')
FETCH NEXT FROM GetDbName
INTO #DbName
END
CLOSE GetDbName
DEALLOCATE GetDbName
Errors of a sufficiently high (20+) severity -- in my experience these are most often corruptions such as invalid text pointers -- will stop whatever SQL job is currently running with extreme prejudice, ignoring try/catch constructs and killing the connection. I would suggest moving this task out to an external process with a new connection for each DBCC CHECKDB command so that it can continue in the face of high-severity errors.
use the undocumented sp_MSforeachdb stored proc
exec sp_msforeachdb 'use ?; dbcc checkdb'

Resources