ChrisTitus.com Resources for IT Professionals

3Nov/100

Grow Database File in SQL

I use both SQL Express 2005 on a server and SQL 2008 on another server. The following script is nice to manually grow a file. By default, SQL auto-grows database files by 10% when it runs low on space. In most cases this is fine, but I'm a perfectionist and hate receiving errors about low DB Space in my SCE (System Center Essentials) console.

use OperationsManager
declare @dbname nvarchar(300)
declare @dbsize dec(15)
declare @UsedSpace dec(15)
declare @FreeProc int
set @dbname=REPLACE(db_name(),'''','''''')
EXEC('DBCC UPDATEUSAGE ('''+@dbname+''')')
set @dbname=QUOTENAME(@dbname)
select @dbsize = sum(convert(dec(15),size))
from dbo.sysfiles
where (status & 64 = 0)
select @UsedSpace=sum(convert(dec(15),reserved))
from sysindexes where indid in (0, 1, 255)
select @dbsize,@UsedSpace
select @FreeProc=((@dbsize-@UsedSpace)*100)/@dbsize
--If free under 20%, grow database by 20%
--The first datafile is used
if @FreeProc<20 begin
declare @filename nvarchar(300)
declare @bytesperpage dec(15)
declare @pagesperMB dec(15)
declare @newsize dec(15)
select @filename=(select top 1 [name] from dbo.sysfiles where (status & 64 = 0) order by groupid,fileid)
set @filename=REPLACE(rtrim(@filename),'''','''''')
select @bytesperpage = [low]
from master.dbo.spt_values
where number = 1 and type = 'E'
set @pagesperMB = 1048576 / @bytesperpage
set @newsize=(@dbsize*12/10)/@pagesperMB
exec('ALTER DATABASE '+@dbname+' MODIFY FILE (NAME = N'''+@filename+''', SIZE = '+@newsize+')')
end

25Oct/101

System Center Essentials 2007 OperationsManager SQL DB too large (4GB +)

I run System Center Essentials 2007 and by default it installs into SQL Server Express on EBS Installations. This is normally not an issue, but if the database grows larger than 4GB than you will no longer get the benefit of SCE 2007 as SQL Server Express has a 4GB limitation on its databases. I noticed that my database was growing considerably and before I knew it my OperationsManager DB was larger than 4GB.

First, I groomed the database and reduced all retention periods via the EBS teams SCE 2007 blog post. For instructions on how to groom SCE 2007, here is their blog post. This outlined how to reduce the retention period of various SCE 2007 buckets in both SQL Server Management Studio and SCE 2007 Administrative Console.

Second, I used shrink on the OperationsManager and OperationsManagerDW Databases in SQL Management Studio. This is done via SQL Query or by Right Clicking Database -> Tasks -> Shrink -> Database. On next screen leave options default and hit OK.

These both helped me considerably, but I noticed the OperationsManager was still quite large. I found a solution on Experts Exchange that finally reduced the Database well below the 4GB limit. There is aparently a bug in SCE 2007 SP1 that bloats the dbo.LocalizedText table to several gigs if left unchecked for a year. Here is a complete Solution step-by-step.

Step 1: Create a Backup of the Database
-Right-Click OperationsManager -> Tasks -> Back Up...

Step 2: Verify you have a large dbo.LocalizedText table
-Run the Following SQL Query on OperationsManager. This is also great for checking other SQL Databases to see which table is eating up all the space.


USE OperationsManager
DECLARE @TblNames Table
(
COUNTER INT IDENTITY(1,1),
tbl_name nvarchar(100) NULL,
row varchar(10),
reserved varchar(10),
data varchar(10),
index_size varchar(10),
unused varchar(10)
)
DECLARE @ROWCOUNT INT
DECLARE @I INT
DECLARE @str nvarchar(100)
SET @I = 1
INSERT INTO @TblNames(tbl_name) SELECT name FROM sys.Tables
SET @ROWCOUNT = @@ROWCOUNT
WHILE @I <= @ROWCOUNT
BEGIN
SELECT @str = tbl_name FROM @TblNames WHERE COUNTER = @I
INSERT INTO @TblNames EXEC sp_spaceused @str
SET @I = @I +1
END
-- Display results in Sorted order
SELECT tbl_name as TableNm, CAST(REPLACE(data, ' kb', '') as int) as TblSize,
CAST(REPLACE(index_size, ' kb', '') as int) as IdxSize
FROM @tblNames ORDER BY TblSize DESC

Step 3: Clean Up Localized Text
-Run the following SQL Query on OperationsManager. (This will take about 10 minutes per gig.)

-
- Create temp table to speed up looking for a PublisherId when we know the MessageId
BEGIN TRY
CREATE TABLE #PublisherMessageReverseIndex(MessageStringId UNIQUEIDENTIFIER,
MessageId INT)
CREATE CLUSTERED INDEX #PublisherMessageReverseIndex_CI ON #PublisherMessageReverseIndex(MessageStringId)
INSERT INTO #PublisherMessageReverseIndex (MessageStringId, MessageId)
SELECT MessageStringId, MessageId
FROM dbo.PublisherMessages
-- Create temp table of message lengths, message id, and Message Hash with the
-- Message String Id so that we can efficiently figure out whether a given message
-- is duplicated. The duplicate messages generated by the converted MP have
-- different PublisherId's, but everything else is identical. INDEX this TABLE so that
-- we can look up quickly by the MessageStringId and also by values we expect to see
-- duplicated.
CREATE TABLE #LTHashStrings (MessageStringId UNIQUEIDENTIFIER,
LTValueLen INT,
LTValueHash VARBINARY(32),
MessageId INT NULL)
CREATE CLUSTERED INDEX #LTHashStrings_CI ON #LTHashStrings(MessageStringId)
CREATE NONCLUSTERED INDEX #LTHashStrings_NCI1 ON #LTHashStrings(LTValueLen, MessageId, LTValueHash)
-- Create temp table for the Orphaned PublisherStrings that we find. These
-- are rows in PublisherMessages whose corresponding Events have already been groomed away.
-- They still have corresponding rows in LocalizedText. We won't add rows for PublisherMessages
-- which are not for a duplicated message.
CREATE TABLE #OrphanedPublisherStrings (PublisherId UNIQUEIDENTIFIER,
MessageStringId UNIQUEIDENTIFIER)
CREATE CLUSTERED INDEX #OrphanedPublisherStrings_CI ON #OrphanedPublisherStrings(MessageStringId)
-- Create temp table to use in looking up whether a PublisherMessages row still
-- has a corresponding Event. Event_01 etc. have no index on PublisherId, so we
-- don't want to do a query that keeps seeking into EventAllView.
-- If a PublisherId occurs multiple times in the Event tables we will only need it
-- once in our temp table, hence the unique clustered index with IGNORE_DUP_KEY.
-- This keeps the temp table relatively small and will be a time saver for
-- seeing which PublisherMessages are orphaned.
CREATE TABLE #EventAllPublishers (PublisherId UNIQUEIDENTIFIER)
CREATE UNIQUE CLUSTERED INDEX #EventAllPublishers_CI ON #EventAllPublishers (PublisherId)
WITH (IGNORE_DUP_KEY = ON)
-- Populate temp table by scanning EventAllView one time
INSERT INTO #EventAllPublishers(PublisherId)
SELECT PublisherId
FROM EventAllView
-- Populate first Temp table for figuring out which messages are duplicated
INSERT INTO #LTHashStrings (MessageStringId, LTValueLen, LTValueHash, MessageId)
SELECT LTStringId, len(LTValue), HashBytes('SHA1', LTValue), MessageId
FROM dbo.LocalizedText LT
JOIN #PublisherMessageReverseIndex PM ON PM.MessageStringId = LTStringId
-- Create second table for figuring out which messages are duplicated.
CREATE TABLE #LTCountByMessage( LTValueLen INT,
MessageId INT,
LTValueHash VARBINARY(32),
MsgCount INT)
CREATE CLUSTERED INDEX #LTCountByMessage_CI ON #LTCountByMessage(LTValueLen, MessageId, LTValueHash)
-- Populate second message for duplicate message detection by scanning INDEX of
-- the first one and doing a grouped count.
INSERT INTO #LTCountByMessage (LTValueLen, MessageId, LTValueHash, MsgCount)
SELECT LTValueLen, MessageId, LTValueHash, COUNT(1)
FROM #LTHashStrings
GROUP BY LTValueLen, MessageId, LTValueHash
-- Now that we are set up to detect both Orphans and duplicated messages by
-- joining to our relatively small (and properly indexed) temp tables,
-- figure out the OrphanedPublisherStrings that have duplicate messages
INSERT INTO #OrphanedPublisherStrings (PublisherId, MessageStringId)
SELECT PM.PublisherId, PM.MessageStringId
FROM dbo.PublisherMessages PM
JOIN #LTHashStrings LTS ON (LTS.MessageStringId = PM.MessageStringId AND LTS.MessageId = PM.MessageId)
JOIN #LTCountByMessage LTC ON (LTC.LTValueLen = LTS.LTValueLen AND
LTC.MessageId = LTS.MessageId AND LTC.LTValueHash = LTS.LTValueHash)
WHERE PM.PublisherId NOT IN (SELECT PublisherId FROM #EventAllPublishers) AND
LTC.MsgCount > 1
-- Deleting all of the OrphanedPublisherStrings and corresponding LocalizedText rows
-- at once may be too large for the transaction log to handle. Create a
-- numbered / ordered table so that we can delete them in relatively small batches
-- and not overtax the transaction log.
CREATE TABLE #NumberOrphanPublisherStrings(OrphanNum INT IDENTITY,
PublisherId UNIQUEIDENTIFIER,
MessageStringId UNIQUEIDENTIFIER)
CREATE CLUSTERED INDEX #NumberOrphanPublisherStrings_CI on #NumberOrphanPublisherStrings(OrphanNum)
-- Populate Numbered TABLE
INSERT INTO #NumberOrphanPublisherStrings (PublisherId, MessageStringId)
SELECT PublisherId, MessageStringId FROM #OrphanedPublisherStrings
END TRY
BEGIN CATCH
GOTO Error
END CATCH
-- Set up variables so that we can delete our orphaned rows
-- If transaction log fills up, try reducing the @OrphanIncrement value,
-- which controls the number of rows that we delete at a time
DECLARE @OrphanNum INT
DECLARE @OrphanIncrement INT
DECLARE @OrphanLimit INT
SET @OrphanNum = 0
SET @OrphanIncrement = 10000
SELECT @OrphanLimit = MAX(OrphanNum) FROM #NumberOrphanPublisherStrings
BEGIN TRY
WHILE @OrphanNum < @OrphanLimit
BEGIN
DELETE dbo.LocalizedText FROM
#NumberOrphanPublisherStrings OPS JOIN dbo.LocalizedText LT
ON LT.LTStringId = OPS.MessageStringId
WHERE OPS.OrphanNum >= @OrphanNum AND OPS.OrphanNum < @OrphanNum + @OrphanIncrement
DELETE dbo.PublisherMessages FROM
#NumberOrphanPublisherStrings OPS JOIN dbo.PublisherMessages PM
ON PM.PublisherId = OPS.PublisherId
WHERE OPS.OrphanNum >= @OrphanNum AND OPS.OrphanNum < @OrphanNum + @OrphanIncrement
SET @OrphanNum = @OrphanNum + @OrphanIncrement
END
END TRY
BEGIN CATCH
GOTO Error
END CATCH
Error:
IF @@ERROR <> 0
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage;
-- Try to drop all of the Temp tables
BEGIN TRY
IF EXISTS (SELECT 1 FROM tempdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '#PublisherMessage%')
DROP TABLE #PublisherMessageReverseIndex
IF EXISTS (SELECT 1 FROM tempdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '#OrphanedPublisherStrings%')
DROP TABLE #OrphanedPublisherStrings
IF EXISTS (SELECT 1 FROM tempdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '#LTHashStrings%')
DROP TABLE #LTHashStrings
IF EXISTS (SELECT 1 FROM tempdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '#EventAllPublishers%')
DROP TABLE #EventAllPublishers
IF EXISTS (SELECT 1 FROM tempdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '#LTCountByMessage%')
DROP TABLE #LTCountByMessage
IF EXISTS (SELECT 1 FROM tempdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '#NumberOrphanPublisherStrings%')
DROP TABLE #NumberOrphanPublisherStrings
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH

Step 4: Reindex Database and finish
-Run the following SQL Query on the OperationsManager.

USE OperationsManager
go
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET QUOTED_IDENTIFIER ON
SET NUMERIC_ROUNDABORT OFF
EXEC SP_MSForEachTable "Print 'Reindexing '+'?' DBCC DBREINDEX ('?')"