DBPedias

Your Database Knowledge Community

Jason Brimhall

  1. MCM Road Less Traveled

    I began this post back in October of 2012 after learning that I had passed the SQL Server 2008 MCM Knowledge exam.  I had set it aside in hopes of polishing it off after my first lab attempt at Summit 2012.  Notice I said first attempt?  I failed that first attempt.  This is a bit of a story about the journey through the exams and the results.

    longuphillbw

    The first attempt was a little crushing.  A mix of emotions concerning the lab came over me because I felt I knew the technology.  While the results were not desirable – the end effect was desirable and I am glad I did not succeed on that first attempt.

    When I took the knowledge exam, I took my time going through the questions and examined the questions.  I think that was a good method for that exam – I was trying to ensure I understood the question and didn’t miss anything that was stated.  Though I took it slowly, that is not an indictment to difficulty for the exam.  I was well prepared and did not want to make any invalid assumptions.

    Fast forward a little bit to the first attempt at the lab exam.  I had a plan going into the exam.  I felt confident in my skills.  I felt relaxed with the technology.  Then the exam started and my plan went out the window.  I started making assumptions about the exam that I really should not have done.  I rushed a few scenarios that I should not have rushed because I could have done them better.  In the end, I was my own worst enemy during the exam.  This is not a characteristic of an MCM.  Even when the pressure is on, doing what you know and being methodical is a wonderful asset.

    Having failed the first time, I wondered if I was ready to be an MCM.  In retrospect, I was not.  Leading up to the exam, I did feel that I was ready for the exam.  I had read several articles such as the following to try and figure out if I was ready.  In the end, it’s more of a gut check and a leap of faith for some.

    Gavin Payne http://gavinpayneuk.com/2012/05/01/knowing-when-youre-ready-to-attempt-to-become-an-mcm-of-sql-server/

    Joe Sack http://blogs.msdn.com/b/joesack/archive/2010/11/21/how-do-you-know-if-you-re-ready-for-sql-mcm.aspx

    I even perused some of the resource type of articles such as the following.

    Nick Haslam http://nhaslam.com/blog/2011/11/19/mcmprep-88-970-sql-mcm-knowledge-exam-sqlmcm/

    MCM Blog http://blogs.technet.com/b/themasterblog/archive/tags/sql+server+mcm/

    Robert Davis Amazon MCM Reading List http://www.amazon.com/Official-Server-Certified-Master-Readiness/lm/R3RB13PQ7D8TKB

    Brent Ozar MCM Articles http://www.brentozar.com/sql-mcm/

    In the end, this post by Rob Farley sums it up nicely for me (http://sqlblog.com/blogs/rob_farley/archive/2012/12/23/the-mcm-lab-exam-two-days-later.aspx).  Rob references an article by Tom LaRock and some of what Tom did and didn’t do.  As well as some theory on how to approach the exam at certain stages.  Of all those things, I think the best advice I read as well as I could recommend is to get a study buddy.

    It’s not sooo much to have somebody to bounce ideas off of, as it is to have somebody to try and teach.  I worked with Wayne Sheffield as we prepped for our retakes.  The biggest benefit was, as I said, in that I could pick a topic and try to teach Wayne.  He did the same to me.  And then, we could question each other on what-if type questions about our selected topics.  Another benefit is to have a heat-check so to speak.  Having a study buddy can help you from straying too far off into tangents or maybe keeping you from wasting too much time on a topic they might feel you understand exceptionally well.

    If you are reading this far, you probably have figured that I have taken the Lab a second time.  I was debating whether to do it this soon due to life and family.  My wife continued to push me to do it (much as Nic Cain experienced here - http://sirsql.net/blog/2012/3/26/achievement-unlockedmcm-sql-server-2008.html).  Without Krista pushing me, I might have delayed even longer.  It was also helpful to have a co-worker and friend pushing me along too (I’m a little competitive).  Thanks again Wayne!  You can read his experiences here.

    I received my notification email while driving home from Vegas last week.  Seeing that email pop up from boB Taylor gets your heart racing a little bit – especially after you have failed the lab previously.  I glanced at the email and only need to go to the first word – “CONGRATULATIONS!”

    MCM_SQL

    Oh yeah!  Vindication, satisfaction, elation, joy and general happiness set in quickly.  Memories of that old “Wide World of Sports” show from a long time ago spilled in with the words “Agony of Defeat, Thrill of Victory.”

    I am glad I took the time to pursue this certification.  This adventure has helped me to learn and grow as a DBA.  I am also grateful to those that helped or pushed in some way or another – whether they knew it or not at the time.  Last but not least, I am thankful for my employer Ntirety.  They understood the importance of this for me and were very supportive in this endeavor.

    As one final take away, I was recently shown this link with some interesting questions to help you decide if you feel you are ready to take the exams.

  2. Audit Configuration Changes

    Do you know the last time a Server Property was changed on your instances of SQL Server?

    Are you wondering when the setting for max degree of parallelism was changed?

    Do you know who changed the setting?

    In some environments there are a lot of hands trying to help mix the pot.  Sometimes more hands can make light work.  This is not always the case though.  More hands in the mix can be a troublesome thing.  Especially when things start changing and the finger pointing starts but nobody really knows for sure who made the change or when the change was made.

    I know, that is why there is this concept called change management.  If you make a change to a setting, it should be documented, approved and communicated.  Unfortunately the process does not always dictate the work flow and it may be circumvented.  This is why auditing is a tool that is in place and should be in place – like it or not.

    Auditing can be a very good tool.  Like many things – too much of a good thing is not a good thing.  Too much auditing can be more of a hindrance than help.  You don’t want to cause interference by auditing too many things.  You also don’t want too much data that the facts get blurred.  I hope that this script strikes more of a balance with just the right amount of data being captured to be of use.

    The basic principle to auditing server configs is to find what values changes, when they were changed and by whom.  There are ancillary details that can be helpful in tracking the source of the change such as the hostname of the source computer making the change.  These are all things that we should capture.  But if a setting hasn’t changed – then we need not necessarily report that the setting was unchanged (it should go without saying).

    So for this, I created a couple of tables and then a script that I can add to a job to run on a regular basis.  I can put the script in a stored procedure should I desire.  I’ll leave that to you as an exercise to perform.

    Code block    
    USE AdminDB;
    GO
    SET NOCOUNT ON;
     
    DECLARE @ConfigLastUpdateDate	DATETIME
    		,@PreviousPollDate		DATETIME
    		,@MaxPollDate			DATETIME
    		,@PATH					NVARCHAR(260);
     
    SELECT @PATH = REVERSE(SUBSTRING(REVERSE([PATH]), 
    						CHARINDEX('\', REVERSE([path])), 260)) + N'LOG.trc'
    	FROM sys.traces 
    	WHERE is_default = 1;
     
    IF OBJECT_ID('tempdb..#DBCCConfig') IS NOT NULL DROP TABLE #DBCCConfig;
    	CREATE TABLE #DBCCConfig(
    		[ParentObject] VARCHAR (100),
    		[Object]       VARCHAR (100),
    		[Field]        VARCHAR (100),
    		[Value]        VARCHAR (100)); 
     
    INSERT INTO #DBCCConfig (ParentObject, Object, Field, Value)
    	EXECUTE ('DBCC CONFIG WITH TABLERESULTS');
     
    WITH cte AS
    (
    SELECT cfgupddate = MAX(CASE WHEN t1.Field = 'cfgupddate' THEN t1.Value ELSE NULL END),
            cfgupdtime = MAX(CASE WHEN t1.Field = 'cfgupdtime' THEN t1.Value ELSE NULL END)
    FROM #DBCCConfig t1
    WHERE   Field IN ('cfgupddate', 'cfgupdtime')
    )
    SELECT  @ConfigLastUpdateDate = CONVERT(DATETIME,t3.configure_upd_dt)
    FROM    cte t1
            CROSS APPLY (SELECT cfgupddate = DATEADD(DAY, CONVERT(INT, t1.cfgupddate), '1900-01-01')) t2
            CROSS APPLY (SELECT configure_upd_dt = DATEADD(ms, CONVERT(INT, t1.cfgupdtime)*3.3, t2.cfgupddate)) t3;
     
    IF NOT EXISTS (SELECT Name 
    					FROM sys.objects 
    					WHERE name = 'SysConfigAudit')
    	CREATE TABLE SysConfigAudit (
    		configuration_id	int
    		,name	NVARCHAR(256)
    		,value	sql_variant
    		,minimum	sql_variant
    		,maximum	sql_variant
    		,value_in_use	sql_variant
    		,description	NVARCHAR(MAX)
    		,is_dynamic	bit
    		,is_advanced	BIT
    		,PollDate		DATE
    		,LastConfigUpdtDate	DATETIME)
     
    IF NOT EXISTS (SELECT Name 
    					FROM sys.objects 
    					WHERE name = 'SysConfigChangeLog')
    	CREATE TABLE SysConfigChangeLog (
    		configuration_id	int
    		,name	NVARCHAR(256)
    		,CurrValue	SQL_VARIANT
    		,PrevValue	SQL_VARIANT
    		,description	NVARCHAR(MAX)
    		,PollDate		DATE
    		,LastConfigUpdtDate	DATETIME
    		,PrevConfigUpdtDate	DATETIME
    		,ChangeDate	DATETIME
    		,ChangeBy	NVARCHAR(256)
    		,HostName	NVARCHAR(256)
    		,ChangeSPID	SQL_VARIANT
    		,Changedata	NVARCHAR(2000)
    		,ApplicationName NVARCHAR(256)
    		,Severity INT
    		,ERROR SQL_VARIANT
    		,ChangeBySessionLogin	NVARCHAR(256))
    IF NOT EXISTS (SELECT Name 
    					FROM sys.key_constraints 
    					WHERE name ='PK_SysConfigChangeLog' 
    						AND OBJECT_NAME(parent_object_id) = 'SysConfigChangeLog')
    BEGIN
    ALTER TABLE dbo.SysConfigChangeLog ADD CONSTRAINT
    	PK_SysConfigChangeLog PRIMARY KEY CLUSTERED 
    	(
    	configuration_id,
    	ChangeDate DESC
    	) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
    		ON Admin_Data
     
    END
     
    SET @MaxPollDate = ISNULL((SELECT TOP 1 PollDate
    								FROM dbo.SysConfigAudit 
    								ORDER BY PollDate DESC),'1/1/1900')
     
    /* Write the configurations out to audit table 
    */
    IF CONVERT(DATE,GETDATE()) <> @MaxPollDate
    BEGIN
    	INSERT INTO SysConfigAudit
    			( configuration_id ,
    			  name ,
    			  value ,
    			  minimum ,
    			  maximum ,
    			  value_in_use ,
    			  description ,
    			  is_dynamic ,
    			  is_advanced,
    			  PollDate,
    			  LastConfigUpdtDate
    			)
    	SELECT configuration_id,name
    			,value
    			,minimum,maximum
    			,value_in_use
    			,description
    			,is_dynamic,is_advanced
    			,GETDATE(),@ConfigLastUpdateDate
    		FROM master.sys.configurations;
    END
     
    /* Recast MaxPollDate */
    SET @MaxPollDate = ISNULL((SELECT TOP 1 PollDate
    								FROM dbo.SysConfigAudit 
    								ORDER BY PollDate DESC),'1/1/1900')
     
    SET @PreviousPollDate = ISNULL((SELECT TOP 1 PollDate 
    									FROM dbo.SysConfigAudit 
    									WHERE Polldate <> @MaxPollDate 
    									ORDER BY PollDate DESC),'1/1/1900');
     
    /*	A configuration has changed and a reboot has occurred 
    	causing the updtdate to be written to the config block of the page 10
    	The configuration may not be written to the page, but we will write it to
    	the table anyway and then compare to the default trace file in all cases.
    	If there are any values changed, then write those to the change log with
    	the login of the person who changed the value
    */
     
    BEGIN
     
    WITH presel AS (
    SELECT df.LoginName,df.TextData,df.StartTime,df.HostName,df.Severity,df.DatabaseName,df.SPID,df.ERROR
    		,df.SessionLoginName,df.ApplicationName
    		,SUBSTRING(df.TextData
    					,CHARINDEX('''',df.TextData)+1
    					,CHARINDEX('''',df.TextData
    								,CHARINDEX('''',df.TextData)+1)-CHARINDEX('''',df.TextData)-1)
    				AS ConfigName
    		,ROW_NUMBER() OVER (PARTITION BY SUBSTRING(df.TextData
    													,CHARINDEX('''',df.TextData)+1
    													,CHARINDEX('''',df.TextData
    															,CHARINDEX('''',df.TextData)+1)-CHARINDEX('''',df.TextData)-1) 
    								ORDER BY df.StartTime DESC) AS RowNum
    	FROM ::fn_trace_gettable( @path, DEFAULT )  df
    	WHERE 1=1
    		AND df.TextData LIKE '%Configuration option%'
    		AND df.ApplicationName NOT IN ('SQLAgent - Initial Boot Probe','SQLAgent - Enabling/disabling Agent XPs')
    		AND df.Severity IS NOT NULL
    )
     
    INSERT INTO SysConfigChangeLog (configuration_id,name,CurrValue,PrevValue,description,PollDate	
    							,LastConfigUpdtDate	,PrevConfigUpdtDate	,ChangeDate,ChangeBy
    							,HostName,ChangeSPID,Changedata,ApplicationName,Severity
    							,ERROR,ChangeBySessionLogin)
    SELECT CUR.configuration_id,cur.NAME,cur.VALUE AS CurrValue,Prev.VALUE AS PrevValue,cur.description
    		,cur.polldate, cur.LastConfigUpdtDate,prev.LastConfigUpdtDate AS PrevConfigUpdtDate
    		,df.StartTime AS ChangeDate,df.LoginName AS ChangeBy,df.HostName,df.SPID AS ChangeSPID
    		,df.TextData AS ChangeData,df.ApplicationName,df.Severity,df.ERROR
    		,df.SessionLoginName AS ChangeBySessionLogin
    	FROM AdminDB.dbo.SysConfigAudit CUR
    		INNER JOIN AdminDB.dbo.SysConfigAudit Prev
    			ON CUR.configuration_id = Prev.configuration_id
    			AND CUR.PollDate = @MaxPollDate
    			AND Prev.PollDate = @PreviousPollDate
    		CROSS APPLY presel  df
    	WHERE df.RowNum = 1
    		AND df.ConfigName = CUR.NAME
    		AND CUR.VALUE <> Prev.VALUE	
    		AND NOT EXISTS (SELECT configuration_id
    							FROM SysConfigChangeLog
    							WHERE Name = Cur.NAME
    								AND CurrValue = CUR.VALUE
    								AND PrevValue = Prev.VALUE
    								AND ChangeDate = df.StartTime
    								AND ChangeBy = df.LoginName)
    		;
    END
     
    SELECT *
    	FROM dbo.SysConfigChangeLog
    	ORDER BY ChangeDate DESC;

    Here I am trapping the config settings on a daily basis (as the script is written for now). I then cross reference the current settings against the previous settings.  Then I check the default trace to see if any of those settings exist in the trace file.

    The default trace captures the data related to configuration changes.  On busy systems, it is still possible for these settings to roll out of the trace files.  For those systems, we may need to make some alterations to the script (such as running more frequently and changing the comparisons to account for smaller time intervals than a day break).

    To ensure proper comparison between the audit tables and the trace file, note the substring function employed.  I can capture the configuration name and then join to the audit tables on configuration name.

    This has proven useful to me so far in tracking who did what to which setting and when they did it.

    I hope you will find good use for it as well.

  3. January S3OLV 2013

    I hope you are ready for some free SQL learning.  Coming up on Thursday January 10th at 6:30 PM Pacific (GMT – 8 ) we will have a stellar opportunity to do just that – Learn!

    Edwin Sarmiento will be presenting for us this at the upcoming monthly Las Vegas User Group meeting.  Edwin has chosen (well he left it up to me to decide) to present on Powershell.

    Title: Windows PowerShell for the SQL Server DBA

    Abstract

    Windows PowerShell is becoming the scripting language of choice for managing Microsoft servers and workstations. And while T-SQL is still the scripting language for DBAs to administer and manage SQL Server, there are tons of stuff that can be easily done with Windows PowerShell. In this session, learn Windows PowerShell from the ground up and how you can use it with SQL Server Management Objects (SMO) to administer and manage a SQL Server instance.

    Here is the bonus behind all of this – we will be broadcasting the presentation to the virtual sphere.Here are the details for joining the meeting via livemeeting.

    Copy this address and paste it into your web browser:

    Copy and paste the required information:

     

  4. A Firm Foundation

    Last week I sent out an invite for the monthly TSQL Tuesday party.

    The theme for the party is a take on the words resolve or resolution.  I was hoping the theme would encourage some reflection and sharing of real life experiences that have led to a difference being made.

    I have resolved on two stories to share.  Both are rather short and simple in nature.

     

    This arch (in Arches National Park, Ut.) has stood RESOLUTE for milennia

    Story the First

    Near the end of the year in 2012, I inherited a database that had not had a consistency check done on it – ever!  In checking the page_verify setting, I found that it was set to none as well.  Both of these should be alarming to any DBA – unless you are completely unconcerned by corrupt data and the potential for corrupt data.  Never-mind the potential business repercussions of having corrupt or lost data.

    To find what level of page verification you have enabled, it is a matter of a quick script like the following.

    Code block    
    SELECT name, page_verify_option_desc
    	FROM sys.databases;

    You can have any one of three settings for your page_verify.  The recommended option is to have CHECKSUM enabled.  If you see NONE or TORN_PAGE_DETECTION, you really need to consider changing that.  Keep in mind if you are still running SQL 2000, CHECKSUM is not an option and the query provided will fail.

    Changing the verify option is very simple as well.  It only requires an Alter Database to be run such as the following.

    Code block    
    ALTER DATABASE [msdb]
    	SET PAGE_VERIFY CHECKSUM;

    You will probably notice that I am using the msdb in my sample script.  There is a reason for this that will be shown later.  Just keep in mind that msdb should not need to be changed because it should already be using the CHECKSUM option.

    What if you have numerous databases that are not using the CHECKSUM method?  It can become rather tedious to change each of those manually.  That is why we might come up with a cursor such as the following.

    Code block    
    DECLARE
    	@DBName SYSNAME,
    	@SQL    VARCHAR(512);
     
    DECLARE dbchecksum CURSOR
    	LOCAL STATIC FORWARD_ONLY READ_ONLY
    	FOR SELECT name
    		FROM sys.databases
    		WHERE name not in ('tempdb')
    			AND state_desc = 'online'
    			AND page_verify_option_desc <> 'Checksum';
     
    OPEN dbchecksum;
    FETCH NEXT FROM dbchecksum INTO @DBName;
    WHILE @@FETCH_STATUS = 0
    BEGIN
    SET @SQL = 'ALTER DATABASE [' + @DBName +'];' +CHAR(10)+CHAR(13)
    SET @SQL = @SQL + 'SET PAGE_VERIFY CHECKSUM;' +CHAR(10)+CHAR(13)
     
    EXECUTE (@SQL);
    SET @SQL = ''
     
    FETCH NEXT FROM dbchecksum INTO @DBName;
    END
    CLOSE dbchecksum;
    DEALLOCATE dbchecksum;

    This script is only checking for databases that are not using CHECKSUM.  Then it loops through and changes the setting to use CHECKSUM.

    I strongly caution about running this in production without an outage window!  I make that recommendation for very simple reasons.  First, the change is to a production system.  Second, the change can have a temporary adverse effect.  Now before you get too excited about it, I have a short demonstration.

    Here is a script broken out into three sections.

    Code block    
    SELECT TOP(10) [type] AS [Memory Clerk Type], SUM(single_pages_kb) AS [SPA Mem, Kb] 
    FROM sys.dm_os_memory_clerks WITH (NOLOCK)
    GROUP BY [type]  
    ORDER BY SUM(single_pages_kb) DESC OPTION (RECOMPILE);
    SELECT DB_NAME(database_id) AS [DATABASE Name],
    COUNT(*) * 8/1024.0 AS [Cached SIZE (MB)]
    FROM sys.dm_os_buffer_descriptors
    WHERE database_id <> 32767 -- ResourceDB
    --AND database_id > 4 -- system databases
    GROUP BY DB_NAME(database_id)
    ORDER BY [Cached SIZE (MB)] DESC OPTION (RECOMPILE);
     
    SELECT DB_NAME(dbid) AS DbName,dbid,SUM(size_in_bytes)/1024/1024 AS TotalPlanCacheSize_in_MB
    FROM sys.dm_exec_cached_plans cp WITH (NOLOCK)
    CROSS APPLY sys.dm_exec_sql_text(plan_handle) st
    GROUP BY dbid
    Code block    
    ALTER DATABASE [msdb]
    		SET PAGE_VERIFY CHECKSUM;
    Code block    
    SELECT TOP(10) [type] AS [Memory Clerk Type], SUM(single_pages_kb) AS [SPA Mem, Kb] 
    FROM sys.dm_os_memory_clerks WITH (NOLOCK)
    GROUP BY [type]  
    ORDER BY SUM(single_pages_kb) DESC OPTION (RECOMPILE);
    SELECT DB_NAME(database_id) AS [DATABASE Name],
    COUNT(*) * 8/1024.0 AS [Cached SIZE (MB)]
    FROM sys.dm_os_buffer_descriptors
    WHERE database_id <> 32767 -- ResourceDB
    --AND database_id > 4 -- system databases
    GROUP BY DB_NAME(database_id)
    ORDER BY [Cached SIZE (MB)] DESC OPTION (RECOMPILE);
     
    SELECT DB_NAME(dbid) AS DbName,dbid,SUM(size_in_bytes)/1024/1024 AS TotalPlanCacheSize_in_MB
    FROM sys.dm_exec_cached_plans cp WITH (NOLOCK)
    CROSS APPLY sys.dm_exec_sql_text(plan_handle) st
    GROUP BY dbid

    Sections one and three are the same.  This script is used to measure various memory components within SQL Server.  The second section is the change we will make to the msdb database.  The queries in the first and third section perform the following: retrieve memory clerk usage (aggregated to memory clerk type), retrieve total data pages stored in cache (aggregated by database), and retrieve the plan cache use (aggregated by database).

    Now on to some pre and post change results.  First with what my results were prior to the change.

    Memory Clerk Usage
    Memory Clerk Type SPA Mem, Kb
    CACHESTORE_SQLCP 156184
    CACHESTORE_PHDR 45904
    CACHESTORE_OBJCP 20664
    USERSTORE_DBMETADATA 8472
    USERSTORE_SCHEMAMGR 6376

     

    Pages in Cache
    Database Name Cached Size (MB)
    msdb 12.265625

     

    Plan Cache
    DbName dbid TotalPlanCacheSize_in_MB
    NULL 32767 42
    NULL NULL 150
    msdb 4 13
    ReportServer$ADMIN 5 0
    MDW 28 8
    AdminDB 14 0

    And the following are the post change results.

    Memory Clerk Usage
    Memory Clerk Type SPA Mem, Kb
    CACHESTORE_SQLCP 109160
    CACHESTORE_PHDR 36744
    CACHESTORE_OBJCP 9152
    USERSTORE_DBMETADATA 8472
    USERSTORE_SCHEMAMGR 6296

     

    Data Pages in Cache
    Database Name Cached Size (MB)
    msdb 12.265625

     

    Plan Cache
    DbName dbid TotalPlanCacheSize_in_MB
    NULL 32767 36
    NULL NULL 104
    ReportServer$ADMIN 5 0
    MDW 28 8
    AdminDB 14 0

    First observation I want to point out is with the second result for both the pre and post run.  Making this change will not affect the pages in cache.  This goes along with what we have been taught by Paul Randal – that a CHECKSUM is not performed immediately (I paraphrased).  You can read more about the CHECKSUM and some misconceptions about it here.

    If we now turn our attention to the first and third result sets, we will see that there are changes in the memory clerks used and the plan cache.  Starting with the the third result set (both pre and post) we see that the ResourceDB decreased in total plan cache size.  The NULL item (adhoc queries not associated to a specific database) also decreased.  After that, the only change in size is the msdb database – disappeared from the results due to no plan cache in use associated to this database.  (Starting to see why I chose the msdb database for this demo?)

    If you now look closer at the results for the first query on both sides of the change, you will see correlating changes to the plan cache.  Notice that CACHESTORE_SQLCP dropped by about 46MB (correlates to the null entry from query 3).  But of those clerks listed, you will see that only USERSTORE_DBMETADATA did not change in size.

    Looking at these results should demonstrate why this change should be performed during a maintenance window.  There will be an effect on performance and I would rather you let the business know what is coming down the pipe.  This change is akin to running DBCC FLUSHPROCINDB(<db_id>);.  There are other database settings that will have the same effect.  You can read a little about that from Kalen Delaney – here.

    Story the Second

    This story is far less interesting and a whole lot shorter.  This falls into the category of professional development and fine tuning my skills.  I took the MCM lab exam during the PASS Summit.  I failed, not unlike many who have attempted it.  That is all fine and well. I learned some things about myself and I learned some areas that may need some resolution (sharpened focus).

    So as more of a resolution upon which I have greater resolve than a New Years resolution, I will be retaking the Lab exam.  And I will be getting my MCM in the near future.  Just sayin’!

  5. SSRS Subscriptions Report

    As a part of my series leading up to Christmas 2012, I shared a script to Report on SSRS Subscriptions.  It was soon found to have a bug with SQL Server 2008 R2 SP2.  IN the comments on that post, I promised to post an updated script.  Here is that update – without the bug.

    Code block    
    DECLARE @ReportName VARCHAR(100)
    SET @ReportName = NULL;
     
    CREATE TABLE #morepower (MonthDate BIGINT,N BIGINT,PowerN BIGINT PRIMARY KEY CLUSTERED
    							,NameofMonth VARCHAR(25),WkDay VARCHAR(25))
    ;
     
    WITH powers(powerN, n) AS (
    	SELECT POWER(2,number), number 
    		FROM master.dbo.spt_values 
    		WHERE type = 'P' AND number < 31)
     
    INSERT INTO #morepower ( MonthDate ,N,PowerN ,NameofMonth ,WkDay)
    	SELECT ROW_NUMBER() OVER (ORDER BY N) AS MonthDate,N,PowerN
    			,CASE WHEN N BETWEEN 0 AND 11 
    				THEN DATENAME(MONTH,DATEADD(MONTH,N+1,0)-1)
    				ELSE NULL
    				END AS NameofMonth
    			,CASE WHEN N BETWEEN 0 AND 6
    				THEN DATENAME(weekday,DATEADD(DAY,n+1,0)-2)
    				ELSE NULL
    				END AS WkDay
    		FROM powers
     
    SELECT DISTINCT s.ScheduleID,Ca.PATH AS ReportManagerPath,Ca.Name AS ReportName
    		, U.UserName AS SubscriptionCreator
    		,Su.Description AS SubscriptionDescription,S.StartDate,Su.LastRunTime
    		,CASE 
    				WHEN s.RecurrenceType = 1 THEN 'One Off'
    				WHEN s.RecurrenceType = 2 THEN 'Hour'
    				WHEN s.RecurrenceType = 4 THEN 'Daily'
    				WHEN s.RecurrenceType = 5 THEN 'Monthly' 
    				WHEN s.RecurrenceType = 6 THEN 'Week of Month' 
    			END AS RecurrenceType
    		,s.EventType
    		,ISNULL(REPLACE(REPLACE(STUFF(
    					(SELECT ', ['+CONVERT(VARCHAR(20),MonthDate)+']' AS [TEXT()] 
    						FROM #morepower m1 
    						WHERE m1.powerN < s.DaysofMonth+1 
    							AND s.DaysofMonth & m1.powerN <>0 
    						ORDER BY N FOR XML PATH(''), TYPE).VALUE('.','VARCHAR(MAX)')
    			   , 1, 2, ''),'[',''),']','')
    			,'N/A') AS DaysofMonth
    		,ISNULL(c1.NameOfMonth,'N/A') AS MonthString
    		,ISNULL(c2.WkDays,'N/A') AS DaysofWeek
    		,CASE MonthlyWeek
    				WHEN 1 THEN 'First'
    				WHEN 2 THEN 'Second'
    				WHEN 3 THEN 'Third'
    				WHEN 4 THEN 'Fourth'
    				WHEN 5 THEN 'Last'
    				ELSE 'N/A'
    			END AS MonthlyWeek
    		,ISNULL(CONVERT(VARCHAR(10),s.DaysInterval),'N/A') AS DaysInterval
    		,ISNULL(CONVERT(VARCHAR(10),s.MinutesInterval),'N/A') AS MinutesInterval
    		,ISNULL(CONVERT(VARCHAR(10),s.WeeksInterval),'N/A') AS WeeksInterval
    	FROM #morepower mp, dbo.Schedule s
    		INNER JOIN ReportSchedule RS
    			ON S.ScheduleID = RS.ScheduleID
    		INNER JOIN CATALOG Ca
    			ON Ca.ItemID = RS.ReportID
    		INNER JOIN Subscriptions Su
    			ON Su.SubscriptionID = RS.SubscriptionID
    		INNER JOIN Users U
    			ON U.UserID = S.CreatedById
    			OR U.UserID = Su.OwnerID
    	CROSS APPLY (SELECT s.ScheduleID,REPLACE(REPLACE(STUFF(
    							(SELECT ', ['+ NameofMonth + ']' AS [TEXT()] 
    								FROM #morepower m1 ,dbo.Schedule s1
    								WHERE m1.NameofMonth IS NOT NULL 
    									AND m1.powerN & s1.MONTH <>0 
    									AND s1.ScheduleID = s.ScheduleID
    								ORDER BY N FOR XML PATH(''), TYPE).VALUE('.','VARCHAR(MAX)')
    							, 1, 2, ''),'[',''),']','') AS NameOfMonth)c1
    	CROSS APPLY (SELECT s.ScheduleID,REPLACE(REPLACE(STUFF(
    							(SELECT ', [' + WkDay + ']' AS [TEXT()] 
    								FROM #morepower m1 ,dbo.Schedule s2
    								WHERE m1.WkDay IS NOT NULL 
    									AND DaysOfWeek & m1.powerN <>0
    									AND  s2.ScheduleID = s.ScheduleID
    								ORDER BY N FOR XML PATH(''), TYPE).VALUE('.','VARCHAR(MAX)')
    							, 1, 2, ''),'[',''),']','') AS WkDays) c2
    	WHERE Ca.Name = ISNULL(@ReportName,Ca.Name);
     
    DROP TABLE #morepower;
  6. On the Twelfth Day…

    Bacon wrapped frog legs (twelve of them) for Christmas.  No more drumming for these guys!!

    What could be better than bacon wrapped frog legs?  Oh yeah, more Virtual lab setup.

    We will delve into setting up a SQL Cluster today.  We will also cover some high level tips for dealing with virtual box.  This will be good information and the type of stuff I would have like to have known heading into setting up a Virtual Lab.

    Season Cleaning First.

    On the Twelfth Day of pre-Christmas…

    My DBA brought to me a Virtual SQL Cluster.  And with that cluster, we have a a few tidbits for Using VirtualBox.

    The first tidbit is an administration aid.  Occasionally it is good to have similar machines grouped together.  At the same time, it is also necessary to start multiple virtual machines at the same time.  This is done through groups in VirtualBox.

    Here you can see some of the groups that I have created.  If I right-click on a machine name, I will be presented a menu that has the Group option.

    Once I have a group created, I can get a few different options if I were to highlight the group name I would get different options as shown in the following image.

    The notable options here are to “Ungroup”, “Rename Group”, and “Add Machine.”  Another option is “Start.”  Though this option is present for the machine menu, the behavior is different.  This option allows you to start the entire group.  This can be a handy tool when dealing with a cluster for instance.

    The next handy tidbit is the snapshot.  A snapshot allows point in time image of the VM to be taken so different configurations can be tested – and then quickly reverted i necessary.  Here is what I have for one of my VMs in the snapshot manager.

    From this very same screen you can also see one of the many methods available to create a clone of a virtual machine.  The clone icon is the little button above the right hand pane that looks like a sheep.  Cloning a VM is a quick way to create several machines for various purposes.  As you will hear from many people – you should build a base image first, then run sysprep against it.  Sysprep is necessary in order to help prevent problems down the road.

    The next tidbit for today is in regards to the file locations for virtual machines and virtual disks.  I recommend changing the default path for the VM files.  This can be done through the preferences option on the file menu.  Shown in the attachment is what it may look like if you have not changed it.  Notice that the default path goes to your user profile directory.

    Ignore the red text on this page for now.  We will not be discussing the Proxy.

    The last tip is in the network settings within the preferences that we already have open.  In the network settings, we can find an option to configure DHCP settings for the Host-Only Ethernet Adapter.  These are settings you may want to configure to ensure you have more control over the environment.  It is also helpful when looking to configure those IP settings for the FreeNAS that we have already discussed.

    As I wrap up these tidbits, I have decided that this is a lot of information to soak in at this point.  So in the spirit of Christmas, I have decided to finish off the clustering information in a 13th day post.  This final post may or may not be available on Christmas day.  Worst case it will be available on the 26th.

    Part of that reason is I want to rebuild my lab following the instructions I will be posting and I need time to test it.  I want the instructions to be of great use.

    Please stay tuned as we conclude this series very soon.

  7. On the Eleventh Day…

    Yesterday we had an introduction into setting up a virtual lab to help the DBA learn and test new technologies while improving his/her own skill set.

    Today we will continue to discuss the building of a virtual lab.  Today we will get a little closer to the SQL portion of things as we will be installing a familiar operating system to SQL Server.

    The Operating System will be 2008.  And the version of SQL Server will be 2008 R2.  I chose these specifically because at the time that I built out my lab, I was setting up the environment to help me study for the MCM exams.

    As a sidebar, I was just informed by a friend of another blog series that is also currently discussing setting up Virtual Machines in Virtual Box.  Fortunately, his series is based on Windows 2012 and SQL 2012 – so there is a bit of a difference.  The author of that series is Matt Velic and you can read his articles on the topic here.

    I’ll be honest, upon hearing that news I had to go check out his articles to make sure I wasn’t doing the exact same thing.  And while there may be a little overlap, it looks like we have different things that we are covering.

    And now that brings us to recap time.

    On the Eleventh Day of pre-Christmas…

    The next pre-requisite for this lab is to install a Domain Controller and Active Directory.  For this Domain Controller, I have the following Virtual Box settings.

    • A single Dynamic Virtual Disk of 20GB
    • 2 Network Adapters (1 NAT and 1 Internal)
    • 1024 MB memory

    To install the operating system, we will mount the iso image the same as we did for the FreeNAS in yesterdays post.  This is a Windows setup, and I will not cover that.

    Once you have installed the operating system, the first thing to do is to install the guest additions for Virtual Box.

    With guest additions installed, next we will turn to the network adapters.  I have two adapters installed for good reason.  One adapter is visible to the virtual network and will be used for the VMs to talk to each other.  The second adapter is installed so I can get windows validated and so patches can be downloaded and installed.

    Talking about patches, this is where we want to make sure the operating system is patched.  Run windows update, finish all of the requisite reboots, and then come back to the network control panel.  Prior to installing the domain, disable the external NIC.  We will do this to limit the potential for errors when joining the subsequent machines to the domain.

    For the Internal adapter, I will also configure a static IP address as shown here.

    Let’s now setup the domain and domain controller on this machine.  From Server Manager, right click roles and select Add Roles.  From the new screen, select Active Directory Domain Services and DNS Server.

    You are now ready to configure your domain.  I am going to allow you to use your favorite resource for the directions on configuring a domain in Windows 2008.  After the domain has been configured, then enable the external network adapter.

    The final step is to configure DNS.  The main concern in DNS to configure is the reverse lookup zones.  I have three subnets (network address ranges) that I will configure.  The relevance of these three zones will become apparent in the final article of the lab setup mini-series.  The configurations will be along the lines as seen in this next screenshot.

    This gets us to where we can start building our SQL Cluster.  We will cover that in the next installment.

  8. On the Tenth Day…

    Silver and Gold have a way of really bringing the look and feel of the Christmas season.

    Silver and Gold also seem to represent something of greater value.

    We are now into the final three articles of the 12 Days of pre-Christmas.  And with these three articles, I hope to bring something that is of more value than anything shared so far.

    Of course, the value of these articles is subjective.  I have my opinion as to why these are more valuable.  I hope to convey that opinion as best as possible to help bring out as much value as can be garnered from these articles.

    Let’s first recap what we have to date.

    On the Tenth Day of pre-Christmas…

    My DBA gave me an education.  Sure, everyday so far in this series could possibly be an education.  This is an education via a lab.  Every DBA should have a lab to be able to test features and hone skills.  A lab is a better place to do some of the testing that needs done than the DEV, QA, or even Production environments.

    Think about it, do we really want to be testing the setup of clustering in the DEV environment and potentially impact the development cycle?  I’d dare so no.

    Unfortunately, reality does not always allow for a lab environment to be accessible to the DBA.  So the DBA needs to make do with other means.  It is due to these types of constraints, that I am devoting the next three days to the setup of a lab.  This lab can even be created on a laptop.  I created this lab on my laptop with only 8GB of ram.  I was quite pleased to see that it performed well enough for my testing purposes.

    We will begin with an introduction to the technology used – VirtualBox.  I will also discuss the creation of enough virtual machines to create a SQL Cluster (domain controller, two sql boxes, and a NAS) along with the configuration steps to ensure it will work.

    For this lab, we will be using Virtual Box.  You can download Virtual Box here.  And yes, the tool is one that is provided by Oracle.  Two of the reasons I want to use Virtual Box is the ability to install multiple operating systems, and the tool is currently free.  Another benefit is that I can easily import virtual machines created in VMWare as well as Microsoft Virtual Server/Virtual PC (I have not tested any created in Hyper-V).

    While you are downloading the Virtual Box app, download the Extension Pack as well.  Links are provided for the extension pack on the same page as the application download.  Be sure to download the Extension Pack for the version of Virtual Box you download.

    The version of VirtualBox I will be using for this article is 4.2.2.  As of the writing of this article a new version has been released – 4.2.6.  The differences in versions may cause the instructions in these articles to be inaccurate for 4.2.6.  You can use whichever version you deem appropriate.  I just won’t be covering version 4.2.6 and don’t know if the screens are different or it the settings are different.

    You can check your version in the Help.About Menu.

    For this lab, we have a few things that will be required prior to setting up the SQL Cluster.  Two big components of this infrastructure are Storage and a Domain.  We are going to simulate shared storage through the use of FreeNAS.  We will be discussing FreeNAS today.

    For starters, we can download FreeNAS from here.  You might be able to find a few configuration guides online for FreeNAS.  Most of them seemed to be for really old versions and were less than usable for the version that I had downloaded.  All settings to be discussed today are pertinent to FreeNAS-8.3.0-RELEASE-x64 (r12701M).

    To setup FreeNAS, we will need to have a Virtual Machine configured with the following settings.

    • A BSD VM with FreeBSD as the version.
    • Ensure the motherboard settings only has the “Enable IO APIC” setting checked.
    • Three Virtual Disks (1 for NAS OS, 1 for SAN Storage, and another for a Quorum)
    • 512 MB memory
    • 2 Network Adapters (1 Internal and 1 connected to the Host-Only Adapter)

    Despite the FreeNAS actual disk requirements being rather small, any fixed disk size less than 2GB causes mount errors.  Any amount of memory less than 512MB also causes a mount problem.  These settings are the minimum configurations to save the hair on your head.

    The Network Adapters is more of a strong suggestion.  I was able to get it to work with only one adapter, but it was more hassle than it was worth.  I found it easier to configure for use by the cluster later if I had two adapters.  The two adapter configuration also allows me easier administration from within the VM environment as well as from the host machine.

    One other thing to do is to mount the FreeNAS ISO that has been downloaded to the CD drive that is created by default with the VM creation.  I mount the ISO before booting by opening the settings for the VM within Virtual Box.  On the storage screen, highlight the “Empty” CD Icon in the middle then click on the CD Menu Icon on the far right as shown below.

    Navigate to the folder where the FreeNAS ISO is saved and then click ok until you are back at the Virtual Box manager screen.  You are now ready to start the machine and finish the install and then configure.

    Once powered on, you should eventually come to the following screen.

    Select to Install/Upgrade.  From here, you will see a few more prompts such as the next screen to select the installation location.

    This should be pretty straight forward installation options for the IT professional.  I will not cover all of the installation prompts.  Once the install is finished, you will need to reboot the VM and un-mount the installation media.  The system will then come to the following screen.

    Now that we are at the console screen, the next step is to configure the Network Interfaces.  You can see that I have already done this based on the IP addresses seen at the bottom of the screen.  I will leave the configuration of the IP addresses to you.  Both the internal network and the host-only network will need to be configured.  The host network should be the second adapter.  Keep track of the IP addresses that have been configured.  We will need to use them later.

    In a browser window we will now start configuring the storage to be used by our Lab.  In the address bar, we will need to input the address we configured for the host network.  In my case, 192.168.56.103.  When that page loads, the first thing we need to do is change the Admin password.

    The default password is empty.  Pick a password you will remember and that is appropriate.  With that done, we can configure the storage.

    The Next setting, I want to configure is the iSCSI setting.  In order to use the volumes that we create, we must enable the iSCSI service.  In the top section, click the Services button.  This will open a new tab in the web browser.  On the Services tab, we need to toggle the slider for iSCSI to the “ON” position as shown in the image.

    Once toggled, we can configure the iSCSI settings for the volumes we will now create.  From here, we click on the storage tab.  And then we will click on the Import Volume button.  Give the Volume a name, specify the disk and then specify the volume type.

    Now go back to the Services tab where we enabled iSCSI.  There is a wrench icon next to the toggle to enable the service.  Click on this wrench and a new tab will be opened (again within the FreeNAS webgui) and the focus will be switched to this new tab.  On the “Target Global Configuration” ensure that Discovery Auth Method is set to “Auto.”  If it is not, make the change and click save at the bottom.

    Next is the Portals.  The portals should be empty so we will need to add a portal.  By default, only one IP address is displayed for configuration on a new Portal entry.  We want to configure two IP addresses.  First, select 0.0.0.0 from the IP Address drop down on the new window that opened when clicking on “Add Portal.”  Then select “Add extra Portal IP”.

    Next is to configure an Initiator.  For this lab, I created on Initiator specifying ALL for the Initiators and Authorized Network as shown here.

    With an initiator and a portal in place, we now proceed to the configuration of the Targets.  I have configured three targets and the main difference is in the name.  They should be configured as shown here.

    Almost done with the setup for the storage.  It will all be well worth it when we are done.  We need to configure Device Extents and then Associate the targets, then we will be done.

    Like with the Targets, I have three device extents configured.  The configuration for each is the same process.  I want to give each a name that is meaningful and then associate the extent to a disk that we imported earlier.

    Last for this setup is the Target to Extent association.  This a pretty straight forward configuration.  I named my targets the same as extents so there was no confusion as to which should go with which.

    That wraps up the configurations needed to get the storage working so we can configure a cluster later on.  Just getting through this configuration is a pretty big step in getting the lab created for use in your studies and career enhancement.

    Next up in this series is to show how to configure (in limited detail) a domain and DNS, and then to install and configure a cluster.  Stay tuned and I will even through in a few tidbits here and there about Virtual Box.

    I didn’t include every screenshot possible throughout the setup of FreeNAS and the configuration of iSCSI.  Part of the fun and education of a lab is troubleshooting and learning as you go.  If you run into issues, I encourage you to troubleshoot and research.  It will definitely strengthen your skill-set.

  9. On the Ninth Day…

    It’s the end of the world as we know it.  And as the song goes…I feel fine!  But hey, we didn’t start the fire.

    Those are a couple of songs that pop into my head every time somebody starts talking doomsday and doomsday prophecies.

    If you are reading this, I dare say that the world is still turning.  And that is a good thing because that gives us a chance to talk about the 12 days of pre-Christmas.

    Today we will be talking about a tool that can be at the DBAs disposal to help in tracking performance as well as problems.

    First there are a couple of items of housekeeping.  First item is that I only realized with this post that the post on the first day was wrong.  I had miscalculated my twelve days to end on Christmas day.  That is wrong!  Counting down from that first post on the 13th means the 12th day will end up on December 24th.  Maybe I will throw in a bonus 13th day post, or delay a post for a day, or just go with it.  It will be a mystery!

    Second item is naturally the daily recap of the 12 days to date.

    On the Ninth Day of pre-Christmas…

    My DBA gave to me a Data Collection!

    If only my DBA had told me that I would need to have Management Data Warehouse (MDW) preconfigured.  Well, that is not a problem.  We can handle that too.  For a tutorial on how to setup MDW, I am going to provide a link to one that has been very well written by Kalen Delaney.

    The article written by Kalen covers the topic of MDW very well all the way from setting up the MDW, to setting up the Data Collectors, and all the way down to the default reports you can run for MDW.  The MDW and canned Data Collectors can provide some really useful information for your environment.

    What I want to share though is a means to add custom data collections to your MDW.  To create a custom collection, we need to rely on two stored procedures provided to us by Microsoft.  Those stored procedures are: sp_syscollector_create_collection_item and sp_syscollector_create_collection_set.  Both of these stored procedures are found in the, you guessed it, msdb database.

    Each of these stored procedures has a number of parameters to help in the creation of an appropriate data collection.  When creating a data collection, you will first create the collection set, and then you add collection items to that set.

    There are a few notable parameters for each stored procedure that I will cover.  Otherwise, you can refer back to the links for each of the stored procedures to see more information about the parameters.

    Starting with the sp_syscollector_create_collection_set stored procedure, I want to point out the @schedule_name, @name, and @collection_mode parameters.  The name is pretty obvious – make sure you have a distinguishable name that is descriptive (my opinion) or at least have good documentation.  The collection mode has two values.  As noted in the documentation, you will want to choose one value over the other depending on the intended use of this data collector.  If running continuously, just remember to run in cached mode.  And lastly is the schedule name.  This will help determine how frequently the job runs.

    Unfortunately, the schedule names are not found in the documentation, but rather you are directed to query the sysschedules tables.  To help you find those schedules, here is a quick query.

    Code block    
    SELECT schedule_id,name
    	FROM dbo.sysschedules
    	WHERE name LIKE '%collector%';

    Now on to the sp_syscollector_create_collection_item stored procedure.  There are three parameters that I want to lightly touch on.  For the rest, you can refer back to the documentation.  The parameters of interest here are @parameters, @frequency and @collector_type_uid.  Starting with the frequency parameter, this tells the collector how often to upload the data to the MDW if running in cached mode.  Be careful here to select an appropriate interval.  Next is the parameters parameter which is really the workhorse of the collection item.  In the case of the custom data collector that I will show in a bit, this is where the tsql query will go.

    Last parameter to discuss is the collector type uid.  Like the schedule for the previous proc, the documentation for this one essentially refers you to a system view - syscollector_collector_types.  Here is a quick query to see the different collector types.

    Code block    
    SELECT name
    	FROM syscollector_collector_types;

    The collector type that I will be using for this example is Generic T-SQL Query Collector Type.  A discussion on the four types of collectors can be reserved for another time.

    Let’s move on to the example now.  This custom data collector is designed to help troubleshoot deadlock problems.  The means I want to accomplish this is by querying the system_health extended event session.

    I can query for deadlock information direct to the system_health session using a query like the following.

    Code block    
    SELECT CAST(
                      REPLACE(
                            REPLACE(XEventData.XEvent.VALUE('(data/value)[1]', 'varchar(max)'), 
                            '', ''),
                      '','')
                AS VARCHAR(4000)) AS DeadlockGraph
    FROM
    (SELECT CAST(target_data AS XML) AS TargetData
    FROM sys.dm_xe_session_targets st
    join sys.dm_xe_sessions s ON s.address = st.event_session_address
    WHERE name = 'system_health') AS DATA
    CROSS APPLY TargetData.nodes ('//RingBufferTarget/event') AS XEventData (XEvent)
    WHERE XEventData.XEvent.VALUE('@name', 'varchar(4000)') = 'xml_deadlock_report';

    You may notice that I have converted to varchar(4000) from XML.  This is in large part to make sure the results will play nicely with the data collector.  Now to convert that to a query that can be used in the @parameters parameter, we get the following.

    Code block    
    <ns:TSQLQueryCollector xmlns:ns="DataCollectorType"><Query><Value>
     
    SELECT CAST(
                      REPLACE(
                            REPLACE(XEventData.XEvent.VALUE(''(DATA/VALUE)[1]'', ''VARCHAR(MAX)''), 
                            '''', ''''),
                      '''','''')
                AS VARCHAR(4000)) AS DeadlockGraph
    FROM
    (SELECT CAST(target_data AS XML) AS TargetData
    FROM sys.dm_xe_session_targets st
    join sys.dm_xe_sessions s ON s.address = st.event_session_address
    WHERE name = ''system_health'') AS DATA
    CROSS APPLY TargetData.nodes (''//RingBufferTarget/event'') AS XEventData (XEvent)
    WHERE XEventData.XEvent.VALUE(''@name'', ''VARCHAR(4000)'') = ''xml_deadlock_report'' 
     
    </Value><OutputTable>systemhealthdeadlock</OutputTable></Query></ns:TSQLQueryCollector>

    With this query, we are loading the necessary schema nodes that correlate to the Data Collector Type that we chose.  Since this parameter is XML, the schema must match or you will get an error.  We are now ready to generate a script that can create a deadlock data collector.

    Code block    
    BEGIN TRANSACTION
    BEGIN Try
    DECLARE @collection_set_id_1 INT
    DECLARE @collection_set_uid_2 UNIQUEIDENTIFIER
    EXEC [msdb].[dbo].[sp_syscollector_create_collection_set]
    	  @name=N'systemhealthdeadlock'
    	, @collection_mode=1
    	, @description=N'systemhealthdeadlock'
    	, @logging_level=1
    	, @days_until_expiration=14
    	, @schedule_name=N'CollectorSchedule_Every_15min'
    	, @collection_set_id=@collection_set_id_1 OUTPUT
    	, @collection_set_uid=@collection_set_uid_2 OUTPUT
     
    SELECT @collection_set_id_1, @collection_set_uid_2
     
    DECLARE @collector_type_uid_3 UNIQUEIDENTIFIER
    SELECT @collector_type_uid_3 = collector_type_uid 
    	FROM [msdb].[dbo].[syscollector_collector_types] 
    	WHERE name = N'Generic T-SQL Query Collector Type';
     
    DECLARE @collection_item_id_4 INT
    EXEC [msdb].[dbo].[sp_syscollector_create_collection_item]
    	  @name=N'systemhealthdeadlock'
    	, @PARAMETERS=N'<ns:TSQLQueryCollector xmlns:ns="DataCollectorType"><Query><Value>
     
    SELECT CAST(
                      REPLACE(
                            REPLACE(XEventData.XEvent.value(''(data/value)[1]'', ''varchar(max)''), 
                            '''', ''''),
                      '''','''')
                AS varchar(4000)) AS DeadlockGraph
    FROM
    (SELECT CAST(target_data AS XML) AS TargetData
    from sys.dm_xe_session_targets st
    join sys.dm_xe_sessions s on s.address = st.event_session_address
    where name = ''system_health'') AS Data
    CROSS APPLY TargetData.nodes (''//RingBufferTarget/event'') AS XEventData (XEvent)
    where XEventData.XEvent.value(''@name'', ''varchar(4000)'') = ''xml_deadlock_report'' 
     
    </Value><OutputTable>systemhealthdeadlock</OutputTable></Query></ns:TSQLQueryCollector>'
    	, @collection_item_id=@collection_item_id_4 OUTPUT
    	, @frequency=30
    	, @collection_set_id=@collection_set_id_1
    	, @collector_type_uid=@collector_type_uid_3
     
    SELECT @collection_item_id_4
     
    COMMIT TRANSACTION;
    END Try
    BEGIN Catch
    ROLLBACK TRANSACTION;
    DECLARE @ErrorMessage NVARCHAR(4000);
    DECLARE @ErrorSeverity INT;
    DECLARE @ErrorState INT;
    DECLARE @ErrorNumber INT;
    DECLARE @ErrorLine INT;
    DECLARE @ErrorProcedure NVARCHAR(200);
    SELECT @ErrorLine = ERROR_LINE(),
           @ErrorSeverity = ERROR_SEVERITY(),
           @ErrorState = ERROR_STATE(),
           @ErrorNumber = ERROR_NUMBER(),
           @ErrorMessage = ERROR_MESSAGE(),
           @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
    RAISERROR (14684, @ErrorSeverity, 1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
     
    END Catch;
     
    GO

    Upon creation, this will create a SQL Agent job with the defined schedule.  Since this is a non-cached data collector set, the Agent job will adhere to the schedule specified and upload data on that interval.

    Now all we need to do is generate a deadlock to see if it is working.  It is also a good idea to introduce you to the table that will be created due to this data collector.  Once we create this collector set, a new table will be created in the MDW database.  In the case of this collector set, we have a table with the schema and name of custom_snapshots.systemhealthdeadlock.

    This new table will have three columns.  One column represents the DeadlockGraph as we retrieved from the query we provided to the @parameters parameter.  The remaining columns are data collector columns for the collection date and the snapshot id.

    Now that we have covered all of that, your favorite deadlock query has had enough time to finally fall victim to a deadlock.  We should also have some information recorded in the custom_snapshots.systemhealthdeadlock table relevant to the deadlock information (if not, it will be there once the agent job runs, or you can run a snapshot from SSMS of the data collector).  With a quick query, we can start looking into the deadlock problem.

    Code block    
    SELECT collection_time,CAST(DeadlockGraph AS XML) AS DeadlockGraph, snapshot_id
    	FROM mdw.custom_snapshots.systemhealthdeadlock;

    This query will give me a few entries (since I went overkill and created a bunch of deadlocks).  If I click the DeadlockGraph cell in the result sets, I can then view the XML of the DeadlockGraph, as in the following.

    Code block    
    <deadlock>
      <victim-list>
        <victimProcess id="process5a4ebc8" />
      </victim-list>
      <process-list>
        <process id="process5a4ebc8" taskpriority="0" logused="0" waitresource="KEY: 26:72057594048020480 (b4903b2250cc)" waittime="609" ownerId="2803145" transactionname="user_transaction" lasttranstarted="2012-12-20T22:32:09.987" XDES="0x8008d950" lockMode="X" schedulerid="8" kpid="13656" status="suspended" spid="87" sbid="0" ecid="0" priority="0" trancount="4" lastbatchstarted="2012-12-20T22:38:50.020" lastbatchcompleted="2012-12-20T22:38:50.020" lastattention="2012-12-20T22:38:24.217" clientapp="Microsoft SQL Server Management Studio - Query" hostname="SomeServer" hostpid="7604" loginname="SomeLogin" isolationlevel="serializable (4)" xactid="2803145" currentdb="26" lockTimeout="4294967295" clientoption1="673187936" clientoption2="390200">
          <executionStack>
            <frame procname="" line="10" stmtstart="40" sqlhandle="0x02000000eac1af36f412db4e21d9dcc86feb261fa6bcd230" />
            <frame procname="" line="10" stmtstart="356" stmtend="518" sqlhandle="0x0200000095b4ee32a25e9724dd73fd6894c60748af6c136b" />
          </executionStack>
          <inputbuf>
    SET NOCOUNT ON;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    WHILE 1=1 
    BEGIN 
    BEGIN TRAN
    UPDATE Purchasing.Vendor
    SET CreditRating = 2
    WHERE BusinessEntityID = 1492;
     
    UPDATE Purchasing.Vendor
    SET CreditRating = 1
    WHERE BusinessEntityID = 1494;
     
    COMMIT TRAN 
    END
       </inputbuf>
        </process>
        <process id="process5a44bc8" taskpriority="0" logused="0" waitresource="KEY: 26:72057594048020480 (ade87e3a717c)" waittime="609" ownerId="2878446" transactionname="user_transaction" lasttranstarted="2012-12-20T22:38:50.020" XDES="0xa9abd950" lockMode="X" schedulerid="7" kpid="15008" status="suspended" spid="86" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2012-12-20T22:38:47.887" lastbatchcompleted="2012-12-20T22:38:47.887" lastattention="2012-12-20T22:36:21.247" clientapp="Microsoft SQL Server Management Studio - Query" hostname="SomeServer" hostpid="7604" loginname="SomeLogin" isolationlevel="serializable (4)" xactid="2878446" currentdb="26" lockTimeout="4294967295" clientoption1="673187936" clientoption2="390200">
          <executionStack>
            <frame procname="" line="9" stmtstart="40" sqlhandle="0x02000000eac1af36f412db4e21d9dcc86feb261fa6bcd230" />
            <frame procname="" line="9" stmtstart="352" stmtend="510" sqlhandle="0x020000000c4b9412577ec884cbd51882e5310dd340216739" />
          </executionStack>
          <inputbuf>
    SET NOCOUNT ON;
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    WHILE 1=1 
    BEGIN 
    BEGIN TRAN
    UPDATE Purchasing.Vendor
    SET CreditRating = 1
    WHERE BusinessEntityID = 1494;
    UPDATE Purchasing.Vendor
    SET CreditRating = 2
    WHERE BusinessEntityID = 1492;
    COMMIT TRAN 
    END
       </inputbuf>
        </process>
      </process-list>
      <resource-list>
        <keylock hobtid="72057594048020480" dbid="26" objectname="" indexname="" id="lock5aeec80" mode="X" associatedObjectId="72057594048020480">
          <owner-list>
            <owner id="process5a44bc8" mode="X" />
          </owner-list>
          <waiter-list>
            <waiter id="process5a4ebc8" mode="X" requestType="wait" />
          </waiter-list>
        </keylock>
        <keylock hobtid="72057594048020480" dbid="26" objectname="" indexname="" id="lock6164d80" mode="X" associatedObjectId="72057594048020480">
          <owner-list>
            <owner id="process5a4ebc8" mode="X" />
          </owner-list>
          <waiter-list>
            <waiter id="process5a44bc8" mode="X" requestType="wait" />
          </waiter-list>
        </keylock>
      </resource-list>
    </deadlock>

    Code to generate deadlock courtesy of “SQL Server 2012 T-SQL Recipes: A Problem-Solution Approach” by Jason Brimhall, Wayne Sheffield et al (Chapter 12, pages 267-268).  If you examine the deadlock graph you will see the code that generated the deadlock.

    Since this is being pulled from the RingBuffer target of the system_health, it can prove useful to store that data into a table such as I have done.  The reason being that the Ringbuffer can be overwritten, and with good timing on the data collector, we can preserve this information for later retrieval and troubleshooting.  Deadlocks don’t always happen at the most opportune time and even less likely to occur when we are staring at the screen waiting for them to happen.

    As you read more about the stored procedures used to create a data collector, you will see that there is a retention parameter.  This helps prevent the table from getting too large on us.  We can also ensure that an appropriate retention is stored for these custom collectors.

    Conclusion

    Creating a custom data collector can be very handy for a DBA.  Especially in times of troubleshooting.  These collectors are also quite useful for trending and analysis.  Consider this a tool in the chest for the poor man. ;)

    Enjoy and stay tuned!

    All scripts and references were for SQL 2008 R2.  The Deadlock script was pulled from the 2012 book, but the script runs the same for the 2008 version of the AdventureWorks database.

  10. On the Eighth Day…

    Today’s post is merely an illusion.  The illusion being that we have finally stopped talking about the msdb database.  I’ll explain about that later in this post.

    This should be a good addition to the script toolbox for those Mere Mortal DBAs out there supporting their corporate SSRS environment.  Everybody could use a script now and then that helps them better support their environment and perform their DBA duties, right?

    No reading ahead now.  We’ll get to the script soon enough.  First, we have a bit of business to cover just as we normally do.

    We need to quickly recap the first seven days thus far (after all, the song does a recap with each day).

     

    1. Runaway Jobs – 7th Day
    2. Maintenance Plan Gravage - 6th Day
    3. Table Compression - 5th Day
    4. Exercise for msdb - 4th Day
    5. Backup, Job and Mail History Cleanup - 3rd Day
    6. Service Broker Out of Control - 2nd Day
    7. Maint Plan Logs - 1st Day

    On the Eighth Day of pre-Christmas…

    My DBA gave to me a means to see Report Subscriptions and their schedules.

    One of the intriguing points that we find with having a reporting environment is that we also need to report on that reporting environment.  And one of the nuisances of dealing with a Reporting Services Environment is that data like report subscription schedules is not very human friendly.

    Part of the issue is that you need to be fluent with math.  Another part of the issue is that you need to be a little familiar with bitwise operations in SQL Server.  That said, it is possible to get by without understanding both very well.  And as a last resort, there is always the manual method of using Report Manager to check the subscriptions for each of the reports that have been deployed to that server.  Though, I think you will find this to be a bit tedious if you have a large number of reports.

    I have seen more than one script that provides the schedule information for the subscriptions without using math and just relying on the bitwise operations.  This tends to produce a lot of repetitive code.  The method works, I’m just not that interested in the repetitive nature employed.

    Within SQL Server you should notice that in several tables, views, and processes employ the powers of 2 or base 2 or binary number system.  This is natural since this is so integral within computer science in general.  Powers of 2 translates to binary fairly easily and then integrates so well with bitwise operations.

    The following table demonstrates the powers of 2 and conversion to binary.

    power of 2 value binary
    0 1 1
    1 2 10
    2 4 100
    3 8 1000
    4 16 10000
    5 32 100000
    6 64 1000000
    7 128 10000000
    8 256 100000000

    To get numbers and values between the binary results or the value results listed above is a matter of addition.  We add the value from a power of 2 to another power of 2.  So if I need a value of 7, then I need 2^0 + 2^1 + 2^2.  This results in a binary value of 0111.  Now this is where the need for bit comparisons comes into play so we will use some bitwise operations (read more here) to figure out quickly which values are used to reach an end value of 7 (so I don’t need to really know a lot of math there ;) ).

    How does this Apply to Schedules?

    This background has everything to do with scheduling in SSRS.  Within the ReportServer database, there is a table called Schedule in the dbo schema.  This table has multiple columns that store pieces of the Subscription Schedule.  The three key columns are DaysofWeek, DaysofMonth and Month.  The values stored in these columns are all sums of the powers of 2 necessary to represent multiple days or months.

    For instance, you may see the following values

    DaysOfWeek DaysOfMonth Month
    62 135283073 2575

    These values are not friendly to normal every day mere mortal DBAs.  The values from the preceding table are shown below with the human friendly data they represent.

    DaysOfWeek DaysOfMonth Month
    62 135283073 2575
    Monday,Tuesday,Wednesday,Thursday,Friday 1,8,9,15,21,28 January,February,March,April,October,December

    That is better for us to understand, but not necessarily better to store in the database.  So, I hope you can see the value of storing it in a numeric representation that is easily deciphered through math and TSQL.

    Without further adieu, we have a script to report on these schedules without too much repetition.

    Code block    
    DECLARE @ReportName VARCHAR(100)
    SET @ReportName = NULL;
     
    CREATE TABLE #morepower (MonthDate BIGINT,N BIGINT,PowerN BIGINT PRIMARY KEY CLUSTERED
    							,NameofMonth VARCHAR(25),WkDay VARCHAR(25))
    ;
    WITH E1(N) AS ( --=== Create Ten 1's
                     SELECT 1 UNION ALL SELECT 1 UNION ALL
                     SELECT 1 UNION ALL SELECT 1 UNION ALL
                     SELECT 1 UNION ALL SELECT 1 UNION ALL
                     SELECT 1 UNION ALL SELECT 1 UNION ALL
                     SELECT 1 UNION ALL SELECT 1 --10
                   ),
          E2(N) AS (SELECT 1 FROM E1 a, E1 b),   --100
    cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT N)) FROM E2  UNION ALL SELECT 0
    ),powers(powerN,n) AS (SELECT POWER(2,N),N FROM cteTally WHERE N < 31)
     
    INSERT INTO #morepower ( MonthDate ,N,PowerN ,NameofMonth ,WkDay)
    	SELECT ROW_NUMBER() OVER (ORDER BY N) AS MonthDate,N,PowerN
    			,CASE WHEN N BETWEEN 0 AND 11 
    				THEN DATENAME(MONTH,DATEADD(MONTH,N+1,0)-1)
    				ELSE NULL
    				END AS NameofMonth
    			,CASE WHEN N BETWEEN 0 AND 6
    				THEN DATENAME(weekday,DATEADD(DAY,n+1,0)-2)
    				ELSE NULL
    				END AS WkDay
    		FROM powers
     
    SELECT DISTINCT s.ScheduleID,Ca.PATH AS ReportManagerPath,Ca.Name AS ReportName
    		, U.UserName AS SubscriptionCreator
    		,Su.Description AS SubscriptionDescription,S.StartDate,Su.LastRunTime
    		,CASE 
    			WHEN s.RecurrenceType = 1 THEN 'One Off'
    			WHEN s.RecurrenceType = 2 THEN 'Hour'
    			WHEN s.RecurrenceType = 4 THEN 'Daily'
    			WHEN s.RecurrenceType = 5 THEN 'Monthly' 
    			WHEN s.RecurrenceType = 6 THEN 'Week of Month' 
    		END AS RecurrenceType
    		,s.EventType
    		,ISNULL(STUFF(
    				(SELECT ','+CONVERT(VARCHAR(50),MonthDate) AS [TEXT()] 
    					FROM #morepower m1 
    					WHERE m1.powerN < s.DaysofMonth+1 
    						AND s.DaysofMonth & m1.powerN <>0 
    					ORDER BY N FOR XML PATH('')),1,1,''),'N/A') AS DaysofMonth
    		,ISNULL(c1.NameOfMonth,'N/A') AS MonthString
    		,ISNULL(c2.WkDays,'N/A') AS DaysofWeek
    		,CASE MonthlyWeek
    				WHEN 1 THEN 'First'
    				WHEN 2 THEN 'Second'
    				WHEN 3 THEN 'Third'
    				WHEN 4 THEN 'Fourth'
    				WHEN 5 THEN 'Last'
    				ELSE 'N/A'
    			END AS MonthlyWeek
    		,ISNULL(CONVERT(VARCHAR(10),s.DaysInterval),'N/A') AS DaysInterval
    		,ISNULL(CONVERT(VARCHAR(10),s.MinutesInterval),'N/A') AS MinutesInterval
    		,ISNULL(CONVERT(VARCHAR(10),s.WeeksInterval),'N/A') AS WeeksInterval
    	FROM #morepower mp, dbo.Schedule s
    		INNER JOIN ReportSchedule RS
    			ON S.ScheduleID = RS.ScheduleID
    		INNER JOIN CATALOG Ca
    			ON Ca.ItemID = RS.ReportID
    		INNER JOIN Subscriptions Su
    			ON Su.SubscriptionID = RS.SubscriptionID
    		INNER JOIN Users U
    			ON U.UserID = S.CreatedById
    			OR U.UserID = Su.OwnerID
    	CROSS APPLY (SELECT s.ScheduleID,STUFF(
    							(SELECT ','+NameofMonth AS [TEXT()] 
    								FROM #morepower m1 ,dbo.Schedule s1
    								WHERE m1.NameofMonth IS NOT NULL 
    									AND m1.powerN & s1.MONTH <>0 
    									AND s1.ScheduleID = s.ScheduleID
    								ORDER BY N FOR XML PATH('')),1,1,'') AS NameOfMonth)c1
    	CROSS APPLY (SELECT s.ScheduleID,STUFF(
    							(SELECT ','+WkDay AS [TEXT()] 
    								FROM #morepower m1 ,dbo.Schedule s2
    								WHERE m1.WkDay IS NOT NULL 
    									AND DaysOfWeek & m1.powerN <>0
    									AND  s2.ScheduleID = s.ScheduleID
    								ORDER BY N FOR XML PATH('')),1,1,'') AS WkDays) c2
    	WHERE Ca.Name = ISNULL(@ReportName,Ca.Name);
     
    DROP TABLE #morepower;

    Consider this as V1 of the script with expected changes coming forth.

    I have set this up so a specific report name can be provided or not.  If not provided, the query will return all scheduling information for all reports.

    Through the use of a numbers table (done through the CTE), I have been able to create a map table for each of the necessary values to be parsed from the schedule later in the script.  In the creation of that map table, note the use of the power function.  This map table was the critical piece in my opinion to create a script that could quickly decipher the values in the schedule and provide something readable to the DBA.

    Conclusion

    I did this script because I feel it important to know what reports are running and when they are running.  Add that management also likes to know that information, so there is value to it.  But, I found scripts on the web that used the bitwise operation piece but a lot of repetitive code to determine each Weekday and month.

    An alternative would be to perform a query against the msdb database since Scheduled reports are done via a SQL Agent job.  I hope you find this report useful and that you can put it to use.

  1. 1
  2. Next ›
  3. Last »