/* ███████╗██████╗ ██╗ ██╗███████╗ █████╗ ██╗ ████████╗██╗ ██╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗ ██╔════╝██╔══██╗ ██║ ██║██╔════╝██╔══██╗██║ ╚══██╔══╝██║ ██║ ██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝ ███████╗██████╔╝ ███████║█████╗ ███████║██║ ██║ ███████║ ██║ ███████║█████╗ ██║ █████╔╝ ╚════██║██╔═══╝ ██╔══██║██╔══╝ ██╔══██║██║ ██║ ██╔══██║ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ███████║██║███████╗██║ ██║███████╗██║ ██║███████╗██║ ██║ ██║███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝ https://sqlhealthcheck.net This script was put together by Salvador Lancaster @sqlslancaster - http://twitter.com/sqlslancaster --===============================================================================================================================-- --======================================================= LICENSE ===============================================================-- --===============================================================================================================================-- sp_health_check is licensed free as long as all its contents are preserved, including this header. The script and all its contents cannot be redistributed or sold either partially or as a whole without the author's expressed written approval. Some scripts that are part of this compound of code were not created by the author and belong to their original creators or owners for all purposes. Other authors' credit is specified within the stored procedure's code. You can find more details on this at https://sqlhealthcheck.net/overview This script is provided as is without guarantee, documentation, or technical support, and should be properly understood and tested before being deployed in a production enviroment. --===============================================================================================================================-- --======================================================= HOW TO ================================================================-- --===============================================================================================================================-- [!] When using for the first time, execute the uncommented code to create the stored procedure, and then use the line below for all subsequent executions: EXEC [dbo].[sp_health_check]; -- run against the database it was stored at, [master] unless it was changed */ USE [master]; -- [!] Change if you have a DBA-specific database and you want to save this stored procedure in it GO IF OBJECT_ID('[dbo].[sp_health_check]') IS NOT NULL BEGIN DROP PROCEDURE [dbo].[sp_health_check]; END; GO CREATE PROCEDURE [dbo].[sp_health_check] AS SET NOCOUNT ON; IF (SELECT [c].[value] FROM [sys].[configurations] [c] WHERE [c].[name]=N'xp_cmdshell')<>1 BEGIN RAISERROR('[sp_health_check] requires [xp_cmdshell] to be enabled',10,1); RETURN; END; IF (IS_SRVROLEMEMBER ( 'sysadmin', SUSER_NAME() ))<>1 BEGIN RAISERROR('[sp_health_check] must execute under sysadmin priviledges',10,1); RETURN; END; DECLARE @cmd NVARCHAR(MAX); --===============================================================================================================================-- --================================================ Performance Baselines Values =================================================-- --===============================================================================================================================-- /* PENDING ITEMS: - Measure IO latency - Capture waits */ -- ### dm_os_performance_counters DECLARE @os_performance_counters INT; SELECT @os_performance_counters=COUNT(*) FROM [sys].[dm_os_performance_counters]; IF @os_performance_counters>0 BEGIN -- ### General perfmon values IF OBJECT_ID('tempdb..#perfmon_baseline') IS NOT NULL BEGIN DROP TABLE [#perfmon_baseline]; END; DECLARE @start_time DATETIME=GETDATE(); SELECT [counter_name], [cntr_value] INTO [#perfmon_baseline] FROM [sys].[dm_os_performance_counters] WHERE ([counter_name] IN ('Batch Requests/sec' ,'Logins/sec' ,'Logouts/sec' ,'Connection Reset/sec' ,'SQL Compilations/sec' ,'SQL Re-Compilations/sec' ,'Query optimizations/sec' ,'Page writes/sec' ,'Page Splits/sec' ,'Page reads/sec' ,'Checkpoint pages/sec' ,'Lazy writes/sec' ,'Forwarded Records/sec' ,'Page Deallocations/sec' ,'Pages Allocated/sec' ,'Readahead pages/sec' ,'Full Scans/sec' ,'Index Searches/sec' ,'Page lookups/sec' ,'Range Scans/sec' ,'Pages compressed/sec' ) AND [instance_name]<>'internal' ) OR ([counter_name] IN ('Transactions/sec' ,'Write Transactions/sec' ,'Number of Deadlocks/sec' ,'Lock Requests/sec' ,'Log Bytes Flushed/sec' ,'Log Flushes/sec' ) AND [instance_name]='_Total' ); -- ### Mirroring values DECLARE @mirror_COUNT INT; SELECT @mirror_COUNT=COUNT([mirroring_guid]) FROM [sys].[database_mirroring] WHERE [mirroring_guid] IS NOT NULL; DECLARE @mirrorCOUNTsynch INT; SELECT @mirrorCOUNTsynch=COUNT([mirroring_guid]) FROM [sys].[database_mirroring] WHERE [mirroring_state]=4; IF @mirror_COUNT>0 BEGIN IF OBJECT_ID('tempdb..#perfmon_baseline_mirroring') IS NOT NULL BEGIN DROP TABLE [#perfmon_baseline_mirroring]; END; SELECT [counter_name] ,[cntr_value] INTO [#perfmon_baseline_mirroring] FROM [sys].[dm_os_performance_counters] WHERE [object_name]='SQLServer:Database Mirroring' AND ([counter_name] IN ('Bytes Sent/sec' ,'Bytes Received/sec' ,'Mirrored Write Transactions/sec' ,'Transaction Delay') AND [instance_name]='_Total' ); END; WAITFOR DELAY '00:00:01:30'; -- Timeframe to compare the captured data DECLARE @BatchRequests_sec BIGINT, @Transactions_sec BIGINT ,@WriteTransactions_sec BIGINT, @Logins_sec BIGINT ,@Logouts_sec BIGINT ,@Compilations_sec BIGINT, @ReCompilations_sec BIGINT ,@QueryOptimizations_sec BIGINT, @BufferPageWrites_sec BIGINT ,@BufferPageReads_sec BIGINT ,@BufferLazyWrites_sec BIGINT, @CheckpointPages_sec BIGINT ,@PageSplits_sec BIGINT; ;WITH [perfmon_results] AS ( SELECT * FROM ( SELECT [perfmon_diff].[counter_name], ( CONVERT(DECIMAL(32,2),([perfmon_diff].[cntr_value])-CONVERT(DECIMAL(32,2),[perfmon_baseline].[cntr_value])) / (DATEDIFF(MILLISECOND,@start_time,GETDATE())/1000) ) [cntr_value] FROM [sys].[dm_os_performance_counters] [perfmon_diff] INNER JOIN [#perfmon_baseline] [perfmon_baseline] ON [perfmon_baseline].[counter_name] = [perfmon_diff].[counter_name] WHERE ([perfmon_diff].[counter_name] IN ('Batch Requests/sec' ,'Logins/sec' ,'Logouts/sec' ,'Connection Reset/sec' ,'SQL Compilations/sec' ,'SQL Re-Compilations/sec' ,'Query optimizations/sec' ,'Page writes/sec' ,'Page Splits/sec' ,'Page reads/sec' ,'Checkpoint pages/sec' ,'Lazy writes/sec' ,'Forwarded Records/sec' ,'Page Deallocations/sec' ,'Pages Allocated/sec' ,'Readahead pages/sec' ,'Full Scans/sec' ,'Index Searches/sec' ,'Page lookups/sec' ,'Range Scans/sec' ,'Pages compressed/sec' ) AND [instance_name]<>'internal' ) OR ([perfmon_diff].[counter_name] IN ('Transactions/sec' ,'Write Transactions/sec' ,'Lock Requests/sec' ,'Log Bytes Flushed/sec' ,'Log Flushes/sec' ) AND [perfmon_diff].[instance_name]='_Total' ) ) [perfmon_results_t] ) SELECT -- @BatchRequests_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Batch Requests/sec'),0) @Transactions_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Transactions/sec'),0) ,@WriteTransactions_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Write Transactions/sec'),0) ,@Logins_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Logins/sec'),0) ,@Logouts_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Logouts/sec'),0) ,@Compilations_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='SQL Compilations/sec'),0) ,@ReCompilations_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='SQL Re-Compilations/sec'),0) ,@QueryOptimizations_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Query optimizations/sec'),0) ,@BufferPageWrites_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Page writes/sec'),0) ,@BufferPageReads_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Page reads/sec'),0) ,@BufferLazyWrites_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Lazy writes/sec'),0) ,@CheckpointPages_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Checkpoint pages/sec'),0) ,@PageSplits_sec=ISNULL((SELECT [perfmon_results].[cntr_value] FROM [perfmon_results] WHERE [perfmon_results].[counter_name]='Page Splits/sec'),0) FROM [perfmon_results]; IF OBJECT_ID('tempdb..#perfmon_baseline') IS NOT NULL BEGIN DROP TABLE [#perfmon_baseline]; END; END; --===============================================================================================================================-- --======================================================= SERVER DETAILS ========================================================-- --===============================================================================================================================-- -- ### Variables DECLARE @ip NVARCHAR(56); SELECT @ip=[dec].[local_net_address] FROM [sys].[dm_exec_connections] AS [dec] WHERE [dec].[session_id] = @@SPID; DECLARE @Domain NVARCHAR(128); EXEC [master].[sys].[xp_regread] 'HKEY_LOCAL_MACHINE', 'SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters', N'Domain',@Domain OUTPUT; IF @Domain IS NULL SET @Domain='Not set'; DECLARE @iscluster INT; SELECT @iscluster=CONVERT(INT,SERVERPROPERTY('IsClustered')); DECLARE @tempDBcreate DATETIME; SELECT @tempDBcreate=[create_date] FROM [sys].[databases] WHERE [name]='tempdb'; DECLARE @engine_minutes DECIMAL, @engine_hours INT; SET @engine_minutes=DATEDIFF(MI,@tempDBcreate,GETDATE()); SET @engine_hours=@engine_minutes/60; SET @engine_minutes=((@engine_minutes/60)-@engine_hours)*60; -- ### Print Server and OS info DECLARE @OS NVARCHAR(128); SELECT @OS=RIGHT(@@VERSION, LEN(@@VERSION)- 0 -CHARINDEX (' Windows', @@VERSION)); SELECT @OS=LEFT(@OS, LEN(@OS)-0 -CHARINDEX(' (',@OS)); PRINT 'SQL Server '+CONVERT(NVARCHAR(128), SERVERPROPERTY('ProductVersion'))+' '+CONVERT(NVARCHAR(128),SERVERPROPERTY('Edition'))+' on '+REPLACE(@OS,CHAR(10),''); -- ### Instance details IF (CONVERT(INT,@@microsoftversion)>=171051460) --SQL2008R2SP1 or greater BEGIN -- ### Is this a virtual machine? SET @cmd = N'SELECT @server_type=CASE WHEN [virtual_machine_type] = 1 THEN ''virtual'' ELSE ''physical'' END FROM sys.dm_os_sys_info'; DECLARE @server_type NVARCHAR(8); EXEC [master].[sys].[sp_executesql] @cmd, N'@server_type NVARCHAR(8) out', @server_type OUTPUT; -- ### Is the service clustered? IF (@iscluster>0) BEGIN PRINT 'The service "'+CONVERT(NVARCHAR,SERVERPROPERTY('ServerName'))+'" is clustered currently running on host "'+CONVERT(NVARCHAR,SERVERPROPERTY('ComputerNamePhysicalNetBIOS'))+'" since '+CONVERT(NVARCHAR(16),@tempDBcreate,101)+' '+CONVERT(NVARCHAR(16),@tempDBcreate,108)+', '+CONVERT(NVARCHAR(6),@engine_hours)+' hours and '+CONVERT(NVARCHAR(3),@engine_minutes)+' minutes ago as process ID '+CONVERT(NVARCHAR,SERVERPROPERTY('ProcessID')); END; IF (@iscluster=0) BEGIN PRINT 'The instance "'+CONVERT(NVARCHAR,SERVERPROPERTY('ServerName'))+'" is non-clustered, runs on the '+@server_type+' host "'+CONVERT(NVARCHAR,SERVERPROPERTY('ComputerNamePhysicalNetBIOS'))+'", ip "'+@ip+'", domain "'+@Domain+'"'; END; END; ELSE --SQL2008R2 or lower BEGIN -- ### Is the service clustered? IF (@iscluster>0) BEGIN PRINT 'The service "'+CONVERT(NVARCHAR,SERVERPROPERTY('ServerName'))+'" is clustered currently running on host "'+CONVERT(NVARCHAR,SERVERPROPERTY('ComputerNamePhysicalNetBIOS'))+'" since '+CONVERT(NVARCHAR(16),@tempDBcreate,101)+' '+CONVERT(NVARCHAR(16),@tempDBcreate,108)+', '+CONVERT(NVARCHAR(6),@engine_hours)+' hours and '+CONVERT(NVARCHAR(3),@engine_minutes)+' minutes ago as process ID '+CONVERT(NVARCHAR,SERVERPROPERTY('ProcessID')); END; IF (@iscluster=0) BEGIN PRINT 'The instance "'+CONVERT(NVARCHAR,SERVERPROPERTY('ServerName'))+'" is non-clustered, runs on the host "'+CONVERT(NVARCHAR,SERVERPROPERTY('ComputerNamePhysicalNetBIOS'))+'", ip "'+@ip+'", domain "'+@Domain+'"'; END; END; --===============================================================================================================================-- --====================================================== SERVICES STATUS ========================================================-- --===============================================================================================================================-- -- ### Is the server in single mode option? DECLARE @IsSingleUser SQL_VARIANT; SELECT @IsSingleUser=SERVERPROPERTY('IsSingleUser'); IF (@IsSingleUser<>0) BEGIN PRINT '[!] SQL Server is configured in Single Mode (startup option -m)'; PRINT '[!] Single-user mode restricts connections to members of the sysadmin fixed server role'; PRINT '[!] http://msdn.microsoft.com/en-us/library/ms188236.aspx'; PRINT ''; END; -- ### Services variables DECLARE @SrvAccDBEngine NVARCHAR(256), @SrvAccAgent NVARCHAR(128); IF (CONVERT(INT,@@microsoftversion)>=171051460) --SQL2008R2SP1 or greater BEGIN SELECT @SrvAccDBEngine=[service_account] FROM [sys].[dm_server_services] WHERE [servicename]='SQL Server (MSSQLSERVER)'; SELECT @SrvAccAgent=[service_account] FROM [sys].[dm_server_services] WHERE [servicename]='SQL Server Agent (MSSQLSERVER)'; END; ELSE BEGIN -- Credit for this block to http://sqlandme.com/2013/08/20/sql-service-get-sql-server-service-account-using-t-sql/ by Vishal@SqlAndMe.com EXEC [master].[sys].[xp_instance_regread] @rootkey = N'HKEY_LOCAL_MACHINE', @key = N'SYSTEM\CurrentControlSet\Services\MSSQLServer', @value_name = N'ObjectName', @value = @SrvAccDBEngine OUTPUT; EXEC [master].[sys].[xp_instance_regread] @rootkey = N'HKEY_LOCAL_MACHINE', @key = N'SYSTEM\CurrentControlSet\Services\SQLServerAgent', @value_name = N'ObjectName', @value = @SrvAccAgent OUTPUT; END; IF CHARINDEX('@',@SrvAccAgent,0)>0 BEGIN SELECT @SrvAccAgent='%'+LEFT(@SrvAccAgent,CHARINDEX('@',@SrvAccAgent,0)-1); END; -- ### Engine service running info PRINT 'The engine has been up since '+CONVERT(NVARCHAR(16),@tempDBcreate,101)+' '+CONVERT(NVARCHAR(16),@tempDBcreate,108)+', '+CONVERT(NVARCHAR(6),@engine_hours)+' hours and '+CONVERT(NVARCHAR(3),@engine_minutes)+' minutes ago as process ID '+CONVERT(NVARCHAR,SERVERPROPERTY('ProcessID'))+' under ['+@SrvAccDBEngine+']'; -- ### Is the agent service running? DECLARE @SQLAgentStart DATETIME; SELECT @SQLAgentStart=[login_time] FROM [sys].[dm_exec_sessions] WHERE [login_name] LIKE @SrvAccAgent AND [program_name] LIKE 'SQLAgent - Generic Refresher%'; IF EXISTS (SELECT TOP 1 [login_name] FROM [sys].[dm_exec_sessions] WHERE [login_name] LIKE @SrvAccAgent AND [program_name] LIKE 'SQLAgent%') BEGIN PRINT 'The agent was last started on '+CONVERT(NVARCHAR(16),@SQLAgentStart,101)+' '+CONVERT(NVARCHAR(16),@SQLAgentStart,108)+', '+CONVERT(NVARCHAR,DATEDIFF(hh,@SQLAgentStart,GETDATE()))+' hours ago, and runs under ['+@SrvAccAgent+']'; END; ELSE BEGIN PRINT '[!] The SQL Agent service is NOT in running status'; --IF (@iscluster=0) --BEGIN --PRINT 'EXEC master.sys.xp_servicecontrol N''querystate'',N''SQLServerAGENT''; --Query current status'; PRINT 'EXEC master.sys.xp_servicecontrol N''start'',N''SQLServerAGENT''; --Start the service'; --PRINT 'EXEC xp_cmdshell ''SC QUERY SQLSERVERAGENT'''; --PRINT 'EXEC xp_cmdshell ''SC START SQLSERVERAGENT'''; --END; END; -- ### Traces, Server Event Notifications, Extended Events and Server Triggers count DECLARE @traces INT; SELECT @traces=COUNT([id]) FROM [sys].[traces] WHERE [stop_time] IS NULL; DECLARE @server_event_notifications INT; SELECT @server_event_notifications=COUNT([name]) FROM [sys].[server_event_notifications]; DECLARE @xe_sessions INT; IF (CONVERT(INT,@@microsoftversion)>=171051460) /*SQL2008R2SP1 or greater*/ BEGIN SELECT @xe_sessions=COUNT([name]) FROM [sys].[dm_xe_sessions]; END; DECLARE @server_triggers INT; SELECT @server_triggers=COUNT([name]) FROM [sys].[server_triggers] WHERE [is_disabled]<>1; PRINT CONVERT(NVARCHAR(32),@traces)+' traces, '+CONVERT(NVARCHAR(32),@server_event_notifications)+' server event notifications, '+CONVERT(NVARCHAR(32),ISNULL(@xe_sessions,0))+' extended events sessions, and '+CONVERT(NVARCHAR(32),@server_triggers)+' server triggers currently running'; PRINT ''; -- Print break --===============================================================================================================================-- --======================================================= FILES STATUSES ========================================================-- --===============================================================================================================================-- DECLARE @dbs INT; SELECT @dbs=COUNT([state_desc]) FROM [sys].[databases]; DECLARE @dbso INT; SELECT @dbso=COUNT([state_desc]) FROM [sys].[databases] WHERE [state_desc]='ONLINE'; DECLARE @dbmu INT; SELECT @dbmu=COUNT([user_access_desc]) FROM [sys].[databases] WHERE [user_access_desc]='MULTI_USER'; DECLARE @dbf INT; SELECT @dbf=COUNT([state_desc]) FROM [sys].[master_files]; DECLARE @dbfo INT; SELECT @dbfo=COUNT([state_desc]) FROM [sys].[master_files] WHERE [state_desc]='ONLINE'; -- ### Check if all databases are in MULTI_USER and ONLINE status IF (@dbs = @dbso) AND (@dbs = @dbmu) BEGIN PRINT 'All '+CONVERT(NVARCHAR(3),@dbs)+' databases attached to the server are in MULTI_USER access and ONLINE status'; END; IF (@dbs > @dbso) OR (@dbs > @dbmu) BEGIN IF (@dbs = @dbso) BEGIN PRINT 'All '+CONVERT(NVARCHAR(3),@dbs)+' databases attached to the server are in ONLINE status'; END; ELSE IF (@dbs > @dbso) BEGIN PRINT '[!] Only '+CONVERT(NVARCHAR(3),@dbso)+' databases are in online status out of '+CONVERT(NVARCHAR(5),@dbs)+' attached on the server'; END; IF (@dbs = @dbmu) BEGIN PRINT 'All '+CONVERT(NVARCHAR(3),@dbs)+' databases attached to the server are in MULTI_USER access status'; END; ELSE IF (@dbs > @dbmu) BEGIN PRINT '[!] Only '+CONVERT(NVARCHAR(3),@dbmu)+' databases are in multi_user access status out of '+CONVERT(NVARCHAR(5),@dbs)+' attached to the server'; END; END; -- ### Check if all data and log files are online IF (@dbf = @dbfo) BEGIN PRINT 'All '+CONVERT(NVARCHAR(3),@dbf)+' data and log files used by the databases on the server are in ONLINE status'; END; ELSE BEGIN PRINT '[!] Only '+CONVERT(NVARCHAR(3),@dbfo)+' out of '+CONVERT(NVARCHAR(3),@dbf)+' of the log and data files used by the databases attached are in online status'; END; --===============================================================================================================================-- --======================================================= FILES' SIZES ==========================================================-- --===============================================================================================================================-- DECLARE @LogFilesTotalSize INT; SELECT @LogFilesTotalSize=SUM(([size]*8)/1024) FROM [sys].[master_files] WHERE [type_desc]='LOG' GROUP BY [type_desc]; DECLARE @DataFilesTotalSize INT; SELECT @DataFilesTotalSize=SUM(([size]*8)/1024) FROM [sys].[master_files] WHERE [type_desc]='ROWS' GROUP BY [type_desc]; PRINT 'The database log files use '+CONVERT(NVARCHAR(32),@LogFilesTotalSize)+' MB, and the data files use '+CONVERT(NVARCHAR(32),@DataFilesTotalSize)+' MB, for a total of '+CONVERT(NVARCHAR(32),@DataFilesTotalSize+@LogFilesTotalSize)+' MB'; PRINT ''; -- Print break --===============================================================================================================================-- --===================================================== SERVER RESOURCES ========================================================-- --===============================================================================================================================-- -- ### Processors' data DECLARE @cpu_name NVARCHAR(56); DECLARE @cpu_info TABLE ([name] NVARCHAR(MAX) NULL); INSERT INTO @cpu_info EXEC [sys].[xp_cmdshell] 'wmic cpu get name'; DELETE @cpu_info WHERE [name] IS NULL OR [name] LIKE '%name%'; SELECT TOP 1 @cpu_name=[name] FROM @cpu_info; IF (CONVERT(INT,@@microsoftversion)>=171051460) --SQL2008R2SP1 or greater BEGIN /* Credit for this block to Basit Farooq http://basitaalishan.com/2014/01/22/get-sql-server-physical-cores-physical-and-virtual-cpus-and-processor-type-information-using-t-sql-script */ DECLARE @number_of_virtual_cpus NVARCHAR(4), @number_of_cores_per_cpu NVARCHAR(4), @number_of_physical_cpus NVARCHAR(4), @total_number_of_cores NVARCHAR(4), @cpu_category NVARCHAR(12); DECLARE @xp_msver TABLE ([idx] [INT] NULL,[c_name] [NVARCHAR](100) NULL,[int_val] [FLOAT] NULL,[c_val] [NVARCHAR](128) NULL); INSERT INTO @xp_msver EXEC ('[master]..[xp_msver]'); WITH [ProcessorInfo] AS (SELECT ([cpu_count] / [hyperthread_ratio]) AS [number_of_physical_cpus],CASE WHEN [hyperthread_ratio] = [cpu_count] THEN [cpu_count] ELSE (([cpu_count] - [hyperthread_ratio]) / ([cpu_count] / [hyperthread_ratio])) END AS [number_of_cores_per_cpu],CASE WHEN [hyperthread_ratio] = [cpu_count] THEN [cpu_count] ELSE ([cpu_count] / [hyperthread_ratio]) * (([cpu_count] - [hyperthread_ratio]) / ([cpu_count] / [hyperthread_ratio])) END AS [total_number_of_cores],[cpu_count] AS [number_of_virtual_cpus],(SELECT [c_val] FROM @xp_msver WHERE [c_name] = 'Platform') AS [cpu_category] FROM [sys].[dm_os_sys_info]) SELECT @number_of_physical_cpus=[ProcessorInfo].[number_of_physical_cpus],@number_of_cores_per_cpu=[ProcessorInfo].[number_of_cores_per_cpu],@total_number_of_cores=[ProcessorInfo].[total_number_of_cores],@number_of_virtual_cpus=[ProcessorInfo].[number_of_virtual_cpus],@cpu_category=LTRIM(RIGHT([ProcessorInfo].[cpu_category], CHARINDEX('x', [ProcessorInfo].[cpu_category]) - 1)) FROM [ProcessorInfo]; PRINT 'Processor '+REPLACE(REPLACE((REPLACE(REPLACE(REPLACE(@cpu_name,' ','<>'),'><',''),'<>',' ')), CHAR(13), ''), CHAR(10), '')+'with ' +@number_of_physical_cpus+' cpus having '+@number_of_cores_per_cpu++' cores each for a total of '+@total_number_of_cores+' cores and '+@number_of_virtual_cpus+' virtual on '+@cpu_category; END; ELSE BEGIN WITH [ProcessorInfoLow] AS (SELECT ([cpu_count] / [hyperthread_ratio]) AS [number_of_physical_cpus],CASE WHEN [hyperthread_ratio] = [cpu_count] THEN [cpu_count] ELSE (([cpu_count] - [hyperthread_ratio]) / ([cpu_count] / [hyperthread_ratio])) END AS [number_of_cores_per_cpu], CASE WHEN [hyperthread_ratio] = [cpu_count] THEN [cpu_count] ELSE ([cpu_count] / [hyperthread_ratio]) * (([cpu_count] - [hyperthread_ratio]) / ([cpu_count] / [hyperthread_ratio])) END AS [total_number_of_cores],[cpu_count] AS [number_of_virtual_cpus] FROM [sys].[dm_os_sys_info] ) SELECT @number_of_physical_cpus=[ProcessorInfoLow].[number_of_physical_cpus],@number_of_cores_per_cpu=[ProcessorInfoLow].[number_of_cores_per_cpu],@total_number_of_cores=[ProcessorInfoLow].[total_number_of_cores],@number_of_virtual_cpus=[ProcessorInfoLow].[number_of_virtual_cpus] FROM [ProcessorInfoLow]; PRINT 'Processor '+REPLACE(REPLACE((REPLACE(REPLACE(REPLACE(@cpu_name,' ','<>'),'><',''),'<>',' ')), CHAR(13), ''), CHAR(10), '')+'with '+@number_of_physical_cpus+' cpus having '+@number_of_cores_per_cpu++' cores each for a total of '+@total_number_of_cores+' cores and '+@number_of_virtual_cpus+' virtual'; END; -- ### CPU Use and MAXDOP /* Credit for this block to Benjamin Nevarez http://http://sqlblog.com/blogs/ben_nevarez/archive/2009/07/26/getting-cpu-utilization-data-from-sql-server.aspx */ DECLARE @CPU_Print NVARCHAR(1024), @CPU_TotalUse INT, @CPU_SQL INT, @CPU_Other INT; SELECT @CPU_TotalUse=(100-[y].[SystemIdle]) ,@CPU_SQL=[y].[SQLProcessUtilization],@CPU_Other=(100-[y].[SystemIdle]-[y].[SQLProcessUtilization]) FROM (SELECT [x].[record].[value]('(./Record/@id)[1]' ,'int') AS [record_id],[x].[record].[value]('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]' ,'int') AS [SystemIdle],[x].[record].[value]('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]' ,'int') AS [SQLProcessUtilization],[x].[timestamp] FROM (SELECT TOP 1 [timestamp],CONVERT(XML ,[record]) AS [record] FROM [sys].[dm_os_ring_buffers] WHERE [ring_buffer_type]=N'RING_BUFFER_SCHEDULER_MONITOR' AND [record] LIKE '%%' ORDER BY [timestamp] DESC) AS [x]) AS [y]; SET @CPU_Print=N'CPU use at '+CONVERT(NVARCHAR(3),@CPU_TotalUse)+N'%, '+CONVERT(NVARCHAR(3),@CPU_SQL)+N'% used by this instance, and '+CONVERT(NVARCHAR(3),@CPU_Other)+N'% by other processes. '; DECLARE @Parallelism_MAXDOP INT, @Parallelism_CostThreshold INT; SELECT @Parallelism_MAXDOP=CONVERT(INT,[value]) FROM [sys].[configurations] WHERE [name]='max degree of parallelism'; SELECT @Parallelism_CostThreshold=CONVERT(INT,[value]) FROM [sys].[configurations] WHERE [name]='cost threshold for parallelism'; PRINT ISNULL(@CPU_Print,'')+N'Max degree of parallelism at '+CONVERT(NVARCHAR(8),@Parallelism_MAXDOP)+' with a cost threshold of '+CONVERT(NVARCHAR(8),@Parallelism_CostThreshold); -- ### Memory data DECLARE @TotalServerMemory INT; SELECT @TotalServerMemory=[cntr_value]/1024 FROM [sys].[dm_os_performance_counters] WHERE [object_name] IN ('SQLServer:Memory Manager') AND [counter_name] IN ('Total Server Memory (KB)'); DECLARE @TargetServerMemory INT; SELECT @TargetServerMemory=[cntr_value]/1024 FROM [sys].[dm_os_performance_counters] WHERE [object_name] IN ('SQLServer:Memory Manager') AND [counter_name] IN ('Target Server Memory (KB)'); DECLARE @max_buff_mem SQL_VARIANT; SELECT @max_buff_mem=[value] FROM [sys].[configurations] WHERE [name] LIKE '%max server memory%'; DECLARE @os_memory INT; IF (CONVERT(INT,@@microsoftversion)>=171051460) --SQL2008R2SP1 or greater BEGIN DECLARE @memorymbavail INT, @memorypercentavail DECIMAL(5,2); SELECT @os_memory= [total_physical_memory_kb]/1024, @memorymbavail=([available_physical_memory_kb]/1024), @memorypercentavail=(CONVERT(DECIMAL(5,2),((CONVERT(DECIMAL(30,2),[available_physical_memory_kb])/CONVERT(DECIMAL(30,2),[total_physical_memory_kb]))*100))) FROM [sys].[dm_os_sys_memory]; IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(20),@os_memory)+' MB of RAM with '+CONVERT(NVARCHAR(32),@memorymbavail)+' available ('+CONVERT(NVARCHAR(5),@memorypercentavail)+'%), '+CONVERT(NVARCHAR(32),@TotalServerMemory)+' currently assigned for SQL which is targeting '+CONVERT(NVARCHAR(32),@TargetServerMemory)+' and maxed at '+CONVERT(NVARCHAR(20),@max_buff_mem); END; END; ELSE BEGIN SET @cmd = N'SELECT @os_memory=(physical_memory_in_bytes/1024)/1024 FROM [master].[sys].[dm_os_sys_info];'; EXEC [master].[sys].[sp_executesql] @cmd, N'@os_memory NVARCHAR(8) out', @os_memory OUTPUT; IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(20),@os_memory)+' MB of memory with '+CONVERT(NVARCHAR(32),@TotalServerMemory)+' currently assigned for SQL which is targeting '+CONVERT(NVARCHAR(32),@TargetServerMemory)+' and maxed at '+CONVERT(NVARCHAR(20),@max_buff_mem); END; END; DECLARE @BufferCHR DECIMAL(10,2); SET @BufferCHR=((SELECT CONVERT(DECIMAL(16,2),[cntr_value]) FROM [sys].[dm_os_performance_counters] WHERE [object_name] ='SQLServer:Buffer Manager' AND [counter_name]='Buffer cache hit ratio') / (SELECT CONVERT(DECIMAL(16,2),[cntr_value]) FROM [sys].[dm_os_performance_counters] WHERE [object_name] ='SQLServer:Buffer Manager' AND [counter_name]='Buffer cache hit ratio base'))*100; DECLARE @PLE BIGINT; SELECT @PLE=[cntr_value] FROM [sys].[dm_os_performance_counters] WHERE [object_name] LIKE '%Manager%' AND [counter_name]='Page life expectancy'; IF (CONVERT(INT,@@microsoftversion)>=171051460) --SQL2008R2SP1 or greater BEGIN DECLARE @MemoryPlanCache INT; SELECT @MemoryPlanCache=[allocations_kb]/1024 FROM [sys].[dm_os_memory_brokers] WHERE [memory_broker_type]='MEMORYBROKER_FOR_CACHE'; DECLARE @BufferDBPages INT; SELECT @BufferDBPages= ([cntr_value]*8)/1024 FROM [sys].[dm_os_performance_counters] WHERE [object_name] IN ('SQLServer:Buffer Manager') AND [counter_name] = 'Database pages'; IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(64),@BufferDBPages)+' MB of memory used as buffer for database pages ('+CONVERT(NVARCHAR(16),CONVERT(DECIMAL(4,2),(CONVERT(DECIMAL(16,4),CONVERT(DECIMAL(16,5),@BufferDBPages) / CONVERT(DECIMAL(16,5),@TotalServerMemory)))*100))+'%), Cache hit ratio at '+CONVERT(NVARCHAR(6),@BufferCHR)+'%, page life expectancy at '+CONVERT(NVARCHAR(15),@PLE)+' secs ('+CONVERT(NVARCHAR(15),@PLE/60)+' min)'; ; END; END; IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(16),@BufferPageReads_sec)+' ('+CONVERT(NVARCHAR(8),((@BufferPageReads_sec*8)/1024))+' MB) page reads, '+CONVERT(NVARCHAR(16),@BufferPageWrites_sec)+' ('+CONVERT(NVARCHAR(8),((@BufferPageWrites_sec*8)/1024))+' MB) page writes, '+CONVERT(NVARCHAR(16),@PageSplits_sec)+' ('+CONVERT(NVARCHAR(16),( (@PageSplits_sec*8)/1024) )+' MB) page splits, '+CONVERT(NVARCHAR(16),@CheckpointPages_sec)+' ('+CONVERT(NVARCHAR(16),( (@CheckpointPages_sec*8)/1024) )+' MB) checkpoint pages, and '+CONVERT(NVARCHAR(16),@BufferLazyWrites_sec)+' lazy writes per second'; END; -- ### Cached objects DECLARE @exec_plans_total_count BIGINT; SELECT @exec_plans_total_count=COUNT(*) FROM [sys].[dm_exec_cached_plans]; DECLARE @exec_plans_total_mb BIGINT; SELECT @exec_plans_total_mb=SUM(CONVERT(BIGINT,[size_in_bytes]))/1024/1024 FROM [sys].[dm_exec_cached_plans] DECLARE @exec_plans_tenorless_mb BIGINT; SELECT @exec_plans_tenorless_mb=SUM(CONVERT(BIGINT,[size_in_bytes]))/1024/1024 FROM [sys].[dm_exec_cached_plans] WHERE [usecounts]<=10; PRINT CONVERT(NVARCHAR(64),@MemoryPlanCache)+' MB used for cached objects ('+CONVERT(NVARCHAR(16),CONVERT(DECIMAL(4,2),(CONVERT(DECIMAL(16,4),CONVERT(DECIMAL(16,5),@MemoryPlanCache) / CONVERT(DECIMAL(16,5),@TotalServerMemory)))*100))+'%), '+CONVERT(NVARCHAR(32),@exec_plans_total_count)+' exec plans stored using '+CONVERT(NVARCHAR(32),@exec_plans_total_mb)+' MB, '+CONVERT(NVARCHAR(32),@exec_plans_tenorless_mb)+' MB ('+CONVERT(NVARCHAR(5),CONVERT(DECIMAL(3,1),CONVERT(DECIMAL(32,2),@exec_plans_tenorless_mb)/CONVERT(DECIMAL(32,2),@exec_plans_total_mb)*100))+'%) for plans used ten times or less' PRINT ''; -- Print break -- ### Check if perfmon counters are missing IF @os_performance_counters=0 BEGIN PRINT '[!] The SQL server performance counters are missing on the server'; END; -- ### Configured vs running values DECLARE @value_valueinuse INT; SELECT @value_valueinuse=SUM(CASE WHEN [value]<>[value_in_use] THEN 1 ELSE 0 END) FROM [sys].[configurations]; IF @value_valueinuse>0 BEGIN PRINT '[!] Some server wide configuration options have been set but not applied and will take effect on the next restart'; END; --===============================================================================================================================-- --==================================================== METRICS AND STATS ========================================================-- --===============================================================================================================================-- -- ### Sessions DECLARE @Transactions INT; SELECT @Transactions=[cntr_value] FROM [sys].[dm_os_performance_counters] WHERE [object_name] IN ('SQLServer:Transactions') AND [counter_name] IN ('Transactions'); DECLARE @TransactionsBlocked INT; SELECT @TransactionsBlocked = [cntr_value] FROM [sys].[dm_os_performance_counters] WHERE ([object_name]='SQLServer:General Statistics' AND [counter_name]='Processes blocked'); DECLARE @connections INT; SELECT @connections=[cntr_value] FROM [sys].[dm_os_performance_counters] WHERE ([object_name]='SQLServer:General Statistics' AND [counter_name]='User Connections'); DECLARE @ConnectionsMemory INT; SELECT @ConnectionsMemory= [cntr_value] FROM [sys].[dm_os_performance_counters] WHERE [object_name] IN ('SQLServer:Memory Manager') AND [counter_name] IN ('Connection Memory (KB)'); IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(8),@Logins_sec)+ ' logins and '+CONVERT(NVARCHAR(8),@Logouts_sec)+' logouts per second, '+ CONVERT(NVARCHAR(8),@connections)+' concurrent user connections using '+CONVERT(NVARCHAR(16),@ConnectionsMemory/1024)+' MB of memory'; PRINT CONVERT(NVARCHAR(16),@BatchRequests_sec)+' batch requests, '+CONVERT(NVARCHAR(16),@Transactions_sec)+' transactions, and '+CONVERT(NVARCHAR(16),@WriteTransactions_sec)+' writting transactions per second, '+CONVERT(NVARCHAR(16),@Transactions)+' currently executing and '+CONVERT(NVARCHAR(8),@TransactionsBlocked)+' blocked'; PRINT CONVERT(NVARCHAR(8),@Compilations_sec)+' compilations, '+CONVERT(NVARCHAR(8),@ReCompilations_sec)+' recompilations, '+CONVERT(NVARCHAR(8),@QueryOptimizations_sec)+' query optimizations per second'; END; -- ### Sessions' isolation levels DECLARE @IsolationReadUncomitted INT, @IsolationReadCommitted INT, @IsolationRepeatable INT, @IsolationSerializable INT, @IsolationSnapshot INT; SELECT @IsolationReadUncomitted=COUNT([transaction_isolation_level]) FROM [sys].[dm_exec_sessions] WHERE [transaction_isolation_level]=1; SELECT @IsolationReadCommitted=COUNT([transaction_isolation_level]) FROM [sys].[dm_exec_sessions] WHERE [transaction_isolation_level]=2; SELECT @IsolationRepeatable=COUNT([transaction_isolation_level]) FROM [sys].[dm_exec_sessions] WHERE [transaction_isolation_level]=3; SELECT @IsolationSerializable=COUNT([transaction_isolation_level]) FROM [sys].[dm_exec_sessions] WHERE [transaction_isolation_level]=4; SELECT @IsolationSnapshot=COUNT([transaction_isolation_level]) FROM [sys].[dm_exec_sessions] WHERE [transaction_isolation_level]=5; IF (@IsolationReadUncomitted+@IsolationRepeatable+@IsolationSerializable+@IsolationSnapshot)>0 BEGIN PRINT 'The sessions'' isolation levels are '+ CONVERT(NVARCHAR(32),@IsolationReadUncomitted) +' Read-Uncomitted, '+ CONVERT(NVARCHAR(32),@IsolationReadCommitted) +' Read-Committed, '+ CONVERT(NVARCHAR(32),@IsolationRepeatable) +' Repeatable, '+ CONVERT(NVARCHAR(32),@IsolationSerializable) +' Serializable, and '+ CONVERT(NVARCHAR(32),@IsolationSnapshot) +' Snapshot'; END; -- ### Long running transactions DECLARE @LongRunningTrans INT; SELECT @LongRunningTrans=COUNT([er].[session_id]) FROM [master].[sys].[dm_exec_requests] [er] LEFT JOIN [master].[sys].[dm_exec_sessions] [es] ON [er].[session_id]=[es].[session_id] WHERE [es].[is_user_process]=1 AND DATEDIFF(mi,[er].[start_time],GETDATE())>1 AND [er].[wait_type]<>'TRACEWRITE'; IF @LongRunningTrans>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(8),@LongRunningTrans)+' transactions have been running for more than 1 minute'; END; -- ### Cursors executing DECLARE @CursorsExec INT, @CursorOldest DATETIME; SELECT @CursorsExec=COUNT([session_id]), @CursorOldest=MIN([creation_time]) FROM [sys].[dm_exec_cursors](0) WHERE [session_id]<>@@SPID GROUP BY [session_id]; IF @CursorsExec>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(32),ISNULL(@CursorsExec,0))+' cursors currently executing on the server, the oldest has been running since '+CONVERT(NVARCHAR(32),ISNULL(@CursorOldest,GETDATE()),120)+', '+CONVERT(NVARCHAR(32),DATEDIFF(ss,ISNULL(@CursorOldest,GETDATE()),GETDATE()))+' seconds ago'; END; -- ### Deadlocks DECLARE @Deadlocks INT; SELECT @Deadlocks=[cntr_value] FROM [sys].[dm_os_performance_counters] WHERE [counter_name]='Number of Deadlocks/sec' AND [instance_name]='_Total'; IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(32),@Deadlocks)+' deadlocks have taken place since the last time the server started'; END; PRINT ''; --Break -- ### Top wait DECLARE @WaitTypeCurrentMain NVARCHAR(64), @WaitTypeCurrentMainWaits INT; SELECT TOP 1 @WaitTypeCurrentMain=[wait_type], @WaitTypeCurrentMainWaits=COUNT([wait_type]) FROM [sys].[dm_os_waiting_tasks] WHERE [wait_type] NOT IN (N'REQUEST_FOR_DEADLOCK_SEARCH',N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',N'SQLTRACE_BUFFER_FLUSH',N'LAZYWRITER_SLEEP',N'XE_TIMER_EVENT',N'XE_DISPATCHER_WAIT',N'FT_IFTS_SCHEDULER_IDLE_WAIT',N'LOGMGR_QUEUE',N'CHECKPOINT_QUEUE',N'BROKER_TO_FLUSH',N'BROKER_TASK_STOP',N'BROKER_EVENTHANDLER',N'SLEEP_TASK',N'WAITFOR',N'DBMIRROR_DBM_MUTEX',N'DBMIRROR_EVENTS_QUEUE',N'DBMIRRORING_CMD',N'DISPATCHER_QUEUE_SEMAPHORE',N'BROKER_RECEIVE_WAITFOR',N'CLR_AUTO_EVENT',N'DIRTY_PAGE_POLL',N'HADR_FILESTREAM_IOMGR_IOCOMPLETION',N'ONDEMAND_TASK_QUEUE',N'FT_IFTSHC_MUTEX',N'CLR_MANUAL_EVENT',N'SP_SERVER_DIAGNOSTICS_SLEEP',N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP') GROUP BY [wait_type] ORDER BY COUNT([wait_type]) DESC; DECLARE @Waits BIGINT; SELECT @Waits=SUM([cntr_value]) FROM [sys].[dm_os_performance_counters] WHERE [object_name]='SQLServer:Wait Statistics' AND [instance_name]='Waits started per second'; IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(32),@Waits)+' waits initiated per second, the top wait type at this moment is "'+REPLACE(@WaitTypeCurrentMain,' ','')+'" with '+CONVERT(NVARCHAR(32),@WaitTypeCurrentMainWaits); END; DECLARE @WaitsCPU INT; SELECT @WaitsCPU=COUNT(DISTINCT [session_id]) FROM [sys].[dm_os_waiting_tasks] WHERE [wait_type]='SOS_SCHEDULER_YIELD'; DECLARE @WaitsCXPACKET INT; SELECT @WaitsCXPACKET=COUNT(DISTINCT [session_id]) FROM [sys].[dm_os_waiting_tasks] WHERE [wait_type]='CXPACKET'; DECLARE @WaitsIO INT; SELECT @WaitsIO=COUNT(DISTINCT [session_id]) FROM [sys].[dm_os_waiting_tasks] WHERE [wait_type] IN ('IO_COMPLETION' ,'PAGEIOLATCH_DT' ,'PAGEIOLATCH_EX' ,'PAGEIOLATCH_KP' ,'PAGEIOLATCH_NL' ,'PAGEIOLATCH_SH' ,'PAGEIOLATCH_UP' ,'PAGELATCH_DT' ,'PAGELATCH_EX' ,'PAGELATCH_KP' ,'PAGELATCH_NL' ,'PAGELATCH_SH' ,'PAGELATCH_UP' ,'PREEMPTIVE_OS_FILEOPS' ,'SLEEP_BPOOL_FLUSH' ,'WRITE_COMPLETION' ,'WRITELOG'); DECLARE @IO_pend_requests INT; SELECT @IO_pend_requests=COUNT([io_pending]) FROM [sys].[dm_io_pending_io_requests] WHERE [io_type]='disk' AND [io_pending]=1; PRINT CONVERT(NVARCHAR(64),@IO_pend_requests)+' pending IO requests, '+CONVERT(NVARCHAR(16),@WaitsIO)++' waits initiated on IO, '+CONVERT(NVARCHAR(16),@WaitsCPU)+' on CPU, and '+CONVERT(NVARCHAR(16),@WaitsCXPACKET)+' on parallelism'; -- ### Memory grants DECLARE @MemoryGrants INT; SELECT @MemoryGrants=[cntr_value] FROM [sys].[dm_os_performance_counters] WHERE [object_name] IN ('SQLServer:Memory Manager') AND [counter_name] IN ('Memory Grants Outstanding'); DECLARE @MemoryGrantsPending INT; SELECT @MemoryGrantsPending=[cntr_value] FROM [sys].[dm_os_performance_counters] WHERE [object_name] IN ('SQLServer:Memory Manager') AND [counter_name] IN ('Memory Grants Pending'); IF (CONVERT(INT,@@microsoftversion)>=171051460) --SQL2008R2SP1 or greater BEGIN DECLARE @MemoryGrantsReserve INT; SELECT @MemoryGrantsReserve=[allocations_kb]/1024 FROM [sys].[dm_os_memory_brokers] WHERE [memory_broker_type]='MEMORYBROKER_FOR_RESERVE'; IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(64),@MemoryGrants)+' existing memory grants, and '+CONVERT(NVARCHAR(64),@MemoryGrantsPending)+' pending, with '+CONVERT(NVARCHAR(64),@MemoryGrantsReserve)+' MB of memory reserved for executions ('+CONVERT(NVARCHAR(16),CONVERT(DECIMAL(4,2),(CONVERT(DECIMAL(16,4),CONVERT(DECIMAL(16,5),@MemoryGrantsReserve) / CONVERT(DECIMAL(16,5),@TotalServerMemory)))*100))+'%)'; END; END; ELSE BEGIN IF @os_performance_counters<>0 BEGIN PRINT CONVERT(NVARCHAR(64),@MemoryGrants)+' memory grants and '+CONVERT(NVARCHAR(64),@MemoryGrantsPending)+' pending'; END; END; DECLARE @WaitTypeAggregatedMain NVARCHAR(64), @WaitTypeAggregatedPercentage DECIMAL(5,2); /* Credit for this block to Paul Randal http://www.sqlskills.com/blogs/paul/wait-statistics-or-please-tell-me-where-it-hurts */ ;WITH [Waits] AS (SELECT [wait_type], [wait_time_ms] / 1000.0 AS [WaitS], ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS], [signal_wait_time_ms] / 1000.0 AS [SignalS], [waiting_tasks_count] AS [WaitCount],100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum] FROM [sys].[dm_os_wait_stats] WHERE [wait_type] NOT IN ( N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE', N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE', N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE', N'ONDEMAND_TASK_QUEUE', N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', N'QDS_SHUTDOWN_QUEUE', N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH', N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP', N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP', N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES', N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT', N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'XE_DISPATCHER_JOIN', N'XE_DISPATCHER_WAIT', N'XE_TIMER_EVENT') AND [waiting_tasks_count] > 0 ) SELECT TOP 1 @WaitTypeAggregatedMain=MAX ([W1].[wait_type]), @WaitTypeAggregatedPercentage=CAST (MAX ([W1].[Percentage]) AS DECIMAL (5,2)) FROM [Waits] AS [W1] INNER JOIN [Waits] AS [W2] ON [W2].[RowNum] <= [W1].[RowNum] GROUP BY [W1].[RowNum] HAVING SUM ([W2].[Percentage]) - MAX ([W1].[Percentage]) < 95; PRINT 'The top wait type since the cache was last cleared is "'+@WaitTypeAggregatedMain+'", having '+CONVERT(NVARCHAR(6),@WaitTypeAggregatedPercentage)+'% in total'; PRINT ''; --Break -- ### SQL log severity 14,16,17,18,19,20,21,22,23,24,25 registry count DECLARE @ErrorLogSevCount INT; IF OBJECT_ID('tempdb..#errorlog_check') IS NOT NULL BEGIN DROP TABLE [#errorlog_check]; END; CREATE TABLE [#errorlog_check] ([LogDate] DATETIME,[ProcessInfo] VARCHAR(12),[Text] VARCHAR(MAX)); SET @cmd='EXEC xp_readerrorlog 0,1,N''Severity: 1'',NULL,N'''+CONVERT(VARCHAR(28),DATEADD(hh,-4,GETDATE()),113)+''',N'''+CONVERT(VARCHAR(28),GETDATE(),113)+''''; INSERT INTO [#errorlog_check] EXEC (@cmd); SET @cmd='EXEC xp_readerrorlog 0,1,N''Severity: 2'',NULL,N'''+CONVERT(VARCHAR(28),DATEADD(hh,-4,GETDATE()),113)+''',N'''+CONVERT(VARCHAR(28),GETDATE(),113)+''''; INSERT INTO [#errorlog_check] EXEC (@cmd); SELECT @ErrorLogSevCount=COUNT([Text]) FROM [#errorlog_check] WHERE LEFT(RIGHT([Text],LEN([Text])-CHARINDEX('Severity',[Text])-9),2)>13 AND LEFT(RIGHT([Text],LEN([Text])-CHARINDEX('Severity',[Text])-9),2)<>15; DROP TABLE [#errorlog_check]; IF @ErrorLogSevCount>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(8),@ErrorLogSevCount)+' non-informational severity errors registered on the current error log on the past 4 hours'; END; ELSE IF @ErrorLogSevCount=0 BEGIN PRINT 'No non-informational severity errors registered on the current error log on the past 4 hours'; END; -- ### Databases configuration DECLARE @is_auto_close_on INT, @is_auto_shrink_on INT, @page_verify_option INT, @is_auto_update_stats_on INT, @is_auto_create_stats_on INT; SELECT @is_auto_close_on=SUM(CONVERT(INT,[is_auto_close_on])), @is_auto_shrink_on=SUM(CONVERT(INT,[is_auto_shrink_on])), @page_verify_option=SUM(CASE [page_verify_option] WHEN 0 THEN 1 WHEN 1 THEN 1 ELSE 0 END), @is_auto_update_stats_on=SUM(CASE [is_auto_update_stats_on] WHEN 1 THEN 0 WHEN 0 THEN 1 END), @is_auto_create_stats_on=SUM(CASE [is_auto_create_stats_on] WHEN 1 THEN 0 WHEN 0 THEN 1 END) FROM [master].[sys].[databases]; IF (@is_auto_close_on+@is_auto_shrink_on+@is_auto_update_stats_on+@is_auto_create_stats_on)>0 BEGIN PRINT '[!] Databases configured with auto-close '+CONVERT(NVARCHAR(8),@is_auto_close_on)+', auto-shrink '+CONVERT(NVARCHAR(8),@is_auto_shrink_on)+', no auto-create-stats '+CONVERT(NVARCHAR(8),@is_auto_create_stats_on)+', no auto-update-stats '+CONVERT(NVARCHAR(8),@is_auto_update_stats_on); END; -- ### Page verification other than CHECKSUM IF (@page_verify_option)>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(8),@page_verify_option)+' databases are configured with page verification other than CHECKSUM'; END; -- ### Logs with high use DECLARE @LogsHighUse INT; SELECT @LogsHighUse=COUNT([cntr_value]) FROM [sys].[dm_os_performance_counters] WHERE [counter_name] ='Percent Log Used' AND [cntr_value]>80 AND [instance_name]<>'_Total'; IF @LogsHighUse>0 BEGIN IF @os_performance_counters<>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(8),@LogsHighUse)+' transaction logs are currently used above 80% of their total size'; END; END; ELSE IF @LogsHighUse=0 BEGIN PRINT 'All transaction logs are currently used below 80% of their total size'; END; -- ### VLF count on all DBs DECLARE @DBs_High_VLfs INT, @TopDB_High_VLfs NVARCHAR(256), @TopDB_High_VLfs_Count INT, @DBName sysname, @VLfs INT; CREATE TABLE [#DatabasesVLFs] ([DBName] sysname); DECLARE @VLFCounts TABLE ([DBName] sysname,[VLFCount] INT); INSERT INTO [#DatabasesVLFs] SELECT [name] FROM [sys].[databases] WHERE [state]=0; IF LEFT(CAST(SERVERPROPERTY('PRODUCTVERSION') AS NVARCHAR(MAX)),CHARINDEX('.',CAST(SERVERPROPERTY('PRODUCTVERSION') AS NVARCHAR(MAX)))-1) < 11 BEGIN DECLARE @DBCCLogInfo TABLE ([FileID] TINYINT, [File_Size] BIGINT, [Start_Offset] BIGINT,[FSeqNo] INT,[Status] TINYINT,[Parity] TINYINT,[Create_LSN] NUMERIC(25,0)); WHILE EXISTS(SELECT TOP 1 [DBName] FROM [#DatabasesVLFs]) BEGIN SET @DBName = (SELECT TOP 1 [DBName] FROM [#DatabasesVLFs]); SET @cmd = 'DBCC LOGINFO (' + '''' + @DBName + ''' ) WITH NO_INFOMSGS'; INSERT INTO @DBCCLogInfo EXEC (@cmd); SET @VLfs=@@ROWCOUNT; INSERT @VLFCounts VALUES(@DBName, @VLfs); DELETE FROM [#DatabasesVLFs] WHERE [DBName]=@DBName; END; END; ELSE BEGIN DECLARE @DBCCLogInfo2012 TABLE ([RECOVERYUNITID] INT, [FileID] TINYINT, [File_Size] BIGINT, [Start_Offset] BIGINT, [FseqNo] INT, [STATUS] TINYINT, [Parity] TINYINT, [Create_LSN] NUMERIC(25,0)); WHILE EXISTS(SELECT TOP 1 [DBName] FROM [#DatabasesVLFs]) BEGIN SET @DBName = (SELECT TOP 1 [DBName] FROM [#DatabasesVLFs]); SET @cmd = 'DBCC LOGINFO (' + '''' + @DBName + ''' ) WITH NO_INFOMSGS'; INSERT INTO @DBCCLogInfo2012 EXEC (@cmd); SET @VLfs = @@ROWCOUNT; INSERT @VLFCounts VALUES(@DBName, @VLfs); DELETE FROM [#DatabasesVLFs] WHERE [DBName] = @DBName; END; END; SELECT @DBs_High_VLfs=COUNT([VLFCount])FROM @VLFCounts WHERE [VLFCount]>300; IF @DBs_High_VLfs>1 BEGIN SELECT TOP 1 @TopDB_High_VLfs_Count=[VLFCount], @TopDB_High_VLfs=[DBName] FROM @VLFCounts ORDER BY [VLFCount] DESC; PRINT '[!] '+CONVERT(NVARCHAR(28),@DBs_High_VLfs)+' transaction logs have more than 300 virtual log files, "'+@TopDB_High_VLfs+'" being top with '+CONVERT(NVARCHAR(28),@TopDB_High_VLfs_Count); END; DROP TABLE [#DatabasesVLFs]; -- ### User databases recovery models DECLARE @recovery_simple INT, @recovery_bulked INT, @recovery_full INT; SELECT @recovery_simple=SUM(CASE [recovery_model] WHEN 3 THEN 1 ELSE 0 END), @recovery_bulked=SUM(CASE [recovery_model] WHEN 2 THEN 1 ELSE 0 END), @recovery_full=SUM(CASE [recovery_model] WHEN 1 THEN 1 ELSE 0 END) FROM [master].[sys].[databases]; BEGIN PRINT 'Databases'' recovery modes are' +' simple '+CONVERT(NVARCHAR(8),@recovery_simple) +', bulk-logged '+CONVERT(NVARCHAR(8),@recovery_bulked) +', full '+CONVERT(NVARCHAR(8),@recovery_full); END; -- ### Databases compatibility levels DECLARE @compatibility_level SMALLINT; SELECT @compatibility_level=ISNULL(COUNT([compatibility_level]),0) FROM [sys].[databases] WHERE LEFT([compatibility_level],2)<>SERVERPROPERTY('ProductMajorVersion'); IF @compatibility_level>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(32),@compatibility_level)+' databases are configured with a compatibility level different from the current engine''s version'; END; -- ### Databases on the server without full backups on the last 7 days DECLARE @DBsNoFullBkps7Days INT; SELECT DISTINCT [database_name] INTO [#backups_full_7days] FROM [msdb].[dbo].[backupmediafamily] INNER JOIN [msdb].[dbo].[backupset] ON [backupmediafamily].[media_set_id] = [backupset].[media_set_id] WHERE DATEDIFF(dd, [backup_start_date],GETDATE())<7 AND [msdb]..[backupset].[type] ='D'; SELECT @DBsNoFullBkps7Days=SUM(CASE WHEN [bkps].[database_name] IS NULL THEN 1 ELSE 0 END) FROM [master].[sys].[databases] [dbs] LEFT JOIN [#backups_full_7days] [bkps] ON [dbs].[name]=[bkps].[database_name] WHERE [dbs].[name] NOT IN ('tempdb','mssqlsystemresource'); DROP TABLE [#backups_full_7days]; IF @DBsNoFullBkps7Days>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(8),@DBsNoFullBkps7Days)+' databases have NOT had a full backup within the last 7 days'; END; ELSE IF @DBsNoFullBkps7Days=0 BEGIN PRINT 'All the databases on the server have had a full backup within the last 7 days'; END; -- ### Databases on the server without an integrity check on the last 7 days /* Credit for this block to Ryan DeVries http://ryandevries.com/ */ DECLARE @DBsNoIntegrityChk INT; SET @DBsNoIntegrityChk=0; CREATE TABLE [#DBInfo] ([ParentObject] VARCHAR(255), [Object] VARCHAR(255), [Field] VARCHAR(255), [Value] VARCHAR(255)); CREATE TABLE [#Value] ([DatabaseName] VARCHAR(255), [LastDBCCCheckDB] DATETIME); EXECUTE [sys].[sp_MSforeachdb] 'INSERT INTO #DBInfo EXECUTE (''DBCC DBINFO ( ''''?'''' ) WITH TABLERESULTS, NO_INFOMSGS''); INSERT INTO #Value (DatabaseName, LastDBCCCheckDB) (SELECT ''?'', [Value] FROM #DBInfo WHERE Field = ''dbi_dbccLastKnownGood''); TRUNCATE TABLE #DBInfo;'; DELETE FROM [#Value] WHERE DATEDIFF(dd,[LastDBCCCheckDB],GETDATE())>15; ;WITH [cte] AS (SELECT ROW_NUMBER() OVER (PARTITION BY [DatabaseName] ORDER BY [LastDBCCCheckDB] DESC) [RN] FROM [#Value]) DELETE FROM [cte] WHERE [cte].[RN] > 1; SELECT @DBsNoIntegrityChk=SUM(CASE WHEN [DatabaseName] IS NULL THEN 1 ELSE 0 END) FROM [master].[sys].[databases] [db] LEFT JOIN [#Value] ON [db].[name]=[DatabaseName]; DROP TABLE [#Value]; DROP TABLE [#DBInfo]; IF @DBsNoIntegrityChk>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(8),@DBsNoIntegrityChk)+' databases have NOT had an integrity check within the last 15 days'; END; ELSE IF @DBsNoIntegrityChk=0 BEGIN PRINT 'All the databases on the server have had an intregrity check within the last 15 days'; END; -- ### Check for existing suspect_pages within the last 15 days /* Feedback from David Klee */ DECLARE @suspect_pages SMALLINT; SELECT @suspect_pages=ISNULL(COUNT(*),0) FROM [msdb].[dbo].[suspect_pages] WHERE [last_update_date]>=(GETDATE()-15); IF @suspect_pages>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(32),@suspect_pages)+' pages have been found marked as curruption-suspect on [msdb].[dbo].[suspect_pages] within the last 15 days'; END; -- ### Check for unsent mail items within the last 7 days DECLARE @unsent_mail SMALLINT; SELECT @unsent_mail=ISNULL(COUNT(*),0) FROM [msdb].[dbo].[sysmail_unsentitems] WHERE [sent_date]>=(GETDATE()-7); IF @unsent_mail>0 BEGIN PRINT '[!] '+CONVERT(NVARCHAR(32),@unsent_mail)+' unsent queued database emails have been found within the last 7 days'; END; --===============================================================================================================================-- --===================================================== MIRRORING STATUS ========================================================-- --===============================================================================================================================-- -- ### Check database mirroring status IF (@mirror_COUNT>0) BEGIN IF (@mirror_COUNT = @mirrorCOUNTsynch) BEGIN PRINT 'All '+CONVERT(NVARCHAR(3),@mirror_COUNT)+' databases configured with mirroring are in sync status'; END; ELSE BEGIN PRINT '[!] '+CONVERT(NVARCHAR(3),(@mirror_COUNT-@mirrorCOUNTsynch))+' databases configured with mirroring are NOT in sync status'; END; -- ### Database mirroring performance stats DECLARE @Mirroring_BytesSent_sec BIGINT ,@Mirroring_BytesReceived_sec BIGINT ,@Mirroring_Mirrored_WriteTransactions_sec BIGINT ,@Mirroring_Transaction_Delay BIGINT ,@Mirroring_LogSendQueueKB BIGINT; ;WITH [perfmon_results_mirroring] AS (SELECT [perfmon_results_t_m].[counter_name], [perfmon_results_t_m].[cntr_value] FROM ( SELECT [perfmon_diff_mirroring].[counter_name], ( CONVERT(DECIMAL(32,2),([perfmon_diff_mirroring].[cntr_value])-CONVERT(DECIMAL(32,2),[perfmon_baseline_m].[cntr_value])) / (DATEDIFF(MILLISECOND,@start_time,GETDATE())/1000) ) [cntr_value] FROM [sys].[dm_os_performance_counters] [perfmon_diff_mirroring] INNER JOIN [#perfmon_baseline_mirroring] [perfmon_baseline_m] ON [perfmon_baseline_m].[counter_name] = [perfmon_diff_mirroring].[counter_name] WHERE [perfmon_diff_mirroring].[object_name]='SQLServer:Database Mirroring' AND ([perfmon_diff_mirroring].[counter_name] IN ('Bytes Sent/sec' ,'Bytes Received/sec' ,'Mirrored Write Transactions/sec' ,'Transaction Delay') AND [perfmon_diff_mirroring].[instance_name]='_Total' ) ) [perfmon_results_t_m] ) SELECT @Mirroring_BytesSent_sec=ISNULL((SELECT [perfmon_results_mirroring].[cntr_value] FROM [perfmon_results_mirroring] WHERE [perfmon_results_mirroring].[counter_name]='Bytes Sent/sec'),0) ,@Mirroring_BytesReceived_sec=ISNULL((SELECT [perfmon_results_mirroring].[cntr_value] FROM [perfmon_results_mirroring] WHERE [perfmon_results_mirroring].[counter_name]='Bytes Received/sec'),0) ,@Mirroring_Mirrored_WriteTransactions_sec=ISNULL((SELECT [perfmon_results_mirroring].[cntr_value] FROM [perfmon_results_mirroring] WHERE [perfmon_results_mirroring].[counter_name]='Mirrored Write Transactions/sec'),0) ,@Mirroring_Transaction_Delay=ISNULL((SELECT [perfmon_results_mirroring].[cntr_value] FROM [perfmon_results_mirroring] WHERE [perfmon_results_mirroring].[counter_name]='Transaction Delay'),0) ,@Mirroring_LogSendQueueKB=ISNULL((SELECT [cntr_value] FROM [sys].[dm_os_performance_counters] WHERE [object_name]='SQLServer:Database Mirroring' AND [instance_name]='_Total' AND [counter_name]='Log Send Queue KB'),0); IF OBJECT_ID('tempdb..#perfmon_baseline_mirroring') IS NOT NULL BEGIN DROP TABLE [#perfmon_baseline_mirroring]; END; PRINT CONVERT(NVARCHAR(8),@Mirroring_Mirrored_WriteTransactions_sec)+' mirrored transactions, sending '+CONVERT(NVARCHAR(8),( @Mirroring_BytesSent_sec/1024) )+ ' KB and receiving '+CONVERT(NVARCHAR(8),(@Mirroring_BytesReceived_sec/1024) )+' KB per second with '+CONVERT(NVARCHAR(8),@Mirroring_Transaction_Delay)+' ms latency, '+CONVERT(NVARCHAR(8),@Mirroring_LogSendQueueKB)+' KB unsent'; END; -- ### Has there been any page autorepair? IF (CONVERT(INT ,@@microsoftversion)>=171051460) --SQL2008R2SP1 or greater BEGIN DECLARE @mirroring_auto_page_repair INT; SELECT @mirroring_auto_page_repair=COUNT([file_id]) FROM [sys].[dm_db_mirroring_auto_page_repair]; IF (@mirroring_auto_page_repair>0) BEGIN PRINT '[!] Mirroring auto page repair has taken place '+CONVERT(NVARCHAR(4) ,@mirroring_auto_page_repair)+' times'; END; END; --===============================================================================================================================-- --======================================================= REPLICATION ===========================================================-- --===============================================================================================================================-- -- ### Replication IF (SELECT SUM(CONVERT(INT,[is_published]))+SUM(CONVERT(INT,[is_subscribed])) FROM [sys].[databases] WHERE [is_published]=1 OR [is_subscribed]=1)>1 BEGIN DECLARE @ReplIsPublished INT, @ReplIsSubscribed INT, @ReplIsDistributor INT; SELECT @ReplIsPublished=SUM(CONVERT(INT,[is_published])), @ReplIsSubscribed=SUM(CONVERT(INT,[is_subscribed])), @ReplIsDistributor=SUM(CONVERT(INT,[is_distributor])) FROM [sys].[databases]; DECLARE @ReplPendingXacts INT; SELECT @ReplPendingXacts=(SELECT SUM([cntr_value]) FROM [sys].[dm_os_performance_counters] WHERE [object_name]='SQLServer:Databases' AND [counter_name]='Repl. Pending Xacts' AND [instance_name] IN ('_Total')) - (SELECT SUM([cntr_value]) FROM [sys].[dm_os_performance_counters] WHERE [object_name]='SQLServer:Databases' AND [counter_name]='Repl. Pending Xacts' AND [instance_name] IN ('master','model','tempdb','msdb')); IF @os_performance_counters<>0 BEGIN PRINT 'Replication is enabled with databases published '+CONVERT(NVARCHAR(64),@ReplIsPublished)+', subscribed '+CONVERT(NVARCHAR(64),@ReplIsSubscribed)+', distributor '+CONVERT(NVARCHAR(64),@ReplIsDistributor)+' having '+CONVERT(NVARCHAR(64),@ReplPendingXacts)+' transactions to be published'; END; END; -- ### Footer print PRINT CHAR(10)+'/* [sp_health_check] by @sqlslancaster - find help at http://sqlhealthcheck.net/how-to */'; GO