SP Blitz
SP Blitz
GO
SET QUOTED_IDENTIFIER ON
GO
IF(@VersionCheckMode = 1)
BEGIN
RETURN;
END;
IF @Help = 1
BEGIN
PRINT '
/*
sp_Blitz from http://FirstResponderKit.org
This script checks the health of your SQL Server and gives you a prioritized
to-do list of the most urgent things you should consider fixing.
Changes - for the full list of improvements and fixes in this version, see:
https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/
Parameter explanations:
MIT License
All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2021.
The above copyright notice and this permission notice shall be included in
all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/';
RETURN;
END; /* @Help = 1 */
/* Last startup */
SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24.
AS NUMERIC(23, 2))
FROM sys.databases
WHERE database_id = 2;
IF @DaysUptime = 0
SET @DaysUptime = .01;
/*
Set the session state of Numeric_RoundAbort to off if any databases
have Numeric Round-Abort enabled.
Stops arithmetic overflow errors during data conversion. See Github
issue #2302 for more info.
*/
IF ( (8192 & @@OPTIONS) = 8192 ) /* Numeric RoundAbort is currently on,
so we may need to turn it off temporarily */
BEGIN
IF EXISTS (SELECT 1
FROM sys.databases
WHERE is_numeric_roundabort_on = 1) /* A
database has it turned on */
BEGIN
SET @NeedToTurnNumericRoundabortBackOn = 1;
SET NUMERIC_ROUNDABORT OFF;
END;
END;
/*
--TOURSTOP01--
See https://www.BrentOzar.com/go/blitztour for a guided tour.
/*
You can build your own table with a list of checks to skip. For
example, you
might have some databases that you don't care about, or some checks you
don't
want to run. Then, when you run sp_Blitz, you can specify these
parameters:
@SkipChecksDatabase = 'DBAtools',
@SkipChecksSchema = 'dbo',
@SkipChecksTable = 'BlitzChecksToSkip'
Pass in the database, schema, and table that contains the list of
checks you
want to skip. This part of the code checks those parameters, gets the
list,
and then saves those in a temp table. As we run each check, we'll see
if we
need to skip it.
*/
/* --TOURSTOP07-- */
IF OBJECT_ID('tempdb..#SkipChecks') IS NOT NULL
DROP TABLE #SkipChecks;
CREATE TABLE #SkipChecks
(
DatabaseName NVARCHAR(128) ,
CheckID INT ,
ServerName NVARCHAR(128)
);
CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID,
DatabaseName);
END;
/* --TOURSTOP08-- */
/* If the server is Amazon RDS, skip checks that it doesn't allow */
IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS
VARCHAR(8000)), 8) = 'EC2AMAZ-'
AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) =
'EC2AMAZ-'
AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) =
'EC2AMAZ-'
AND db_id('rdsadmin') IS NOT NULL
AND EXISTS(SELECT * FROM master.sys.all_objects WHERE name IN
('rds_startup_tasks', 'rds_help_revlogin', 'rds_hexadecimal',
'rds_failover_tracking', 'rds_database_tracking', 'rds_track_change'))
BEGIN
INSERT INTO #SkipChecks (CheckID) VALUES
(6); /* Security - Jobs Owned By Users per https://github.com/BrentOzarULTD/SQL-
Server-First-Responder-Kit/issues/1919 */
INSERT INTO #SkipChecks (CheckID) VALUES
(29); /* tables in model database created by users - not allowed */
INSERT INTO #SkipChecks (CheckID) VALUES
(40); /* TempDB only has one data file in RDS */
INSERT INTO #SkipChecks (CheckID) VALUES
(62); /* Database compatibility level - cannot change in RDS */
INSERT INTO #SkipChecks (CheckID) VALUES
(68); /*Check for the last good DBCC CHECKDB date - can't run DBCC DBINFO() */
INSERT INTO #SkipChecks (CheckID) VALUES
(69); /* High VLF check - requires DBCC LOGINFO permission */
INSERT INTO #SkipChecks (CheckID) VALUES
(73); /* No Failsafe Operator Configured check */
INSERT INTO #SkipChecks (CheckID) VALUES
(92); /* Drive info check - requires xp_Fixeddrives permission */
INSERT INTO #SkipChecks (CheckID) VALUES (100);
/* Remote DAC disabled */
INSERT INTO #SkipChecks (CheckID) VALUES (177);
/* Disabled Internal Monitoring Features check - requires dm_server_registry access
*/
INSERT INTO #SkipChecks (CheckID) VALUES (180);
/* 180/181 are maintenance plans checks - Maint plans not available in RDS*/
INSERT INTO #SkipChecks (CheckID) VALUES (181);
/*Find repetitive maintenance tasks*/
'https://stackoverflow.com/questions/1169634/limitations-of-sql-server-express' AS
URL ,
'Express Edition detected, so we skipped
some checks that are not currently possible, relevant, or practical there.' AS
Details;
END; /* Express Edition skipped checks */
'https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-
index' AS URL ,
'Managed Instance detected, so we skipped
some checks that are not currently possible, relevant, or practical there.' AS
Details;
END; /* Azure Managed Instance skipped checks */
/*
That's the end of the SkipChecks stuff.
The next several tables are used by various checks later.
*/
IF OBJECT_ID('tempdb..#ConfigurationDefaults') IS NOT NULL
DROP TABLE #ConfigurationDefaults;
CREATE TABLE #ConfigurationDefaults
(
name NVARCHAR(128) ,
DefaultValue BIGINT,
CheckID INT
);
IF OBJECT_ID('tempdb..#DatabaseScopedConfigurationDefaults') IS NOT
NULL
DROP TABLE #DatabaseScopedConfigurationDefaults;
CREATE TABLE #DatabaseScopedConfigurationDefaults
(ID INT IDENTITY(1,1), configuration_id INT, [name] NVARCHAR(60),
default_value sql_variant, default_value_for_secondary sql_variant, CheckID INT, );
'https://www.brentozar.com/go/waits',
'Someone ran DBCC SQLPERF to clear
sys.dm_os_wait_stats at approximately: '
+ CONVERT(NVARCHAR(100),
DATEADD(MINUTE, (-1. *
(@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120));
END;
/*
Whew! we're finally done with the setup, and we can start doing checks.
First, let's make sure we're actually supposed to do checks on this
server.
The user could have passed in a SkipChecks table that specified to skip
ALL
checks on this server, so let's check for that:
*/
IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName
FROM
#SkipChecks
WHERE
DatabaseName IS NULL
/*
Our very first check! We'll put more comments in this one
just to
explain exactly how it works. First, we check to see if
we're
supposed to skip CheckID 1 (that's the check we're working
on.)
*/
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL AND
CheckID = 1 )
BEGIN
/*
Below, we check master.sys.databases looking
for databases
that haven't had a backup in the last week. If
we find any,
we insert them into #BlitzResults, the temp
table that
tracks our server's problems. Note that if the
check does
NOT find any problems, we don't save that.
We're only
saving the problems, not the successful checks.
*/
'https://www.brentozar.com/go/nobak' AS URL ,
'Last backed up: '
+
COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details
FROM master.sys.databases d
LEFT OUTER JOIN
msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS =
b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS
DatabaseName
FROM #SkipChecks
-7, GETDATE())
OR MAX(b.backup_finish_date) IS NULL;
END;
'https://www.brentozar.com/go/nobak' AS URL ,
'Last backed up: '
+
COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details
FROM master.sys.databases d
LEFT OUTER JOIN
msdb.dbo.backupset b ON d.name COLLATE SQL_Latin1_General_CP1_CI_AS =
b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS
DatabaseName
FROM #SkipChecks
-7, GETDATE())
OR MAX(b.backup_finish_date) IS NULL;
END;
/*
And there you have it. The rest of this stored
procedure works the same
way: it asks:
- Should I skip this check?
- If not, do I find problems?
- Insert the results into #BlitzResults
*/
END;
/*
And that's the end of CheckID #1.
'https://www.brentozar.com/go/biglogs' AS URL ,
( 'The ' +
CAST(CAST((SELECT ((SUM([mf].[size]) * 8.) / 1024.) FROM sys.[master_files] AS [mf]
WHERE [mf].[database_id] = d.[database_id] AND [mf].[type_desc] = 'LOG') AS
DECIMAL(18,2)) AS VARCHAR(30)) + 'MB log file has not been backed up in the last
week.' ) AS Details
FROM master.sys.databases d
WHERE d.recovery_model IN ( 1,
2 )
AND d.database_id NOT IN
( 2, 3 )
AND d.source_database_id
IS NULL
AND d.state NOT IN(1, 6,
10) /* Not currently offline or restoring, like log shipping databases */
AND d.is_in_standby =
0 /* Not a log shipping target database */
AND d.source_database_id
IS NULL /* Excludes database snapshots */
AND d.name NOT IN
( SELECT DISTINCT
DatabaseName
FROM #SkipChecks
FROM msdb.dbo.backupset b
-7, GETDATE()) );
END;
/*
CheckID #256 is searching for backups to NUL.
*/
'https://www.brentozar.com/go/nul' AS URL ,
N'The transaction log
file has been backed up ' + CAST((SELECT count(*)
msdb.dbo.backupmediafamily AS bmf
ON b.media_set_id = bmf.media_set_id
msdb.dbo.backupmediafamily AS bmf
ON b.media_set_id = bmf.media_set_id
-7, GETDATE()) );
END;
/*
Next up, we've got CheckID 8. (These don't have to go in
order.) This one
won't work on SQL Server 2005 because it relies on a new
DMV that didn't
exist prior to SQL Server 2008. This means we have to check
the SQL Server
version first, then build a dynamic string with the query
we want to run:
*/
EXECUTE(@StringToExecute);
END;
END;
/*
But what if you need to run a query in every individual
database?
Hop down to the @CheckUserDatabaseObjects section.
And that's the basic idea! You can read through the rest of
the
checks if you like - some more exciting stuff happens
closer to the
end of the stored proc, where we start doing things like
checking
the plan cache, but those aren't as cleanly commented.
If you'd like to contribute your own check, use one of the
check
formats shown above and email it to Help@BrentOzar.com. You
don't
have to pick a CheckID or a link - we'll take care of that
when we
test and publish the code. Thanks!
*/
'https://www.brentozar.com/go/backup' AS URL ,
CAST(COUNT(1) AS
VARCHAR(50)) + ' backups done on drive '
+
UPPER(LEFT(bmf.physical_device_name, 3))
+ ' in the last two
weeks, where database files also live. This represents a serious risk if that array
fails.' Details
FROM msdb.dbo.backupmediafamily
AS bmf
INNER JOIN
msdb.dbo.backupset AS bs ON bmf.media_set_id = bs.media_set_id
-14, GETDATE()) )
/* Filter out databases
that were recently restored: */
LEFT OUTER JOIN
msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name AND
rh.restore_date > DATEADD(dd, -14, GETDATE())
WHERE
UPPER(LEFT(bmf.physical_device_name, 3)) <> 'HTT' AND
bmf.physical_device_name NOT LIKE '\\%' AND
-- GitHub Issue #2141
@IsWindowsOperatingSystem = 1 AND -- GitHub
Issue #1995
UPPER(LEFT(bmf.physical_device_name COLLATE
SQL_Latin1_General_CP1_CI_AS, 3)) IN (
SELECT DISTINCT
''https://www.brentozar.com/go/tde'' AS URL,
''The certificate '' + c.name + ''
is used to encrypt database '' + db_name(dek.database_id) + ''. Last backup date:
'' + COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS
Details
FROM sys.certificates c INNER JOIN
sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint
WHERE pvt_key_last_backup_date IS
NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION
(RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL
AND CheckID = 202 )
AND EXISTS ( SELECT *
FROM sys.all_columns c
WHERE c.name =
'pvt_key_last_backup_date' )
AND EXISTS ( SELECT *
FROM
msdb.INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_NAME =
'backupset' AND c.COLUMN_NAME = 'encryptor_thumbprint' )
BEGIN
''https://www.brentozar.com/go/tde'' AS URL,
''The certificate '' + c.name + ''
is used to encrypt database backups. Last backup date: '' +
COALESCE(CAST(c.pvt_key_last_backup_date AS VARCHAR(100)), ''Never'') AS Details
FROM sys.certificates c
INNER JOIN msdb.dbo.backupset bs ON c.thumbprint =
bs.encryptor_thumbprint
WHERE pvt_key_last_backup_date IS NULL OR
pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
BEGIN
'https://www.brentozar.com/go/history' AS URL ,
( 'Database backup
history retained back to '
+
CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details
FROM msdb.dbo.backupset bs
LEFT OUTER JOIN msdb.dbo.restorehistory rh ON
bs.database_name = rh.destination_database_name
WHERE rh.destination_database_name IS NULL
ORDER BY bs.backup_start_date ASC;
END;
END;
BEGIN
'https://www.brentozar.com/go/history' AS URL ,
( 'Database backup
history only retained back to '
+
CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details
FROM msdb.dbo.backupset bs
ORDER BY backup_start_date
ASC;
END;
END;
'https://www.brentozar.com/go/snaps' AS URL ,
( CAST(COUNT(*) AS
VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks,
indicating that IO may be freezing up.') AS Details
FROM msdb.dbo.backupset bs
WHERE bs.type = 'D'
AND bs.backup_size >=
50000000000 /* At least 50GB */
AND DATEDIFF(SECOND,
bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60
seconds */
AND bs.backup_finish_date >=
DATEADD(DAY, -14, GETDATE()); /* In the last 2 weeks */
END;
'https://www.brentozar.com/go/toomanysnaps' AS URL ,
( CAST(SUM(1) AS
VARCHAR(20)) + ' databases snapshotted at once in the last two weeks, indicating
that IO may be freezing up. Microsoft does not recommend VSS snaps for 35 or more
databases.') AS Details
FROM msdb.dbo.backupset bs
WHERE bs.type = 'D'
AND bs.backup_finish_date >=
DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */
GROUP BY bs.backup_finish_date
HAVING SUM(1) >= 35
ORDER BY SUM(1) DESC;
END;
'https://www.brentozar.com/go/sa' AS URL ,
( 'Login [' + l.name
+ '] is a sysadmin -
meaning they can do absolutely anything in SQL Server, including dropping databases
or hiding their tracks.' ) AS Details
FROM master.sys.syslogins l
WHERE l.sysadmin = 1
AND l.name <>
SUSER_SNAME(0x01)
AND l.denylogin = 0
AND l.name NOT LIKE 'NT
SERVICE\%'
AND l.name <>
'l_certSignSmDetach'; /* Added in SQL 2016 */
END;
'https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-
procedures/sp-validatelogins-transact-sql' AS URL ,
( 'Windows user or group
' + QUOTENAME(LoginName) + ' is mapped to a SQL Server principal but no longer
exists in the Windows environment.') AS Details
FROM #InvalidLogins
;
END;
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL AND
CheckID = 5 )
BEGIN
'https://www.brentozar.com/go/sa' AS URL ,
( 'Login [' + l.name
+ '] is a security
admin - meaning they can give themselves permission to do absolutely anything in
SQL Server, including dropping databases or hiding their tracks.' ) AS Details
FROM master.sys.syslogins l
WHERE l.securityadmin = 1
AND l.name <>
SUSER_SNAME(0x01)
AND l.denylogin = 0;
END;
'https://www.brentozar.com/go/sa' AS [URL] ,
'Login [' + pri.[name]
+ '] has the CONTROL
SERVER permission - meaning they can do absolutely anything in SQL Server,
including dropping databases or hiding their tracks.' AS [Details]
FROM sys.server_principals AS
pri
WHERE pri.[principal_id] IN (
SELECT p.
[grantee_principal_id]
FROM
sys.server_permissions AS p
WHERE p.[state] IN
( 'G', 'W' )
AND p.
[class] = 100
AND p.[type]
= 'CL' )
AND pri.[name] NOT LIKE
'##%##';
END;
'https://www.brentozar.com/go/owners' AS URL ,
( 'Job [' + j.name + ']
is owned by ['
+
SUSER_SNAME(j.owner_sid)
+ '] - meaning if
their login is disabled or not available due to Active Directory problems, the job
will stop working.' ) AS Details
FROM msdb.dbo.sysjobs j
WHERE j.enabled = 1
AND
SUSER_SNAME(j.owner_sid) <> SUSER_SNAME(0x01);
END;
/* --TOURSTOP06-- */
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL AND
CheckID = 7 )
BEGIN
/* --TOURSTOP02-- */
'https://www.brentozar.com/go/startup' AS URL ,
( 'Stored procedure
[master].['
+ r.SPECIFIC_SCHEMA +
'].['
+ r.SPECIFIC_NAME
+ '] runs
automatically when SQL Server starts up. Make sure you know exactly what this
stored procedure is doing, because it could pose a security risk.' ) AS Details
FROM
master.INFORMATION_SCHEMA.ROUTINES r
WHERE
OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME),
'ExecIsStartup') = 1;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 10) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 11) WITH NOWAIT;
'https://www.brentozar.com/go/autoclose' AS URL ,
( 'Database [' + [name]
+ '] has auto-close
enabled. This setting can dramatically decrease performance.' ) AS Details
FROM sys.databases
WHERE is_auto_close_on = 1
AND name NOT IN ( SELECT
DISTINCT
DatabaseName
FROM #SkipChecks
'https://www.brentozar.com/go/autoshrink' AS URL ,
( 'Database [' + [name]
+ '] has auto-shrink
enabled. This setting can dramatically decrease performance.' ) AS Details
FROM sys.databases
WHERE is_auto_shrink_on = 1
AND state <> 6 /*
Offline */
AND name NOT IN ( SELECT
DISTINCT
DatabaseName
FROM #SkipChecks
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 14) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
END;
'https://www.brentozar.com/go/acs' AS URL ,
( 'Database [' + [name]
+ '] has auto-create-
stats disabled. SQL Server uses statistics to build better execution plans, and
without the ability to automatically create more, performance may suffer.' ) AS
Details
FROM sys.databases
WHERE is_auto_create_stats_on = 0
AND name NOT IN ( SELECT
DISTINCT
DatabaseName
FROM #SkipChecks
'https://www.brentozar.com/go/aus' AS URL ,
( 'Database [' + [name]
+ '] has auto-update-
stats disabled. SQL Server uses statistics to build better execution plans, and
without the ability to automatically update them, performance may suffer.' ) AS
Details
FROM sys.databases
WHERE is_auto_update_stats_on = 0
AND name NOT IN ( SELECT
DISTINCT
DatabaseName
FROM #SkipChecks
'https://www.brentozar.com/go/asyncstats' AS URL ,
( 'Database [' + [name]
+ '] has auto-update-
stats-async enabled. When SQL Server gets a query for a table with out-of-date
statistics, it will run the query with the stats it has - while updating stats to
make later queries better. The initial run of the query may suffer, though.' ) AS
Details
FROM sys.databases
WHERE
is_auto_update_stats_async_on = 1
AND name NOT IN ( SELECT
DISTINCT
DatabaseName
FROM #SkipChecks
'https://www.brentozar.com/go/corr' AS URL ,
( 'Database [' + [name]
+ '] has date
correlation enabled. This is not a default setting, and it has some performance
overhead. It tells SQL Server that date fields in two tables are related, and SQL
Server maintains statistics showing that relation.' ) AS Details
FROM sys.databases
WHERE is_date_correlation_on = 1
AND name NOT IN ( SELECT
DISTINCT
DatabaseName
FROM #SkipChecks
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 21) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
END;
/*
Believe it or not, SQL Server doesn't track the default
values
for sp_configure options! We'll make our own list here.
*/
'https://www.brentozar.com/go/conf' AS URL ,
( 'This sp_configure
option has been changed. Its default value is '
+ COALESCE(CAST(cd.
[DefaultValue] AS VARCHAR(100)),
'(unknown)')
+ ' and it has been
set to '
+ CAST(cr.value_in_use
AS VARCHAR(100))
+ '.' ) AS Details
FROM sys.configurations cr
INNER JOIN
#ConfigurationDefaults cd ON cd.name = cr.name
LEFT OUTER JOIN
#ConfigurationDefaults cdUsed ON cdUsed.name = cr.name
AND cdUsed.DefaultValue = cr.value_in_use
WHERE cdUsed.name IS NULL;
END;
IF (@MinServerMemory = @MaxServerMemory)
BEGIN
'https://www.brentozar.com/go/memory',
'Minimum Server Memory
setting is the same as the Maximum (both set to ' + CAST(@MinServerMemory AS
NVARCHAR(50)) + '). This will not allow dynamic memory. Please revise memory
settings'
);
END;
END;
'https://www.brentozar.com/go/cxpacket' AS URL ,
( 'Set to ' +
CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this
sp_configure setting may reduce CXPACKET waits.')
FROM sys.configurations cr
INNER JOIN
#ConfigurationDefaults cd ON cd.name = cr.name
AND
cr.value_in_use = cd.DefaultValue
WHERE cr.name = 'cost threshold
for parallelism'
OR (cr.name = 'max degree of
parallelism' AND (@NUMANodes > 1 OR @Processors > 8));
END;
'https://www.brentozar.com/go/cdrive' AS URL ,
( 'The ' +
DB_NAME(database_id)
+ ' database has a
file on the C drive. Putting system databases on the C drive runs the risk of
crashing the server when it runs out of space.' ) AS Details
FROM sys.master_files
WHERE UPPER(LEFT(physical_name,
1)) = 'C'
AND DB_NAME(database_id)
IN ( 'master',
'model', 'msdb' );
END;
'https://www.brentozar.com/go/cdrive' AS URL ,
CASE WHEN growth > 0
THEN ( 'The
tempdb database has files on the C drive. TempDB frequently grows unpredictably,
putting your server at risk of running out of C drive space and crashing hard. C
is also often much slower than other drives, so performance may be suffering.' )
ELSE ( 'The
tempdb database has files on the C drive. TempDB is not set to Autogrow, hopefully
it is big enough. C is also often much slower than other drives, so performance
may be suffering.' )
END AS Details
FROM sys.master_files
WHERE UPPER(LEFT(physical_name,
1)) = 'C'
AND DB_NAME(database_id)
= 'tempdb';
END;
'https://www.brentozar.com/go/cdrive' AS URL ,
( 'The ' +
DB_NAME(database_id)
+ ' database has a
file on the C drive. Putting databases on the C drive runs the risk of crashing
the server when it runs out of space.' ) AS Details
FROM sys.master_files
WHERE UPPER(LEFT(physical_name,
1)) = 'C'
AND DB_NAME(database_id)
NOT IN ( 'master',
'model', 'msdb',
'tempdb' )
AND DB_NAME(database_id)
NOT IN (
SELECT DISTINCT
DatabaseName
FROM #SkipChecks
WHERE CheckID IS NULL OR
CheckID = 26 );
END;
'https://www.brentozar.com/go/mastuser' AS URL ,
( 'The ' + name
+ ' table in the
master database was created by end users on '
+ CAST(create_date AS
VARCHAR(20))
+ '. Tables in the
master database may not be restored in the event of a disaster.' ) AS Details
FROM master.sys.tables
WHERE is_ms_shipped = 0
AND name NOT IN
('CommandLog','SqlServerVersions','$ndo$srvproperty');
/* That last one is the Dynamics
NAV licensing table: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-
Kit/issues/2426 */
END;
'https://www.brentozar.com/go/msdbuser' AS URL ,
( 'The ' + name
+ ' table in the msdb
database was created by end users on '
+ CAST(create_date AS
VARCHAR(20))
+ '. Tables in the
msdb database may not be restored in the event of a disaster.' ) AS Details
FROM msdb.sys.tables
WHERE is_ms_shipped = 0 AND name
NOT LIKE '%DTA_%';
END;
'https://www.brentozar.com/go/model' AS URL ,
( 'The ' + name
+ ' table in the model
database was created by end users on '
+ CAST(create_date AS
VARCHAR(20))
+ '. Tables in the
model database are automatically copied into all new databases.' ) AS Details
FROM model.sys.tables
WHERE is_ms_shipped = 0;
END;
BEGIN
'https://www.brentozar.com/go/alert' AS URL ,
( 'Not all SQL
Server Agent alerts have been configured. This is a free, easy way to get notified
of corruption, job failures, or major outages even before monitoring systems pick
it up.' ) AS Details;
END;
END;
BEGIN
'https://www.brentozar.com/go/alert' AS URL ,
( 'SQL Server
Agent alerts have been configured but they either do not notify anyone or else they
do not take any action. This is a free, easy way to get notified of corruption,
job failures, or major outages even before monitoring systems pick it up.' ) AS
Details;
END;
END;
BEGIN;
'https://www.brentozar.com/go/alert' AS URL ,
( 'SQL Server
Agent alerts do not exist for errors 823, 824, and 825. These three errors can
give you notification about early hardware failure. Enabling them can prevent you a
lot of heartbreak.' ) AS Details;
END;
END;
BEGIN
'https://www.brentozar.com/go/alert' AS URL ,
( 'SQL Server
Agent alerts do not exist for severity levels 19 through 25. These are some very
severe SQL Server errors. Knowing that these are happening may let you recover from
errors faster.' ) AS Details;
END;
END;
BEGIN
'https://www.brentozar.com/go/alert' AS URL ,
( 'The following
Alert is disabled, please review and enable if desired: '
+ name ) AS
Details
FROM msdb.dbo.sysalerts
WHERE enabled = 0;
END;
END;
BEGIN
'https://www.brentozar.com/go/op' AS URL ,
( 'No SQL Server
Agent operators (emails) have been configured. This is a free, easy way to get
notified of corruption, job failures, or major outages even before monitoring
systems pick it up.' ) AS Details;
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 34) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 89) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 90) WITH NOWAIT;
SET @StringToExecute = 'INSERT INTO
#BlitzResults
(CheckID,
DatabaseName,
Priority,
FindingsGroup,
Finding,
URL,
Details)
SELECT DISTINCT
90 AS CheckID ,
db.name ,
1 AS Priority ,
''Corruption'' AS FindingsGroup ,
''Database Corruption Detected'' AS Finding ,
''https://www.brentozar.com/go/repair'' AS URL ,
( ''SQL Server has detected at least one corrupt page in the last 30
days. For more information, query the system table msdb.dbo.suspect_pages.'' ) AS
Details
FROM msdb.dbo.suspect_pages sp
INNER JOIN master.sys.databases db ON sp.database_id = db.database_id
WHERE sp.last_update_date >= DATEADD(dd, -30, GETDATE()) OPTION
(RECOMPILE);';
EXECUTE(@StringToExecute);
END;
END;
'https://www.brentozar.com/go/slow' AS URL ,
'Writes are averaging
longer than 100ms for at least one database on this drive. For specific database
file speeds, run the query from the information link.' AS Details
FROM
sys.dm_io_virtual_file_stats(NULL, NULL)
AS fs
INNER JOIN
sys.master_files AS mf ON fs.database_id = mf.database_id
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 40) WITH NOWAIT;
'https://www.brentozar.com/go/tempdb' ,
'TempDB is only
configured with one data file. More data files are usually required to alleviate
SGAM contention.'
);
END;
END;
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 183) WITH NOWAIT;
INSERT INTO #BlitzResults
( CheckID ,
DatabaseName ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
VALUES ( 183 ,
'tempdb' ,
170 ,
'File Configuration' ,
'TempDB Unevenly Sized
Data Files' ,
'https://www.brentozar.com/go/tempdb' ,
'TempDB data files are
not configured with the same size. Unevenly sized tempdb data files will result in
unevenly sized workloads.'
);
END;
END;
'https://www.brentozar.com/go/hints' AS URL ,
CAST(occurrence AS
VARCHAR(10))
+ ' instances of order
hinting have been recorded since restart. This means queries are bossing the SQL
Server optimizer around, and if they don''t know what they''re doing, this can
cause more harm than good. This can also explain why DBA tuning efforts aren''t
working.' AS Details
FROM
sys.dm_exec_query_optimizer_info
WHERE counter = 'order hint'
AND occurrence > 1000;
END;
'https://www.brentozar.com/go/hints' AS URL ,
CAST(occurrence AS
VARCHAR(10))
+ ' instances of join
hinting have been recorded since restart. This means queries are bossing the SQL
Server optimizer around, and if they don''t know what they''re doing, this can
cause more harm than good. This can also explain why DBA tuning efforts aren''t
working.' AS Details
FROM
sys.dm_exec_query_optimizer_info
WHERE counter = 'join hint'
AND occurrence > 1000;
END;
'https://www.brentozar.com/go/link' AS URL ,
+CASE WHEN l.remote_name
= 'sa'
THEN
COALESCE(s.data_source, s.provider)
+ ' is
configured as a linked server. Check its security configuration as it is connecting
with sa, because any user who queries it will get admin-level permissions.'
ELSE
COALESCE(s.data_source, s.provider)
+ ' is
configured as a linked server. Check its security configuration to make sure it
isn''t connecting with SA or some other bone-headed administrative login, because
any user who queries it might get admin-level permissions.'
END AS Details
FROM sys.servers s
INNER JOIN
sys.linked_logins l ON s.server_id = l.server_id
WHERE s.is_linked = 1;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 50) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 51) WITH NOWAIT
EXECUTE(@StringToExecute);
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 159) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
END;
SELECT @AOAG =
CAST(SERVERPROPERTY('IsHadrEnabled') AS INT)
SELECT @AOFCI =
CAST(SERVERPROPERTY('IsClustered') AS INT)
SELECT @HAType =
CASE
WHEN @AOFCI = 1 AND @AOAG =1
THEN 'FCIAG'
WHEN @AOFCI = 1 AND @AOAG =0
THEN 'FCI'
WHEN @AOFCI = 0 AND @AOAG =1
THEN 'AG'
ELSE 'STANDALONE'
END
IF (@HAType IN ('FCIAG','FCI','AG'))
BEGIN
SELECT 53 AS CheckID ,
200 AS Priority ,
'Informational' AS FindingsGroup
,
'Cluster Node' AS Finding ,
'https://BrentOzar.com/go/node'
AS URL ,
'This is a node in a cluster.'
AS Details
IF @HAType = 'AG'
BEGIN
INSERT INTO
#BlitzResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT 53 AS
CheckID ,
200 AS Priority ,
'Informational' AS
FindingsGroup ,
'Cluster Node Info'
AS Finding ,
'https://BrentOzar.com/go/node' AS URL,
'The cluster nodes
are: ' + STUFF((SELECT ', ' + CASE ar.replica_server_name WHEN
dhags.primary_replica THEN 'PRIMARY'
ELSE 'SECONDARY'
END + '=' +
UPPER(ar.replica_server_name)
FROM
sys.availability_groups AS ag
LEFT OUTER JOIN
sys.availability_replicas AS ar ON ag.group_id = ar.group_id
LEFT OUTER JOIN
sys.dm_hadr_availability_group_states as dhags ON ag.group_id = dhags.group_id
ORDER BY 1
FOR XML PATH ('')
), 1, 1, '') + ' -
High Availability solution used is AlwaysOn Availability Group (AG)' As Details
END
IF @HAType = 'FCI'
BEGIN
INSERT INTO
#BlitzResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT 53 AS
CheckID ,
200 AS Priority ,
'Informational' AS
FindingsGroup ,
'Cluster Node Info'
AS Finding ,
'https://BrentOzar.com/go/node' AS URL,
'The cluster nodes
are: ' + STUFF((SELECT ', ' + CASE is_current_owner
WHEN 1
THEN 'PRIMARY'
ELSE
'SECONDARY'
END + '=' +
UPPER(NodeName)
FROM
sys.dm_os_cluster_nodes
ORDER BY 1
FOR XML PATH ('')
), 1, 1, '') + ' -
High Availability solution used is AlwaysOn Failover Cluster Instance (FCI)' As
Details
END
IF @HAType = 'FCIAG'
BEGIN
INSERT INTO
#BlitzResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT 53 AS
CheckID ,
200 AS Priority ,
'Informational' AS
FindingsGroup ,
'Cluster Node Info'
AS Finding ,
'https://BrentOzar.com/go/node' AS URL,
'The cluster nodes
are: ' + STUFF((SELECT ', ' + HighAvailabilityRoleDesc + '=' + ServerName
FROM (SELECT
UPPER(ar.replica_server_name) AS ServerName
ELSE 'SECONDARY'
END AS HighAvailabilityRoleDesc
FROM sys.availability_groups AS ag
UNION ALL
,CASE is_current_owner
ELSE 'SECONDARY'
END AS HighAvailabilityRoleDesc
FROM sys.dm_os_cluster_nodes) AS Z
ORDER BY 1
FOR XML PATH ('')
), 1, 1, '') + ' -
High Availability solution used is AlwaysOn FCI with AG (Failover Cluster Instance
with Availability Group).'As Details
END
END
END;
IF @UsualDBOwner IS NULL
SET @UsualDBOwner = SUSER_SNAME(0x01);
FROM #SkipChecks
'https://www.brentozar.com/go/startup' AS URL ,
( 'Job [' + j.name
+ '] runs
automatically when SQL Server Agent starts up. Make sure you know exactly what
this job is doing, because it could pose a security risk.' ) AS Details
FROM msdb.dbo.sysschedules sched
JOIN
msdb.dbo.sysjobschedules jsched ON sched.schedule_id = jsched.schedule_id
JOIN msdb.dbo.sysjobs j
ON jsched.job_id = j.job_id
WHERE sched.freq_type = 64
AND sched.enabled = 1;
END;
'https://www.brentozar.com/go/workgroup' AS URL ,
( 'This server is using
'
+
CAST(SERVERPROPERTY('edition') AS VARCHAR(100))
+ ', which is capped
at low amounts of CPU and memory.' ) AS Details
WHERE
CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'
AND
CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%'
AND
CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Data Center%'
AND
CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%'
AND
CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Business Intelligence%';
END;
'https://www.brentozar.com/go/32bit' AS URL ,
( 'This server uses the
32-bit x86 binaries for SQL Server instead of the 64-bit x64 binaries. The amount
of memory available for query workspace and execution plans is heavily limited.' )
AS Details
WHERE
CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%';
END;
'https://www.brentozar.com/go/compatlevel' AS URL ,
( 'Database ' + [name]
+ ' is compatibility
level '
+
CAST(compatibility_level AS VARCHAR(20))
+ ', which may cause
unwanted results when trying to run queries that have newer T-SQL features.' ) AS
Details
FROM sys.databases
WHERE name NOT IN ( SELECT
DISTINCT
DatabaseName
FROM
#SkipChecks
'https://www.brentozar.com/go/alerts' AS URL ,
'The job ' + [name]
+ ' has not been set up
to notify an operator if it fails.' AS Details
FROM msdb.[dbo].[sysjobs] j
WHERE j.enabled = 1
AND
j.notify_email_operator_id = 0
AND
j.notify_netsend_operator_id = 0
AND
j.notify_page_operator_id = 0
AND j.category_id <>
100; /* Exclude SSRS category */
END;
IF EXISTS ( SELECT 1
FROM sys.configurations
WHERE name = 'remote admin connections'
AND value_in_use = 0 )
AND NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL
AND CheckID = 100 )
BEGIN
'https://www.brentozar.com/go/dac' AS URL ,
'Remote access to the
Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote
troubleshooting much easier when SQL Server is unresponsive.';
END;
IF EXISTS ( SELECT *
FROM sys.dm_os_schedulers
WHERE is_online = 0 )
AND NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL
AND CheckID = 101 )
BEGIN
'https://www.brentozar.com/go/schedulers' AS URL ,
'Some CPU cores are not
accessible to SQL Server due to affinity masking or licensing problems.';
END;
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
50 AS Priority ,
''Performance'' AS FindingGroup ,
''https://www.brentozar.com/go/schedulers'' AS URL ,
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
IF EXISTS ( SELECT *
FROM sys.databases
WHERE state > 1 )
AND NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL
AND CheckID = 102 )
BEGIN
'https://www.brentozar.com/go/repair' AS URL ,
'This database may not
be online.'
FROM sys.databases
WHERE state > 1;
END;
IF EXISTS ( SELECT *
FROM master.sys.extended_procedures )
AND NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL
AND CheckID = 105 )
BEGIN
'https://www.brentozar.com/go/clr' AS URL ,
'The [' + name
+ '] extended stored
procedure is in the master database. CLR may be in use, and the master database now
needs to be part of your backup/recovery planning.'
FROM
master.sys.extended_procedures;
END;
'https://www.brentozar.com/go/serializable' AS URL ,
'https://www.brentozar.com/go/shipping' AS URL ,
d.[name] + ' is in
a restoring state, but has not had a backup applied in the last two days. This is a
possible indication of a broken transaction log shipping setup.'
FROM
[master].sys.databases d
INNER JOIN
[master].sys.database_mirroring dm ON d.database_id = dm.database_id
AND
dm.mirroring_role IS NULL
WHERE ( d.[state]
= 1
OR (d.[state] = 0
AND d.[is_in_standby] = 1) )
AND NOT
EXISTS(SELECT * FROM msdb.dbo.restorehistory rh
INNER JOIN
msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id
WHERE d.[name]
COLLATE SQL_Latin1_General_CP1_CI_AS = rh.destination_database_name COLLATE
SQL_Latin1_General_CP1_CI_AS
AND
rh.restore_date >= DATEADD(dd, -2, GETDATE()));
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 112) WITH NOWAIT;
''https://www.brentozar.com/go/tracking'' AS URL,
( d.[name] + '' has change tracking
enabled. This is not a default setting, and it has some performance overhead. It
keeps track of changes to rows in tables that have change tracking turned on.'' )
AS Details FROM sys.change_tracking_databases AS ctd INNER JOIN sys.databases AS d
ON ctd.database_id = d.database_id OPTION (RECOMPILE)';
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
''https://www.brentozar.com/go/backup'' AS URL ,
''Uncompressed
full backups have happened recently, and backup compression is not turned on at the
server level. Backup compression is included with SQL Server 2008R2 & newer, even
in Standard Edition. We recommend turning backup compression on by default so that
ad-hoc backups will get compressed.''
FROM
sys.configurations
WHERE
configuration_id = 1579 AND CAST(value_in_use AS INT) = 0
AND EXISTS (SELECT * FROM
msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND
backup_finish_date >= DATEADD(DD, -14, GETDATE())) OPTION (RECOMPILE);';
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 117) WITH NOWAIT;
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 124) WITH NOWAIT;
'https://www.brentozar.com/go/deadlocks',
CAST(CAST(p.cntr_value /
@DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find
them, run sp_BlitzLock.' AS Details
FROM sys.dm_os_performance_counters
p
INNER JOIN sys.databases d ON
d.name = 'tempdb'
WHERE RTRIM(p.counter_name) =
'Number of Deadlocks/sec'
AND RTRIM(p.instance_name) =
'_Total'
AND p.cntr_value > 0
AND (1.0 * p.cntr_value /
NULLIF(datediff(DD,create_date,CURRENT_TIMESTAMP),0)) > 10;
END;
DECLARE @user_perm_sql
NVARCHAR(MAX) = N'';
DECLARE @user_perm_gb_out
DECIMAL(38,2);
IF @ProductVersionMajor >= 11
BEGIN
END
IF @ProductVersionMajor < 11
BEGIN
SET @user_perm_sql += N'
SELECT @user_perm_gb = CASE
WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2.
THEN
CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.))
ELSE NULL
END
FROM sys.dm_os_memory_clerks
WHERE type =
''USERSTORE_TOKENPERM''
AND name =
''TokenAndPermUserStore''
';
END
EXEC sys.sp_executesql
@user_perm_sql,
IF (@ProductVersionMajor = 15 AND
@ProductVersionMinor < 2000) OR
(@ProductVersionMajor = 14 AND
@ProductVersionMinor < 1000) OR
(@ProductVersionMajor = 13 AND
@ProductVersionMinor < 5026) OR
(@ProductVersionMajor = 12 AND
@ProductVersionMinor < 6024) OR
(@ProductVersionMajor = 11 AND
@ProductVersionMinor < 7001) OR
(@ProductVersionMajor = 10.5 /*AND
@ProductVersionMinor < 6000*/) OR
(@ProductVersionMajor = 10 /*AND
@ProductVersionMinor < 6000*/) OR
(@ProductVersionMajor = 9 /*AND
@ProductVersionMinor <= 5000*/)
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 129) WITH NOWAIT;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 157) WITH NOWAIT;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 189) WITH NOWAIT;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT;
END;
/* Cumulative Update Available */
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS
NULL AND CheckID = 217 )
AND SERVERPROPERTY('EngineEdition') NOT IN (5,8) /*
Azure Managed Instances and Azure SQL DB*/
AND EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'SqlServerVersions' AND TABLE_TYPE = 'BASE TABLE')
AND NOT EXISTS (SELECT * FROM #BlitzResults WHERE
CheckID IN (128, 129, 157, 189, 216)) /* Other version checks */
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 217) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 145) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 146) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 147) WITH NOWAIT
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 148) WITH NOWAIT;
'https://www.brentozar.com/go/nas' AS URL ,
( 'Files for this
database are on: ' + LEFT(mf.physical_name, 30)) AS Details
FROM sys.databases d
INNER JOIN sys.master_files mf ON
d.database_id = mf.database_id
WHERE mf.physical_name LIKE '\\%'
AND d.name NOT IN
( SELECT DISTINCT
DatabaseName
FROM #SkipChecks
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 149) WITH NOWAIT;
'https://www.brentozar.com/go/azurefiles' AS URL ,
( 'Files for this
database are on: ' + LEFT(mf.physical_name, 30)) AS Details
FROM sys.databases d
INNER JOIN sys.master_files mf ON
d.database_id = mf.database_id
WHERE mf.physical_name LIKE
'http://%'
AND d.name NOT IN
( SELECT DISTINCT
DatabaseName
FROM #SkipChecks
CONVERT(NVARCHAR(4000),t.TextData) ,
t.DatabaseName ,
t.EventClass ,
t.Severity ,
t.StartTime ,
t.EndTime ,
t.Duration ,
t.NTUserName ,
t.NTDomainName ,
t.HostName ,
t.ApplicationName ,
t.LoginName ,
t.DBUserName
FROM
sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t
WHERE
(
t.EventClass = 22
AND t.Severity >= 17
AND t.StartTime > DATEADD(dd, -30,
GETDATE())
)
OR
(
t.EventClass IN (92, 93)
AND t.StartTime > DATEADD(dd, -30, GETDATE())
AND t.Duration > 15000000
)
OR
(
t.EventClass IN (94, 95, 116)
)
SET @TraceFileIssue = 0
END TRY
BEGIN CATCH
SET @TraceFileIssue = 1
END CATCH
IF @TraceFileIssue = 1
BEGIN
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL AND
CheckID = 199 )
'https://www.brentozar.com/go/defaulttrace' AS URL ,
'Somebody
has been messing with your trace files. Check the files are present at ' +
@base_tracefilename AS Details
END
'https://www.brentozar.com/go/defaulttrace' AS URL ,
CAST(t.TextData AS
NVARCHAR(4000)) AS Details
FROM #fnTraceGettable t
WHERE t.EventClass = 22
/* Removed these as they're unnecessary,
we filter this when inserting data into #fnTraceGettable */
--AND t.Severity >= 17
--AND t.StartTime > DATEADD(dd, -30,
GETDATE());
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 151) WITH NOWAIT;
'https://www.brentozar.com/go/filegrowth' AS URL ,
CAST(COUNT(*) AS
NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file
autogrowth to a smaller increment.' AS Details
FROM #fnTraceGettable t
WHERE t.EventClass IN (92, 93)
/* Removed these as they're unnecessary,
we filter this when inserting data into #fnTraceGettable */
--AND t.StartTime >
DATEADD(dd, -30, GETDATE())
--AND t.Duration > 15000000
GROUP BY t.DatabaseName
HAVING COUNT(*) > 1;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 160) WITH NOWAIT;
''https://www.brentozar.com/go/parameterization'' AS URL,
CAST(COUNT(DISTINCT plan_handle) AS
NVARCHAR(50)) + '' plans are present for a single query in the plan cache - meaning
we probably have parameterization issues.'' AS Details
FROM sys.dm_exec_query_stats qs
CROSS APPLY
sys.dm_exec_plan_attributes(qs.plan_handle) pa
WHERE pa.attribute = ''dbid''
GROUP BY qs.query_hash, pa.value
HAVING COUNT(DISTINCT plan_handle) > ';
SET @StringToExecute =
@StringToExecute + N' ORDER BY COUNT(DISTINCT plan_handle) DESC OPTION
(RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 161) WITH NOWAIT;
''https://www.brentozar.com/go/planlimits'' AS URL,
''Your server configuration is limited to
'' + CAST(ht.buckets_count * 4 AS VARCHAR(20)) + '' '' + ht.name + '', and you are
currently caching '' + CAST(cc.entries_count AS VARCHAR(20)) + ''.'' AS Details
FROM sys.dm_os_memory_cache_hash_tables
ht
INNER JOIN
sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type
where ht.name IN ( ''SQL Plans'' ,
''Object Plans'' , ''Bound Trees'' )
AND cc.entries_count >= (3 *
ht.buckets_count) OPTION (RECOMPILE)';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 165) WITH NOWAIT;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 155) WITH NOWAIT;
'http://FirstResponderKit.org/' AS URL ,
'Some things get better
with age, like fine wine and your T-SQL. However, sp_Blitz is not one of those
things - time to go download the current one.' AS Details;
END;
OPEN DatabaseDefaultsLoop;
FETCH NEXT FROM DatabaseDefaultsLoop into
@CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority,
@CurrentFinding, @CurrentURL, @CurrentDetails;
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC (@StringToExecute);
CLOSE DatabaseDefaultsLoop;
DEALLOCATE DatabaseDefaultsLoop;
DECLARE
@tri nvarchar(max) = N'
SELECT
DatabaseName =
d.name,
CheckId =
257,
Priority =
50,
FindingsGroup =
N''Performance'',
Finding =
N''Recovery Interval Not Optimal'',
URL =
N''https://sqlperformance.com/2020/05/system-
configuration/0-to-60-switching-to-indirect-checkpoints'',
Details =
N''The database '' +
QUOTENAME(d.name) +
N'' has a target recovery interval of '' +
RTRIM(d.target_recovery_time_in_seconds) +
CASE
WHEN d.target_recovery_time_in_seconds = 0
THEN N'', which is a legacy default, and should be
changed to 60.''
WHEN d.target_recovery_time_in_seconds <> 0
THEN N'', which is probably a mistake, and should be
changed to 60.''
END
FROM sys.databases AS d
WHERE d.database_id > 4
AND d.is_read_only = 0
AND d.is_in_standby = 0
AND d.target_recovery_time_in_seconds <> 60;
';
INSERT INTO
#BlitzResults
(
DatabaseName,
CheckID,
Priority,
FindingsGroup,
Finding,
URL,
Details
)
EXEC sys.sp_executesql
@tri;
END;
END;
SELECT
167 AS [CheckID] ,
250 AS [Priority] ,
'Server Info' AS [FindingsGroup] ,
'Agent is Currently Offline' AS [Finding]
,
'' AS [URL] ,
( 'Oops! It looks like the ' +
[servicename] + ' service is ' + [status_desc] + '. The startup type is ' +
[startup_type_desc] + '.'
) AS [Details]
FROM
[sys].[dm_server_services]
WHERE [status_desc] <> 'Running'
AND [servicename] LIKE 'SQL Server Agent%'
AND CAST(SERVERPROPERTY('Edition') AS
VARCHAR(1000)) NOT LIKE '%xpress%';
END;
END;
SELECT
168 AS [CheckID] ,
250 AS [Priority] ,
'Server Info' AS [FindingsGroup] ,
'Full-text Filter Daemon Launcher is
Currently Offline' AS [Finding] ,
'' AS [URL] ,
( 'Oops! It looks like the ' +
[servicename] + ' service is ' + [status_desc] + '. The startup type is ' +
[startup_type_desc] + '.'
) AS [Details]
FROM
[sys].[dm_server_services]
WHERE [status_desc] <> 'Running'
AND [servicename] LIKE 'SQL Full-text Filter
Daemon Launcher%';
END;
END;
BEGIN
IF EXISTS ( SELECT 1
FROM
sys.all_objects
WHERE name =
'dm_server_services' )
BEGIN
SELECT
169 AS [CheckID] ,
250 AS [Priority] ,
'Informational' AS [FindingsGroup] ,
'SQL Server is running under an NT
Service account' AS [Finding] ,
'https://www.brentozar.com/go/setup' AS
[URL] ,
( 'I''m running as ' + [service_account]
+ '.'
) AS [Details]
FROM
[sys].[dm_server_services]
WHERE [service_account] LIKE 'NT Service%'
AND [servicename] LIKE 'SQL Server%'
AND [servicename] NOT LIKE 'SQL Server Agent
%'
AND [servicename] NOT LIKE 'SQL Server
Launchpad%';
END;
END;
BEGIN
IF EXISTS ( SELECT 1
FROM
sys.all_objects
WHERE name =
'dm_server_services' )
BEGIN
SELECT
170 AS [CheckID] ,
250 AS [Priority] ,
'Informational' AS [FindingsGroup] ,
'SQL Server Agent is running under an NT
Service account' AS [Finding] ,
'https://www.brentozar.com/go/setup' AS
[URL] ,
( 'I''m running as ' + [service_account]
+ '.'
) AS [Details]
FROM
[sys].[dm_server_services]
WHERE [service_account] LIKE 'NT Service%'
AND [servicename] LIKE 'SQL Server Agent%';
END;
END;
SET @canExitLoop = 0;
SET @frkIsConsistent = 0;
-- Filling #StatementsToRun4FRKVersionCheck
INSERT INTO #StatementsToRun4FRKVersionCheck (
CheckName,StatementText,SubjectName,SubjectFullPath,
StatementOutputsCounter,OutputCounterExpectedValue,StatementOutputsExecRet,Statemen
tOutputsDateTime
)
SELECT
'Mandatory',
'SELECT @cnt = COUNT(*) FROM #FRKObjects WHERE ObjectSchemaName IS NULL AND
ObjectName = ''' + ObjectName + ''' AND MandatoryComponent = 1;',
ObjectName,
QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' +
QUOTENAME(ObjectName),
1,
0,
0,
0
FROM #FRKObjects
UNION ALL
SELECT
'VersionCheckMode',
'SELECT @cnt = COUNT(*) FROM ' +
QUOTENAME(DatabaseName) + '.sys.all_parameters ' +
'where object_id = OBJECT_ID(''' + QUOTENAME(DatabaseName) + '.' +
QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ''') AND [name] =
''@VersionCheckMode'';',
ObjectName,
QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' +
QUOTENAME(ObjectName),
1,
1,
0,
0
FROM #FRKObjects
WHERE ObjectType = 'P'
AND ObjectSchemaName IS NOT NULL
UNION ALL
SELECT
'VersionCheck',
'EXEC @ExecRet = ' + QUOTENAME(DatabaseName) + '.' +
QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ' @VersionCheckMode = 1
, @VersionDate = @ObjDate OUTPUT;',
ObjectName,
QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' +
QUOTENAME(ObjectName),
0,
0,
1,
1
FROM #FRKObjects
WHERE ObjectType = 'P'
AND ObjectSchemaName IS NOT NULL
;
IF(@Debug in (1,2))
BEGIN
SELECT *
FROM #StatementsToRun4FRKVersionCheck ORDER BY
SubjectName,SubjectFullPath,StatementId -- in case of schema change ;
END;
-- loop on queries...
WHILE(@canExitLoop = 0)
BEGIN
SET @CurrentStatementId = NULL;
SELECT TOP 1
@StatementCheckName = CheckName,
@CurrentStatementId = StatementId ,
@CurrentComponentName = SubjectName,
@CurrentComponentFullName = SubjectFullPath,
@tsql = StatementText,
@StatementOutputsCounter = StatementOutputsCounter,
@OutputCounterExpectedValue = OutputCounterExpectedValue ,
@StatementOutputsExecRet = StatementOutputsExecRet,
@StatementOutputsDateTime = StatementOutputsDateTime
FROM #StatementsToRun4FRKVersionCheck
ORDER BY SubjectName, SubjectFullPath,StatementId /* in case of schema
change */
;
IF(@StatementCheckName NOT IN
('Mandatory','VersionCheckMode','VersionCheck'))
BEGIN
INSERT INTO #BlitzResults(
CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT
226 AS CheckID ,
253 AS Priority ,
'First Responder Kit' AS FindingsGroup ,
'Version Check Failed (code generator changed)' AS Finding ,
'http://FirstResponderKit.org' AS URL ,
'Download an updated First Responder Kit. Your version check failed
because a change has been made to the version check code generator.' + @crlf +
'Error: No handler for check with name "' +
ISNULL(@StatementCheckName,'') + '"' AS Details
;
-- we will stop the test because it's possible to get the same message
for other components
SET @canExitLoop = 1;
CONTINUE;
END;
IF(@StatementCheckName = 'Mandatory')
BEGIN
-- outputs counter
EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt
OUTPUT;
IF(@ExecRet <> 0)
BEGIN
-- we will stop the test because it's possible to get the same
message for other components
SET @canExitLoop = 1;
CONTINUE;
END;
SET @CurrentComponentMandatoryCheckOK = 1;
END;
IF(@StatementCheckName = 'VersionCheckMode')
BEGIN
IF(@CurrentComponentMandatoryCheckOK = 0)
BEGIN
INSERT INTO #BlitzResults(
CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT
226 AS CheckID ,
253 AS Priority ,
'First Responder Kit' AS FindingsGroup ,
'Version Check Failed (unexpectedly modified checks ordering)'
AS Finding ,
'http://FirstResponderKit.org' AS URL ,
'Download an updated First Responder Kit. Version check failed
because "Mandatory" check has not been completed before for current component' +
@crlf +
'Error: version check mode happenned before "Mandatory" check
for component called "' + @CurrentComponentFullName + '"'
;
-- we will stop the test because it's possible to get the same
message for other components
SET @canExitLoop = 1;
CONTINUE;
END;
-- outputs counter
EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt
OUTPUT;
IF(@ExecRet <> 0)
BEGIN
INSERT INTO #BlitzResults(
CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT
226 AS CheckID ,
253 AS Priority ,
'First Responder Kit' AS FindingsGroup ,
'Version Check Failed (dynamic query failure)' AS Finding ,
'http://FirstResponderKit.org' AS URL ,
'Download an updated First Responder Kit. Version check failed
because a change has been made to the code generator.' + @crlf +
'Error: following query failed at execution (check if component
[' + @CurrentComponentFullName + '] can run in VersionCheckMode)' + @crlf +
@tsql AS Details
;
-- we will stop the test because it's possible to get the same
message for other components
SET @canExitLoop = 1;
CONTINUE;
END;
SET @CurrentComponentVersionCheckModeOK = 1;
END;
IF(@StatementCheckName = 'VersionCheck')
BEGIN
IF(@CurrentComponentMandatoryCheckOK = 0 OR
@CurrentComponentVersionCheckModeOK = 0)
BEGIN
INSERT INTO #BlitzResults(
CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT
226 AS CheckID ,
253 AS Priority ,
'First Responder Kit' AS FindingsGroup ,
'Version Check Failed (unexpectedly modified checks ordering)'
AS Finding ,
'http://FirstResponderKit.org' AS URL ,
'Download an updated First Responder Kit. Version check failed
because "VersionCheckMode" check has not been completed before for component called
"' + @CurrentComponentFullName + '"' + @crlf +
'Error: VersionCheck happenned before "VersionCheckMode" check
for component called "' + @CurrentComponentFullName + '"'
;
-- we will stop the test because it's possible to get the same
message for other components
SET @canExitLoop = 1;
CONTINUE;
END;
IF(@ExecRet <> 0)
BEGIN
INSERT INTO #BlitzResults(
CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT
226 AS CheckID ,
253 AS Priority ,
'First Responder Kit' AS FindingsGroup ,
'Version Check Failed (dynamic query failure)' AS Finding ,
'http://FirstResponderKit.org' AS URL ,
'Download an updated First Responder Kit. The version check
failed because a change has been made to the code generator.' + @crlf +
'Error: following query failed at execution (check if component
[' + @CurrentComponentFullName + '] is at the expected version)' + @crlf +
@tsql AS Details
;
-- we will stop the test because it's possible to get the same
message for other components
SET @canExitLoop = 1;
CONTINUE;
END;
IF(@InnerExecRet <> 0)
BEGIN
INSERT INTO #BlitzResults(
CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT
226 AS CheckID ,
253 AS Priority ,
'First Responder Kit' AS FindingsGroup ,
'Version Check Failed (Failed dynamic SP call to ' +
@CurrentComponentFullName + ')' AS Finding ,
'http://FirstResponderKit.org' AS URL ,
'Download an updated First Responder Kit. Error: following
query failed at execution (check if component [' + @CurrentComponentFullName + ']
is at the expected version)' + @crlf +
'Return code: ' + CONVERT(VARCHAR(10),@InnerExecRet) + @crlf +
'T-SQL Query: ' + @crlf +
@tsql AS Details
;
RAISERROR('Component %s is
outdated',10,1,@CurrentComponentFullName);
-- advance to next component
DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath
= @CurrentComponentFullName ;
CONTINUE;
END;
/*This counts memory dumps and gives min and max date of in view*/
IF @ProductVersionMajor >= 10
AND NOT (@ProductVersionMajor = 10.5 AND @ProductVersionMinor <
4297) /* Skip due to crash bug: https://support.microsoft.com/en-us/help/2908087 */
AND NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL AND
CheckID = 171 )
BEGIN
IF EXISTS ( SELECT 1
FROM
sys.all_objects
WHERE name =
'dm_server_memory_dumps' )
BEGIN
IF EXISTS (SELECT * FROM [sys].
[dm_server_memory_dumps] WHERE [creation_time] >= DATEADD(YEAR, -1, GETDATE()))
BEGIN
SELECT
171 AS [CheckID] ,
20 AS [Priority] ,
'Reliability' AS [FindingsGroup] ,
'Memory Dumps Have Occurred' AS
[Finding] ,
'https://www.brentozar.com/go/dump'
AS [URL] ,
( 'That ain''t good. I''ve had ' +
CAST(COUNT(*) AS
VARCHAR(100)) + ' memory dumps between ' +
END;
END;
END;
SELECT
173 AS [CheckID] ,
200 AS [Priority] ,
'Licensing' AS [FindingsGroup] ,
'Non-Production License' AS [Finding] ,
'https://www.brentozar.com/go/licensing'
AS [URL] ,
( 'We''re not the licensing police, but
if this is supposed to be a production server, and you''re running ' +
CAST(SERVERPROPERTY('edition') AS
VARCHAR(100)) +
' the good folks at Microsoft might get
upset with you. Better start counting those cores.'
) AS [Details]
WHERE CAST(SERVERPROPERTY('edition') AS
VARCHAR(100)) LIKE '%Developer%'
OR CAST(SERVERPROPERTY('edition') AS
VARCHAR(100)) LIKE '%Evaluation%';
END;
SELECT
174 AS [CheckID] ,
200 AS [Priority] ,
'Performance' AS [FindingsGroup] ,
'Buffer Pool Extensions Enabled' AS
[Finding] ,
'https://www.brentozar.com/go/bpe' AS
[URL] ,
( 'You have Buffer Pool Extensions
enabled, and one lives here: ' +
[path] +
'. It''s currently ' +
CASE WHEN [current_size_in_kb] /
1024. / 1024. > 0
+ ' GB'
+ ' MB'
END +
'. Did you know that BPEs only
provide single threaded access 8KB (one page) at a time?'
) AS [Details]
FROM
sys.dm_os_buffer_pool_extension_configuration
WHERE [state_description] <> 'BUFFER
POOL EXTENSION DISABLED';
END;
'https://www.brentozar.com/go/tempdb' AS URL ,
'Woah, Nelly! TempDB has
' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop
somewhere?' AS Details
FROM sys.[master_files] AS [mf]
WHERE [mf].[database_id] = 2 AND
[mf].[type] = 0
HAVING COUNT_BIG(*) > 16;
END;
IF EXISTS ( SELECT 1
FROM sys.all_objects
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 176) WITH NOWAIT;
( 'AlwaysOn_health',
'system_health',
'telemetry_xevents',
'sp_server_diagnostics',
'sp_server_diagnostics session',
'hkenginexesession' )
AND name NOT
LIKE '%$A%'
HAVING
COUNT_BIG(*) >= 2;
END;
END;
FROM sys.all_objects
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 177) WITH NOWAIT;
SELECT
179 AS [CheckID] ,
5 AS [Priority] ,
'Reliability' AS [FindingsGroup] ,
'Dangerous Third Party Modules' AS
[Finding] ,
'https://support.microsoft.com/en-us/kb/2033238' AS [URL] ,
( COALESCE(company, '') + ' - ' +
COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous
third party module is installed.') AS [Details]
FROM sys.dm_os_loaded_modules
WHERE UPPER(name) LIKE UPPER('%\
ENTAPI.DLL') /* McAfee VirusScan Enterprise */
OR UPPER(name) LIKE UPPER('%\HIPI.DLL')
OR UPPER(name) LIKE UPPER('%\HcSQL.dll') OR UPPER(name) LIKE UPPER('%\HcApi.dll')
OR UPPER(name) LIKE UPPER('%\HcThe.dll') /* McAfee Host Intrusion */
OR UPPER(name) LIKE UPPER('%\
SOPHOS_DETOURED.DLL') OR UPPER(name) LIKE UPPER('%\SOPHOS_DETOURED_x64.DLL') OR
UPPER(name) LIKE UPPER('%\SWI_IFSLSP_64.dll') OR UPPER(name) LIKE UPPER('%\SOPHOS~
%.dll') /* Sophos AV */
OR UPPER(name) LIKE UPPER('%\
PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL'); /* OSISoft PI data access
*/
END;
WITH XMLNAMESPACES
('www.microsoft.com/SqlServer/Dts' AS [dts])
,[maintenance_plan_steps] AS (
SELECT [name]
, [id] -- ID required to link
maintenace plan with jobs and jobhistory (sp_Blitz Issue #776)
, CAST(CAST([packagedata] AS
VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml]
FROM [msdb].[dbo].[sysssispackages]
WHERE [packagetype] = 6
)
INSERT INTO [#BlitzResults]
( [CheckID] ,
[Priority] ,
[FindingsGroup] ,
[Finding] ,
[URL] ,
[Details] )
SELECT
180 AS [CheckID] ,
-- sp_Blitz Issue #776
-- Job has history and was executed in the last
30 days
CASE WHEN (cast(datediff(dd,
substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' +
substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' +
substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR
(j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN
100
ELSE -- no job history (implicit) AND job not
run in the past 30 days AND (Job disabled OR Job Schedule disabled)
200
END AS Priority,
'Performance' AS [FindingsGroup] ,
'Shrink Database Step In Maintenance Plan' AS
[Finding] ,
'https://www.brentozar.com/go/autoshrink' AS
[URL] ,
'The maintenance plan ' + [mps].[name] + ' has
a step to shrink databases in it. Shrinking databases is as outdated as maintenance
plans.'
+ CASE WHEN COALESCE(ssc.name,'0') != '0' THEN
+ ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details]
FROM [maintenance_plan_steps] [mps]
CROSS APPLY [maintenance_plan_xml].
[nodes]('//dts:Executables/dts:Executable') [t]([c])
join msdb.dbo.sysmaintplan_subplans as sms
on mps.id = sms.plan_id
JOIN msdb.dbo.sysjobs j
on sms.job_id = j.job_id
LEFT OUTER JOIN msdb.dbo.sysjobsteps AS step
ON j.job_id = step.job_id
LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc
ON j.job_id = sjsc.job_id
LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc
ON sjsc.schedule_id = ssc.schedule_id
AND sjsc.job_id = j.job_id
LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh
ON j.job_id = sjh.job_id
AND step.step_id = sjh.step_id
AND sjh.run_date IN (SELECT max(sjh2.run_date) FROM
msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id = j.job_id) -- get the latest
entry date
AND sjh.run_time IN (SELECT max(sjh3.run_time) FROM
msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id = j.job_id AND sjh3.run_date =
sjh.run_date) -- get the latest entry time
WHERE [c].[value]('(@dts:ObjectName)',
'VARCHAR(128)') = 'Shrink Database Task';
END;
WITH XMLNAMESPACES
('www.microsoft.com/SqlServer/Dts' AS [dts])
,[maintenance_plan_steps] AS (
SELECT [name]
, CAST(CAST([packagedata] AS
VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml]
FROM [msdb].[dbo].[sysssispackages]
WHERE [packagetype] = 6
), [maintenance_plan_table] AS (
SELECT [mps].[name]
,[c].[value]('(@dts:ObjectName)',
'NVARCHAR(128)') AS [step_name]
FROM [maintenance_plan_steps] [mps]
CROSS APPLY [maintenance_plan_xml].
[nodes]('//dts:Executables/dts:Executable') [t]([c])
), [mp_steps_pretty] AS (SELECT DISTINCT [m1].
[name] ,
STUFF((SELECT N', ' + [m2].
[step_name] FROM [maintenance_plan_table] AS [m2] WHERE [m1].[name] = [m2].[name]
FOR XML PATH(N'')), 1, 2, N'') AS
[maintenance_plan_steps]
FROM [maintenance_plan_table] AS [m1])
SELECT
181 AS [CheckID] ,
100 AS [Priority] ,
'Performance' AS [FindingsGroup] ,
'Repetitive Steps In Maintenance Plans' AS
[Finding] ,
'https://ola.hallengren.com/' AS [URL] ,
'The maintenance plan ' + [m].[name] + ' is
doing repetitive work on indexes and statistics. Perhaps it''s time to try
something more modern?' AS [Details]
FROM [mp_steps_pretty] m
WHERE m.[maintenance_plan_steps] LIKE '%Rebuild
%Reorganize%'
OR m.[maintenance_plan_steps] LIKE '%Rebuild
%Update%';
END;
SELECT TOP 1
184 AS CheckID ,
20 AS Priority ,
''Reliability'' AS FindingsGroup ,
''No Failover Cluster Nodes Available''
AS Finding ,
''https://www.brentozar.com/go/node''
AS URL ,
''There are no failover cluster nodes
available if the active node fails'' AS Details
FROM (
SELECT SUM(CASE WHEN [status] = 0 AND
[is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes]
FROM sys.dm_os_cluster_nodes
) a
WHERE [available_nodes] < 1 OPTION
(RECOMPILE)';
EXECUTE(@StringToExecute);
END;
SELECT
191 AS [CheckID] ,
50 AS [Priority] ,
'Reliability' AS [FindingsGroup] ,
'TempDB File Error' AS [Finding] ,
'https://www.brentozar.com/go/tempdboops' AS
[URL] ,
'Mismatch between the number of TempDB files in
sys.master_files versus tempdb.sys.database_files' AS [Details];
END;
END;
LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )
, '') --This replaces any optional WITH clause to
a DBCC command, like tableresults.
) AS [dbcc_event_trunc_upper],
UPPER(
REPLACE(
CONVERT(NVARCHAR(MAX), t.TextData),
SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData),
ISNULL(
NULLIF(
END;
END;
END;
END;
END;
END;
''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL ,
N''The database '' +
DB_NAME(s.database_id)
+ '' has ''
+ CONVERT(NVARCHAR(20),
COUNT_BIG(*))
+ '' open implicit
transactions with an oldest begin time of ''
+ CONVERT(NVARCHAR(30),
MIN(tat.transaction_begin_time))
+ '' Run sp_BlitzWho and
check the is_implicit_transaction column to see the culprits.'' AS details
FROM
sys.dm_tran_active_transactions AS tat
LEFT JOIN
sys.dm_tran_session_transactions AS tst
ON tst.transaction_id =
tat.transaction_id
LEFT JOIN sys.dm_exec_sessions AS s
ON s.session_id = tst.session_id
WHERE tat.name =
''implicit_transaction''
GROUP BY DB_NAME(s.database_id),
transaction_type, transaction_state;';
IF @Debug = 2 AND @StringToExecute IS NOT
NULL PRINT @StringToExecute;
IF @Debug = 2 AND @StringToExecute IS
NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
WITH reboot_airhorn
AS
(
SELECT create_date
FROM sys.databases
WHERE database_id = 2
UNION ALL
SELECT CAST(DATEADD(SECOND,
( ms_ticks / 1000 ) * ( -1 ), GETDATE()) AS DATETIME)
FROM sys.dm_os_sys_info
)
INSERT INTO #BlitzResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
END;
'https://www.BrentOzar.com/go/workgroup' AS URL,
'This server will stop working on:
' + CAST(CONVERT(DATETIME, DATEADD(DD, 180, create_date), 102) AS VARCHAR(100)) AS
details
FROM sys.server_principals
WHERE sid = 0x010100000000000512000000;
END;
''https://www.BrentOzar.com/go/userstore'' AS URL,
N''UserStore_TokenPerm clerk is
using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name =
''TokenAndPermUserStore'' THEN pages_kb * 1.0 ELSE 0.0 END) / 1024.0 / 1024.0 AS
INT) AS NVARCHAR(100))
+ N''GB RAM, total
buffer pool is '' + CAST(CAST(SUM(pages_kb) / 1024.0 / 1024.0 AS INT) AS
NVARCHAR(100)) + N''GB.''
AS details
FROM sys.dm_os_memory_clerks
HAVING SUM(CASE WHEN type =
''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN pages_kb * 1.0
ELSE 0.0 END) / SUM(pages_kb) >= 0.1
AND SUM(pages_kb) / 1024.0 / 1024.0 >=
1; /* At least 1GB RAM overall */';
EXEC sp_executesql @StringToExecute;
END
ELSE
BEGIN
/* Antiques Roadshow SQL 2008R2 - version
*/
SET @StringToExecute = N'
INSERT INTO #BlitzResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
''https://www.BrentOzar.com/go/userstore'' AS URL,
N''UserStore_TokenPerm clerk is
using '' + CAST(CAST(SUM(CASE WHEN type = ''USERSTORE_TOKENPERM'' AND name =
''TokenAndPermUserStore'' THEN single_pages_kb + multi_pages_kb * 1.0 ELSE 0.0 END)
/ 1024.0 / 1024.0 AS INT) AS NVARCHAR(100))
+ N''GB RAM, total
buffer pool is '' + CAST(CAST(SUM(single_pages_kb + multi_pages_kb) / 1024.0 /
1024.0 AS INT) AS NVARCHAR(100)) + N''GB.''
AS details
FROM sys.dm_os_memory_clerks
HAVING SUM(CASE WHEN type =
''USERSTORE_TOKENPERM'' AND name = ''TokenAndPermUserStore'' THEN single_pages_kb +
multi_pages_kb * 1.0 ELSE 0.0 END) / SUM(single_pages_kb + multi_pages_kb) >= 0.1
AND SUM(single_pages_kb +
multi_pages_kb) / 1024.0 / 1024.0 >= 1; /* At least 1GB RAM overall */';
EXEC sp_executesql @StringToExecute;
END
END;
IF @CheckUserDatabaseObjects = 1
BEGIN
/*
But what if you need to run a query in every individual
database?
Check out CheckID 99 below. Yes, it uses
sp_MSforeachdb, and no,
we're not happy about that. sp_MSforeachdb is known
to have a lot
of issues, like skipping databases sometimes.
However, this is the
only built-in option that we have. If you're
writing your own code
for database maintenance, consider Aaron Bertrand's
alternative:
http://www.mssqltips.com/sqlservertip/2201/making-
a-more-reliable-and-flexible-spmsforeachdb/
We don't include that as part of sp_Blitz, of
course, because
copying and distributing copyrighted code from
others without their
written permission isn't a good idea.
*/
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS
NULL AND CheckID = 99 )
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT;
''https://www.brentozar.com/go/querystore'',
(''The new SQL Server 2016 Query Store
feature has not been enabled on this database.'')
FROM [?].sys.database_query_store_options
WHERE desired_state = 0
AND N''?'' NOT IN
(''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'',
''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION
(RECOMPILE)';
END;
IF @ProductVersionMajor = 13 AND
@ProductVersionMinor < 2149 --2016 CU1 has the fix in it
AND NOT EXISTS ( SELECT 1
FROM
#SkipChecks
WHERE
DatabaseName IS NULL AND CheckID = 182 )
AND CAST(SERVERPROPERTY('edition') AS
VARCHAR(100)) NOT LIKE '%Enterprise%'
AND CAST(SERVERPROPERTY('edition') AS
VARCHAR(100)) NOT LIKE '%Developer%'
BEGIN
(CheckID,
DatabaseName,
Priority,
FindingsGroup,
Finding,
URL,
Details)
SELECT
TOP 1
182,
''Serv
er'',
20,
''Reli
ability'',
''Quer
y Store Cleanup Disabled'',
''http
s://www.brentozar.com/go/cleanup'',
(''SQL
2016 RTM has a bug involving dumps that happen every time Query Store cleanup jobs
run. This is fixed in CU1 and later: https://sqlserverupdates.com/sql-server-2016-
updates/'')
FROM
sys.databases AS d
WHERE
d.is_query_store_on = 1 OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 235) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 41) WITH NOWAIT;
''https://www.brentozar.com/go/manylogs'',
(''The ['' + DB_NAME() + ''] database has
multiple log files on the '' + LEFT(physical_name, 1) + '' drive. This is not a
performance booster because log file access is sequential, not parallel.'')
FROM [?].sys.database_files WHERE
type_desc = ''LOG''
AND N''?'' <> ''[tempdb]''
GROUP BY LEFT(physical_name, 1)
HAVING COUNT(*) > 1 OPTION (RECOMPILE);';
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 42) WITH NOWAIT;
''https://www.brentozar.com/go/grow'',
(''The ['' + DB_NAME() + ''] database
has multiple data files in one filegroup, but they are not all set up to grow in
identical amounts. This can lead to uneven file activity inside the filegroup.'')
FROM [?].sys.database_files
WHERE type_desc = ''ROWS''
GROUP BY data_space_id
HAVING COUNT(DISTINCT growth) > 1 OR
COUNT(DISTINCT is_percent_growth) > 1 OPTION (RECOMPILE);';
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 82) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 158) WITH NOWAIT;
''https://www.brentozar.com/go/percentgrowth'' AS URL,
''The ['' + DB_NAME() + ''] database
file '' + f.physical_name + '' is using 1MB filegrowth settings, but it has grown
to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB. Time to up the growth
amount.''
FROM [?].sys.database_files f
WHERE is_percent_growth = 0 and growth=128
and size > 128000 OPTION (RECOMPILE);';
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 33) WITH NOWAIT;
EXEC dbo.sp_MSforeachdb
'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO #BlitzResults
(CheckID,
DatabaseName,
Priority,
FindingsGroup,
Finding,
URL,
Details)
SELECT DISTINCT 33,
db_name(),
200,
''Licensing'',
''Enterprise Edition Features In
Use'',
''https://www.brentozar.com/go/ee'',
(''The ['' + DB_NAME() + ''] database
is using '' + feature_name + ''. If this database is restored onto a Standard
Edition server, the restore will fail on versions prior to 2016 SP1.'')
FROM
[?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);';
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 19) WITH NOWAIT;
SELECT 19 AS CheckID ,
[name] AS
DatabaseName ,
200 AS
Priority ,
'Informational'
AS FindingsGroup ,
'Replication In
Use' AS Finding ,
'https://www.brentozar.com/go/repl' AS URL ,
( 'Database [' +
[name]
+ '] is a
replication publisher, subscriber, or distributor.' ) AS Details
FROM sys.databases
WHERE name NOT IN
( SELECT DISTINCT
DatabaseName
FROM #SkipChecks
''https://www.brentozar.com/go/repl'',
(''['' + DB_NAME() + ''] has
MSreplication_objects tables in it, indicating it is a replication subscriber.'')
FROM [?].sys.tables
WHERE name =
''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)';
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 32) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 164) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 46) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 47) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 48) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 56) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 95) WITH NOWAIT;
EXEC dbo.sp_MSforeachdb
'USE [?];
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO #BlitzResults
(CheckID,
DatabaseName,
Priority,
FindingsGroup,
Finding,
URL,
Details)
SELECT TOP 1 95 AS CheckID,
N''?'' as DatabaseName,
110 AS Priority,
''Performance'' AS FindingsGroup,
''Plan Guides Enabled'' AS Finding,
''https://www.brentozar.com/go/guides'' AS URL,
(''Database ['' + DB_NAME() + ''] has query plan guides so a
query will always get a specific execution plan. If you are having trouble getting
query performance to improve, it might be due to a frozen plan. Review the DMV
sys.plan_guides to learn more about the plan guides in place on this server.'') AS
Details
FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION
(RECOMPILE);';
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 60) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 78) WITH NOWAIT;
EXECUTE master.sys.sp_MSforeachdb
'USE [?];
SET TRANSACTION ISOLATION LEVEL READ
UNCOMMITTED;
INSERT INTO #Recompile
SELECT DISTINCT DBName = DB_Name(), SPName =
SO.name, SM.is_recompiled, ISR.SPECIFIC_SCHEMA
FROM sys.sql_modules AS SM
LEFT OUTER JOIN master.sys.databases AS sDB ON
SM.object_id = DB_id()
LEFT OUTER JOIN dbo.sysobjects AS SO ON
SM.object_id = SO.id and type = ''P''
LEFT OUTER JOIN INFORMATION_SCHEMA.ROUTINES AS
ISR on ISR.Routine_Name = SO.name AND ISR.SPECIFIC_CATALOG = DB_Name()
WHERE SM.is_recompiled=1 OPTION
(RECOMPILE); /* oh the rich irony of recompile here */
';
INSERT INTO #BlitzResults
(Priority,
FindingsGroup,
Finding,
DatabaseName,
URL,
Details,
CheckID)
SELECT [Priority] = '100',
FindingsGroup = 'Performance',
Finding = 'Stored Procedure WITH RECOMPILE',
DatabaseName = DBName,
URL = 'https://www.brentozar.com/go/recompile',
Details = '[' + DBName + '].[' + SPSchema + '].
[' + ProcName + '] has WITH RECOMPILE in the stored procedure code, which may cause
increased CPU usage due to constant recompiles of the code.',
CheckID = '78'
FROM #Recompile AS TR WHERE ProcName NOT LIKE
'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE
'sp_Blitz%';
DROP TABLE #Recompile;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT;
FROM #SkipChecks
IF @Debug IN
(1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 72) WITH NOWAIT;
EXEC
dbo.sp_MSforeachdb 'USE [?];
SET TRANSACTION ISOLATION LEVEL
READ UNCOMMITTED;
insert into #partdb(dbname, objectname, type_desc)
SELECT distinct db_name(DB_ID()) as
DBName,o.name Object_Name,ds.type_desc
FROM sys.objects AS o JOIN
sys.indexes AS i ON o.object_id = i.object_id
JOIN sys.data_spaces ds on
ds.data_space_id = i.data_space_id
LEFT OUTER JOIN
sys.dm_db_index_usage_stats AS s ON i.object_id = s.object_id AND i.index_id =
s.index_id AND s.database_id = DB_ID()
WHERE o.type = ''u''
-- Clustered and Non-Clustered
indexes
AND i.type IN (1, 2)
AND o.object_id in
(
SELECT a.object_id from
(SELECT ob.object_id,
ds.type_desc from sys.objects ob JOIN sys.indexes ind on ind.object_id =
ob.object_id join sys.data_spaces ds on ds.data_space_id = ind.data_space_id
GROUP BY ob.object_id,
ds.type_desc ) a group by a.object_id having COUNT (*) > 1
) OPTION (RECOMPILE);';
INSERT INTO
#BlitzResults
( CheckID ,
DatabaseName ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
SELECT DISTINCT
72 AS CheckID ,
dbname AS DatabaseName ,
100 AS Priority ,
'Performance' AS FindingsGroup ,
'https://www.brentozar.com/go/aligned' AS URL ,
FROM #partdb
DatabaseName
FROM #SkipChecks
''https://www.brentozar.com/go/fulltext'',
(''At least one full text index in this
database has not been crawled in the last week.'')
from [?].sys.fulltext_indexes i WHERE
change_tracking_state_desc <> ''AUTO'' AND i.is_enabled = 1 AND i.crawl_end_date <
DATEADD(dd, -7, GETDATE()) OPTION (RECOMPILE);';
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 115) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 122) WITH NOWAIT;
EXEC dbo.sp_MSforeachdb
'USE [?];
SET
TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO #BlitzResults
(CheckID,
DatabaseName,
Priority,
FindingsGroup,
Finding,
URL,
Details)
SELECT TOP 1
122,
N''?'',
200,
''Performanc
e'',
''User-
Created Statistics In Place'',
''https://www.brentozar.com/go/userstats'',
(''['' +
DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics.
This indicates that someone is being a rocket scientist with the stats, and might
actually be slowing things down, especially during stats updates.'')
from
[?].sys.stats WHERE user_created = 1 AND is_temporary = 0
HAVING SUM(1) > 0 OPTION
(RECOMPILE);';
ELSE
EXEC dbo.sp_MSforeachdb
'USE [?];
SET
TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO #BlitzResults
(CheckID,
DatabaseName,
Priority,
FindingsGroup,
Finding,
URL,
Details)
SELECT 122,
N''?'',
200,
''Performanc
e'',
''User-
Created Statistics In Place'',
''https://www.brentozar.com/go/userstats'',
(''['' +
DB_NAME() + ''] has '' + CAST(SUM(1) AS NVARCHAR(10)) + '' user-created statistics.
This indicates that someone is being a rocket scientist with the stats, and might
actually be slowing things down, especially during stats updates.'')
from
[?].sys.stats WHERE user_created = 1
HAVING SUM(1) > 0 OPTION
(RECOMPILE);';
/*Check for high VLF count: this will omit any database
snapshots*/
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d] (2012 version of Log Info).', 0, 1, 69) WITH
NOWAIT;
EXEC sp_MSforeachdb
N'USE [?];
SET TRANSACTION ISOLATION LEVEL
READ UNCOMMITTED;
INSERT INTO #LogInfo2012
EXEC sp_executesql N''DBCC
LogInfo() WITH NO_INFOMSGS'';
IF @@ROWCOUNT > 999
BEGIN
INSERT INTO #BlitzResults
( CheckID
,DatabaseName
,Priority
,FindingsGroup
,Finding
,URL
,Details)
SELECT 69
,DB_NAME()
,170
,''File Configuration''
,''High VLF Count''
,''https://www.brentozar.com/go/vlf''
,''The ['' + DB_NAME() + '']
database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs).
This may be slowing down startup, restores, and even inserts/updates/deletes.''
FROM #LogInfo2012
WHERE EXISTS (SELECT name
FROM master.sys.databases
WHERE
source_database_id is null) OPTION (RECOMPILE);
END
TRUNCATE TABLE #LogInfo2012;';
DROP TABLE #LogInfo2012;
END;
ELSE
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d] (pre-2012 version of Log Info).', 0, 1, 69) WITH
NOWAIT;
EXEC sp_MSforeachdb
N'USE [?];
SET TRANSACTION ISOLATION LEVEL
READ UNCOMMITTED;
INSERT INTO #LogInfo
EXEC sp_executesql N''DBCC
LogInfo() WITH NO_INFOMSGS'';
IF @@ROWCOUNT > 999
BEGIN
INSERT INTO #BlitzResults
( CheckID
,DatabaseName
,Priority
,FindingsGroup
,Finding
,URL
,Details)
SELECT 69
,DB_NAME()
,170
,''File Configuration''
,''High VLF Count''
,''https://www.brentozar.com/go/vlf''
,''The ['' + DB_NAME() + '']
database has '' + CAST(COUNT(*) as VARCHAR(20)) + '' virtual log files (VLFs).
This may be slowing down startup, restores, and even inserts/updates/deletes.''
FROM #LogInfo
WHERE EXISTS (SELECT name
FROM master.sys.databases
WHERE source_database_id is
null) OPTION (RECOMPILE);
END
TRUNCATE TABLE #LogInfo;';
DROP TABLE #LogInfo;
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 80) WITH NOWAIT;
DELETE br
FROM #BlitzResults br
INNER JOIN #SkipChecks sc ON
sc.CheckID = 80 AND br.DatabaseName = sc.DatabaseName;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT;
INSERT INTO
#DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value,
default_value_for_secondary, CheckID)
SELECT 1, 'MAXDOP', '0',
NULL, 194
UNION ALL
SELECT 2,
'LEGACY_CARDINALITY_ESTIMATION', '0', NULL, 195
UNION ALL
SELECT 3,
'PARAMETER_SNIFFING', '1', NULL, 196
UNION ALL
SELECT 4,
'QUERY_OPTIMIZER_HOTFIXES', '0', NULL, 197
UNION ALL
SELECT 6, 'IDENTITY_CACHE',
'1', NULL, 237
UNION ALL
SELECT 7,
'INTERLEAVED_EXECUTION_TVF', '1', NULL, 238
UNION ALL
SELECT 8,
'BATCH_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 239
UNION ALL
SELECT 9,
'BATCH_MODE_ADAPTIVE_JOINS', '1', NULL, 240
UNION ALL
SELECT 10,
'TSQL_SCALAR_UDF_INLINING', '1', NULL, 241
UNION ALL
SELECT 11, 'ELEVATE_ONLINE',
'OFF', NULL, 242
UNION ALL
SELECT 12,
'ELEVATE_RESUMABLE', 'OFF', NULL, 243
UNION ALL
SELECT 13,
'OPTIMIZE_FOR_AD_HOC_WORKLOADS', '0', NULL, 244
UNION ALL
SELECT 14,
'XTP_PROCEDURE_EXECUTION_STATISTICS', '0', NULL, 245
UNION ALL
SELECT 15,
'XTP_QUERY_EXECUTION_STATISTICS', '0', NULL, 246
UNION ALL
SELECT 16,
'ROW_MODE_MEMORY_GRANT_FEEDBACK', '1', NULL, 247
UNION ALL
SELECT 17,
'ISOLATE_SECURITY_POLICY_CARDINALITY', '0', NULL, 248
UNION ALL
SELECT 18,
'BATCH_MODE_ON_ROWSTORE', '1', NULL, 249
UNION ALL
SELECT 19,
'DEFERRED_COMPILATION_TV', '1', NULL, 250
UNION ALL
SELECT 20,
'ACCELERATED_PLAN_FORCING', '1', NULL, 251
UNION ALL
SELECT 21,
'GLOBAL_TEMPORARY_TABLE_AUTO_DROP', '1', NULL, 252
UNION ALL
SELECT 22,
'LIGHTWEIGHT_QUERY_PROFILING', '1', NULL, 253
UNION ALL
SELECT 23,
'VERBOSE_TRUNCATION_WARNINGS', '1', NULL, 254
UNION ALL
SELECT 24,
'LAST_QUERY_PLAN_STATS', '0', NULL, 255;
EXEC dbo.sp_MSforeachdb 'USE [?]; SET
TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID,
DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
SELECT def1.CheckID,
DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name],
''https://www.brentozar.com/go/dbscope'', (''Set value: '' +
COALESCE(CAST(dsc.value AS NVARCHAR(100)),''Empty'') + '' Default: '' +
COALESCE(CAST(def1.default_value AS NVARCHAR(100)),''Empty'') + '' Set value for
secondary: '' + COALESCE(CAST(dsc.value_for_secondary AS NVARCHAR(100)),''Empty'')
+ '' Default value for secondary: '' +
COALESCE(CAST(def1.default_value_for_secondary AS NVARCHAR(100)),''Empty''))
FROM
[?].sys.database_scoped_configurations dsc
INNER JOIN
#DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id =
def1.configuration_id
LEFT OUTER JOIN
#DatabaseScopedConfigurationDefaults def ON dsc.configuration_id =
def.configuration_id AND (cast(dsc.value as nvarchar(100)) = cast(def.default_value
as nvarchar(100)) OR dsc.value IS NULL) AND (dsc.value_for_secondary =
def.default_value_for_secondary OR dsc.value_for_secondary IS NULL)
LEFT OUTER JOIN #SkipChecks
sk ON (sk.CheckID IS NULL OR def.CheckID = sk.CheckID) AND (sk.DatabaseName IS NULL
OR sk.DatabaseName = DB_NAME())
WHERE def.configuration_id IS
NULL AND sk.CheckID IS NULL ORDER BY 1
OPTION (RECOMPILE);';
END;
WITH DB2
AS ( SELECT DISTINCT
Field ,
Value ,
DbName
FROM #DBCCs
INNER JOIN sys.databases d ON
#DBCCs.DbName = d.name
WHERE Field =
'dbi_dbccLastKnownGood'
AND d.create_date <
DATEADD(dd, -14, GETDATE())
)
INSERT INTO #BlitzResults
( CheckID ,
DatabaseName ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT 68 AS CheckID ,
DB2.DbName AS DatabaseName ,
1 AS PRIORITY ,
'Reliability' AS
FindingsGroup ,
'Last good DBCC CHECKDB over
2 weeks old' AS Finding ,
'https://www.brentozar.com/go/checkdb' AS URL ,
'Last successful CHECKDB: '
+ CASE DB2.Value
WHEN '1900-01-01
00:00:00.000'
THEN ' never.'
ELSE DB2.Value
END AS Details
FROM DB2
WHERE DB2.DbName <> 'tempdb'
AND DB2.DbName NOT IN
( SELECT DISTINCT
DatabaseName
FROM
#SkipChecks
FROM sys.databases
WHERE is_read_only = 1)
AND CONVERT(DATETIME,
DB2.Value, 121) < DATEADD(DD,
-14,
CURRENT_TIMESTAMP);
END;
END; /* IF @CheckUserDatabaseObjects = 1 */
IF @CheckProcedureCache = 1
BEGIN
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 35) WITH NOWAIT;
'https://www.brentozar.com/go/single' AS URL ,
( CAST(COUNT(*) AS VARCHAR(10))
+ ' query
plans are taking up memory in the procedure cache. This may be wasted memory if we
cache plans for queries that never get called again. This may be a good use case
for SQL Server 2008''s Optimize for Ad Hoc or for Forced Parameterization.' ) AS
Details
FROM
sys.dm_exec_cached_plans AS cp
WHERE cp.usecounts = 1
AND
cp.objtype = 'Adhoc'
AND EXISTS (
SELECT
FROM sys.configurations
WHERE
AND value_in_use = 0 )
HAVING COUNT(*) > 1;
END;
EXECUTE(@StringToExecute);
END;
IF @CheckProcedureCacheFilter =
'Reads'
OR @CheckProcedureCacheFilter
IS NULL
BEGIN
SET @StringToExecute =
'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],
[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],
[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],
[max_worker_time],[total_physical_reads],[last_physical_reads],
[min_physical_reads],[max_physical_reads],[total_logical_writes],
[last_logical_writes],[min_logical_writes],[max_logical_writes],
[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],
[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],
[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time]
FROM sys.dm_exec_query_stats qs
ORDER BY qs.total_logical_reads DESC)
INSERT INTO #dm_exec_query_stats ([sql_handle],
[statement_start_offset],[statement_end_offset],[plan_generation_num],
[plan_handle],[creation_time],[last_execution_time],[execution_count],
[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],
[total_physical_reads],[last_physical_reads],[min_physical_reads],
[max_physical_reads],[total_logical_writes],[last_logical_writes],
[min_logical_writes],[max_logical_writes],[total_logical_reads],
[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],
[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],
[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
SELECT qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time]
FROM queries qs
LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle =
qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND
qs.statement_start_offset = qsCaught.statement_start_offset
WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @CheckProcedureCacheFilter =
'ExecCount'
OR @CheckProcedureCacheFilter
IS NULL
BEGIN
SET @StringToExecute =
'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],
[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],
[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],
[max_worker_time],[total_physical_reads],[last_physical_reads],
[min_physical_reads],[max_physical_reads],[total_logical_writes],
[last_logical_writes],[min_logical_writes],[max_logical_writes],
[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],
[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],
[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time]
FROM sys.dm_exec_query_stats qs
ORDER BY qs.execution_count DESC)
INSERT INTO #dm_exec_query_stats ([sql_handle],
[statement_start_offset],[statement_end_offset],[plan_generation_num],
[plan_handle],[creation_time],[last_execution_time],[execution_count],
[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],
[total_physical_reads],[last_physical_reads],[min_physical_reads],
[max_physical_reads],[total_logical_writes],[last_logical_writes],
[min_logical_writes],[max_logical_writes],[total_logical_reads],
[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],
[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],
[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
SELECT qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time]
FROM queries qs
LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle =
qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND
qs.statement_start_offset = qsCaught.statement_start_offset
WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @CheckProcedureCacheFilter =
'Duration'
OR @CheckProcedureCacheFilter
IS NULL
BEGIN
SET @StringToExecute =
'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],
[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],
[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],
[max_worker_time],[total_physical_reads],[last_physical_reads],
[min_physical_reads],[max_physical_reads],[total_logical_writes],
[last_logical_writes],[min_logical_writes],[max_logical_writes],
[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],
[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],
[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time]
FROM sys.dm_exec_query_stats qs
ORDER BY qs.total_elapsed_time DESC)
INSERT INTO #dm_exec_query_stats ([sql_handle],
[statement_start_offset],[statement_end_offset],[plan_generation_num],
[plan_handle],[creation_time],[last_execution_time],[execution_count],
[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],
[total_physical_reads],[last_physical_reads],[min_physical_reads],
[max_physical_reads],[total_logical_writes],[last_logical_writes],
[min_logical_writes],[max_logical_writes],[total_logical_reads],
[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],
[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],
[last_elapsed_time],[min_elapsed_time],[max_elapsed_time])
SELECT qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time]
FROM queries qs
LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle =
qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND
qs.statement_start_offset = qsCaught.statement_start_offset
WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
END;
IF @ProductVersionMajor >= 10
BEGIN
IF @CheckProcedureCacheFilter =
'CPU'
OR @CheckProcedureCacheFilter
IS NULL
BEGIN
SET @StringToExecute =
'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],
[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],
[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],
[max_worker_time],[total_physical_reads],[last_physical_reads],
[min_physical_reads],[max_physical_reads],[total_logical_writes],
[last_logical_writes],[min_logical_writes],[max_logical_writes],
[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],
[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],
[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],
[query_hash],[query_plan_hash])
AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
FROM sys.dm_exec_query_stats qs
ORDER BY qs.total_worker_time DESC)
INSERT INTO #dm_exec_query_stats ([sql_handle],
[statement_start_offset],[statement_end_offset],[plan_generation_num],
[plan_handle],[creation_time],[last_execution_time],[execution_count],
[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],
[total_physical_reads],[last_physical_reads],[min_physical_reads],
[max_physical_reads],[total_logical_writes],[last_logical_writes],
[min_logical_writes],[max_logical_writes],[total_logical_reads],
[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],
[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],
[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],
[query_plan_hash])
SELECT qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
FROM queries qs
LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle =
qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND
qs.statement_start_offset = qsCaught.statement_start_offset
WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @CheckProcedureCacheFilter =
'Reads'
OR @CheckProcedureCacheFilter
IS NULL
BEGIN
SET @StringToExecute =
'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],
[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],
[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],
[max_worker_time],[total_physical_reads],[last_physical_reads],
[min_physical_reads],[max_physical_reads],[total_logical_writes],
[last_logical_writes],[min_logical_writes],[max_logical_writes],
[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],
[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],
[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],
[query_hash],[query_plan_hash])
AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
FROM sys.dm_exec_query_stats qs
ORDER BY qs.total_logical_reads DESC)
INSERT INTO #dm_exec_query_stats ([sql_handle],
[statement_start_offset],[statement_end_offset],[plan_generation_num],
[plan_handle],[creation_time],[last_execution_time],[execution_count],
[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],
[total_physical_reads],[last_physical_reads],[min_physical_reads],
[max_physical_reads],[total_logical_writes],[last_logical_writes],
[min_logical_writes],[max_logical_writes],[total_logical_reads],
[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],
[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],
[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],
[query_plan_hash])
SELECT qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
FROM queries qs
LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle =
qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND
qs.statement_start_offset = qsCaught.statement_start_offset
WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @CheckProcedureCacheFilter =
'ExecCount'
OR @CheckProcedureCacheFilter
IS NULL
BEGIN
SET @StringToExecute =
'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],
[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],
[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],
[max_worker_time],[total_physical_reads],[last_physical_reads],
[min_physical_reads],[max_physical_reads],[total_logical_writes],
[last_logical_writes],[min_logical_writes],[max_logical_writes],
[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],
[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],
[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],
[query_hash],[query_plan_hash])
AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
FROM sys.dm_exec_query_stats qs
ORDER BY qs.execution_count DESC)
INSERT INTO #dm_exec_query_stats ([sql_handle],
[statement_start_offset],[statement_end_offset],[plan_generation_num],
[plan_handle],[creation_time],[last_execution_time],[execution_count],
[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],
[total_physical_reads],[last_physical_reads],[min_physical_reads],
[max_physical_reads],[total_logical_writes],[last_logical_writes],
[min_logical_writes],[max_logical_writes],[total_logical_reads],
[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],
[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],
[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],
[query_plan_hash])
SELECT qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
FROM queries qs
LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle =
qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND
qs.statement_start_offset = qsCaught.statement_start_offset
WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @CheckProcedureCacheFilter =
'Duration'
OR @CheckProcedureCacheFilter
IS NULL
BEGIN
SET @StringToExecute =
'WITH queries ([sql_handle],[statement_start_offset],[statement_end_offset],
[plan_generation_num],[plan_handle],[creation_time],[last_execution_time],
[execution_count],[total_worker_time],[last_worker_time],[min_worker_time],
[max_worker_time],[total_physical_reads],[last_physical_reads],
[min_physical_reads],[max_physical_reads],[total_logical_writes],
[last_logical_writes],[min_logical_writes],[max_logical_writes],
[total_logical_reads],[last_logical_reads],[min_logical_reads],[max_logical_reads],
[total_clr_time],[last_clr_time],[min_clr_time],[max_clr_time],
[total_elapsed_time],[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],
[query_hash],[query_plan_hash])
AS (SELECT TOP 20 qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
FROM sys.dm_exec_query_stats qs
ORDER BY qs.total_elapsed_time DESC)
INSERT INTO #dm_exec_query_stats ([sql_handle],
[statement_start_offset],[statement_end_offset],[plan_generation_num],
[plan_handle],[creation_time],[last_execution_time],[execution_count],
[total_worker_time],[last_worker_time],[min_worker_time],[max_worker_time],
[total_physical_reads],[last_physical_reads],[min_physical_reads],
[max_physical_reads],[total_logical_writes],[last_logical_writes],
[min_logical_writes],[max_logical_writes],[total_logical_reads],
[last_logical_reads],[min_logical_reads],[max_logical_reads],[total_clr_time],
[last_clr_time],[min_clr_time],[max_clr_time],[total_elapsed_time],
[last_elapsed_time],[min_elapsed_time],[max_elapsed_time],[query_hash],
[query_plan_hash])
SELECT qs.[sql_handle],qs.[statement_start_offset],qs.
[statement_end_offset],qs.[plan_generation_num],qs.[plan_handle],qs.
[creation_time],qs.[last_execution_time],qs.[execution_count],qs.
[total_worker_time],qs.[last_worker_time],qs.[min_worker_time],qs.
[max_worker_time],qs.[total_physical_reads],qs.[last_physical_reads],qs.
[min_physical_reads],qs.[max_physical_reads],qs.[total_logical_writes],qs.
[last_logical_writes],qs.[min_logical_writes],qs.[max_logical_writes],qs.
[total_logical_reads],qs.[last_logical_reads],qs.[min_logical_reads],qs.
[max_logical_reads],qs.[total_clr_time],qs.[last_clr_time],qs.[min_clr_time],qs.
[max_clr_time],qs.[total_elapsed_time],qs.[last_elapsed_time],qs.
[min_elapsed_time],qs.[max_elapsed_time],qs.[query_hash],qs.[query_plan_hash]
FROM queries qs
LEFT OUTER JOIN #dm_exec_query_stats qsCaught ON qs.sql_handle =
qsCaught.sql_handle AND qs.plan_handle = qsCaught.plan_handle AND
qs.statement_start_offset = qsCaught.statement_start_offset
WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
qs.statement_start_offset,
qs.statement_end_offset)
AS qp;
END;
( qs.statement_start_offset
/ 2 ) + 1,
( ( CASE qs.statement_end_offset
WHEN -1
THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END
- qs.statement_start_offset )
/ 2 ) + 1)
FROM #dm_exec_query_stats qs
CROSS APPLY
sys.dm_exec_sql_text(qs.sql_handle) AS st
CROSS APPLY
sys.dm_exec_query_plan(qs.plan_handle)
AS qp;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 63) WITH NOWAIT;
'https://www.brentozar.com/go/implicit' AS URL ,
( 'One of
the top resource-intensive queries is comparing two fields that are not the same
datatype.' ) AS Details ,
qs.query_plan ,
qs.query_plan_filtered
FROM
#dm_exec_query_stats qs
WHERE
COALESCE(qs.query_plan_filtered,
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 64) WITH NOWAIT;
'https://www.brentozar.com/go/implicit' AS URL ,
( 'One of
the top resource-intensive queries has an implicit conversion that is affecting
cardinality estimation.' ) AS Details ,
qs.query_plan ,
qs.query_plan_filtered
FROM
#dm_exec_query_stats qs
WHERE
COALESCE(qs.query_plan_filtered,
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 118) WITH NOWAIT;
QueryPlanFiltered
)
SELECT 118 AS
CheckID ,
120 AS
Priority ,
'Query
Plans' AS FindingsGroup ,
'RID
or Key Lookups' AS Finding ,
'https
://www.brentozar.com/go/lookup' AS URL ,
'One
of the top resource-intensive queries contains RID or Key Lookups. Try to avoid
them by creating covering indexes.' AS Details ,
qs.query_plan ,
qs.query_plan_filtered
FROM
#dm_exec_query_stats qs
WHERE
COALESCE(qs.query_plan_filtered,
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 65) WITH NOWAIT;
'https://www.brentozar.com/go/missingindex' AS URL ,
( 'One of
the top resource-intensive queries may be dramatically improved by adding an
index.' ) AS Details ,
qs.query_plan ,
qs.query_plan_filtered
FROM
#dm_exec_query_stats qs
WHERE
COALESCE(qs.query_plan_filtered,
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 66) WITH NOWAIT;
'https://www.brentozar.com/go/cursor' AS URL ,
( 'One of
the top resource-intensive queries is using a cursor.' ) AS Details ,
qs.query_plan ,
qs.query_plan_filtered
FROM
#dm_exec_query_stats qs
WHERE
COALESCE(qs.query_plan_filtered,
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 67) WITH NOWAIT;
'https://www.brentozar.com/go/functions' AS URL ,
( 'One of
the top resource-intensive queries is using a user-defined scalar function that may
inhibit parallelism.' ) AS Details ,
qs.query_plan ,
qs.query_plan_filtered
FROM
#dm_exec_query_stats qs
WHERE
COALESCE(qs.query_plan_filtered,
END; /* IF @CheckProcedureCache = 1 */
END;
/*Check to see if the HA endpoint account is set at the same as the SQL
Server Service Account*/
IF @ProductVersionMajor >= 10
AND NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL AND
CheckID = 187 )
IF SERVERPROPERTY('IsHadrEnabled') = 1
BEGIN
'https://www.brentozar.com/go/servername' AS URL ,
'@@Servername
variable is null. You can fix it by executing: "sp_addserver ''<LocalServerName>'',
local"' AS Details;
END;
IF /* @@SERVERNAME IS set */
(@@SERVERNAME IS NOT NULL
AND
/* not a named instance */
CHARINDEX(CHAR(92),CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))) = 0
AND
/* not clustered, when computername may be
different than the servername */
SERVERPROPERTY('IsClustered') = 0
AND
/* @@SERVERNAME is different than the computer
name */
@@SERVERNAME <>
CAST(ISNULL(SERVERPROPERTY('ComputerNamePhysicalNetBIOS'),@@SERVERNAME) AS
NVARCHAR(128)) )
BEGIN
END;
/*Check to see if a failsafe operator has been configured*/
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL AND
CheckID = 73 )
BEGIN
'https://www.brentozar.com/go/failsafe' AS URL ,
( 'No failsafe operator
is configured on this server. This is a good idea just in-case there are issues
with the [msdb] database that prevents alerting.' ) AS Details
FROM @AlertInfo
WHERE FailSafeOperator IS NULL;
END;
'https://www.brentozar.com/go/poison' AS URL ,
CONVERT(VARCHAR(10), (MAX([wait_time_ms]) /
1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (MAX([wait_time_ms]) /
1000), 0), 108) + ' of this wait have been recorded'
+ CASE WHEN ts.status = 1 THEN ' despite
enabling trace flag 8048 already.'
ELSE '. In servers with over 8 cores per
NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.'
END
FROM sys.dm_os_nodes n
INNER JOIN sys.[dm_os_wait_stats] w ON
w.wait_type = 'CMEMTHREAD'
LEFT OUTER JOIN #TraceStatus ts ON ts.TraceFlag = 8048
AND ts.status = 1
WHERE n.node_id = 0 AND
n.online_scheduler_count >= 8
AND EXISTS (SELECT * FROM
sys.dm_os_nodes WHERE node_id > 0 AND node_state_desc NOT LIKE '%DAC')
GROUP BY w.wait_type, ts.status
HAVING SUM([wait_time_ms]) > (SELECT 5000
* datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM
sys.databases WHERE name='tempdb')
AND SUM([wait_time_ms]) > 60000;
END;
'https://www.brentozar.com/go/biglog' AS URL ,
'The database [' +
DB_NAME(a.database_id)
+ '] has a ' +
CAST((CAST(a.size AS BIGINT) * 8 / 1000000) AS NVARCHAR(20)) + ' GB transaction log
file, larger than the total data file sizes. This may indicate that transaction log
backups are not being performed or not performed often enough.' AS Details
FROM sys.master_files a
WHERE a.type = 1
AND
DB_NAME(a.database_id) NOT IN (
SELECT DISTINCT
DatabaseName
FROM #SkipChecks
WHERE CheckID = 75 OR
CheckID IS NULL)
AND a.size > 125000 /*
Size is measured in pages here, so this gets us log files over 1GB. */
AND a.size > ( SELECT
SUM(CAST(b.size AS BIGINT))
FROM sys.master_files b
AND b.type = 0
)
AND a.database_id IN (
SELECT database_id
FROM sys.databases
WHERE
source_database_id IS NULL );
END;
'https://www.brentozar.com/go/collate' AS URL ,
'Collation differences
between user databases and tempdb can cause conflicts especially when comparing
string values' AS Details
FROM sys.databases
WHERE name NOT IN ( 'master', 'model',
'msdb')
AND name NOT LIKE
'ReportServer%'
AND name NOT IN ( SELECT
DISTINCT
DatabaseName
FROM #SkipChecks
collation_name
FROM
sys.databases
WHERE
name = 'tempdb'
);
END;
'https://www.brentozar.com/go/snapshot' AS URL ,
'Database [' + dSnap.
[name]
+ '] is a snapshot of ['
+ dOriginal.[name]
+ ']. Make sure you have
enough drive space to maintain the snapshot as the original database grows.' AS
Details
FROM sys.databases dSnap
INNER JOIN sys.databases
dOriginal ON dSnap.source_database_id = dOriginal.database_id
FROM #SkipChecks
INSERTINTO #BlitzResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT 79 AS CheckID ,
-- sp_Blitz Issue #776
-- Job has history and
was executed in the last 30 days OR Job is enabled AND Job Schedule is enabled
CASE WHEN (cast(datediff(dd,
substring(cast(sjh.run_date as nvarchar(10)), 1, 4) + '-' +
substring(cast(sjh.run_date as nvarchar(10)), 5, 2) + '-' +
substring(cast(sjh.run_date as nvarchar(10)), 7, 2), GETDATE()) AS INT) < 30) OR
(j.[enabled] = 1 AND ssc.[enabled] = 1 )THEN
100
ELSE -- no job history (implicit)
AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled)
200
END AS Priority,
'Performance' AS
FindingsGroup ,
'Shrink Database Job' AS
Finding ,
'https://www.brentozar.com/go/autoshrink' AS URL ,
'In the [' + j.[name] +
'] job, step ['
+ step.[step_name]
+ '] has SHRINKDATABASE
or SHRINKFILE, which may be causing database fragmentation.'
+ CASE WHEN
COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + ''
END AS Details
FROM msdb.dbo.sysjobs j
INNER JOIN
msdb.dbo.sysjobsteps step ON j.job_id = step.job_id
LEFT OUTER JOIN
msdb.dbo.sysjobschedules AS sjsc
ON j.job_id =
sjsc.job_id
LEFT OUTER JOIN
msdb.dbo.sysschedules AS ssc
ON sjsc.schedule_id
= ssc.schedule_id
AND sjsc.job_id =
j.job_id
LEFT OUTER JOIN
msdb.dbo.sysjobhistory AS sjh
ON j.job_id =
sjh.job_id
AND step.step_id =
sjh.step_id
AND sjh.run_date IN
(SELECT max(sjh2.run_date) FROM msdb.dbo.sysjobhistory AS sjh2 WHERE sjh2.job_id =
j.job_id) -- get the latest entry date
AND sjh.run_time IN
(SELECT max(sjh3.run_time) FROM msdb.dbo.sysjobhistory AS sjh3 WHERE sjh3.job_id =
j.job_id AND sjh3.run_date = sjh.run_date) -- get the latest entry time
WHERE step.command LIKE
N'%SHRINKDATABASE%'
OR step.command LIKE
N'%SHRINKFILE%';
END;
'https://www.BrentOzar.com/blitz/sp_configure/' AS URL ,
( 'This sp_configure
option isn''t running under its set value. Its set value is '
+ CAST(cr.[value] AS
VARCHAR(100))
+ ' and its running
value is '
+ CAST(cr.value_in_use
AS VARCHAR(100))
+ '. When someone does
a RECONFIGURE or restarts the instance, this setting will start taking effect.' )
AS Details
FROM sys.configurations cr
WHERE cr.value <> cr.value_in_use
AND NOT (cr.name = 'min server memory (MB)' AND
cr.value IN (0,16) AND cr.value_in_use IN (0,16));
END;
'https://www.brentozar.com/go/busyagent/' AS URL ,
( 'Multiple SQL Server
Agent jobs are configured to start simultaneously. For detailed schedule listings,
see the query in the URL.' ) AS Details
FROM msdb.dbo.sysjobactivity
WHERE start_execution_date >
DATEADD(dd, -14, GETDATE())
GROUP BY start_execution_date
HAVING COUNT(*) > 1;
END;
IF @CheckServerInfo = 1
BEGIN
SELECT
172 AS [CheckID] ,
250 AS [Priority] ,
'Server Info' AS [FindingsGroup] ,
'Operating System Version' AS [Finding] ,
( CASE WHEN @IsWindowsOperatingSystem = 1
THEN
'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions'
ELSE
'https://en.wikipedia.org/wiki/List_of_Linux_distributions'
END
) AS [URL] ,
( CASE
WHEN [ohi].[host_platform] =
'Linux' THEN 'You''re running the ' + CAST([ohi].[host_distribution] AS
VARCHAR(35)) + ' distribution of ' + CAST([ohi].[host_platform] AS VARCHAR(35)) +
', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
WHEN [ohi].[host_platform] =
'Windows' AND [ohi].[host_release] = '5' THEN 'You''re running Windows 2000,
version ' + CAST([ohi].[host_release] AS VARCHAR(5))
WHEN [ohi].[host_platform] =
'Windows' AND [ohi].[host_release] > '5' THEN 'You''re running ' + CAST([ohi].
[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS
VARCHAR(5))
ELSE 'You''re running ' +
CAST([ohi].[host_distribution] AS VARCHAR(35)) + ', version ' + CAST([ohi].
[host_release] AS VARCHAR(5))
END
) AS [Details]
FROM [sys].[dm_os_host_info] [ohi];
END;
ELSE
BEGIN
-- Otherwise, stick with Windows-only detection
IF EXISTS ( SELECT 1
FROM
sys.all_objects
WHERE name
= 'dm_os_windows_info' )
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT;
[FindingsGroup] ,
[Finding] ,
[URL] ,
[Details] )
SELECT
172 AS [CheckID] ,
250 AS [Priority] ,
'Server Info' AS
[FindingsGroup] ,
'Windows Version' AS
[Finding] ,
'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' AS [URL] ,
( CASE
WHEN [owi].
[windows_release] = '5' THEN 'You''re running Windows 2000, version ' + CAST([owi].
[windows_release] AS VARCHAR(5))
WHEN [owi].
[windows_release] > '5' AND [owi].[windows_release] < '6' THEN 'You''re running
Windows Server 2003/2003R2 era, version ' + CAST([owi].[windows_release] AS
VARCHAR(5))
WHEN [owi].
[windows_release] >= '6' AND [owi].[windows_release] <= '6.1' THEN 'You''re running
Windows Server 2008/2008R2 era, version ' + CAST([owi].[windows_release] AS
VARCHAR(5))
WHEN [owi].
[windows_release] >= '6.2' AND [owi].[windows_release] <= '6.3' THEN 'You''re
running Windows Server 2012/2012R2 era, version ' + CAST([owi].[windows_release] AS
VARCHAR(5))
WHEN [owi].
[windows_release] = '10.0' THEN 'You''re running Windows Server 2016/2019 era,
version ' + CAST([owi].[windows_release] AS VARCHAR(5))
ELSE 'You''re running
Windows Server, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
END
) AS [Details]
FROM [sys].
[dm_os_windows_info] [owi];
END;
END;
END;
/*
This check hits the dm_os_process_memory system view
to see if locked_page_allocations_kb is > 0,
which could indicate that locked pages in memory is enabled.
*/
IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL AND
CheckID = 166 )
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT;
SET @StringToExecute =
'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL,
Details)
SELECT 166 AS CheckID ,
250 AS Priority ,
''Server Info'' AS FindingsGroup ,
''Memory Model Unconventional'' AS Finding ,
''https://www.brentozar.com/go/lpim'' AS URL ,
''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100))
FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION
(RECOMPILE);';
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
/*
Starting with SQL Server 2014 SP2, Instant File Initialization
is logged in the SQL Server Error Log.
*/
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS NULL
AND CheckID = 193 )
AND ((@ProductVersionMajor >= 13) OR
(@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000))
BEGIN
IF @@ROWCOUNT > 0
begin
INSERT INTO #BlitzResults
( CheckID ,
[Priority] ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT
193 AS
[CheckID] ,
250 AS
[Priority] ,
'Server
Info' AS [FindingsGroup] ,
'Instant
File Initialization Enabled' AS [Finding] ,
'https://www.brentozar.com/go/instant' AS [URL] ,
'The service
account has the Perform Volume Maintenance Tasks permission.';
end
else -- if version of sql server has
instant_file_initialization_enabled column in dm_server_services, check that too
-- in the event the error log has
been cycled and the startup messages are not in the current error log
begin
if EXISTS ( SELECT *
FROM
sys.all_objects o
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 192) WITH NOWAIT;
SET @StringToExecute =
'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL,
Details)
SELECT 192 AS CheckID ,
50 AS Priority ,
''Server Info'' AS FindingsGroup ,
''Instant File Initialization Not Enabled'' AS Finding ,
''https://www.brentozar.com/go/instant'' AS URL ,
''Consider enabling IFI for faster restores and data file
growths.''
FROM sys.dm_server_services WHERE
instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%''
OPTION (RECOMPILE);';
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 130) WITH NOWAIT;
@@SERVERNAME AS Details
WHERE
@@SERVERNAME IS NOT NULL;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 83) WITH NOWAIT;
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT;
SET @StringToExecute =
'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL,
Details)
SELECT 84 AS CheckID ,
250 AS Priority ,
''Server Info'' AS FindingsGroup ,
''Hardware'' AS Finding ,
'''' AS URL ,
''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''.
Physical memory: '' + CAST( CAST(ROUND((physical_memory_kb / 1024.0 / 1024), 1) AS
INT) AS VARCHAR(50)) + ''GB.''
FROM sys.dm_os_sys_info OPTION (RECOMPILE);';
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT;
SET @StringToExecute =
'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL,
Details)
SELECT 84 AS CheckID ,
250 AS Priority ,
''Server Info'' AS FindingsGroup ,
''Hardware'' AS Finding ,
'''' AS URL ,
''Logical processors: '' + CAST(cpu_count AS VARCHAR(50)) + ''.
Physical memory: '' + CAST( CAST(ROUND((physical_memory_in_bytes / 1024.0 / 1024 /
1024), 1) AS INT) AS VARCHAR(50)) + ''GB.''
FROM sys.dm_os_sys_info OPTION (RECOMPILE);';
IF @Debug = 2 AND
@StringToExecute IS NOT NULL PRINT @StringToExecute;
IF @Debug = 2 AND
@StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
EXECUTE(@StringToExecute);
END;
END;
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS
NULL AND CheckID = 85 )
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 85) WITH NOWAIT;
0) AS VARCHAR(100))
+ N'.
Availability Groups Manager Status: '
+
CAST(COALESCE(SERVERPROPERTY('HadrManagerStatus'),
0) AS VARCHAR(100));
END;
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS
NULL AND CheckID = 88 )
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 88) WITH NOWAIT;
CAST(create_date AS VARCHAR(100))
FROM sys.databases
WHERE database_id = 2;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 91) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 92) WITH NOWAIT;
SUBSTRING(volume_mount_point, 1, 1) AS volume_mount_point
,CASE
WHEN ISNULL(logical_volume_name,'''') = '''' THEN '''' ELSE ''('' +
logical_volume_name + '')'' END AS logical_volume_name
,total
_bytes/1024/1024 AS total_MB
,avail
able_bytes/1024/1024 AS available_MB
,
(CONVERT(DECIMAL(5,2),(total_bytes/1.0 - available_bytes)/total_bytes * 100)) AS
used_percent
FROM
database_id
,file_id
,SUBSTRING(physical_name,1,1) AS Drive
FROM
sys.master_files
ORDER
BY ROW_NUMBER() OVER(PARTITION BY SUBSTRING(physical_name,1,1) ORDER BY
database_id)
) f
CROSS APPLY
sys.dm_os_volume_stats(f.database_id, f.file_id)
) as v on #driveInfo.drive =
v.volume_mount_point;';
EXECUTE(@StringToExecute);
END;
IF (@ProductVersionMajor >=
11)
BEGIN
SET
@StringToExecute=REPLACE(@StringToExecute,'CAST(i.available_MB/1024 AS
NUMERIC(18,2))','FORMAT(i.available_MB/1024,''N2'')');
SET
@StringToExecute=REPLACE(@StringToExecute,'CAST(i.total_MB/1024 AS
NUMERIC(18,2))','FORMAT(i.total_MB/1024,''N2'')');
END;
EXECUTE(@StringToExecute);
DROP TABLE #driveInfo;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 103) WITH NOWAIT;
''https://www.brentozar.com/go/virtual'' AS URL,
''Type: ('' +
virtual_machine_type_desc + '')'' AS Details
FROM sys.dm_os_sys_info
WHERE virtual_machine_type <>
0 OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 214) WITH NOWAIT;
''https://www.brentozar.com/go/virtual'' AS URL,
''Type: ('' +
container_type_desc + '')'' AS Details
FROM sys.dm_os_sys_info
WHERE container_type_desc <>
''NONE'' OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 114) WITH NOWAIT;
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT;
@key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings',
@value_name = 'ActivePowerScheme',
@value = @outval OUTPUT,
@no_output = 'no_output';
@value_name = 'ActivePowerScheme',
@value =
@outval OUTPUT;
@value_name = '~MHz',
@value =
@cpu_speed_mhz OUTPUT;
SELECT @cpu_speed_ghz =
CAST(CAST(@cpu_speed_mhz AS DECIMAL) / 1000 AS DECIMAL(18,2));
'https://www.brentozar.com/blitz/power-mode/' AS URL,
'Your server has '
+ CAST(@cpu_speed_ghz as
VARCHAR(4))
+ 'GHz CPUs, and is in '
+ CASE @outval
WHEN 'a1841308-3541-4fab-
bc81-f71556f20b4a'
THEN 'power saving mode --
are you sure this is a production SQL Server?'
WHEN '381b4222-f694-41f0-
9685-ff5bb260df2e'
THEN 'balanced power mode --
Uh... you want your CPUs to run at full speed, right?'
WHEN '8c5e7fda-e8bf-4a96-
9a85-a6e23a8c635c'
THEN 'high performance power
mode'
WHEN 'e9a42b02-d5df-448d-
aa00-03f14749eb61'
THEN 'ultimate performance
power mode'
ELSE 'an unknown power
mode.'
END AS Details
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 212) WITH NOWAIT;
@value_name = 'InstalledInstances'
SELECT
212 AS CheckId ,
250 AS Priority ,
'Server Info' AS
FindingsGroup ,
'Instance Stacking' AS
Finding ,
'https://www.brentozar.com/go/babygotstacked/' AS URL ,
'Your Server has ' +
@InstanceCount + ' Instances of SQL Server installed. More than one is usually a
bad idea. Read the URL for more info.'
END;
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 106) WITH NOWAIT;
,'https://www.brentozar.com/go/trace' AS URL
,'The
default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+'
hours of data'
+' between
'+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30))
+('. The
default trace files are located in:
'+left( @curr_tracefilename,len(@curr_tracefilename) - @indx)
) as Details
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 152) WITH NOWAIT;
WITH os(wait_type,
waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms)
AS
(SELECT ws.wait_type,
waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms
FROM
sys.dm_os_wait_stats ws
LEFT OUTER JOIN
#IgnorableWaits i ON ws.wait_type = i.wait_type
WHERE i.wait_type
IS NULL
AND
wait_time_ms > .1 * @CpuMsSinceWaitsCleared
AND
waiting_tasks_count > 0)
INSERT INTO #BlitzResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT TOP 9
152
AS CheckID
,240
AS Priority
,'Wait
Stats' AS FindingsGroup
,
CAST(ROW_NUMBER() OVER(ORDER BY os.wait_time_ms DESC) AS NVARCHAR(10)) + N' - ' +
os.wait_type AS Finding
,'http
s://www.sqlskills.com/help/waits/' + LOWER(os.wait_type) + '/' AS URL
,
Details = CAST(CAST(SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY
os.wait_type) AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' hours of waits, ' +
CAST(CAST((SUM(60.0 * os.wait_time_ms) OVER (PARTITION BY os.wait_type) ) /
@MsSinceWaitsCleared AS NUMERIC(18,1)) AS NVARCHAR(20)) + N' minutes average wait
time per hour, ' +
/*
CAST(CAST(
CAST(CAST(
CAST(
AS NUMERIC(18,1))
ELSE 0
END AS NVARCHAR(40)) + N' ms average wait time.'
FROM os
ORDER BY
SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) DESC;
END; /* IF EXISTS (SELECT *
FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0 AND waiting_tasks_count > 0) */
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 153) WITH NOWAIT;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 222) WITH NOWAIT;
''https://www.BrentOzar.com/go/azurevm'' AS URL ,
''cpu_rate:
'' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) +
'',
memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) +
'',
process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS
NVARCHAR(20)) +
'',
workingset_limit_mb: '' + CAST(COALESCE(workingset_limit_mb, 0) AS NVARCHAR(20))
FROM
sys.dm_os_job_object OPTION (RECOMPILE);';
EXECUTE(@StringToExecute);
END;
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 224) WITH NOWAIT;
IF EXISTS (SELECT 1
FROM #services
WHERE cmdshell_output LIKE '%SQL
Server Reporting Services%'
OR cmdshell_output LIKE '%SQL
Server Integration Services%'
OR cmdshell_output LIKE '%SQL
Server Analysis Services%')
BEGIN
INSERT INTO #BlitzResults
( CheckID ,
Priority ,
FindingsGroup ,
Finding ,
URL ,
Details
)
SELECT
224 AS
CheckID
,200 AS
Priority
,'Performance' AS FindingsGroup
,'https://www.BrentOzar.com/go/services' AS URL
,'Did you
know you have other SQL Server services installed on this box other than the
engine? It can be a real performance pain.' as Details
END;
END;
END;
/* CheckID 232 - Server Info - Data Size */
IF NOT EXISTS ( SELECT 1
FROM #SkipChecks
WHERE DatabaseName IS
NULL AND CheckID = 232 )
BEGIN
IF @Debug IN (1, 2)
RAISERROR('Running CheckId [%d].', 0, 1, 232) WITH NOWAIT;
IF
OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL
DROP TABLE #MasterFiles;
CREATE TABLE #MasterFiles
(database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255),
physical_name NVARCHAR(255), size BIGINT);
/* Azure SQL Database doesn't have
sys.master_files, so we have to build our own. */
IF ((SERVERPROPERTY('Edition')) =
'SQL Azure'
AND
(OBJECT_ID('sys.master_files') IS NULL))
SET @StringToExecute =
'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name,
size) SELECT DB_ID(), file_id, type_desc, name, physical_name, size FROM
sys.database_files;';
ELSE
SET @StringToExecute =
'INSERT INTO #MasterFiles (database_id, file_id, type_desc, name, physical_name,
size) SELECT database_id, file_id, type_desc, name, physical_name, size FROM
sys.master_files;';
EXEC(@StringToExecute);
END;
END; /* IF @CheckServerInfo = 1 */
END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT
ServerName */
DELETE br
FROM #BlitzResults br
WHERE EXISTS (SELECT * FROM #BlitzResults brLower
WHERE br.FindingsGroup = brLower.FindingsGroup AND br.Finding = brLower.Finding AND
br.Priority = brLower.Priority AND br.ID > brLower.ID);
END;
/* Add credits for the nice folks who put so much time into
building and maintaining this for free: */
)
VALUES ( -1 ,
0 ,
'sp_Blitz ' + CAST(CONVERT(DATETIME,
@VersionDate, 102) AS VARCHAR(100)),
'SQL Server First Responder Kit' ,
'http://FirstResponderKit.org/' ,
'To get help or add your own contributions,
join us at http://FirstResponderKit.org.'
);
)
SELECT 156 ,
254 ,
'Rundate' ,
GETDATE() ,
'http://FirstResponderKit.org/' ,
'Captain''s log: stardate something and
something...';
IF @EmailRecipients IS NOT NULL
BEGIN
EXEC(@StringToExecute);
END;
ELSE
BEGIN
IF @OutputXMLasNVARCHAR = 1
BEGIN
SET @StringToExecute = N' IF
EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA
WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName + ''') INSERT '
+ @OutputDatabaseName + '.'
+ @OutputSchemaName + '.'
+ @OutputTableName
+ ' (ServerName, CheckDate,
CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan,
QueryPlanFiltered) SELECT '''
+ CAST(SERVERPROPERTY('ServerName')
AS NVARCHAR(128))
+ ''', SYSDATETIMEOFFSET(),
CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details,
CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY
Priority , FindingsGroup , Finding , DatabaseName , Details';
END;
ELSE
begin
SET @StringToExecute = N' IF
EXISTS(SELECT * FROM '
+ @OutputDatabaseName
+ '.INFORMATION_SCHEMA.SCHEMATA
WHERE QUOTENAME(SCHEMA_NAME) = '''
+ @OutputSchemaName + ''') INSERT '
+ @OutputDatabaseName + '.'
+ @OutputSchemaName + '.'
+ @OutputTableName
+ ' (ServerName, CheckDate,
CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan,
QueryPlanFiltered) SELECT '''
+ CAST(SERVERPROPERTY('ServerName')
AS NVARCHAR(128))
+ ''', SYSDATETIMEOFFSET(),
CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan,
QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding ,
DatabaseName , Details';
END;
EXEC(@StringToExecute);
END;
END;
ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##')
BEGIN
IF @ValidOutputServer = 1
BEGIN
RAISERROR('Due to the nature of
temporary tables, outputting to a linked server requires a permanent table.', 16,
0);
END;
ELSE
BEGIN
SET @StringToExecute = N' IF
(OBJECT_ID(''tempdb..'
+ @OutputTableName
+ ''') IS NOT NULL) DROP
TABLE ' + @OutputTableName + ';'
+ 'CREATE TABLE '
+ @OutputTableName
+ ' (ID INT IDENTITY(1,1) NOT
NULL,
ServerName
NVARCHAR(128),
CheckDate
DATETIMEOFFSET,
Priority TINYINT ,
FindingsGroup
VARCHAR(50) ,
Finding VARCHAR(200) ,
DatabaseName
NVARCHAR(128),
URL VARCHAR(200) ,
Details NVARCHAR(4000) ,
QueryPlan [XML] NULL ,
QueryPlanFiltered
[NVARCHAR](MAX) NULL,
CheckID INT ,
CONSTRAINT [PK_' +
CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
+ ' INSERT '
+ @OutputTableName
+ ' (ServerName, CheckDate,
CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan,
QueryPlanFiltered) SELECT '''
+
CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
+ ''', SYSDATETIMEOFFSET(),
CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan,
QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding ,
DatabaseName , Details';
EXEC(@StringToExecute);
END;
END;
ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#')
BEGIN
RAISERROR('Due to the nature of Dymamic SQL,
only global (i.e. double pound (##)) temp tables are supported for
@OutputTableName', 16, 0);
END;
IF @OutputType = 'COUNT'
BEGIN
SELECT COUNT(*) AS Warnings
FROM #BlitzResults;
END;
ELSE
IF @OutputType IN ( 'CSV', 'RSV' )
BEGIN
'(N/A)') + @separator
+ COALESCE([Finding],
'(N/A)') + @separator
+ COALESCE(DatabaseName,
'(N/A)') + @separator
+ COALESCE([URL], '(N/A)') +
@separator
+ COALESCE([Details],
'(N/A)')
FROM #BlitzResults
ORDER BY Priority ,
FindingsGroup ,
Finding ,
DatabaseName ,
Details;
END;
ELSE IF @OutputXMLasNVARCHAR = 1 AND @OutputType <>
'NONE'
BEGIN
SELECT [Priority] ,
[FindingsGroup] ,
[Finding] ,
[DatabaseName] ,
[URL] ,
[Details] ,
CAST([QueryPlan] AS
NVARCHAR(MAX)) AS QueryPlan,
[QueryPlanFiltered] ,
CheckID
FROM #BlitzResults
ORDER BY Priority ,
FindingsGroup ,
Finding ,
DatabaseName ,
Details;
END;
ELSE IF @OutputType = 'MARKDOWN'
BEGIN
WITH Results AS (SELECT row_number() OVER
(ORDER BY Priority, FindingsGroup, Finding, DatabaseName, Details) AS rownum, *
FROM
#BlitzResults
WHERE
Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT
NULL
AND
FindingsGroup <> 'Security' /* Specifically excluding security checks for public
exports */)
SELECT
CASE
WHEN r.Priority <>
COALESCE(rPrior.Priority, 0) OR r.FindingsGroup <> rPrior.FindingsGroup THEN @crlf
+ N'**Priority ' + CAST(COALESCE(r.Priority,N'') AS NVARCHAR(5)) + N': ' +
COALESCE(r.FindingsGroup,N'') + N'**:' + @crlf + @crlf
ELSE N''
END
+ CASE WHEN r.Finding <>
COALESCE(rPrior.Finding,N'') AND r.Finding <> COALESCE(rNext.Finding,N'') THEN N'-
' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' +
COALESCE(r.Details,N'') + @crlf
WHEN r.Finding <>
COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding AND r.Details =
rNext.Details THEN N'- ' + COALESCE(r.Finding,N'') + N' - ' +
COALESCE(r.Details,N'') + @crlf + @crlf + N' * ' + COALESCE(r.DatabaseName, N'')
+ @crlf
WHEN r.Finding <>
COALESCE(rPrior.Finding,N'') AND r.Finding = rNext.Finding THEN N'- ' +
COALESCE(r.Finding,N'') + @crlf + CASE WHEN r.DatabaseName IS NULL THEN N'' ELSE
N' * ' + COALESCE(r.DatabaseName,N'') END + CASE WHEN r.Details <>
rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') + @crlf ELSE '' END
ELSE CASE WHEN
r.DatabaseName IS NULL THEN N'' ELSE N' * ' + COALESCE(r.DatabaseName,N'') END
+ CASE WHEN r.Details <> rPrior.Details THEN N' - ' + COALESCE(r.Details,N'') +
@crlf ELSE N'' + @crlf END
END + @crlf
FROM Results r
LEFT OUTER JOIN Results rPrior ON
r.rownum = rPrior.rownum + 1
LEFT OUTER JOIN Results rNext ON
r.rownum = rNext.rownum - 1
ORDER BY r.rownum FOR XML PATH(N'');
END;
ELSE IF @OutputType = 'XML'
BEGIN
/* --TOURSTOP05-- */
SELECT [Priority] ,
[FindingsGroup] ,
[Finding] ,
[DatabaseName] ,
[URL] ,
[Details] ,
[QueryPlanFiltered] ,
CheckID
FROM #BlitzResults
ORDER BY Priority ,
FindingsGroup ,
Finding ,
DatabaseName ,
Details
FOR XML PATH('Result'), ROOT('sp_Blitz_Output');
END;
ELSE IF @OutputType <> 'NONE'
BEGIN
/* --TOURSTOP05-- */
SELECT [Priority] ,
[FindingsGroup] ,
[Finding] ,
[DatabaseName] ,
[URL] ,
[Details] ,
[QueryPlan] ,
[QueryPlanFiltered] ,
CheckID
FROM #BlitzResults
ORDER BY Priority ,
FindingsGroup ,
Finding ,
DatabaseName ,
Details;
END;
IF @OutputProcedureCache = 1
AND @CheckProcedureCache = 1
SELECT TOP 20
total_worker_time / execution_count AS
AvgCPU ,
total_worker_time AS TotalCPU ,
CAST(ROUND(100.00 * total_worker_time
/ ( SELECT
SUM(total_worker_time)
FROM
sys.dm_exec_query_stats
), 2) AS MONEY) AS
PercentCPU ,
total_elapsed_time / execution_count AS
AvgDuration ,
total_elapsed_time AS TotalDuration ,
CAST(ROUND(100.00 * total_elapsed_time
/ ( SELECT
SUM(total_elapsed_time)
FROM
sys.dm_exec_query_stats
), 2) AS MONEY) AS
PercentDuration ,
total_logical_reads / execution_count AS
AvgReads ,
total_logical_reads AS TotalReads ,
CAST(ROUND(100.00 * total_logical_reads
/ ( SELECT
SUM(total_logical_reads)
FROM
sys.dm_exec_query_stats
), 2) AS MONEY) AS
PercentReads ,
execution_count ,
CAST(ROUND(100.00 * execution_count
/ ( SELECT
SUM(execution_count)
FROM
sys.dm_exec_query_stats
), 2) AS MONEY) AS
PercentExecutions ,
CASE WHEN DATEDIFF(mi, creation_time,
qs.last_execution_time) = 0 THEN 0
ELSE CAST(( 1.00 * execution_count
/ DATEDIFF(mi,
creation_time,
qs.last_execution_time) ) AS MONEY)
END AS executions_per_minute ,
qs.creation_time AS plan_creation_time ,
qs.last_execution_time ,
text ,
text_filtered ,
query_plan ,
query_plan_filtered ,
sql_handle ,
query_hash ,
plan_handle ,
query_plan_hash
FROM #dm_exec_query_stats qs
ORDER BY CASE UPPER(@CheckProcedureCacheFilter)
WHEN 'CPU' THEN total_worker_time
WHEN 'READS' THEN total_logical_reads
WHEN 'EXECCOUNT' THEN execution_count
WHEN 'DURATION' THEN
total_elapsed_time
ELSE total_worker_time
END DESC;
*/
/*
Reset the Nmumeric_RoundAbort session state back to enabled if it was
disabled earlier.
See Github issue #2302 for more info.
*/
IF @NeedToTurnNumericRoundabortBackOn = 1
SET NUMERIC_ROUNDABORT ON;