· 5 years ago · Mar 25, 2020, 04:56 PM
1IF OBJECT_ID('dbo.sp_Blitz') IS NULL
2 EXEC ('CREATE PROCEDURE dbo.sp_Blitz AS RETURN 0;');
3GO
4
5ALTER PROCEDURE [dbo].[sp_Blitz]
6 @Help TINYINT = 0 ,
7 @CheckUserDatabaseObjects TINYINT = 1 ,
8 @CheckProcedureCache TINYINT = 0 ,
9 @OutputType VARCHAR(20) = 'TABLE' ,
10 @OutputProcedureCache TINYINT = 0 ,
11 @CheckProcedureCacheFilter VARCHAR(10) = NULL ,
12 @CheckServerInfo TINYINT = 0 ,
13 @SkipChecksServer NVARCHAR(256) = NULL ,
14 @SkipChecksDatabase NVARCHAR(256) = NULL ,
15 @SkipChecksSchema NVARCHAR(256) = NULL ,
16 @SkipChecksTable NVARCHAR(256) = NULL ,
17 @IgnorePrioritiesBelow INT = NULL ,
18 @IgnorePrioritiesAbove INT = NULL ,
19 @OutputServerName NVARCHAR(256) = NULL ,
20 @OutputDatabaseName NVARCHAR(256) = NULL ,
21 @OutputSchemaName NVARCHAR(256) = NULL ,
22 @OutputTableName NVARCHAR(256) = NULL ,
23 @OutputXMLasNVARCHAR TINYINT = 0 ,
24 @EmailRecipients VARCHAR(MAX) = NULL ,
25 @EmailProfile sysname = NULL ,
26 @SummaryMode TINYINT = 0 ,
27 @BringThePain TINYINT = 0 ,
28 @UsualDBOwner sysname = NULL ,
29 @SkipBlockingChecks TINYINT = 1 ,
30 @Debug TINYINT = 0 ,
31 @Version VARCHAR(30) = NULL OUTPUT,
32 @VersionDate DATETIME = NULL OUTPUT,
33 @VersionCheckMode BIT = 0
34WITH RECOMPILE
35AS
36 SET NOCOUNT ON;
37 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
38
39
40 SELECT @Version = '7.94', @VersionDate = '20200324';
41 SET @OutputType = UPPER(@OutputType);
42
43 IF(@VersionCheckMode = 1)
44 BEGIN
45 RETURN;
46 END;
47
48 IF @Help = 1 PRINT '
49 /*
50 sp_Blitz from http://FirstResponderKit.org
51
52 This script checks the health of your SQL Server and gives you a prioritized
53 to-do list of the most urgent things you should consider fixing.
54
55 To learn more, visit http://FirstResponderKit.org where you can download new
56 versions for free, watch training videos on how it works, get more info on
57 the findings, contribute your own code, and more.
58
59 Known limitations of this version:
60 - Only Microsoft-supported versions of SQL Server. Sorry, 2005 and 2000.
61 - If a database name has a question mark in it, some tests will fail. Gotta
62 love that unsupported sp_MSforeachdb.
63 - If you have offline databases, sp_Blitz fails the first time you run it,
64 but does work the second time. (Hoo, boy, this will be fun to debug.)
65 - @OutputServerName will output QueryPlans as NVARCHAR(MAX) since Microsoft
66 has refused to support XML columns in Linked Server queries. The bug is now
67 16 years old! *~ \o/ ~*
68
69 Unknown limitations of this version:
70 - None. (If we knew them, they would be known. Duh.)
71
72 Changes - for the full list of improvements and fixes in this version, see:
73 https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/
74
75 Parameter explanations:
76
77 @CheckUserDatabaseObjects 1=review user databases for triggers, heaps, etc. Takes more time for more databases and objects.
78 @CheckServerInfo 1=show server info like CPUs, memory, virtualization
79 @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues.
80 @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm
81 @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount''
82 @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''MARKDOWN''=bulleted list | ''SCHEMA''=version and field list | ''XML'' =table output as XML | ''NONE'' = none
83 @IgnorePrioritiesBelow 50=ignore priorities below 50
84 @IgnorePrioritiesAbove 50=ignore priorities above 50
85 For the rest of the parameters, see https://www.BrentOzar.com/blitz/documentation for details.
86
87 MIT License
88
89 Copyright for portions of sp_Blitz are held by Microsoft as part of project
90 tigertoolbox and are provided under the MIT license:
91 https://github.com/Microsoft/tigertoolbox
92
93 All other copyrights for sp_Blitz are held by Brent Ozar Unlimited, 2020.
94
95 Copyright (c) 2020 Brent Ozar Unlimited
96
97 Permission is hereby granted, free of charge, to any person obtaining a copy
98 of this software and associated documentation files (the "Software"), to deal
99 in the Software without restriction, including without limitation the rights
100 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
101 copies of the Software, and to permit persons to whom the Software is
102 furnished to do so, subject to the following conditions:
103
104 The above copyright notice and this permission notice shall be included in all
105 copies or substantial portions of the Software.
106
107 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
108 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
109 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
110 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
111 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
112 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
113 SOFTWARE.
114
115 */';
116 ELSE IF @OutputType = 'SCHEMA'
117 BEGIN
118 SELECT FieldList = '[Priority] TINYINT, [FindingsGroup] VARCHAR(50), [Finding] VARCHAR(200), [DatabaseName] NVARCHAR(128), [URL] VARCHAR(200), [Details] NVARCHAR(4000), [QueryPlan] NVARCHAR(MAX), [QueryPlanFiltered] NVARCHAR(MAX), [CheckID] INT';
119
120 END;/* IF @OutputType = 'SCHEMA' */
121 ELSE
122 BEGIN
123
124 DECLARE @StringToExecute NVARCHAR(4000)
125 ,@curr_tracefilename NVARCHAR(500)
126 ,@base_tracefilename NVARCHAR(500)
127 ,@indx int
128 ,@query_result_separator CHAR(1)
129 ,@EmailSubject NVARCHAR(255)
130 ,@EmailBody NVARCHAR(MAX)
131 ,@EmailAttachmentFilename NVARCHAR(255)
132 ,@ProductVersion NVARCHAR(128)
133 ,@ProductVersionMajor DECIMAL(10,2)
134 ,@ProductVersionMinor DECIMAL(10,2)
135 ,@CurrentName NVARCHAR(128)
136 ,@CurrentDefaultValue NVARCHAR(200)
137 ,@CurrentCheckID INT
138 ,@CurrentPriority INT
139 ,@CurrentFinding VARCHAR(200)
140 ,@CurrentURL VARCHAR(200)
141 ,@CurrentDetails NVARCHAR(4000)
142 ,@MsSinceWaitsCleared DECIMAL(38,0)
143 ,@CpuMsSinceWaitsCleared DECIMAL(38,0)
144 ,@ResultText NVARCHAR(MAX)
145 ,@crlf NVARCHAR(2)
146 ,@Processors int
147 ,@NUMANodes int
148 ,@MinServerMemory bigint
149 ,@MaxServerMemory bigint
150 ,@ColumnStoreIndexesInUse bit
151 ,@TraceFileIssue bit
152 -- Flag for Windows OS to help with Linux support
153 ,@IsWindowsOperatingSystem BIT
154 ,@DaysUptime NUMERIC(23,2)
155 /* For First Responder Kit consistency check:*/
156 ,@spBlitzFullName VARCHAR(1024)
157 ,@BlitzIsOutdatedComparedToOthers BIT
158 ,@tsql NVARCHAR(MAX)
159 ,@VersionCheckModeExistsTSQL NVARCHAR(MAX)
160 ,@BlitzProcDbName VARCHAR(256)
161 ,@ExecRet INT
162 ,@InnerExecRet INT
163 ,@TmpCnt INT
164 ,@PreviousComponentName VARCHAR(256)
165 ,@PreviousComponentFullPath VARCHAR(1024)
166 ,@CurrentStatementId INT
167 ,@CurrentComponentSchema VARCHAR(256)
168 ,@CurrentComponentName VARCHAR(256)
169 ,@CurrentComponentType VARCHAR(256)
170 ,@CurrentComponentVersionDate DATETIME2
171 ,@CurrentComponentFullName VARCHAR(1024)
172 ,@CurrentComponentMandatory BIT
173 ,@MaximumVersionDate DATETIME
174 ,@StatementCheckName VARCHAR(256)
175 ,@StatementOutputsCounter BIT
176 ,@OutputCounterExpectedValue INT
177 ,@StatementOutputsExecRet BIT
178 ,@StatementOutputsDateTime BIT
179 ,@CurrentComponentMandatoryCheckOK BIT
180 ,@CurrentComponentVersionCheckModeOK BIT
181 ,@canExitLoop BIT
182 ,@frkIsConsistent BIT
183 ,@NeedToTurnNumericRoundabortBackOn BIT;
184
185 /* End of declarations for First Responder Kit consistency check:*/
186 ;
187
188 SET @crlf = NCHAR(13) + NCHAR(10);
189 SET @ResultText = 'sp_Blitz Results: ' + @crlf;
190
191 /* Last startup */
192 SELECT @DaysUptime = CAST(DATEDIFF(HOUR, create_date, GETDATE()) / 24. AS NUMERIC(23, 2))
193 FROM sys.databases
194 WHERE database_id = 2;
195
196 IF @DaysUptime = 0
197 SET @DaysUptime = .01;
198
199 /*
200 Set the session state of Numeric_RoundAbort to off if any databases have Numeric Round-Abort enabled.
201 Stops arithmetic overflow errors during data conversion. See Github issue #2302 for more info.
202 */
203 IF ( (8192 & @@OPTIONS) = 8192 ) /* Numeric RoundAbort is currently on, so we may need to turn it off temporarily */
204 BEGIN
205 IF EXISTS (SELECT 1
206 FROM sys.databases
207 WHERE is_numeric_roundabort_on = 1) /* A database has it turned on */
208 BEGIN
209 SET @NeedToTurnNumericRoundabortBackOn = 1;
210 SET NUMERIC_ROUNDABORT OFF;
211 END;
212 END;
213
214
215
216
217 /*
218 --TOURSTOP01--
219 See https://www.BrentOzar.com/go/blitztour for a guided tour.
220
221 We start by creating #BlitzResults. It's a temp table that will store all of
222 the results from our checks. Throughout the rest of this stored procedure,
223 we're running a series of checks looking for dangerous things inside the SQL
224 Server. When we find a problem, we insert rows into #BlitzResults. At the
225 end, we return these results to the end user.
226
227 #BlitzResults has a CheckID field, but there's no Check table. As we do
228 checks, we insert data into this table, and we manually put in the CheckID.
229 For a list of checks, visit http://FirstResponderKit.org.
230 */
231 IF OBJECT_ID('tempdb..#BlitzResults') IS NOT NULL
232 DROP TABLE #BlitzResults;
233 CREATE TABLE #BlitzResults
234 (
235 ID INT IDENTITY(1, 1) ,
236 CheckID INT ,
237 DatabaseName NVARCHAR(128) ,
238 Priority TINYINT ,
239 FindingsGroup VARCHAR(50) ,
240 Finding VARCHAR(200) ,
241 URL VARCHAR(200) ,
242 Details NVARCHAR(4000) ,
243 QueryPlan [XML] NULL ,
244 QueryPlanFiltered [NVARCHAR](MAX) NULL
245 );
246
247 IF OBJECT_ID('tempdb..#TemporaryDatabaseResults') IS NOT NULL
248 DROP TABLE #TemporaryDatabaseResults;
249 CREATE TABLE #TemporaryDatabaseResults
250 (
251 DatabaseName NVARCHAR(128) ,
252 Finding NVARCHAR(128)
253 );
254
255 /* First Responder Kit consistency (temporary tables) */
256
257 IF(OBJECT_ID('tempdb..#FRKObjects') IS NOT NULL)
258 BEGIN
259 EXEC sp_executesql N'DROP TABLE #FRKObjects;';
260 END;
261
262 -- this one represents FRK objects
263 CREATE TABLE #FRKObjects (
264 DatabaseName VARCHAR(256) NOT NULL,
265 ObjectSchemaName VARCHAR(256) NULL,
266 ObjectName VARCHAR(256) NOT NULL,
267 ObjectType VARCHAR(256) NOT NULL,
268 MandatoryComponent BIT NOT NULL
269 );
270
271
272 IF(OBJECT_ID('tempdb..#StatementsToRun4FRKVersionCheck') IS NOT NULL)
273 BEGIN
274 EXEC sp_executesql N'DROP TABLE #StatementsToRun4FRKVersionCheck;';
275 END;
276
277
278 -- This one will contain the statements to be executed
279 -- order: 1- Mandatory, 2- VersionCheckMode, 3- VersionCheck
280
281 CREATE TABLE #StatementsToRun4FRKVersionCheck (
282 StatementId INT IDENTITY(1,1),
283 CheckName VARCHAR(256),
284 SubjectName VARCHAR(256),
285 SubjectFullPath VARCHAR(1024),
286 StatementText NVARCHAR(MAX),
287 StatementOutputsCounter BIT,
288 OutputCounterExpectedValue INT,
289 StatementOutputsExecRet BIT,
290 StatementOutputsDateTime BIT
291 );
292
293 /* End of First Responder Kit consistency (temporary tables) */
294
295
296 /*
297 You can build your own table with a list of checks to skip. For example, you
298 might have some databases that you don't care about, or some checks you don't
299 want to run. Then, when you run sp_Blitz, you can specify these parameters:
300 @SkipChecksDatabase = 'DBAtools',
301 @SkipChecksSchema = 'dbo',
302 @SkipChecksTable = 'BlitzChecksToSkip'
303 Pass in the database, schema, and table that contains the list of checks you
304 want to skip. This part of the code checks those parameters, gets the list,
305 and then saves those in a temp table. As we run each check, we'll see if we
306 need to skip it.
307
308 Really anal-retentive users will note that the @SkipChecksServer parameter is
309 not used. YET. We added that parameter in so that we could avoid changing the
310 stored proc's surface area (interface) later.
311 */
312 /* --TOURSTOP07-- */
313 IF OBJECT_ID('tempdb..#SkipChecks') IS NOT NULL
314 DROP TABLE #SkipChecks;
315 CREATE TABLE #SkipChecks
316 (
317 DatabaseName NVARCHAR(128) ,
318 CheckID INT ,
319 ServerName NVARCHAR(128)
320 );
321 CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName);
322
323 IF @SkipChecksTable IS NOT NULL
324 AND @SkipChecksSchema IS NOT NULL
325 AND @SkipChecksDatabase IS NOT NULL
326 BEGIN
327
328 IF @Debug IN (1, 2) RAISERROR('Inserting SkipChecks', 0, 1) WITH NOWAIT;
329
330 SET @StringToExecute = 'INSERT INTO #SkipChecks(DatabaseName, CheckID, ServerName )
331 SELECT DISTINCT DatabaseName, CheckID, ServerName
332 FROM ' + QUOTENAME(@SkipChecksDatabase) + '.' + QUOTENAME(@SkipChecksSchema) + '.' + QUOTENAME(@SkipChecksTable)
333 + ' WHERE ServerName IS NULL OR ServerName = SERVERPROPERTY(''ServerName'') OPTION (RECOMPILE);';
334 EXEC(@StringToExecute);
335 END;
336
337 -- Flag for Windows OS to help with Linux support
338 IF EXISTS ( SELECT 1
339 FROM sys.all_objects
340 WHERE name = 'dm_os_host_info' )
341 BEGIN
342 SELECT @IsWindowsOperatingSystem = CASE WHEN host_platform = 'Windows' THEN 1 ELSE 0 END FROM sys.dm_os_host_info ;
343 END;
344 ELSE
345 BEGIN
346 SELECT @IsWindowsOperatingSystem = 1 ;
347 END;
348
349 IF NOT EXISTS ( SELECT 1
350 FROM #SkipChecks
351 WHERE DatabaseName IS NULL AND CheckID = 106 )
352 AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1
353 BEGIN
354
355 select @curr_tracefilename = [path] from sys.traces where is_default = 1 ;
356 set @curr_tracefilename = reverse(@curr_tracefilename);
357
358 -- Set the trace file path separator based on underlying OS
359 IF (@IsWindowsOperatingSystem = 1) AND @curr_tracefilename IS NOT NULL
360 BEGIN
361 select @indx = patindex('%\%', @curr_tracefilename) ;
362 set @curr_tracefilename = reverse(@curr_tracefilename) ;
363 set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '\log.trc' ;
364 END;
365 ELSE
366 BEGIN
367 select @indx = patindex('%/%', @curr_tracefilename) ;
368 set @curr_tracefilename = reverse(@curr_tracefilename) ;
369 set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '/log.trc' ;
370 END;
371
372 END;
373
374 /* If the server has any databases on Antiques Roadshow, skip the checks that would break due to CTEs. */
375 IF @CheckUserDatabaseObjects = 1 AND EXISTS(SELECT * FROM sys.databases WHERE compatibility_level < 90)
376 BEGIN
377 SET @CheckUserDatabaseObjects = 0;
378 PRINT 'Databases with compatibility level < 90 found, so setting @CheckUserDatabaseObjects = 0.';
379 PRINT 'The database-level checks rely on CTEs, which are not supported in SQL 2000 compat level databases.';
380 PRINT 'Get with the cool kids and switch to a current compatibility level, Grandpa. To find the problems, run:';
381 PRINT 'SELECT * FROM sys.databases WHERE compatibility_level < 90;';
382 INSERT INTO #BlitzResults
383 ( CheckID ,
384 Priority ,
385 FindingsGroup ,
386 Finding ,
387 URL ,
388 Details
389 )
390 SELECT 204 AS CheckID ,
391 0 AS Priority ,
392 'Informational' AS FindingsGroup ,
393 '@CheckUserDatabaseObjects Disabled' AS Finding ,
394 'https://www.BrentOzar.com/blitz/' AS URL ,
395 'Since you have databases with compatibility_level < 90, we can''t run @CheckUserDatabaseObjects = 1. To find them: SELECT * FROM sys.databases WHERE compatibility_level < 90' AS Details;
396 END;
397
398 /* --TOURSTOP08-- */
399 /* If the server is Amazon RDS, skip checks that it doesn't allow */
400 IF LEFT(CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(8000)), 8) = 'EC2AMAZ-'
401 AND LEFT(CAST(SERVERPROPERTY('MachineName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-'
402 AND LEFT(CAST(SERVERPROPERTY('ServerName') AS VARCHAR(8000)), 8) = 'EC2AMAZ-'
403 AND db_id('rdsadmin') IS NOT NULL
404 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'))
405 BEGIN
406 INSERT INTO #SkipChecks (CheckID) VALUES (6);
407 INSERT INTO #SkipChecks (CheckID) VALUES (29);
408 INSERT INTO #SkipChecks (CheckID) VALUES (30);
409 INSERT INTO #SkipChecks (CheckID) VALUES (31);
410 INSERT INTO #SkipChecks (CheckID) VALUES (40); /* TempDB only has one data file */
411 INSERT INTO #SkipChecks (CheckID) VALUES (57);
412 INSERT INTO #SkipChecks (CheckID) VALUES (59);
413 INSERT INTO #SkipChecks (CheckID) VALUES (61);
414 INSERT INTO #SkipChecks (CheckID) VALUES (62);
415 INSERT INTO #SkipChecks (CheckID) VALUES (68);
416 INSERT INTO #SkipChecks (CheckID) VALUES (69);
417 INSERT INTO #SkipChecks (CheckID) VALUES (73);
418 INSERT INTO #SkipChecks (CheckID) VALUES (79);
419 INSERT INTO #SkipChecks (CheckID) VALUES (92);
420 INSERT INTO #SkipChecks (CheckID) VALUES (94);
421 INSERT INTO #SkipChecks (CheckID) VALUES (96);
422 INSERT INTO #SkipChecks (CheckID) VALUES (98);
423 INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled */
424 INSERT INTO #SkipChecks (CheckID) VALUES (123);
425 INSERT INTO #SkipChecks (CheckID) VALUES (177);
426 INSERT INTO #SkipChecks (CheckID) VALUES (180); /* 180/181 are maintenance plans */
427 INSERT INTO #SkipChecks (CheckID) VALUES (181);
428 INSERT INTO #SkipChecks (CheckID) VALUES (184); /* xp_readerrorlog checking for IFI */
429 INSERT INTO #SkipChecks (CheckID) VALUES (211); /* xp_regread checking for power saving */
430 INSERT INTO #SkipChecks (CheckID) VALUES (212); /* xp_regread */
431 INSERT INTO #SkipChecks (CheckID) VALUES (219);
432 INSERT INTO #BlitzResults
433 ( CheckID ,
434 Priority ,
435 FindingsGroup ,
436 Finding ,
437 URL ,
438 Details
439 )
440 SELECT 223 AS CheckID ,
441 0 AS Priority ,
442 'Informational' AS FindingsGroup ,
443 'Some Checks Skipped' AS Finding ,
444 'https://aws.amazon.com/rds/sqlserver/' AS URL ,
445 'Amazon RDS detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details;
446 END; /* Amazon RDS skipped checks */
447
448 /* If the server is ExpressEdition, skip checks that it doesn't allow */
449 IF CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%'
450 BEGIN
451 INSERT INTO #SkipChecks (CheckID) VALUES (30); /* Alerts not configured */
452 INSERT INTO #SkipChecks (CheckID) VALUES (31); /* Operators not configured */
453 INSERT INTO #SkipChecks (CheckID) VALUES (61); /* Agent alerts 19-25 */
454 INSERT INTO #SkipChecks (CheckID) VALUES (73); /* Failsafe operator */
455 INSERT INTO #SkipChecks (CheckID) VALUES (96); /* Agent alerts for corruption */
456 INSERT INTO #BlitzResults
457 ( CheckID ,
458 Priority ,
459 FindingsGroup ,
460 Finding ,
461 URL ,
462 Details
463 )
464 SELECT 223 AS CheckID ,
465 0 AS Priority ,
466 'Informational' AS FindingsGroup ,
467 'Some Checks Skipped' AS Finding ,
468 'https://stackoverflow.com/questions/1169634/limitations-of-sql-server-express' AS URL ,
469 'Express Edition detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details;
470 END; /* Express Edition skipped checks */
471
472 /* If the server is an Azure Managed Instance, skip checks that it doesn't allow */
473 IF SERVERPROPERTY('EngineEdition') = 8
474 BEGIN
475 INSERT INTO #SkipChecks (CheckID) VALUES (1); /* Full backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */
476 INSERT INTO #SkipChecks (CheckID) VALUES (2); /* Log backups - because of the MI GUID name bug mentioned here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */
477 INSERT INTO #SkipChecks (CheckID) VALUES (6); /* Security - Jobs Owned By Users per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
478 INSERT INTO #SkipChecks (CheckID) VALUES (21); /* Informational - Database Encrypted per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
479 INSERT INTO #SkipChecks (CheckID) VALUES (24); /* File Configuration - System Database on C Drive per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
480 INSERT INTO #SkipChecks (CheckID) VALUES (50); /* Max Server Memory Set Too High - because they max it out */
481 INSERT INTO #SkipChecks (CheckID) VALUES (55); /* Security - Database Owner <> sa per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
482 INSERT INTO #SkipChecks (CheckID) VALUES (74); /* TraceFlag On - because Azure Managed Instances go wild and crazy with the trace flags */
483 INSERT INTO #SkipChecks (CheckID) VALUES (97); /* Unusual SQL Server Edition */
484 INSERT INTO #SkipChecks (CheckID) VALUES (100); /* Remote DAC disabled - but it's working anyway, details here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */
485 INSERT INTO #SkipChecks (CheckID) VALUES (186); /* MSDB Backup History Purged Too Frequently */
486 INSERT INTO #SkipChecks (CheckID) VALUES (199); /* Default trace, details here: https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1481 */
487 INSERT INTO #SkipChecks (CheckID) VALUES (211); /*Power Plan */
488 INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'master'); /* Max file size set */
489 INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'model'); /* Max file size set */
490 INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'msdb'); /* Max file size set */
491 INSERT INTO #SkipChecks (CheckID, DatabaseName) VALUES (80, 'tempdb'); /* Max file size set */
492 INSERT INTO #BlitzResults
493 ( CheckID ,
494 Priority ,
495 FindingsGroup ,
496 Finding ,
497 URL ,
498 Details
499 )
500 SELECT 223 AS CheckID ,
501 0 AS Priority ,
502 'Informational' AS FindingsGroup ,
503 'Some Checks Skipped' AS Finding ,
504 'https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-index' AS URL ,
505 'Managed Instance detected, so we skipped some checks that are not currently possible, relevant, or practical there.' AS Details;
506 END; /* Azure Managed Instance skipped checks */
507
508 /*
509 That's the end of the SkipChecks stuff.
510 The next several tables are used by various checks later.
511 */
512 IF OBJECT_ID('tempdb..#ConfigurationDefaults') IS NOT NULL
513 DROP TABLE #ConfigurationDefaults;
514 CREATE TABLE #ConfigurationDefaults
515 (
516 name NVARCHAR(128) ,
517 DefaultValue BIGINT,
518 CheckID INT
519 );
520
521 IF OBJECT_ID ('tempdb..#Recompile') IS NOT NULL
522 DROP TABLE #Recompile;
523 CREATE TABLE #Recompile(
524 DBName varchar(200),
525 ProcName varchar(300),
526 RecompileFlag varchar(1),
527 SPSchema varchar(50)
528 );
529
530 IF OBJECT_ID('tempdb..#DatabaseDefaults') IS NOT NULL
531 DROP TABLE #DatabaseDefaults;
532 CREATE TABLE #DatabaseDefaults
533 (
534 name NVARCHAR(128) ,
535 DefaultValue NVARCHAR(200),
536 CheckID INT,
537 Priority INT,
538 Finding VARCHAR(200),
539 URL VARCHAR(200),
540 Details NVARCHAR(4000)
541 );
542
543 IF OBJECT_ID('tempdb..#DatabaseScopedConfigurationDefaults') IS NOT NULL
544 DROP TABLE #DatabaseScopedConfigurationDefaults;
545 CREATE TABLE #DatabaseScopedConfigurationDefaults
546 (ID INT IDENTITY(1,1), configuration_id INT, [name] NVARCHAR(60), default_value sql_variant, default_value_for_secondary sql_variant, CheckID INT, );
547
548 IF OBJECT_ID('tempdb..#DBCCs') IS NOT NULL
549 DROP TABLE #DBCCs;
550 CREATE TABLE #DBCCs
551 (
552 ID INT IDENTITY(1, 1)
553 PRIMARY KEY ,
554 ParentObject VARCHAR(255) ,
555 Object VARCHAR(255) ,
556 Field VARCHAR(255) ,
557 Value VARCHAR(255) ,
558 DbName NVARCHAR(128) NULL
559 );
560
561 IF OBJECT_ID('tempdb..#LogInfo2012') IS NOT NULL
562 DROP TABLE #LogInfo2012;
563 CREATE TABLE #LogInfo2012
564 (
565 recoveryunitid INT ,
566 FileID SMALLINT ,
567 FileSize BIGINT ,
568 StartOffset BIGINT ,
569 FSeqNo BIGINT ,
570 [Status] TINYINT ,
571 Parity TINYINT ,
572 CreateLSN NUMERIC(38)
573 );
574
575 IF OBJECT_ID('tempdb..#LogInfo') IS NOT NULL
576 DROP TABLE #LogInfo;
577 CREATE TABLE #LogInfo
578 (
579 FileID SMALLINT ,
580 FileSize BIGINT ,
581 StartOffset BIGINT ,
582 FSeqNo BIGINT ,
583 [Status] TINYINT ,
584 Parity TINYINT ,
585 CreateLSN NUMERIC(38)
586 );
587
588 IF OBJECT_ID('tempdb..#partdb') IS NOT NULL
589 DROP TABLE #partdb;
590 CREATE TABLE #partdb
591 (
592 dbname NVARCHAR(128) ,
593 objectname NVARCHAR(200) ,
594 type_desc NVARCHAR(128)
595 );
596
597 IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL
598 DROP TABLE #TraceStatus;
599 CREATE TABLE #TraceStatus
600 (
601 TraceFlag VARCHAR(10) ,
602 status BIT ,
603 Global BIT ,
604 Session BIT
605 );
606
607 IF OBJECT_ID('tempdb..#driveInfo') IS NOT NULL
608 DROP TABLE #driveInfo;
609 CREATE TABLE #driveInfo
610 (
611 drive NVARCHAR ,
612 SIZE DECIMAL(18, 2)
613 );
614
615 IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL
616 DROP TABLE #dm_exec_query_stats;
617 CREATE TABLE #dm_exec_query_stats
618 (
619 [id] [int] NOT NULL
620 IDENTITY(1, 1) ,
621 [sql_handle] [varbinary](64) NOT NULL ,
622 [statement_start_offset] [int] NOT NULL ,
623 [statement_end_offset] [int] NOT NULL ,
624 [plan_generation_num] [bigint] NOT NULL ,
625 [plan_handle] [varbinary](64) NOT NULL ,
626 [creation_time] [datetime] NOT NULL ,
627 [last_execution_time] [datetime] NOT NULL ,
628 [execution_count] [bigint] NOT NULL ,
629 [total_worker_time] [bigint] NOT NULL ,
630 [last_worker_time] [bigint] NOT NULL ,
631 [min_worker_time] [bigint] NOT NULL ,
632 [max_worker_time] [bigint] NOT NULL ,
633 [total_physical_reads] [bigint] NOT NULL ,
634 [last_physical_reads] [bigint] NOT NULL ,
635 [min_physical_reads] [bigint] NOT NULL ,
636 [max_physical_reads] [bigint] NOT NULL ,
637 [total_logical_writes] [bigint] NOT NULL ,
638 [last_logical_writes] [bigint] NOT NULL ,
639 [min_logical_writes] [bigint] NOT NULL ,
640 [max_logical_writes] [bigint] NOT NULL ,
641 [total_logical_reads] [bigint] NOT NULL ,
642 [last_logical_reads] [bigint] NOT NULL ,
643 [min_logical_reads] [bigint] NOT NULL ,
644 [max_logical_reads] [bigint] NOT NULL ,
645 [total_clr_time] [bigint] NOT NULL ,
646 [last_clr_time] [bigint] NOT NULL ,
647 [min_clr_time] [bigint] NOT NULL ,
648 [max_clr_time] [bigint] NOT NULL ,
649 [total_elapsed_time] [bigint] NOT NULL ,
650 [last_elapsed_time] [bigint] NOT NULL ,
651 [min_elapsed_time] [bigint] NOT NULL ,
652 [max_elapsed_time] [bigint] NOT NULL ,
653 [query_hash] [binary](8) NULL ,
654 [query_plan_hash] [binary](8) NULL ,
655 [query_plan] [xml] NULL ,
656 [query_plan_filtered] [nvarchar](MAX) NULL ,
657 [text] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS
658 NULL ,
659 [text_filtered] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS
660 NULL
661 );
662
663 IF OBJECT_ID('tempdb..#ErrorLog') IS NOT NULL
664 DROP TABLE #ErrorLog;
665 CREATE TABLE #ErrorLog
666 (
667 LogDate DATETIME ,
668 ProcessInfo NVARCHAR(20) ,
669 [Text] NVARCHAR(1000)
670 );
671
672 IF OBJECT_ID('tempdb..#fnTraceGettable') IS NOT NULL
673 DROP TABLE #fnTraceGettable;
674 CREATE TABLE #fnTraceGettable
675 (
676 TextData NVARCHAR(4000) ,
677 DatabaseName NVARCHAR(256) ,
678 EventClass INT ,
679 Severity INT ,
680 StartTime DATETIME ,
681 EndTime DATETIME ,
682 Duration BIGINT ,
683 NTUserName NVARCHAR(256) ,
684 NTDomainName NVARCHAR(256) ,
685 HostName NVARCHAR(256) ,
686 ApplicationName NVARCHAR(256) ,
687 LoginName NVARCHAR(256) ,
688 DBUserName NVARCHAR(256)
689 );
690
691 IF OBJECT_ID('tempdb..#Instances') IS NOT NULL
692 DROP TABLE #Instances;
693 CREATE TABLE #Instances
694 (
695 Instance_Number NVARCHAR(MAX) ,
696 Instance_Name NVARCHAR(MAX) ,
697 Data_Field NVARCHAR(MAX)
698 );
699
700 IF OBJECT_ID('tempdb..#IgnorableWaits') IS NOT NULL
701 DROP TABLE #IgnorableWaits;
702 CREATE TABLE #IgnorableWaits (wait_type NVARCHAR(60));
703 INSERT INTO #IgnorableWaits VALUES ('BROKER_EVENTHANDLER');
704 INSERT INTO #IgnorableWaits VALUES ('BROKER_RECEIVE_WAITFOR');
705 INSERT INTO #IgnorableWaits VALUES ('BROKER_TASK_STOP');
706 INSERT INTO #IgnorableWaits VALUES ('BROKER_TO_FLUSH');
707 INSERT INTO #IgnorableWaits VALUES ('BROKER_TRANSMITTER');
708 INSERT INTO #IgnorableWaits VALUES ('CHECKPOINT_QUEUE');
709 INSERT INTO #IgnorableWaits VALUES ('CLR_AUTO_EVENT');
710 INSERT INTO #IgnorableWaits VALUES ('CLR_MANUAL_EVENT');
711 INSERT INTO #IgnorableWaits VALUES ('CLR_SEMAPHORE');
712 INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_EVENT');
713 INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_DBM_MUTEX');
714 INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_EVENTS_QUEUE');
715 INSERT INTO #IgnorableWaits VALUES ('DBMIRROR_WORKER_QUEUE');
716 INSERT INTO #IgnorableWaits VALUES ('DBMIRRORING_CMD');
717 INSERT INTO #IgnorableWaits VALUES ('DIRTY_PAGE_POLL');
718 INSERT INTO #IgnorableWaits VALUES ('DISPATCHER_QUEUE_SEMAPHORE');
719 INSERT INTO #IgnorableWaits VALUES ('FT_IFTS_SCHEDULER_IDLE_WAIT');
720 INSERT INTO #IgnorableWaits VALUES ('FT_IFTSHC_MUTEX');
721 INSERT INTO #IgnorableWaits VALUES ('HADR_CLUSAPI_CALL');
722 INSERT INTO #IgnorableWaits VALUES ('HADR_FABRIC_CALLBACK');
723 INSERT INTO #IgnorableWaits VALUES ('HADR_FILESTREAM_IOMGR_IOCOMPLETION');
724 INSERT INTO #IgnorableWaits VALUES ('HADR_LOGCAPTURE_WAIT');
725 INSERT INTO #IgnorableWaits VALUES ('HADR_NOTIFICATION_DEQUEUE');
726 INSERT INTO #IgnorableWaits VALUES ('HADR_TIMER_TASK');
727 INSERT INTO #IgnorableWaits VALUES ('HADR_WORK_QUEUE');
728 INSERT INTO #IgnorableWaits VALUES ('LAZYWRITER_SLEEP');
729 INSERT INTO #IgnorableWaits VALUES ('LOGMGR_QUEUE');
730 INSERT INTO #IgnorableWaits VALUES ('ONDEMAND_TASK_QUEUE');
731 INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_DRAIN_WORKER');
732 INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_LOG_CACHE');
733 INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_TRAN_LIST');
734 INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_SYNC');
735 INSERT INTO #IgnorableWaits VALUES ('PARALLEL_REDO_WORKER_WAIT_WORK');
736 INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_HADR_LEASE_MECHANISM');
737 INSERT INTO #IgnorableWaits VALUES ('PREEMPTIVE_SP_SERVER_DIAGNOSTICS');
738 INSERT INTO #IgnorableWaits VALUES ('QDS_ASYNC_QUEUE');
739 INSERT INTO #IgnorableWaits VALUES ('QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP');
740 INSERT INTO #IgnorableWaits VALUES ('QDS_PERSIST_TASK_MAIN_LOOP_SLEEP');
741 INSERT INTO #IgnorableWaits VALUES ('QDS_SHUTDOWN_QUEUE');
742 INSERT INTO #IgnorableWaits VALUES ('REDO_THREAD_PENDING_WORK');
743 INSERT INTO #IgnorableWaits VALUES ('REQUEST_FOR_DEADLOCK_SEARCH');
744 INSERT INTO #IgnorableWaits VALUES ('SLEEP_SYSTEMTASK');
745 INSERT INTO #IgnorableWaits VALUES ('SLEEP_TASK');
746 INSERT INTO #IgnorableWaits VALUES ('SOS_WORK_DISPATCHER');
747 INSERT INTO #IgnorableWaits VALUES ('SP_SERVER_DIAGNOSTICS_SLEEP');
748 INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_BUFFER_FLUSH');
749 INSERT INTO #IgnorableWaits VALUES ('SQLTRACE_INCREMENTAL_FLUSH_SLEEP');
750 INSERT INTO #IgnorableWaits VALUES ('UCS_SESSION_REGISTRATION');
751 INSERT INTO #IgnorableWaits VALUES ('WAIT_XTP_OFFLINE_CKPT_NEW_LOG');
752 INSERT INTO #IgnorableWaits VALUES ('WAITFOR');
753 INSERT INTO #IgnorableWaits VALUES ('XE_DISPATCHER_WAIT');
754 INSERT INTO #IgnorableWaits VALUES ('XE_LIVE_TARGET_TVF');
755 INSERT INTO #IgnorableWaits VALUES ('XE_TIMER_EVENT');
756
757 IF @Debug IN (1, 2) RAISERROR('Setting @MsSinceWaitsCleared', 0, 1) WITH NOWAIT;
758
759 SELECT @MsSinceWaitsCleared = DATEDIFF(MINUTE, create_date, CURRENT_TIMESTAMP) * 60000.0
760 FROM sys.databases
761 WHERE name = 'tempdb';
762
763 /* Have they cleared wait stats? Using a 10% fudge factor */
764 IF @MsSinceWaitsCleared * .9 > (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE'))
765 BEGIN
766
767 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 185) WITH NOWAIT;
768
769 SET @MsSinceWaitsCleared = (SELECT MAX(wait_time_ms) FROM sys.dm_os_wait_stats WHERE wait_type IN ('SP_SERVER_DIAGNOSTICS_SLEEP', 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', 'REQUEST_FOR_DEADLOCK_SEARCH', 'HADR_FILESTREAM_IOMGR_IOCOMPLETION', 'LAZYWRITER_SLEEP', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', 'DIRTY_PAGE_POLL', 'LOGMGR_QUEUE'));
770 IF @MsSinceWaitsCleared = 0 SET @MsSinceWaitsCleared = 1;
771 INSERT INTO #BlitzResults
772 ( CheckID ,
773 Priority ,
774 FindingsGroup ,
775 Finding ,
776 URL ,
777 Details
778 )
779 VALUES( 185,
780 240,
781 'Wait Stats',
782 'Wait Stats Have Been Cleared',
783 'https://BrentOzar.com/go/waits',
784 'Someone ran DBCC SQLPERF to clear sys.dm_os_wait_stats at approximately: '
785 + CONVERT(NVARCHAR(100),
786 DATEADD(MINUTE, (-1. * (@MsSinceWaitsCleared) / 1000. / 60.), GETDATE()), 120));
787 END;
788
789 /* @CpuMsSinceWaitsCleared is used for waits stats calculations */
790
791 IF @Debug IN (1, 2) RAISERROR('Setting @CpuMsSinceWaitsCleared', 0, 1) WITH NOWAIT;
792
793 SELECT @CpuMsSinceWaitsCleared = @MsSinceWaitsCleared * scheduler_count
794 FROM sys.dm_os_sys_info;
795
796 /* If we're outputting CSV or Markdown, don't bother checking the plan cache because we cannot export plans. */
797 IF @OutputType = 'CSV' OR @OutputType = 'MARKDOWN'
798 SET @CheckProcedureCache = 0;
799
800 /* If we're posting a question on Stack, include background info on the server */
801 IF @OutputType = 'MARKDOWN'
802 SET @CheckServerInfo = 1;
803
804 /* Only run CheckUserDatabaseObjects if there are less than 50 databases. */
805 IF @BringThePain = 0 AND 50 <= (SELECT COUNT(*) FROM sys.databases) AND @CheckUserDatabaseObjects = 1
806 BEGIN
807 SET @CheckUserDatabaseObjects = 0;
808 PRINT 'Running sp_Blitz @CheckUserDatabaseObjects = 1 on a server with 50+ databases may cause temporary insanity for the server and/or user.';
809 PRINT 'If you''re sure you want to do this, run again with the parameter @BringThePain = 1.';
810 INSERT INTO #BlitzResults
811 ( CheckID ,
812 Priority ,
813 FindingsGroup ,
814 Finding ,
815 URL ,
816 Details
817 )
818 SELECT 201 AS CheckID ,
819 0 AS Priority ,
820 'Informational' AS FindingsGroup ,
821 '@CheckUserDatabaseObjects Disabled' AS Finding ,
822 'https://www.BrentOzar.com/blitz/' AS URL ,
823 'If you want to check 50+ databases, you have to also use @BringThePain = 1.' AS Details;
824 END;
825
826 /* Sanitize our inputs */
827 SELECT
828 @OutputServerName = QUOTENAME(@OutputServerName),
829 @OutputDatabaseName = QUOTENAME(@OutputDatabaseName),
830 @OutputSchemaName = QUOTENAME(@OutputSchemaName),
831 @OutputTableName = QUOTENAME(@OutputTableName);
832
833 /* Get the major and minor build numbers */
834
835 IF @Debug IN (1, 2) RAISERROR('Getting version information.', 0, 1) WITH NOWAIT;
836
837 SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128));
838 SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ),
839 @ProductVersionMinor = PARSENAME(CONVERT(varchar(32), @ProductVersion), 2);
840
841 /*
842 Whew! we're finally done with the setup, and we can start doing checks.
843 First, let's make sure we're actually supposed to do checks on this server.
844 The user could have passed in a SkipChecks table that specified to skip ALL
845 checks on this server, so let's check for that:
846 */
847 IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName
848 FROM #SkipChecks
849 WHERE DatabaseName IS NULL
850 AND CheckID IS NULL ) )
851 OR ( @SkipChecksTable IS NULL )
852 )
853 BEGIN
854
855 /*
856 Our very first check! We'll put more comments in this one just to
857 explain exactly how it works. First, we check to see if we're
858 supposed to skip CheckID 1 (that's the check we're working on.)
859 */
860 IF NOT EXISTS ( SELECT 1
861 FROM #SkipChecks
862 WHERE DatabaseName IS NULL AND CheckID = 1 )
863 BEGIN
864
865 /*
866 Below, we check master.sys.databases looking for databases
867 that haven't had a backup in the last week. If we find any,
868 we insert them into #BlitzResults, the temp table that
869 tracks our server's problems. Note that if the check does
870 NOT find any problems, we don't save that. We're only
871 saving the problems, not the successful checks.
872 */
873
874 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 1) WITH NOWAIT;
875
876 IF SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances need a special query */
877 BEGIN
878 INSERT INTO #BlitzResults
879 ( CheckID ,
880 DatabaseName ,
881 Priority ,
882 FindingsGroup ,
883 Finding ,
884 URL ,
885 Details
886 )
887 SELECT 1 AS CheckID ,
888 d.[name] AS DatabaseName ,
889 1 AS Priority ,
890 'Backup' AS FindingsGroup ,
891 'Backups Not Performed Recently' AS Finding ,
892 'https://BrentOzar.com/go/nobak' AS URL ,
893 'Last backed up: '
894 + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details
895 FROM master.sys.databases d
896 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
897 AND b.type = 'D'
898 AND b.server_name = SERVERPROPERTY('ServerName') /*Backupset ran on current server */
899 WHERE d.database_id <> 2 /* Bonus points if you know what that means */
900 AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */
901 AND d.is_in_standby = 0 /* Not a log shipping target database */
902 AND d.source_database_id IS NULL /* Excludes database snapshots */
903 AND d.name NOT IN ( SELECT DISTINCT
904 DatabaseName
905 FROM #SkipChecks
906 WHERE CheckID IS NULL OR CheckID = 1)
907 /*
908 The above NOT IN filters out the databases we're not supposed to check.
909 */
910 GROUP BY d.name
911 HAVING MAX(b.backup_finish_date) <= DATEADD(dd,
912 -7, GETDATE())
913 OR MAX(b.backup_finish_date) IS NULL;
914 END;
915
916 ELSE /* SERVERPROPERTY('EngineName') must be 8, Azure Managed Instances */
917 BEGIN
918 INSERT INTO #BlitzResults
919 ( CheckID ,
920 DatabaseName ,
921 Priority ,
922 FindingsGroup ,
923 Finding ,
924 URL ,
925 Details
926 )
927 SELECT 1 AS CheckID ,
928 d.[name] AS DatabaseName ,
929 1 AS Priority ,
930 'Backup' AS FindingsGroup ,
931 'Backups Not Performed Recently' AS Finding ,
932 'https://BrentOzar.com/go/nobak' AS URL ,
933 'Last backed up: '
934 + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details
935 FROM master.sys.databases d
936 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
937 AND b.type = 'D'
938 WHERE d.database_id <> 2 /* Bonus points if you know what that means */
939 AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */
940 AND d.is_in_standby = 0 /* Not a log shipping target database */
941 AND d.source_database_id IS NULL /* Excludes database snapshots */
942 AND d.name NOT IN ( SELECT DISTINCT
943 DatabaseName
944 FROM #SkipChecks
945 WHERE CheckID IS NULL OR CheckID = 1)
946 /*
947 The above NOT IN filters out the databases we're not supposed to check.
948 */
949 GROUP BY d.name
950 HAVING MAX(b.backup_finish_date) <= DATEADD(dd,
951 -7, GETDATE())
952 OR MAX(b.backup_finish_date) IS NULL;
953 END;
954
955
956
957 /*
958 And there you have it. The rest of this stored procedure works the same
959 way: it asks:
960 - Should I skip this check?
961 - If not, do I find problems?
962 - Insert the results into #BlitzResults
963 */
964
965 END;
966
967 /*
968 And that's the end of CheckID #1.
969
970 CheckID #2 is a little simpler because it only involves one query, and it's
971 more typical for queries that people contribute. But keep reading, because
972 the next check gets more complex again.
973 */
974
975 IF NOT EXISTS ( SELECT 1
976 FROM #SkipChecks
977 WHERE DatabaseName IS NULL AND CheckID = 2 )
978 BEGIN
979
980 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 2) WITH NOWAIT;
981
982 INSERT INTO #BlitzResults
983 ( CheckID ,
984 DatabaseName ,
985 Priority ,
986 FindingsGroup ,
987 Finding ,
988 URL ,
989 Details
990 )
991 SELECT DISTINCT
992 2 AS CheckID ,
993 d.name AS DatabaseName ,
994 1 AS Priority ,
995 'Backup' AS FindingsGroup ,
996 'Full Recovery Model w/o Log Backups' AS Finding ,
997 'https://BrentOzar.com/go/biglogs' AS URL ,
998 ( '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
999 FROM master.sys.databases d
1000 WHERE d.recovery_model IN ( 1, 2 )
1001 AND d.database_id NOT IN ( 2, 3 )
1002 AND d.source_database_id IS NULL
1003 AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */
1004 AND d.is_in_standby = 0 /* Not a log shipping target database */
1005 AND d.source_database_id IS NULL /* Excludes database snapshots */
1006 AND d.name NOT IN ( SELECT DISTINCT
1007 DatabaseName
1008 FROM #SkipChecks
1009 WHERE CheckID IS NULL OR CheckID = 2)
1010 AND NOT EXISTS ( SELECT *
1011 FROM msdb.dbo.backupset b
1012 WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS
1013 AND b.type = 'L'
1014 AND b.backup_finish_date >= DATEADD(dd,
1015 -7, GETDATE()) );
1016 END;
1017
1018 /*
1019 Next up, we've got CheckID 8. (These don't have to go in order.) This one
1020 won't work on SQL Server 2005 because it relies on a new DMV that didn't
1021 exist prior to SQL Server 2008. This means we have to check the SQL Server
1022 version first, then build a dynamic string with the query we want to run:
1023 */
1024
1025 IF NOT EXISTS ( SELECT 1
1026 FROM #SkipChecks
1027 WHERE DatabaseName IS NULL AND CheckID = 8 )
1028 BEGIN
1029 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
1030 AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
1031 BEGIN
1032
1033 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 8) WITH NOWAIT;
1034
1035 SET @StringToExecute = 'INSERT INTO #BlitzResults
1036 (CheckID, Priority,
1037 FindingsGroup,
1038 Finding, URL,
1039 Details)
1040 SELECT 8 AS CheckID,
1041 230 AS Priority,
1042 ''Security'' AS FindingsGroup,
1043 ''Server Audits Running'' AS Finding,
1044 ''https://BrentOzar.com/go/audits'' AS URL,
1045 (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status OPTION (RECOMPILE);';
1046
1047 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
1048 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
1049
1050 EXECUTE(@StringToExecute);
1051 END;
1052 END;
1053
1054 /*
1055 But what if you need to run a query in every individual database?
1056 Hop down to the @CheckUserDatabaseObjects section.
1057
1058 And that's the basic idea! You can read through the rest of the
1059 checks if you like - some more exciting stuff happens closer to the
1060 end of the stored proc, where we start doing things like checking
1061 the plan cache, but those aren't as cleanly commented.
1062
1063 If you'd like to contribute your own check, use one of the check
1064 formats shown above and email it to Help@BrentOzar.com. You don't
1065 have to pick a CheckID or a link - we'll take care of that when we
1066 test and publish the code. Thanks!
1067 */
1068
1069 IF NOT EXISTS ( SELECT 1
1070 FROM #SkipChecks
1071 WHERE DatabaseName IS NULL AND CheckID = 93 )
1072 BEGIN
1073
1074 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 93) WITH NOWAIT;
1075
1076 INSERT INTO #BlitzResults
1077 ( CheckID ,
1078 Priority ,
1079 FindingsGroup ,
1080 Finding ,
1081 URL ,
1082 Details
1083 )
1084 SELECT
1085 93 AS CheckID ,
1086 1 AS Priority ,
1087 'Backup' AS FindingsGroup ,
1088 'Backing Up to Same Drive Where Databases Reside' AS Finding ,
1089 'https://BrentOzar.com/go/backup' AS URL ,
1090 CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive '
1091 + UPPER(LEFT(bmf.physical_device_name, 3))
1092 + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details
1093 FROM msdb.dbo.backupmediafamily AS bmf
1094 INNER JOIN msdb.dbo.backupset AS bs ON bmf.media_set_id = bs.media_set_id
1095 AND bs.backup_start_date >= ( DATEADD(dd,
1096 -14, GETDATE()) )
1097 /* Filter out databases that were recently restored: */
1098 LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name AND rh.restore_date > DATEADD(dd, -14, GETDATE())
1099 WHERE UPPER(LEFT(bmf.physical_device_name, 3)) <> 'HTT' AND
1100 bmf.physical_device_name NOT LIKE '\\%' AND -- GitHub Issue #2141
1101 @IsWindowsOperatingSystem = 1 AND -- GitHub Issue #1995
1102 UPPER(LEFT(bmf.physical_device_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) IN (
1103 SELECT DISTINCT
1104 UPPER(LEFT(mf.physical_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3))
1105 FROM sys.master_files AS mf
1106 WHERE mf.database_id <> 2 )
1107 AND rh.destination_database_name IS NULL
1108 GROUP BY UPPER(LEFT(bmf.physical_device_name, 3));
1109 END;
1110
1111 IF NOT EXISTS ( SELECT 1
1112 FROM #SkipChecks
1113 WHERE DatabaseName IS NULL AND CheckID = 119 )
1114 AND EXISTS ( SELECT *
1115 FROM sys.all_objects o
1116 WHERE o.name = 'dm_database_encryption_keys' )
1117 BEGIN
1118
1119 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 119) WITH NOWAIT;
1120
1121 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, DatabaseName, URL, Details)
1122 SELECT 119 AS CheckID,
1123 1 AS Priority,
1124 ''Backup'' AS FindingsGroup,
1125 ''TDE Certificate Not Backed Up Recently'' AS Finding,
1126 db_name(dek.database_id) AS DatabaseName,
1127 ''https://BrentOzar.com/go/tde'' AS URL,
1128 ''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
1129 FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint
1130 WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);';
1131
1132 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
1133 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
1134
1135 EXECUTE(@StringToExecute);
1136 END;
1137
1138 IF NOT EXISTS ( SELECT 1
1139 FROM #SkipChecks
1140 WHERE DatabaseName IS NULL AND CheckID = 202 )
1141 AND EXISTS ( SELECT *
1142 FROM sys.all_columns c
1143 WHERE c.name = 'pvt_key_last_backup_date' )
1144 AND EXISTS ( SELECT *
1145 FROM msdb.INFORMATION_SCHEMA.COLUMNS c
1146 WHERE c.TABLE_NAME = 'backupset' AND c.COLUMN_NAME = 'encryptor_thumbprint' )
1147 BEGIN
1148
1149 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 202) WITH NOWAIT;
1150
1151 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
1152 SELECT DISTINCT 202 AS CheckID,
1153 1 AS Priority,
1154 ''Backup'' AS FindingsGroup,
1155 ''Encryption Certificate Not Backed Up Recently'' AS Finding,
1156 ''https://BrentOzar.com/go/tde'' AS URL,
1157 ''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
1158 FROM sys.certificates c
1159 INNER JOIN msdb.dbo.backupset bs ON c.thumbprint = bs.encryptor_thumbprint
1160 WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);';
1161
1162 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
1163 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
1164
1165 EXECUTE(@StringToExecute);
1166 END;
1167
1168 IF NOT EXISTS ( SELECT 1
1169 FROM #SkipChecks
1170 WHERE DatabaseName IS NULL AND CheckID = 3 )
1171 BEGIN
1172 IF DATEADD(dd, -60, GETDATE()) > (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date)
1173
1174 BEGIN
1175
1176 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 3) WITH NOWAIT;
1177
1178 INSERT INTO #BlitzResults
1179 ( CheckID ,
1180 DatabaseName ,
1181 Priority ,
1182 FindingsGroup ,
1183 Finding ,
1184 URL ,
1185 Details
1186 )
1187 SELECT TOP 1
1188 3 AS CheckID ,
1189 'msdb' ,
1190 200 AS Priority ,
1191 'Backup' AS FindingsGroup ,
1192 'MSDB Backup History Not Purged' AS Finding ,
1193 'https://BrentOzar.com/go/history' AS URL ,
1194 ( 'Database backup history retained back to '
1195 + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details
1196 FROM msdb.dbo.backupset bs
1197 LEFT OUTER JOIN msdb.dbo.restorehistory rh ON bs.database_name = rh.destination_database_name
1198 WHERE rh.destination_database_name IS NULL
1199 ORDER BY bs.backup_start_date ASC;
1200 END;
1201 END;
1202
1203 IF NOT EXISTS ( SELECT 1
1204 FROM #SkipChecks
1205 WHERE DatabaseName IS NULL AND CheckID = 186 )
1206 BEGIN
1207 IF DATEADD(dd, -2, GETDATE()) < (SELECT TOP 1 backup_start_date FROM msdb.dbo.backupset ORDER BY backup_start_date)
1208
1209 BEGIN
1210
1211 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 186) WITH NOWAIT;
1212
1213 INSERT INTO #BlitzResults
1214 ( CheckID ,
1215 DatabaseName ,
1216 Priority ,
1217 FindingsGroup ,
1218 Finding ,
1219 URL ,
1220 Details
1221 )
1222 SELECT TOP 1
1223 186 AS CheckID ,
1224 'msdb' ,
1225 200 AS Priority ,
1226 'Backup' AS FindingsGroup ,
1227 'MSDB Backup History Purged Too Frequently' AS Finding ,
1228 'https://BrentOzar.com/go/history' AS URL ,
1229 ( 'Database backup history only retained back to '
1230 + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details
1231 FROM msdb.dbo.backupset bs
1232 ORDER BY backup_start_date ASC;
1233 END;
1234 END;
1235
1236 IF NOT EXISTS ( SELECT 1
1237 FROM #SkipChecks
1238 WHERE DatabaseName IS NULL AND CheckID = 178 )
1239 AND EXISTS (SELECT *
1240 FROM msdb.dbo.backupset bs
1241 WHERE bs.type = 'D'
1242 AND bs.backup_size >= 50000000000 /* At least 50GB */
1243 AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */
1244 AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()) /* In the last 2 weeks */)
1245 BEGIN
1246
1247 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 178) WITH NOWAIT;
1248
1249 INSERT INTO #BlitzResults
1250 ( CheckID ,
1251 Priority ,
1252 FindingsGroup ,
1253 Finding ,
1254 URL ,
1255 Details
1256 )
1257 SELECT 178 AS CheckID ,
1258 200 AS Priority ,
1259 'Performance' AS FindingsGroup ,
1260 'Snapshot Backups Occurring' AS Finding ,
1261 'https://BrentOzar.com/go/snaps' AS URL ,
1262 ( CAST(COUNT(*) AS VARCHAR(20)) + ' snapshot-looking backups have occurred in the last two weeks, indicating that IO may be freezing up.') AS Details
1263 FROM msdb.dbo.backupset bs
1264 WHERE bs.type = 'D'
1265 AND bs.backup_size >= 50000000000 /* At least 50GB */
1266 AND DATEDIFF(SECOND, bs.backup_start_date, bs.backup_finish_date) <= 60 /* Backup took less than 60 seconds */
1267 AND bs.backup_finish_date >= DATEADD(DAY, -14, GETDATE()); /* In the last 2 weeks */
1268 END;
1269
1270 IF NOT EXISTS ( SELECT 1
1271 FROM #SkipChecks
1272 WHERE DatabaseName IS NULL AND CheckID = 4 )
1273 BEGIN
1274
1275 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 4) WITH NOWAIT;
1276
1277 INSERT INTO #BlitzResults
1278 ( CheckID ,
1279 Priority ,
1280 FindingsGroup ,
1281 Finding ,
1282 URL ,
1283 Details
1284 )
1285 SELECT 4 AS CheckID ,
1286 230 AS Priority ,
1287 'Security' AS FindingsGroup ,
1288 'Sysadmins' AS Finding ,
1289 'https://BrentOzar.com/go/sa' AS URL ,
1290 ( 'Login [' + l.name
1291 + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details
1292 FROM master.sys.syslogins l
1293 WHERE l.sysadmin = 1
1294 AND l.name <> SUSER_SNAME(0x01)
1295 AND l.denylogin = 0
1296 AND l.name NOT LIKE 'NT SERVICE\%'
1297 AND l.name <> 'l_certSignSmDetach'; /* Added in SQL 2016 */
1298 END;
1299
1300 IF NOT EXISTS ( SELECT 1
1301 FROM #SkipChecks
1302 WHERE DatabaseName IS NULL AND CheckID = 5 )
1303 BEGIN
1304
1305 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 5) WITH NOWAIT;
1306
1307 INSERT INTO #BlitzResults
1308 ( CheckID ,
1309 Priority ,
1310 FindingsGroup ,
1311 Finding ,
1312 URL ,
1313 Details
1314 )
1315 SELECT 5 AS CheckID ,
1316 230 AS Priority ,
1317 'Security' AS FindingsGroup ,
1318 'Security Admins' AS Finding ,
1319 'https://BrentOzar.com/go/sa' AS URL ,
1320 ( 'Login [' + l.name
1321 + '] 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
1322 FROM master.sys.syslogins l
1323 WHERE l.securityadmin = 1
1324 AND l.name <> SUSER_SNAME(0x01)
1325 AND l.denylogin = 0;
1326 END;
1327
1328 IF NOT EXISTS ( SELECT 1
1329 FROM #SkipChecks
1330 WHERE DatabaseName IS NULL AND CheckID = 104 )
1331 BEGIN
1332
1333 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 104) WITH NOWAIT;
1334
1335 INSERT INTO #BlitzResults
1336 ( [CheckID] ,
1337 [Priority] ,
1338 [FindingsGroup] ,
1339 [Finding] ,
1340 [URL] ,
1341 [Details]
1342 )
1343 SELECT 104 AS [CheckID] ,
1344 230 AS [Priority] ,
1345 'Security' AS [FindingsGroup] ,
1346 'Login Can Control Server' AS [Finding] ,
1347 'https://BrentOzar.com/go/sa' AS [URL] ,
1348 'Login [' + pri.[name]
1349 + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details]
1350 FROM sys.server_principals AS pri
1351 WHERE pri.[principal_id] IN (
1352 SELECT p.[grantee_principal_id]
1353 FROM sys.server_permissions AS p
1354 WHERE p.[state] IN ( 'G', 'W' )
1355 AND p.[class] = 100
1356 AND p.[type] = 'CL' )
1357 AND pri.[name] NOT LIKE '##%##';
1358 END;
1359
1360 IF NOT EXISTS ( SELECT 1
1361 FROM #SkipChecks
1362 WHERE DatabaseName IS NULL AND CheckID = 6 )
1363 BEGIN
1364
1365 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 6) WITH NOWAIT;
1366
1367 INSERT INTO #BlitzResults
1368 ( CheckID ,
1369 Priority ,
1370 FindingsGroup ,
1371 Finding ,
1372 URL ,
1373 Details
1374 )
1375 SELECT 6 AS CheckID ,
1376 230 AS Priority ,
1377 'Security' AS FindingsGroup ,
1378 'Jobs Owned By Users' AS Finding ,
1379 'https://BrentOzar.com/go/owners' AS URL ,
1380 ( 'Job [' + j.name + '] is owned by ['
1381 + SUSER_SNAME(j.owner_sid)
1382 + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details
1383 FROM msdb.dbo.sysjobs j
1384 WHERE j.enabled = 1
1385 AND SUSER_SNAME(j.owner_sid) <> SUSER_SNAME(0x01);
1386 END;
1387
1388 /* --TOURSTOP06-- */
1389 IF NOT EXISTS ( SELECT 1
1390 FROM #SkipChecks
1391 WHERE DatabaseName IS NULL AND CheckID = 7 )
1392 BEGIN
1393 /* --TOURSTOP02-- */
1394
1395 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 7) WITH NOWAIT;
1396
1397 INSERT INTO #BlitzResults
1398 ( CheckID ,
1399 Priority ,
1400 FindingsGroup ,
1401 Finding ,
1402 URL ,
1403 Details
1404 )
1405 SELECT 7 AS CheckID ,
1406 230 AS Priority ,
1407 'Security' AS FindingsGroup ,
1408 'Stored Procedure Runs at Startup' AS Finding ,
1409 'https://BrentOzar.com/go/startup' AS URL ,
1410 ( 'Stored procedure [master].['
1411 + r.SPECIFIC_SCHEMA + '].['
1412 + r.SPECIFIC_NAME
1413 + '] 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
1414 FROM master.INFORMATION_SCHEMA.ROUTINES r
1415 WHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME),
1416 'ExecIsStartup') = 1;
1417 END;
1418
1419 IF NOT EXISTS ( SELECT 1
1420 FROM #SkipChecks
1421 WHERE DatabaseName IS NULL AND CheckID = 10 )
1422 BEGIN
1423 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
1424 AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
1425 BEGIN
1426
1427 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 10) WITH NOWAIT;
1428
1429 SET @StringToExecute = 'INSERT INTO #BlitzResults
1430 (CheckID,
1431 Priority,
1432 FindingsGroup,
1433 Finding,
1434 URL,
1435 Details)
1436 SELECT 10 AS CheckID,
1437 100 AS Priority,
1438 ''Performance'' AS FindingsGroup,
1439 ''Resource Governor Enabled'' AS Finding,
1440 ''https://BrentOzar.com/go/rg'' AS URL,
1441 (''Resource Governor is enabled. Queries may be throttled. Make sure you understand how the Classifier Function is configured.'') AS Details FROM sys.resource_governor_configuration WHERE is_enabled = 1 OPTION (RECOMPILE);';
1442
1443 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
1444 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
1445
1446 EXECUTE(@StringToExecute);
1447 END;
1448 END;
1449
1450 IF NOT EXISTS ( SELECT 1
1451 FROM #SkipChecks
1452 WHERE DatabaseName IS NULL AND CheckID = 11 )
1453 BEGIN
1454 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
1455 BEGIN
1456
1457 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 11) WITH NOWAIT;
1458
1459 SET @StringToExecute = 'INSERT INTO #BlitzResults
1460 (CheckID,
1461 Priority,
1462 FindingsGroup,
1463 Finding,
1464 URL,
1465 Details)
1466 SELECT 11 AS CheckID,
1467 100 AS Priority,
1468 ''Performance'' AS FindingsGroup,
1469 ''Server Triggers Enabled'' AS Finding,
1470 ''https://BrentOzar.com/go/logontriggers/'' AS URL,
1471 (''Server Trigger ['' + [name] ++ ''] is enabled. Make sure you understand what that trigger is doing - the less work it does, the better.'') AS Details FROM sys.server_triggers WHERE is_disabled = 0 AND is_ms_shipped = 0 OPTION (RECOMPILE);';
1472
1473 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
1474 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
1475
1476 EXECUTE(@StringToExecute);
1477 END;
1478 END;
1479
1480 IF NOT EXISTS ( SELECT 1
1481 FROM #SkipChecks
1482 WHERE DatabaseName IS NULL AND CheckID = 12 )
1483 BEGIN
1484
1485 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 12) WITH NOWAIT;
1486
1487 INSERT INTO #BlitzResults
1488 ( CheckID ,
1489 DatabaseName ,
1490 Priority ,
1491 FindingsGroup ,
1492 Finding ,
1493 URL ,
1494 Details
1495 )
1496 SELECT 12 AS CheckID ,
1497 [name] AS DatabaseName ,
1498 10 AS Priority ,
1499 'Performance' AS FindingsGroup ,
1500 'Auto-Close Enabled' AS Finding ,
1501 'https://BrentOzar.com/go/autoclose' AS URL ,
1502 ( 'Database [' + [name]
1503 + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details
1504 FROM sys.databases
1505 WHERE is_auto_close_on = 1
1506 AND name NOT IN ( SELECT DISTINCT
1507 DatabaseName
1508 FROM #SkipChecks
1509 WHERE CheckID IS NULL OR CheckID = 12);
1510 END;
1511
1512 IF NOT EXISTS ( SELECT 1
1513 FROM #SkipChecks
1514 WHERE DatabaseName IS NULL AND CheckID = 13 )
1515 BEGIN
1516
1517 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 13) WITH NOWAIT;
1518
1519 INSERT INTO #BlitzResults
1520 ( CheckID ,
1521 DatabaseName ,
1522 Priority ,
1523 FindingsGroup ,
1524 Finding ,
1525 URL ,
1526 Details
1527 )
1528 SELECT 13 AS CheckID ,
1529 [name] AS DatabaseName ,
1530 10 AS Priority ,
1531 'Performance' AS FindingsGroup ,
1532 'Auto-Shrink Enabled' AS Finding ,
1533 'https://BrentOzar.com/go/autoshrink' AS URL ,
1534 ( 'Database [' + [name]
1535 + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details
1536 FROM sys.databases
1537 WHERE is_auto_shrink_on = 1
1538 AND name NOT IN ( SELECT DISTINCT
1539 DatabaseName
1540 FROM #SkipChecks
1541 WHERE CheckID IS NULL OR CheckID = 13);
1542 END;
1543
1544 IF NOT EXISTS ( SELECT 1
1545 FROM #SkipChecks
1546 WHERE DatabaseName IS NULL AND CheckID = 14 )
1547 BEGIN
1548 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
1549 BEGIN
1550
1551 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 14) WITH NOWAIT;
1552
1553 SET @StringToExecute = 'INSERT INTO #BlitzResults
1554 (CheckID,
1555 DatabaseName,
1556 Priority,
1557 FindingsGroup,
1558 Finding,
1559 URL,
1560 Details)
1561 SELECT 14 AS CheckID,
1562 [name] as DatabaseName,
1563 50 AS Priority,
1564 ''Reliability'' AS FindingsGroup,
1565 ''Page Verification Not Optimal'' AS Finding,
1566 ''https://BrentOzar.com/go/torn'' AS URL,
1567 (''Database ['' + [name] + ''] has '' + [page_verify_option_desc] + '' for page verification. SQL Server may have a harder time recognizing and recovering from storage corruption. Consider using CHECKSUM instead.'') COLLATE database_default AS Details
1568 FROM sys.databases
1569 WHERE page_verify_option < 2
1570 AND name <> ''tempdb''
1571 AND state <> 1 /* Restoring */
1572 and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 14) OPTION (RECOMPILE);';
1573
1574 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
1575 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
1576
1577 EXECUTE(@StringToExecute);
1578 END;
1579 END;
1580
1581 IF NOT EXISTS ( SELECT 1
1582 FROM #SkipChecks
1583 WHERE DatabaseName IS NULL AND CheckID = 15 )
1584 BEGIN
1585
1586 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 15) WITH NOWAIT;
1587
1588 INSERT INTO #BlitzResults
1589 ( CheckID ,
1590 DatabaseName ,
1591 Priority ,
1592 FindingsGroup ,
1593 Finding ,
1594 URL ,
1595 Details
1596 )
1597 SELECT 15 AS CheckID ,
1598 [name] AS DatabaseName ,
1599 110 AS Priority ,
1600 'Performance' AS FindingsGroup ,
1601 'Auto-Create Stats Disabled' AS Finding ,
1602 'https://BrentOzar.com/go/acs' AS URL ,
1603 ( 'Database [' + [name]
1604 + '] 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
1605 FROM sys.databases
1606 WHERE is_auto_create_stats_on = 0
1607 AND name NOT IN ( SELECT DISTINCT
1608 DatabaseName
1609 FROM #SkipChecks
1610 WHERE CheckID IS NULL OR CheckID = 15);
1611 END;
1612
1613 IF NOT EXISTS ( SELECT 1
1614 FROM #SkipChecks
1615 WHERE DatabaseName IS NULL AND CheckID = 16 )
1616 BEGIN
1617
1618 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 16) WITH NOWAIT;
1619
1620 INSERT INTO #BlitzResults
1621 ( CheckID ,
1622 DatabaseName ,
1623 Priority ,
1624 FindingsGroup ,
1625 Finding ,
1626 URL ,
1627 Details
1628 )
1629 SELECT 16 AS CheckID ,
1630 [name] AS DatabaseName ,
1631 110 AS Priority ,
1632 'Performance' AS FindingsGroup ,
1633 'Auto-Update Stats Disabled' AS Finding ,
1634 'https://BrentOzar.com/go/aus' AS URL ,
1635 ( 'Database [' + [name]
1636 + '] 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
1637 FROM sys.databases
1638 WHERE is_auto_update_stats_on = 0
1639 AND name NOT IN ( SELECT DISTINCT
1640 DatabaseName
1641 FROM #SkipChecks
1642 WHERE CheckID IS NULL OR CheckID = 16);
1643 END;
1644
1645 IF NOT EXISTS ( SELECT 1
1646 FROM #SkipChecks
1647 WHERE DatabaseName IS NULL AND CheckID = 17 )
1648 BEGIN
1649
1650 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 17) WITH NOWAIT;
1651
1652 INSERT INTO #BlitzResults
1653 ( CheckID ,
1654 DatabaseName ,
1655 Priority ,
1656 FindingsGroup ,
1657 Finding ,
1658 URL ,
1659 Details
1660 )
1661 SELECT 17 AS CheckID ,
1662 [name] AS DatabaseName ,
1663 150 AS Priority ,
1664 'Performance' AS FindingsGroup ,
1665 'Stats Updated Asynchronously' AS Finding ,
1666 'https://BrentOzar.com/go/asyncstats' AS URL ,
1667 ( 'Database [' + [name]
1668 + '] 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
1669 FROM sys.databases
1670 WHERE is_auto_update_stats_async_on = 1
1671 AND name NOT IN ( SELECT DISTINCT
1672 DatabaseName
1673 FROM #SkipChecks
1674 WHERE CheckID IS NULL OR CheckID = 17);
1675 END;
1676
1677 IF NOT EXISTS ( SELECT 1
1678 FROM #SkipChecks
1679 WHERE DatabaseName IS NULL AND CheckID = 20 )
1680 BEGIN
1681
1682 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 20) WITH NOWAIT;
1683
1684 INSERT INTO #BlitzResults
1685 ( CheckID ,
1686 DatabaseName ,
1687 Priority ,
1688 FindingsGroup ,
1689 Finding ,
1690 URL ,
1691 Details
1692 )
1693 SELECT 20 AS CheckID ,
1694 [name] AS DatabaseName ,
1695 200 AS Priority ,
1696 'Informational' AS FindingsGroup ,
1697 'Date Correlation On' AS Finding ,
1698 'https://BrentOzar.com/go/corr' AS URL ,
1699 ( 'Database [' + [name]
1700 + '] 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
1701 FROM sys.databases
1702 WHERE is_date_correlation_on = 1
1703 AND name NOT IN ( SELECT DISTINCT
1704 DatabaseName
1705 FROM #SkipChecks
1706 WHERE CheckID IS NULL OR CheckID = 20);
1707 END;
1708
1709 IF NOT EXISTS ( SELECT 1
1710 FROM #SkipChecks
1711 WHERE DatabaseName IS NULL AND CheckID = 21 )
1712 BEGIN
1713 /* --TOURSTOP04-- */
1714 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
1715 AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
1716 BEGIN
1717
1718 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 21) WITH NOWAIT;
1719
1720 SET @StringToExecute = 'INSERT INTO #BlitzResults
1721 (CheckID,
1722 DatabaseName,
1723 Priority,
1724 FindingsGroup,
1725 Finding,
1726 URL,
1727 Details)
1728 SELECT 21 AS CheckID,
1729 [name] as DatabaseName,
1730 200 AS Priority,
1731 ''Informational'' AS FindingsGroup,
1732 ''Database Encrypted'' AS Finding,
1733 ''https://BrentOzar.com/go/tde'' AS URL,
1734 (''Database ['' + [name] + ''] has Transparent Data Encryption enabled. Make absolutely sure you have backed up the certificate and private key, or else you will not be able to restore this database.'') AS Details
1735 FROM sys.databases
1736 WHERE is_encrypted = 1
1737 and name not in (select distinct DatabaseName from #SkipChecks WHERE CheckID IS NULL OR CheckID = 21) OPTION (RECOMPILE);';
1738
1739 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
1740 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
1741
1742 EXECUTE(@StringToExecute);
1743 END;
1744 END;
1745
1746 /*
1747 Believe it or not, SQL Server doesn't track the default values
1748 for sp_configure options! We'll make our own list here.
1749 */
1750
1751 IF @Debug IN (1, 2) RAISERROR('Generating default configuration values', 0, 1) WITH NOWAIT;
1752
1753 INSERT INTO #ConfigurationDefaults
1754 VALUES ( 'access check cache bucket count', 0, 1001 );
1755 INSERT INTO #ConfigurationDefaults
1756 VALUES ( 'access check cache quota', 0, 1002 );
1757 INSERT INTO #ConfigurationDefaults
1758 VALUES ( 'Ad Hoc Distributed Queries', 0, 1003 );
1759 INSERT INTO #ConfigurationDefaults
1760 VALUES ( 'affinity I/O mask', 0, 1004 );
1761 INSERT INTO #ConfigurationDefaults
1762 VALUES ( 'affinity mask', 0, 1005 );
1763 INSERT INTO #ConfigurationDefaults
1764 VALUES ( 'affinity64 mask', 0, 1066 );
1765 INSERT INTO #ConfigurationDefaults
1766 VALUES ( 'affinity64 I/O mask', 0, 1067 );
1767 INSERT INTO #ConfigurationDefaults
1768 VALUES ( 'Agent XPs', 0, 1071 );
1769 INSERT INTO #ConfigurationDefaults
1770 VALUES ( 'allow updates', 0, 1007 );
1771 INSERT INTO #ConfigurationDefaults
1772 VALUES ( 'awe enabled', 0, 1008 );
1773 INSERT INTO #ConfigurationDefaults
1774 VALUES ( 'backup checksum default', 0, 1070 );
1775 INSERT INTO #ConfigurationDefaults
1776 VALUES ( 'backup compression default', 0, 1073 );
1777 INSERT INTO #ConfigurationDefaults
1778 VALUES ( 'blocked process threshold', 0, 1009 );
1779 INSERT INTO #ConfigurationDefaults
1780 VALUES ( 'blocked process threshold (s)', 0, 1009 );
1781 INSERT INTO #ConfigurationDefaults
1782 VALUES ( 'c2 audit mode', 0, 1010 );
1783 INSERT INTO #ConfigurationDefaults
1784 VALUES ( 'clr enabled', 0, 1011 );
1785 INSERT INTO #ConfigurationDefaults
1786 VALUES ( 'common criteria compliance enabled', 0, 1074 );
1787 INSERT INTO #ConfigurationDefaults
1788 VALUES ( 'contained database authentication', 0, 1068 );
1789 INSERT INTO #ConfigurationDefaults
1790 VALUES ( 'cost threshold for parallelism', 5, 1012 );
1791 INSERT INTO #ConfigurationDefaults
1792 VALUES ( 'cross db ownership chaining', 0, 1013 );
1793 INSERT INTO #ConfigurationDefaults
1794 VALUES ( 'cursor threshold', -1, 1014 );
1795 INSERT INTO #ConfigurationDefaults
1796 VALUES ( 'Database Mail XPs', 0, 1072 );
1797 INSERT INTO #ConfigurationDefaults
1798 VALUES ( 'default full-text language', 1033, 1016 );
1799 INSERT INTO #ConfigurationDefaults
1800 VALUES ( 'default language', 0, 1017 );
1801 INSERT INTO #ConfigurationDefaults
1802 VALUES ( 'default trace enabled', 1, 1018 );
1803 INSERT INTO #ConfigurationDefaults
1804 VALUES ( 'disallow results from triggers', 0, 1019 );
1805 INSERT INTO #ConfigurationDefaults
1806 VALUES ( 'EKM provider enabled', 0, 1075 );
1807 INSERT INTO #ConfigurationDefaults
1808 VALUES ( 'filestream access level', 0, 1076 );
1809 INSERT INTO #ConfigurationDefaults
1810 VALUES ( 'fill factor (%)', 0, 1020 );
1811 INSERT INTO #ConfigurationDefaults
1812 VALUES ( 'ft crawl bandwidth (max)', 100, 1021 );
1813 INSERT INTO #ConfigurationDefaults
1814 VALUES ( 'ft crawl bandwidth (min)', 0, 1022 );
1815 INSERT INTO #ConfigurationDefaults
1816 VALUES ( 'ft notify bandwidth (max)', 100, 1023 );
1817 INSERT INTO #ConfigurationDefaults
1818 VALUES ( 'ft notify bandwidth (min)', 0, 1024 );
1819 INSERT INTO #ConfigurationDefaults
1820 VALUES ( 'index create memory (KB)', 0, 1025 );
1821 INSERT INTO #ConfigurationDefaults
1822 VALUES ( 'in-doubt xact resolution', 0, 1026 );
1823 INSERT INTO #ConfigurationDefaults
1824 VALUES ( 'lightweight pooling', 0, 1027 );
1825 INSERT INTO #ConfigurationDefaults
1826 VALUES ( 'locks', 0, 1028 );
1827 INSERT INTO #ConfigurationDefaults
1828 VALUES ( 'max degree of parallelism', 0, 1029 );
1829 INSERT INTO #ConfigurationDefaults
1830 VALUES ( 'max full-text crawl range', 4, 1030 );
1831 INSERT INTO #ConfigurationDefaults
1832 VALUES ( 'max server memory (MB)', 2147483647, 1031 );
1833 INSERT INTO #ConfigurationDefaults
1834 VALUES ( 'max text repl size (B)', 65536, 1032 );
1835 INSERT INTO #ConfigurationDefaults
1836 VALUES ( 'max worker threads', 0, 1033 );
1837 INSERT INTO #ConfigurationDefaults
1838 VALUES ( 'media retention', 0, 1034 );
1839 INSERT INTO #ConfigurationDefaults
1840 VALUES ( 'min memory per query (KB)', 1024, 1035 );
1841 /* Accepting both 0 and 16 below because both have been seen in the wild as defaults. */
1842 IF EXISTS ( SELECT *
1843 FROM sys.configurations
1844 WHERE name = 'min server memory (MB)'
1845 AND value_in_use IN ( 0, 16 ) )
1846 INSERT INTO #ConfigurationDefaults
1847 SELECT 'min server memory (MB)' ,
1848 CAST(value_in_use AS BIGINT), 1036
1849 FROM sys.configurations
1850 WHERE name = 'min server memory (MB)';
1851 ELSE
1852 INSERT INTO #ConfigurationDefaults
1853 VALUES ( 'min server memory (MB)', 0, 1036 );
1854 INSERT INTO #ConfigurationDefaults
1855 VALUES ( 'nested triggers', 1, 1037 );
1856 INSERT INTO #ConfigurationDefaults
1857 VALUES ( 'network packet size (B)', 4096, 1038 );
1858 INSERT INTO #ConfigurationDefaults
1859 VALUES ( 'Ole Automation Procedures', 0, 1039 );
1860 INSERT INTO #ConfigurationDefaults
1861 VALUES ( 'open objects', 0, 1040 );
1862 INSERT INTO #ConfigurationDefaults
1863 VALUES ( 'optimize for ad hoc workloads', 0, 1041 );
1864 INSERT INTO #ConfigurationDefaults
1865 VALUES ( 'PH timeout (s)', 60, 1042 );
1866 INSERT INTO #ConfigurationDefaults
1867 VALUES ( 'precompute rank', 0, 1043 );
1868 INSERT INTO #ConfigurationDefaults
1869 VALUES ( 'priority boost', 0, 1044 );
1870 INSERT INTO #ConfigurationDefaults
1871 VALUES ( 'query governor cost limit', 0, 1045 );
1872 INSERT INTO #ConfigurationDefaults
1873 VALUES ( 'query wait (s)', -1, 1046 );
1874 INSERT INTO #ConfigurationDefaults
1875 VALUES ( 'recovery interval (min)', 0, 1047 );
1876 INSERT INTO #ConfigurationDefaults
1877 VALUES ( 'remote access', 1, 1048 );
1878 INSERT INTO #ConfigurationDefaults
1879 VALUES ( 'remote admin connections', 0, 1049 );
1880 /* SQL Server 2012 changes a configuration default */
1881 IF @@VERSION LIKE '%Microsoft SQL Server 2005%'
1882 OR @@VERSION LIKE '%Microsoft SQL Server 2008%'
1883 BEGIN
1884 INSERT INTO #ConfigurationDefaults
1885 VALUES ( 'remote login timeout (s)', 20, 1069 );
1886 END;
1887 ELSE
1888 BEGIN
1889 INSERT INTO #ConfigurationDefaults
1890 VALUES ( 'remote login timeout (s)', 10, 1069 );
1891 END;
1892 INSERT INTO #ConfigurationDefaults
1893 VALUES ( 'remote proc trans', 0, 1050 );
1894 INSERT INTO #ConfigurationDefaults
1895 VALUES ( 'remote query timeout (s)', 600, 1051 );
1896 INSERT INTO #ConfigurationDefaults
1897 VALUES ( 'Replication XPs', 0, 1052 );
1898 INSERT INTO #ConfigurationDefaults
1899 VALUES ( 'RPC parameter data validation', 0, 1053 );
1900 INSERT INTO #ConfigurationDefaults
1901 VALUES ( 'scan for startup procs', 0, 1054 );
1902 INSERT INTO #ConfigurationDefaults
1903 VALUES ( 'server trigger recursion', 1, 1055 );
1904 INSERT INTO #ConfigurationDefaults
1905 VALUES ( 'set working set size', 0, 1056 );
1906 INSERT INTO #ConfigurationDefaults
1907 VALUES ( 'show advanced options', 0, 1057 );
1908 INSERT INTO #ConfigurationDefaults
1909 VALUES ( 'SMO and DMO XPs', 1, 1058 );
1910 INSERT INTO #ConfigurationDefaults
1911 VALUES ( 'SQL Mail XPs', 0, 1059 );
1912 INSERT INTO #ConfigurationDefaults
1913 VALUES ( 'transform noise words', 0, 1060 );
1914 INSERT INTO #ConfigurationDefaults
1915 VALUES ( 'two digit year cutoff', 2049, 1061 );
1916 INSERT INTO #ConfigurationDefaults
1917 VALUES ( 'user connections', 0, 1062 );
1918 INSERT INTO #ConfigurationDefaults
1919 VALUES ( 'user options', 0, 1063 );
1920 INSERT INTO #ConfigurationDefaults
1921 VALUES ( 'Web Assistant Procedures', 0, 1064 );
1922 INSERT INTO #ConfigurationDefaults
1923 VALUES ( 'xp_cmdshell', 0, 1065 );
1924
1925 IF NOT EXISTS ( SELECT 1
1926 FROM #SkipChecks
1927 WHERE DatabaseName IS NULL AND CheckID = 22 )
1928 BEGIN
1929
1930 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 22) WITH NOWAIT;
1931
1932 INSERT INTO #BlitzResults
1933 ( CheckID ,
1934 Priority ,
1935 FindingsGroup ,
1936 Finding ,
1937 URL ,
1938 Details
1939 )
1940 SELECT cd.CheckID ,
1941 200 AS Priority ,
1942 'Non-Default Server Config' AS FindingsGroup ,
1943 cr.name AS Finding ,
1944 'https://BrentOzar.com/go/conf' AS URL ,
1945 ( 'This sp_configure option has been changed. Its default value is '
1946 + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)),
1947 '(unknown)')
1948 + ' and it has been set to '
1949 + CAST(cr.value_in_use AS VARCHAR(100))
1950 + '.' ) AS Details
1951 FROM sys.configurations cr
1952 INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name
1953 LEFT OUTER JOIN #ConfigurationDefaults cdUsed ON cdUsed.name = cr.name
1954 AND cdUsed.DefaultValue = cr.value_in_use
1955 WHERE cdUsed.name IS NULL;
1956 END;
1957
1958 IF NOT EXISTS ( SELECT 1
1959 FROM #SkipChecks
1960 WHERE DatabaseName IS NULL AND CheckID = 190 )
1961 BEGIN
1962
1963 IF @Debug IN (1, 2) RAISERROR('Setting @MinServerMemory and @MaxServerMemory', 0, 1) WITH NOWAIT;
1964
1965 SELECT @MinServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'min server memory (MB)';
1966 SELECT @MaxServerMemory = CAST(value_in_use as BIGINT) FROM sys.configurations WHERE name = 'max server memory (MB)';
1967
1968 IF (@MinServerMemory = @MaxServerMemory)
1969 BEGIN
1970
1971 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 190) WITH NOWAIT;
1972
1973 INSERT INTO #BlitzResults
1974 ( CheckID ,
1975 Priority ,
1976 FindingsGroup ,
1977 Finding ,
1978 URL ,
1979 Details
1980 )
1981 VALUES
1982 ( 190,
1983 200,
1984 'Performance',
1985 'Non-Dynamic Memory',
1986 'https://BrentOzar.com/go/memory',
1987 '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'
1988 );
1989 END;
1990 END;
1991
1992 IF NOT EXISTS ( SELECT 1
1993 FROM #SkipChecks
1994 WHERE DatabaseName IS NULL AND CheckID = 188 )
1995 BEGIN
1996
1997 /* Let's set variables so that our query is still SARGable */
1998
1999 IF @Debug IN (1, 2) RAISERROR('Setting @Processors.', 0, 1) WITH NOWAIT;
2000
2001 SET @Processors = (SELECT cpu_count FROM sys.dm_os_sys_info);
2002
2003 IF @Debug IN (1, 2) RAISERROR('Setting @NUMANodes', 0, 1) WITH NOWAIT;
2004
2005 SET @NUMANodes = (SELECT COUNT(1)
2006 FROM sys.dm_os_performance_counters pc
2007 WHERE pc.object_name LIKE '%Buffer Node%'
2008 AND counter_name = 'Page life expectancy');
2009 /* If Cost Threshold for Parallelism is default then flag as a potential issue */
2010 /* If MAXDOP is default and processors > 8 or NUMA nodes > 1 then flag as potential issue */
2011
2012 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 188) WITH NOWAIT;
2013
2014 INSERT INTO #BlitzResults
2015 ( CheckID ,
2016 Priority ,
2017 FindingsGroup ,
2018 Finding ,
2019 URL ,
2020 Details
2021 )
2022 SELECT 188 AS CheckID ,
2023 200 AS Priority ,
2024 'Performance' AS FindingsGroup ,
2025 cr.name AS Finding ,
2026 'https://BrentOzar.com/go/cxpacket' AS URL ,
2027 ( 'Set to ' + CAST(cr.value_in_use AS NVARCHAR(50)) + ', its default value. Changing this sp_configure setting may reduce CXPACKET waits.')
2028 FROM sys.configurations cr
2029 INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name
2030 AND cr.value_in_use = cd.DefaultValue
2031 WHERE cr.name = 'cost threshold for parallelism'
2032 OR (cr.name = 'max degree of parallelism' AND (@NUMANodes > 1 OR @Processors > 8));
2033 END;
2034
2035 IF NOT EXISTS ( SELECT 1
2036 FROM #SkipChecks
2037 WHERE DatabaseName IS NULL AND CheckID = 24 )
2038 BEGIN
2039
2040 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 24) WITH NOWAIT;
2041
2042 INSERT INTO #BlitzResults
2043 ( CheckID ,
2044 DatabaseName ,
2045 Priority ,
2046 FindingsGroup ,
2047 Finding ,
2048 URL ,
2049 Details
2050 )
2051 SELECT DISTINCT
2052 24 AS CheckID ,
2053 DB_NAME(database_id) AS DatabaseName ,
2054 170 AS Priority ,
2055 'File Configuration' AS FindingsGroup ,
2056 'System Database on C Drive' AS Finding ,
2057 'https://BrentOzar.com/go/cdrive' AS URL ,
2058 ( 'The ' + DB_NAME(database_id)
2059 + ' 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
2060 FROM sys.master_files
2061 WHERE UPPER(LEFT(physical_name, 1)) = 'C'
2062 AND DB_NAME(database_id) IN ( 'master',
2063 'model', 'msdb' );
2064 END;
2065
2066 IF NOT EXISTS ( SELECT 1
2067 FROM #SkipChecks
2068 WHERE DatabaseName IS NULL AND CheckID = 25 )
2069 AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
2070 BEGIN
2071
2072 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 25) WITH NOWAIT;
2073
2074 INSERT INTO #BlitzResults
2075 ( CheckID ,
2076 DatabaseName ,
2077 Priority ,
2078 FindingsGroup ,
2079 Finding ,
2080 URL ,
2081 Details
2082 )
2083 SELECT TOP 1
2084 25 AS CheckID ,
2085 'tempdb' ,
2086 20 AS Priority ,
2087 'File Configuration' AS FindingsGroup ,
2088 'TempDB on C Drive' AS Finding ,
2089 'https://BrentOzar.com/go/cdrive' AS URL ,
2090 CASE WHEN growth > 0
2091 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.' )
2092 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.' )
2093 END AS Details
2094 FROM sys.master_files
2095 WHERE UPPER(LEFT(physical_name, 1)) = 'C'
2096 AND DB_NAME(database_id) = 'tempdb';
2097 END;
2098
2099 IF NOT EXISTS ( SELECT 1
2100 FROM #SkipChecks
2101 WHERE DatabaseName IS NULL AND CheckID = 26 )
2102 AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
2103 BEGIN
2104
2105 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 26) WITH NOWAIT;
2106
2107 INSERT INTO #BlitzResults
2108 ( CheckID ,
2109 DatabaseName ,
2110 Priority ,
2111 FindingsGroup ,
2112 Finding ,
2113 URL ,
2114 Details
2115 )
2116 SELECT DISTINCT
2117 26 AS CheckID ,
2118 DB_NAME(database_id) AS DatabaseName ,
2119 20 AS Priority ,
2120 'Reliability' AS FindingsGroup ,
2121 'User Databases on C Drive' AS Finding ,
2122 'https://BrentOzar.com/go/cdrive' AS URL ,
2123 ( 'The ' + DB_NAME(database_id)
2124 + ' 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
2125 FROM sys.master_files
2126 WHERE UPPER(LEFT(physical_name, 1)) = 'C'
2127 AND DB_NAME(database_id) NOT IN ( 'master',
2128 'model', 'msdb',
2129 'tempdb' )
2130 AND DB_NAME(database_id) NOT IN (
2131 SELECT DISTINCT
2132 DatabaseName
2133 FROM #SkipChecks
2134 WHERE CheckID IS NULL OR CheckID = 26 );
2135 END;
2136
2137 IF NOT EXISTS ( SELECT 1
2138 FROM #SkipChecks
2139 WHERE DatabaseName IS NULL AND CheckID = 27 )
2140 BEGIN
2141
2142 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 27) WITH NOWAIT;
2143
2144 INSERT INTO #BlitzResults
2145 ( CheckID ,
2146 DatabaseName ,
2147 Priority ,
2148 FindingsGroup ,
2149 Finding ,
2150 URL ,
2151 Details
2152 )
2153 SELECT 27 AS CheckID ,
2154 'master' AS DatabaseName ,
2155 200 AS Priority ,
2156 'Informational' AS FindingsGroup ,
2157 'Tables in the Master Database' AS Finding ,
2158 'https://BrentOzar.com/go/mastuser' AS URL ,
2159 ( 'The ' + name
2160 + ' table in the master database was created by end users on '
2161 + CAST(create_date AS VARCHAR(20))
2162 + '. Tables in the master database may not be restored in the event of a disaster.' ) AS Details
2163 FROM master.sys.tables
2164 WHERE is_ms_shipped = 0
2165 AND name NOT IN ('CommandLog','SqlServerVersions');
2166 END;
2167
2168 IF NOT EXISTS ( SELECT 1
2169 FROM #SkipChecks
2170 WHERE DatabaseName IS NULL AND CheckID = 28 )
2171 BEGIN
2172
2173 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 28) WITH NOWAIT;
2174
2175 INSERT INTO #BlitzResults
2176 ( CheckID ,
2177 DatabaseName ,
2178 Priority ,
2179 FindingsGroup ,
2180 Finding ,
2181 URL ,
2182 Details
2183 )
2184 SELECT 28 AS CheckID ,
2185 'msdb' AS DatabaseName ,
2186 200 AS Priority ,
2187 'Informational' AS FindingsGroup ,
2188 'Tables in the MSDB Database' AS Finding ,
2189 'https://BrentOzar.com/go/msdbuser' AS URL ,
2190 ( 'The ' + name
2191 + ' table in the msdb database was created by end users on '
2192 + CAST(create_date AS VARCHAR(20))
2193 + '. Tables in the msdb database may not be restored in the event of a disaster.' ) AS Details
2194 FROM msdb.sys.tables
2195 WHERE is_ms_shipped = 0 AND name NOT LIKE '%DTA_%';
2196 END;
2197
2198 IF NOT EXISTS ( SELECT 1
2199 FROM #SkipChecks
2200 WHERE DatabaseName IS NULL AND CheckID = 29 )
2201 BEGIN
2202
2203 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 29) WITH NOWAIT;
2204
2205 INSERT INTO #BlitzResults
2206 ( CheckID ,
2207 DatabaseName ,
2208 Priority ,
2209 FindingsGroup ,
2210 Finding ,
2211 URL ,
2212 Details
2213 )
2214 SELECT 29 AS CheckID ,
2215 'model' AS DatabaseName ,
2216 200 AS Priority ,
2217 'Informational' AS FindingsGroup ,
2218 'Tables in the Model Database' AS Finding ,
2219 'https://BrentOzar.com/go/model' AS URL ,
2220 ( 'The ' + name
2221 + ' table in the model database was created by end users on '
2222 + CAST(create_date AS VARCHAR(20))
2223 + '. Tables in the model database are automatically copied into all new databases.' ) AS Details
2224 FROM model.sys.tables
2225 WHERE is_ms_shipped = 0;
2226 END;
2227
2228 IF NOT EXISTS ( SELECT 1
2229 FROM #SkipChecks
2230 WHERE DatabaseName IS NULL AND CheckID = 30 )
2231 BEGIN
2232 IF ( SELECT COUNT(*)
2233 FROM msdb.dbo.sysalerts
2234 WHERE severity BETWEEN 19 AND 25
2235 ) < 7
2236
2237 BEGIN
2238
2239 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 30) WITH NOWAIT;
2240
2241 INSERT INTO #BlitzResults
2242 ( CheckID ,
2243 Priority ,
2244 FindingsGroup ,
2245 Finding ,
2246 URL ,
2247 Details
2248 )
2249 SELECT 30 AS CheckID ,
2250 200 AS Priority ,
2251 'Monitoring' AS FindingsGroup ,
2252 'Not All Alerts Configured' AS Finding ,
2253 'https://BrentOzar.com/go/alert' AS URL ,
2254 ( '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;
2255 END;
2256 END;
2257
2258 IF NOT EXISTS ( SELECT 1
2259 FROM #SkipChecks
2260 WHERE DatabaseName IS NULL AND CheckID = 59 )
2261 BEGIN
2262 IF EXISTS ( SELECT *
2263 FROM msdb.dbo.sysalerts
2264 WHERE enabled = 1
2265 AND COALESCE(has_notification, 0) = 0
2266 AND (job_id IS NULL OR job_id = 0x))
2267
2268 BEGIN
2269
2270 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 59) WITH NOWAIT;
2271
2272 INSERT INTO #BlitzResults
2273 ( CheckID ,
2274 Priority ,
2275 FindingsGroup ,
2276 Finding ,
2277 URL ,
2278 Details
2279 )
2280 SELECT 59 AS CheckID ,
2281 200 AS Priority ,
2282 'Monitoring' AS FindingsGroup ,
2283 'Alerts Configured without Follow Up' AS Finding ,
2284 'https://BrentOzar.com/go/alert' AS URL ,
2285 ( '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;
2286
2287 END;
2288 END;
2289
2290 IF NOT EXISTS ( SELECT 1
2291 FROM #SkipChecks
2292 WHERE DatabaseName IS NULL AND CheckID = 96 )
2293 BEGIN
2294 IF NOT EXISTS ( SELECT *
2295 FROM msdb.dbo.sysalerts
2296 WHERE message_id IN ( 823, 824, 825 ) )
2297
2298 BEGIN;
2299
2300 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 96) WITH NOWAIT;
2301
2302 INSERT INTO #BlitzResults
2303 ( CheckID ,
2304 Priority ,
2305 FindingsGroup ,
2306 Finding ,
2307 URL ,
2308 Details
2309 )
2310 SELECT 96 AS CheckID ,
2311 200 AS Priority ,
2312 'Monitoring' AS FindingsGroup ,
2313 'No Alerts for Corruption' AS Finding ,
2314 'https://BrentOzar.com/go/alert' AS URL ,
2315 ( '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;
2316
2317 END;
2318 END;
2319
2320 IF NOT EXISTS ( SELECT 1
2321 FROM #SkipChecks
2322 WHERE DatabaseName IS NULL AND CheckID = 61 )
2323 BEGIN
2324 IF NOT EXISTS ( SELECT *
2325 FROM msdb.dbo.sysalerts
2326 WHERE severity BETWEEN 19 AND 25 )
2327
2328 BEGIN
2329
2330 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 61) WITH NOWAIT;
2331
2332 INSERT INTO #BlitzResults
2333 ( CheckID ,
2334 Priority ,
2335 FindingsGroup ,
2336 Finding ,
2337 URL ,
2338 Details
2339 )
2340 SELECT 61 AS CheckID ,
2341 200 AS Priority ,
2342 'Monitoring' AS FindingsGroup ,
2343 'No Alerts for Sev 19-25' AS Finding ,
2344 'https://BrentOzar.com/go/alert' AS URL ,
2345 ( '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;
2346
2347 END;
2348
2349 END;
2350
2351 --check for disabled alerts
2352 IF NOT EXISTS ( SELECT 1
2353 FROM #SkipChecks
2354 WHERE DatabaseName IS NULL AND CheckID = 98 )
2355 BEGIN
2356 IF EXISTS ( SELECT name
2357 FROM msdb.dbo.sysalerts
2358 WHERE enabled = 0 )
2359
2360 BEGIN
2361
2362 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 98) WITH NOWAIT;
2363
2364 INSERT INTO #BlitzResults
2365 ( CheckID ,
2366 Priority ,
2367 FindingsGroup ,
2368 Finding ,
2369 URL ,
2370 Details
2371 )
2372 SELECT 98 AS CheckID ,
2373 200 AS Priority ,
2374 'Monitoring' AS FindingsGroup ,
2375 'Alerts Disabled' AS Finding ,
2376 'https://BrentOzar.com/go/alert' AS URL ,
2377 ( 'The following Alert is disabled, please review and enable if desired: '
2378 + name ) AS Details
2379 FROM msdb.dbo.sysalerts
2380 WHERE enabled = 0;
2381 END;
2382 END;
2383
2384 --check for alerts that do NOT include event descriptions in their outputs via email/pager/net-send
2385 IF NOT EXISTS (
2386 SELECT 1
2387 FROM #SkipChecks
2388 WHERE DatabaseName IS NULL
2389 AND CheckID = 219
2390 )
2391 BEGIN;
2392 IF @Debug IN (1, 2)
2393 BEGIN;
2394 RAISERROR ('Running CheckId [%d].', 0, 1, 219) WITH NOWAIT;
2395 END;
2396
2397 INSERT INTO #BlitzResults (
2398 CheckID
2399 ,[Priority]
2400 ,FindingsGroup
2401 ,Finding
2402 ,[URL]
2403 ,Details
2404 )
2405 SELECT 219 AS CheckID
2406 ,200 AS [Priority]
2407 ,'Monitoring' AS FindingsGroup
2408 ,'Alerts Without Event Descriptions' AS Finding
2409 ,'https://BrentOzar.com/go/alert' AS [URL]
2410 ,('The following Alert is not including detailed event descriptions in its output messages: ' + QUOTENAME([name])
2411 + '. You can fix it by ticking the relevant boxes in its Properties --> Options page.') AS Details
2412 FROM msdb.dbo.sysalerts
2413 WHERE [enabled] = 1
2414 AND include_event_description = 0 --bitmask: 1 = email, 2 = pager, 4 = net send
2415 ;
2416 END;
2417
2418 --check whether we have NO ENABLED operators!
2419 IF NOT EXISTS ( SELECT 1
2420 FROM #SkipChecks
2421 WHERE DatabaseName IS NULL AND CheckID = 31 )
2422 BEGIN;
2423 IF NOT EXISTS ( SELECT *
2424 FROM msdb.dbo.sysoperators
2425 WHERE enabled = 1 )
2426
2427 BEGIN
2428
2429 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 31) WITH NOWAIT;
2430
2431 INSERT INTO #BlitzResults
2432 ( CheckID ,
2433 Priority ,
2434 FindingsGroup ,
2435 Finding ,
2436 URL ,
2437 Details
2438 )
2439 SELECT 31 AS CheckID ,
2440 200 AS Priority ,
2441 'Monitoring' AS FindingsGroup ,
2442 'No Operators Configured/Enabled' AS Finding ,
2443 'https://BrentOzar.com/go/op' AS URL ,
2444 ( '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;
2445
2446 END;
2447 END;
2448
2449 IF NOT EXISTS ( SELECT 1
2450 FROM #SkipChecks
2451 WHERE DatabaseName IS NULL AND CheckID = 34 )
2452 BEGIN
2453 IF EXISTS ( SELECT *
2454 FROM sys.all_objects
2455 WHERE name = 'dm_db_mirroring_auto_page_repair' )
2456 BEGIN
2457
2458 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 34) WITH NOWAIT;
2459
2460 SET @StringToExecute = 'INSERT INTO #BlitzResults
2461 (CheckID,
2462 DatabaseName,
2463 Priority,
2464 FindingsGroup,
2465 Finding,
2466 URL,
2467 Details)
2468 SELECT DISTINCT
2469 34 AS CheckID ,
2470 db.name ,
2471 1 AS Priority ,
2472 ''Corruption'' AS FindingsGroup ,
2473 ''Database Corruption Detected'' AS Finding ,
2474 ''https://BrentOzar.com/go/repair'' AS URL ,
2475 ( ''Database mirroring has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_db_mirroring_auto_page_repair.'' ) AS Details
2476 FROM (SELECT rp2.database_id, rp2.modification_time
2477 FROM sys.dm_db_mirroring_auto_page_repair rp2
2478 WHERE rp2.[database_id] not in (
2479 SELECT db2.[database_id]
2480 FROM sys.databases as db2
2481 WHERE db2.[state] = 1
2482 ) ) as rp
2483 INNER JOIN master.sys.databases db ON rp.database_id = db.database_id
2484 WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);';
2485
2486 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
2487 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
2488
2489 EXECUTE(@StringToExecute);
2490 END;
2491 END;
2492
2493 IF NOT EXISTS ( SELECT 1
2494 FROM #SkipChecks
2495 WHERE DatabaseName IS NULL AND CheckID = 89 )
2496 BEGIN
2497 IF EXISTS ( SELECT *
2498 FROM sys.all_objects
2499 WHERE name = 'dm_hadr_auto_page_repair' )
2500 BEGIN
2501
2502 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 89) WITH NOWAIT;
2503
2504 SET @StringToExecute = 'INSERT INTO #BlitzResults
2505 (CheckID,
2506 DatabaseName,
2507 Priority,
2508 FindingsGroup,
2509 Finding,
2510 URL,
2511 Details)
2512 SELECT DISTINCT
2513 89 AS CheckID ,
2514 db.name ,
2515 1 AS Priority ,
2516 ''Corruption'' AS FindingsGroup ,
2517 ''Database Corruption Detected'' AS Finding ,
2518 ''https://BrentOzar.com/go/repair'' AS URL ,
2519 ( ''Availability Groups has automatically repaired at least one corrupt page in the last 30 days. For more information, query the DMV sys.dm_hadr_auto_page_repair.'' ) AS Details
2520 FROM sys.dm_hadr_auto_page_repair rp
2521 INNER JOIN master.sys.databases db ON rp.database_id = db.database_id
2522 WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE) ;';
2523
2524 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
2525 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
2526
2527 EXECUTE(@StringToExecute);
2528 END;
2529 END;
2530
2531 IF NOT EXISTS ( SELECT 1
2532 FROM #SkipChecks
2533 WHERE DatabaseName IS NULL AND CheckID = 90 )
2534 BEGIN
2535 IF EXISTS ( SELECT *
2536 FROM msdb.sys.all_objects
2537 WHERE name = 'suspect_pages' )
2538 BEGIN
2539
2540 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 90) WITH NOWAIT;
2541
2542 SET @StringToExecute = 'INSERT INTO #BlitzResults
2543 (CheckID,
2544 DatabaseName,
2545 Priority,
2546 FindingsGroup,
2547 Finding,
2548 URL,
2549 Details)
2550 SELECT DISTINCT
2551 90 AS CheckID ,
2552 db.name ,
2553 1 AS Priority ,
2554 ''Corruption'' AS FindingsGroup ,
2555 ''Database Corruption Detected'' AS Finding ,
2556 ''https://BrentOzar.com/go/repair'' AS URL ,
2557 ( ''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
2558 FROM msdb.dbo.suspect_pages sp
2559 INNER JOIN master.sys.databases db ON sp.database_id = db.database_id
2560 WHERE sp.last_update_date >= DATEADD(dd, -30, GETDATE()) OPTION (RECOMPILE);';
2561
2562 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
2563 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
2564
2565 EXECUTE(@StringToExecute);
2566 END;
2567 END;
2568
2569 IF NOT EXISTS ( SELECT 1
2570 FROM #SkipChecks
2571 WHERE DatabaseName IS NULL AND CheckID = 36 )
2572 BEGIN
2573
2574 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 36) WITH NOWAIT;
2575
2576 INSERT INTO #BlitzResults
2577 ( CheckID ,
2578 Priority ,
2579 FindingsGroup ,
2580 Finding ,
2581 URL ,
2582 Details
2583 )
2584 SELECT DISTINCT
2585 36 AS CheckID ,
2586 150 AS Priority ,
2587 'Performance' AS FindingsGroup ,
2588 'Slow Storage Reads on Drive '
2589 + UPPER(LEFT(mf.physical_name, 1)) AS Finding ,
2590 'https://BrentOzar.com/go/slow' AS URL ,
2591 'Reads are averaging longer than 200ms for at least one database on this drive. For specific database file speeds, run the query from the information link.' AS Details
2592 FROM sys.dm_io_virtual_file_stats(NULL, NULL)
2593 AS fs
2594 INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id
2595 AND fs.[file_id] = mf.[file_id]
2596 WHERE ( io_stall_read_ms / ( 1.0 + num_of_reads ) ) > 200
2597 AND num_of_reads > 100000;
2598 END;
2599
2600 IF NOT EXISTS ( SELECT 1
2601 FROM #SkipChecks
2602 WHERE DatabaseName IS NULL AND CheckID = 37 )
2603 BEGIN
2604
2605 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 37) WITH NOWAIT;
2606
2607 INSERT INTO #BlitzResults
2608 ( CheckID ,
2609 Priority ,
2610 FindingsGroup ,
2611 Finding ,
2612 URL ,
2613 Details
2614 )
2615 SELECT DISTINCT
2616 37 AS CheckID ,
2617 150 AS Priority ,
2618 'Performance' AS FindingsGroup ,
2619 'Slow Storage Writes on Drive '
2620 + UPPER(LEFT(mf.physical_name, 1)) AS Finding ,
2621 'https://BrentOzar.com/go/slow' AS URL ,
2622 '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
2623 FROM sys.dm_io_virtual_file_stats(NULL, NULL)
2624 AS fs
2625 INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id
2626 AND fs.[file_id] = mf.[file_id]
2627 WHERE ( io_stall_write_ms / ( 1.0
2628 + num_of_writes ) ) > 100
2629 AND num_of_writes > 100000;
2630 END;
2631
2632 IF NOT EXISTS ( SELECT 1
2633 FROM #SkipChecks
2634 WHERE DatabaseName IS NULL AND CheckID = 40 )
2635 BEGIN
2636 IF ( SELECT COUNT(*)
2637 FROM tempdb.sys.database_files
2638 WHERE type_desc = 'ROWS'
2639 ) = 1
2640 BEGIN
2641
2642 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 40) WITH NOWAIT;
2643
2644 INSERT INTO #BlitzResults
2645 ( CheckID ,
2646 DatabaseName ,
2647 Priority ,
2648 FindingsGroup ,
2649 Finding ,
2650 URL ,
2651 Details
2652 )
2653 VALUES ( 40 ,
2654 'tempdb' ,
2655 170 ,
2656 'File Configuration' ,
2657 'TempDB Only Has 1 Data File' ,
2658 'https://BrentOzar.com/go/tempdb' ,
2659 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.'
2660 );
2661 END;
2662 END;
2663
2664 IF NOT EXISTS ( SELECT 1
2665 FROM #SkipChecks
2666 WHERE DatabaseName IS NULL AND CheckID = 183 )
2667
2668 BEGIN
2669
2670 IF ( SELECT COUNT (distinct [size])
2671 FROM tempdb.sys.database_files
2672 WHERE type_desc = 'ROWS'
2673 HAVING MAX((size * 8) / (1024. * 1024)) - MIN((size * 8) / (1024. * 1024)) > 1.
2674 ) <> 1
2675 BEGIN
2676
2677 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 183) WITH NOWAIT;
2678
2679 INSERT INTO #BlitzResults
2680 ( CheckID ,
2681 DatabaseName ,
2682 Priority ,
2683 FindingsGroup ,
2684 Finding ,
2685 URL ,
2686 Details
2687 )
2688 VALUES ( 183 ,
2689 'tempdb' ,
2690 170 ,
2691 'File Configuration' ,
2692 'TempDB Unevenly Sized Data Files' ,
2693 'https://BrentOzar.com/go/tempdb' ,
2694 'TempDB data files are not configured with the same size. Unevenly sized tempdb data files will result in unevenly sized workloads.'
2695 );
2696 END;
2697 END;
2698
2699 IF NOT EXISTS ( SELECT 1
2700 FROM #SkipChecks
2701 WHERE DatabaseName IS NULL AND CheckID = 44 )
2702 BEGIN
2703
2704 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 44) WITH NOWAIT;
2705
2706 INSERT INTO #BlitzResults
2707 ( CheckID ,
2708 Priority ,
2709 FindingsGroup ,
2710 Finding ,
2711 URL ,
2712 Details
2713 )
2714 SELECT 44 AS CheckID ,
2715 150 AS Priority ,
2716 'Performance' AS FindingsGroup ,
2717 'Queries Forcing Order Hints' AS Finding ,
2718 'https://BrentOzar.com/go/hints' AS URL ,
2719 CAST(occurrence AS VARCHAR(10))
2720 + ' 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
2721 FROM sys.dm_exec_query_optimizer_info
2722 WHERE counter = 'order hint'
2723 AND occurrence > 1000;
2724 END;
2725
2726 IF NOT EXISTS ( SELECT 1
2727 FROM #SkipChecks
2728 WHERE DatabaseName IS NULL AND CheckID = 45 )
2729 BEGIN
2730
2731 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 45) WITH NOWAIT;
2732
2733 INSERT INTO #BlitzResults
2734 ( CheckID ,
2735 Priority ,
2736 FindingsGroup ,
2737 Finding ,
2738 URL ,
2739 Details
2740 )
2741 SELECT 45 AS CheckID ,
2742 150 AS Priority ,
2743 'Performance' AS FindingsGroup ,
2744 'Queries Forcing Join Hints' AS Finding ,
2745 'https://BrentOzar.com/go/hints' AS URL ,
2746 CAST(occurrence AS VARCHAR(10))
2747 + ' 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
2748 FROM sys.dm_exec_query_optimizer_info
2749 WHERE counter = 'join hint'
2750 AND occurrence > 1000;
2751 END;
2752
2753 IF NOT EXISTS ( SELECT 1
2754 FROM #SkipChecks
2755 WHERE DatabaseName IS NULL AND CheckID = 49 )
2756 BEGIN
2757
2758 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 49) WITH NOWAIT;
2759
2760 INSERT INTO #BlitzResults
2761 ( CheckID ,
2762 Priority ,
2763 FindingsGroup ,
2764 Finding ,
2765 URL ,
2766 Details
2767 )
2768 SELECT DISTINCT
2769 49 AS CheckID ,
2770 200 AS Priority ,
2771 'Informational' AS FindingsGroup ,
2772 'Linked Server Configured' AS Finding ,
2773 'https://BrentOzar.com/go/link' AS URL ,
2774 +CASE WHEN l.remote_name = 'sa'
2775 THEN COALESCE(s.data_source, s.provider)
2776 + ' 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.'
2777 ELSE COALESCE(s.data_source, s.provider)
2778 + ' 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.'
2779 END AS Details
2780 FROM sys.servers s
2781 INNER JOIN sys.linked_logins l ON s.server_id = l.server_id
2782 WHERE s.is_linked = 1;
2783 END;
2784
2785 IF NOT EXISTS ( SELECT 1
2786 FROM #SkipChecks
2787 WHERE DatabaseName IS NULL AND CheckID = 50 )
2788 BEGIN
2789 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
2790 AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
2791 BEGIN
2792
2793 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 50) WITH NOWAIT;
2794
2795 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
2796 SELECT 50 AS CheckID ,
2797 100 AS Priority ,
2798 ''Performance'' AS FindingsGroup ,
2799 ''Max Memory Set Too High'' AS Finding ,
2800 ''https://BrentOzar.com/go/max'' AS URL ,
2801 ''SQL Server max memory is set to ''
2802 + CAST(c.value_in_use AS VARCHAR(20))
2803 + '' megabytes, but the server only has ''
2804 + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20))
2805 + '' megabytes. SQL Server may drain the system dry of memory, and under certain conditions, this can cause Windows to swap to disk.'' AS Details
2806 FROM sys.dm_os_sys_memory m
2807 INNER JOIN sys.configurations c ON c.name = ''max server memory (MB)''
2808 WHERE CAST(m.total_physical_memory_kb AS BIGINT) < ( CAST(c.value_in_use AS BIGINT) * 1024 ) OPTION (RECOMPILE);';
2809
2810 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
2811 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
2812
2813 EXECUTE(@StringToExecute);
2814 END;
2815 END;
2816
2817 IF NOT EXISTS ( SELECT 1
2818 FROM #SkipChecks
2819 WHERE DatabaseName IS NULL AND CheckID = 51 )
2820 BEGIN
2821 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
2822 AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
2823 BEGIN
2824
2825 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 51) WITH NOWAIT
2826
2827 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
2828 SELECT 51 AS CheckID ,
2829 1 AS Priority ,
2830 ''Performance'' AS FindingsGroup ,
2831 ''Memory Dangerously Low'' AS Finding ,
2832 ''https://BrentOzar.com/go/max'' AS URL ,
2833 ''The server has '' + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20)) + '' megabytes of physical memory, but only '' + CAST(( CAST(m.available_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20))
2834 + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details
2835 FROM sys.dm_os_sys_memory m
2836 WHERE CAST(m.available_physical_memory_kb AS BIGINT) < 262144 OPTION (RECOMPILE);';
2837
2838 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
2839 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
2840
2841 EXECUTE(@StringToExecute);
2842 END;
2843 END;
2844
2845 IF NOT EXISTS ( SELECT 1
2846 FROM #SkipChecks
2847 WHERE DatabaseName IS NULL AND CheckID = 159 )
2848 BEGIN
2849 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
2850 AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
2851 BEGIN
2852
2853 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 159) WITH NOWAIT;
2854
2855 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
2856 SELECT DISTINCT 159 AS CheckID ,
2857 1 AS Priority ,
2858 ''Performance'' AS FindingsGroup ,
2859 ''Memory Dangerously Low in NUMA Nodes'' AS Finding ,
2860 ''https://BrentOzar.com/go/max'' AS URL ,
2861 ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details
2862 FROM sys.dm_os_nodes m
2863 WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'' OPTION (RECOMPILE);';
2864
2865 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
2866 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
2867
2868 EXECUTE(@StringToExecute);
2869 END;
2870 END;
2871
2872 IF NOT EXISTS ( SELECT 1
2873 FROM #SkipChecks
2874 WHERE DatabaseName IS NULL AND CheckID = 53 )
2875 BEGIN
2876
2877 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 53) WITH NOWAIT;
2878
2879 INSERT INTO #BlitzResults
2880 ( CheckID ,
2881 Priority ,
2882 FindingsGroup ,
2883 Finding ,
2884 URL ,
2885 Details
2886 )
2887 SELECT TOP 1
2888 53 AS CheckID ,
2889 200 AS Priority ,
2890 'Informational' AS FindingsGroup ,
2891 'Cluster Node' AS Finding ,
2892 'https://BrentOzar.com/go/node' AS URL ,
2893 'This is a node in a cluster.' AS Details
2894 FROM sys.dm_os_cluster_nodes;
2895 END;
2896
2897 IF NOT EXISTS ( SELECT 1
2898 FROM #SkipChecks
2899 WHERE DatabaseName IS NULL AND CheckID = 55 )
2900 BEGIN
2901
2902 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 55) WITH NOWAIT;
2903
2904 IF @UsualDBOwner IS NULL
2905 SET @UsualDBOwner = SUSER_SNAME(0x01);
2906
2907 INSERT INTO #BlitzResults
2908 ( CheckID ,
2909 DatabaseName ,
2910 Priority ,
2911 FindingsGroup ,
2912 Finding ,
2913 URL ,
2914 Details
2915 )
2916 SELECT 55 AS CheckID ,
2917 [name] AS DatabaseName ,
2918 230 AS Priority ,
2919 'Security' AS FindingsGroup ,
2920 'Database Owner <> ' + @UsualDBOwner AS Finding ,
2921 'https://BrentOzar.com/go/owndb' AS URL ,
2922 ( 'Database name: ' + [name] + ' '
2923 + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details
2924 FROM sys.databases
2925 WHERE (((SUSER_SNAME(owner_sid) <> SUSER_SNAME(0x01)) AND (name IN (N'master', N'model', N'msdb', N'tempdb')))
2926 OR ((SUSER_SNAME(owner_sid) <> @UsualDBOwner) AND (name NOT IN (N'master', N'model', N'msdb', N'tempdb')))
2927 )
2928 AND name NOT IN ( SELECT DISTINCT DatabaseName
2929 FROM #SkipChecks
2930 WHERE CheckID IS NULL OR CheckID = 55);
2931 END;
2932
2933 IF NOT EXISTS ( SELECT 1
2934 FROM #SkipChecks
2935 WHERE DatabaseName IS NULL AND CheckID = 213 )
2936 BEGIN
2937
2938 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 213) WITH NOWAIT;
2939
2940 INSERT INTO #BlitzResults
2941 ( CheckID ,
2942 DatabaseName ,
2943 Priority ,
2944 FindingsGroup ,
2945 Finding ,
2946 URL ,
2947 Details
2948 )
2949 SELECT 213 AS CheckID ,
2950 [name] AS DatabaseName ,
2951 230 AS Priority ,
2952 'Security' AS FindingsGroup ,
2953 'Database Owner is Unknown' AS Finding ,
2954 '' AS URL ,
2955 ( 'Database name: ' + [name] + ' '
2956 + 'Owner name: ' + ISNULL(SUSER_SNAME(owner_sid),'~~ UNKNOWN ~~') ) AS Details
2957 FROM sys.databases
2958 WHERE SUSER_SNAME(owner_sid) is NULL
2959 AND name NOT IN ( SELECT DISTINCT DatabaseName
2960 FROM #SkipChecks
2961 WHERE CheckID IS NULL OR CheckID = 213);
2962 END;
2963
2964 IF NOT EXISTS ( SELECT 1
2965 FROM #SkipChecks
2966 WHERE DatabaseName IS NULL AND CheckID = 57 )
2967 BEGIN
2968
2969 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 57) WITH NOWAIT;
2970
2971 INSERT INTO #BlitzResults
2972 ( CheckID ,
2973 Priority ,
2974 FindingsGroup ,
2975 Finding ,
2976 URL ,
2977 Details
2978 )
2979 SELECT 57 AS CheckID ,
2980 230 AS Priority ,
2981 'Security' AS FindingsGroup ,
2982 'SQL Agent Job Runs at Startup' AS Finding ,
2983 'https://BrentOzar.com/go/startup' AS URL ,
2984 ( 'Job [' + j.name
2985 + '] 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
2986 FROM msdb.dbo.sysschedules sched
2987 JOIN msdb.dbo.sysjobschedules jsched ON sched.schedule_id = jsched.schedule_id
2988 JOIN msdb.dbo.sysjobs j ON jsched.job_id = j.job_id
2989 WHERE sched.freq_type = 64
2990 AND sched.enabled = 1;
2991 END;
2992
2993 IF NOT EXISTS ( SELECT 1
2994 FROM #SkipChecks
2995 WHERE DatabaseName IS NULL AND CheckID = 97 )
2996 BEGIN
2997
2998 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 97) WITH NOWAIT;
2999
3000 INSERT INTO #BlitzResults
3001 ( CheckID ,
3002 Priority ,
3003 FindingsGroup ,
3004 Finding ,
3005 URL ,
3006 Details
3007 )
3008 SELECT 97 AS CheckID ,
3009 100 AS Priority ,
3010 'Performance' AS FindingsGroup ,
3011 'Unusual SQL Server Edition' AS Finding ,
3012 'https://BrentOzar.com/go/workgroup' AS URL ,
3013 ( 'This server is using '
3014 + CAST(SERVERPROPERTY('edition') AS VARCHAR(100))
3015 + ', which is capped at low amounts of CPU and memory.' ) AS Details
3016 WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'
3017 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%'
3018 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Data Center%'
3019 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%'
3020 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Business Intelligence%';
3021 END;
3022
3023 IF NOT EXISTS ( SELECT 1
3024 FROM #SkipChecks
3025 WHERE DatabaseName IS NULL AND CheckID = 154 )
3026 AND SERVERPROPERTY('EngineEdition') <> 8
3027 BEGIN
3028
3029 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 154) WITH NOWAIT;
3030
3031 INSERT INTO #BlitzResults
3032 ( CheckID ,
3033 Priority ,
3034 FindingsGroup ,
3035 Finding ,
3036 URL ,
3037 Details
3038 )
3039 SELECT 154 AS CheckID ,
3040 10 AS Priority ,
3041 'Performance' AS FindingsGroup ,
3042 '32-bit SQL Server Installed' AS Finding ,
3043 'https://BrentOzar.com/go/32bit' AS URL ,
3044 ( '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
3045 WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%';
3046 END;
3047
3048 IF NOT EXISTS ( SELECT 1
3049 FROM #SkipChecks
3050 WHERE DatabaseName IS NULL AND CheckID = 62 )
3051 BEGIN
3052
3053 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 62) WITH NOWAIT;
3054
3055 INSERT INTO #BlitzResults
3056 ( CheckID ,
3057 DatabaseName ,
3058 Priority ,
3059 FindingsGroup ,
3060 Finding ,
3061 URL ,
3062 Details
3063 )
3064 SELECT 62 AS CheckID ,
3065 [name] AS DatabaseName ,
3066 200 AS Priority ,
3067 'Performance' AS FindingsGroup ,
3068 'Old Compatibility Level' AS Finding ,
3069 'https://BrentOzar.com/go/compatlevel' AS URL ,
3070 ( 'Database ' + [name]
3071 + ' is compatibility level '
3072 + CAST(compatibility_level AS VARCHAR(20))
3073 + ', which may cause unwanted results when trying to run queries that have newer T-SQL features.' ) AS Details
3074 FROM sys.databases
3075 WHERE name NOT IN ( SELECT DISTINCT
3076 DatabaseName
3077 FROM #SkipChecks
3078 WHERE CheckID IS NULL OR CheckID = 62)
3079 AND compatibility_level <= 90;
3080 END;
3081
3082 IF NOT EXISTS ( SELECT 1
3083 FROM #SkipChecks
3084 WHERE DatabaseName IS NULL AND CheckID = 94 )
3085 BEGIN
3086
3087 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 94) WITH NOWAIT;
3088
3089 INSERT INTO #BlitzResults
3090 ( CheckID ,
3091 Priority ,
3092 FindingsGroup ,
3093 Finding ,
3094 URL ,
3095 Details
3096 )
3097 SELECT 94 AS CheckID ,
3098 200 AS [Priority] ,
3099 'Monitoring' AS FindingsGroup ,
3100 'Agent Jobs Without Failure Emails' AS Finding ,
3101 'https://BrentOzar.com/go/alerts' AS URL ,
3102 'The job ' + [name]
3103 + ' has not been set up to notify an operator if it fails.' AS Details
3104 FROM msdb.[dbo].[sysjobs] j
3105 INNER JOIN ( SELECT DISTINCT
3106 [job_id]
3107 FROM [msdb].[dbo].[sysjobschedules]
3108 WHERE next_run_date > 0
3109 ) s ON j.job_id = s.job_id
3110 WHERE j.enabled = 1
3111 AND j.notify_email_operator_id = 0
3112 AND j.notify_netsend_operator_id = 0
3113 AND j.notify_page_operator_id = 0
3114 AND j.category_id <> 100; /* Exclude SSRS category */
3115 END;
3116
3117 IF EXISTS ( SELECT 1
3118 FROM sys.configurations
3119 WHERE name = 'remote admin connections'
3120 AND value_in_use = 0 )
3121 AND NOT EXISTS ( SELECT 1
3122 FROM #SkipChecks
3123 WHERE DatabaseName IS NULL AND CheckID = 100 )
3124 BEGIN
3125
3126 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 100) WITH NOWAIT;
3127
3128 INSERT INTO #BlitzResults
3129 ( CheckID ,
3130 Priority ,
3131 FindingsGroup ,
3132 Finding ,
3133 URL ,
3134 Details
3135 )
3136 SELECT 100 AS CheckID ,
3137 50 AS Priority ,
3138 'Reliability' AS FindingGroup ,
3139 'Remote DAC Disabled' AS Finding ,
3140 'https://BrentOzar.com/go/dac' AS URL ,
3141 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.';
3142 END;
3143
3144 IF EXISTS ( SELECT *
3145 FROM sys.dm_os_schedulers
3146 WHERE is_online = 0 )
3147 AND NOT EXISTS ( SELECT 1
3148 FROM #SkipChecks
3149 WHERE DatabaseName IS NULL AND CheckID = 101 )
3150 BEGIN
3151
3152 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 101) WITH NOWAIT;
3153
3154 INSERT INTO #BlitzResults
3155 ( CheckID ,
3156 Priority ,
3157 FindingsGroup ,
3158 Finding ,
3159 URL ,
3160 Details
3161 )
3162 SELECT 101 AS CheckID ,
3163 50 AS Priority ,
3164 'Performance' AS FindingGroup ,
3165 'CPU Schedulers Offline' AS Finding ,
3166 'https://BrentOzar.com/go/schedulers' AS URL ,
3167 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.';
3168 END;
3169
3170 IF NOT EXISTS ( SELECT 1
3171 FROM #SkipChecks
3172 WHERE DatabaseName IS NULL AND CheckID = 110 )
3173 AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_os_memory_nodes')
3174 BEGIN
3175
3176 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 110) WITH NOWAIT;
3177
3178 SET @StringToExecute = 'IF EXISTS (SELECT *
3179 FROM sys.dm_os_nodes n
3180 INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id
3181 WHERE n.node_state_desc = ''OFFLINE'')
3182 INSERT INTO #BlitzResults
3183 ( CheckID ,
3184 Priority ,
3185 FindingsGroup ,
3186 Finding ,
3187 URL ,
3188 Details
3189 )
3190 SELECT 110 AS CheckID ,
3191 50 AS Priority ,
3192 ''Performance'' AS FindingGroup ,
3193 ''Memory Nodes Offline'' AS Finding ,
3194 ''https://BrentOzar.com/go/schedulers'' AS URL ,
3195 ''Due to affinity masking or licensing problems, some of the memory may not be available.'' OPTION (RECOMPILE)';
3196
3197 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
3198 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
3199
3200 EXECUTE(@StringToExecute);
3201 END;
3202
3203 IF EXISTS ( SELECT *
3204 FROM sys.databases
3205 WHERE state > 1 )
3206 AND NOT EXISTS ( SELECT 1
3207 FROM #SkipChecks
3208 WHERE DatabaseName IS NULL AND CheckID = 102 )
3209 BEGIN
3210
3211 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 102) WITH NOWAIT;
3212
3213 INSERT INTO #BlitzResults
3214 ( CheckID ,
3215 DatabaseName ,
3216 Priority ,
3217 FindingsGroup ,
3218 Finding ,
3219 URL ,
3220 Details
3221 )
3222 SELECT 102 AS CheckID ,
3223 [name] ,
3224 20 AS Priority ,
3225 'Reliability' AS FindingGroup ,
3226 'Unusual Database State: ' + [state_desc] AS Finding ,
3227 'https://BrentOzar.com/go/repair' AS URL ,
3228 'This database may not be online.'
3229 FROM sys.databases
3230 WHERE state > 1;
3231 END;
3232
3233 IF EXISTS ( SELECT *
3234 FROM master.sys.extended_procedures )
3235 AND NOT EXISTS ( SELECT 1
3236 FROM #SkipChecks
3237 WHERE DatabaseName IS NULL AND CheckID = 105 )
3238 BEGIN
3239
3240 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 105) WITH NOWAIT;
3241
3242 INSERT INTO #BlitzResults
3243 ( CheckID ,
3244 DatabaseName ,
3245 Priority ,
3246 FindingsGroup ,
3247 Finding ,
3248 URL ,
3249 Details
3250 )
3251 SELECT 105 AS CheckID ,
3252 'master' ,
3253 200 AS Priority ,
3254 'Reliability' AS FindingGroup ,
3255 'Extended Stored Procedures in Master' AS Finding ,
3256 'https://BrentOzar.com/go/clr' AS URL ,
3257 'The [' + name
3258 + '] 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.'
3259 FROM master.sys.extended_procedures;
3260 END;
3261
3262 IF NOT EXISTS ( SELECT 1
3263 FROM #SkipChecks
3264 WHERE DatabaseName IS NULL AND CheckID = 107 )
3265 BEGIN
3266
3267 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 107) WITH NOWAIT;
3268
3269 INSERT INTO #BlitzResults
3270 ( CheckID ,
3271 Priority ,
3272 FindingsGroup ,
3273 Finding ,
3274 URL ,
3275 Details
3276 )
3277 SELECT 107 AS CheckID ,
3278 50 AS Priority ,
3279 'Performance' AS FindingGroup ,
3280 'Poison Wait Detected: ' + wait_type AS Finding ,
3281 'https://BrentOzar.com/go/poison/#' + wait_type AS URL ,
3282 CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of this wait have been recorded. This wait often indicates killer performance problems.'
3283 FROM sys.[dm_os_wait_stats]
3284 WHERE wait_type IN('IO_QUEUE_LIMIT', 'IO_RETRY', 'LOG_RATE_GOVERNOR', 'POOL_LOG_RATE_GOVERNOR', 'PREEMPTIVE_DEBUG', 'RESMGR_THROTTLED', 'RESOURCE_SEMAPHORE', 'RESOURCE_SEMAPHORE_QUERY_COMPILE','SE_REPL_CATCHUP_THROTTLE','SE_REPL_COMMIT_ACK','SE_REPL_COMMIT_TURN','SE_REPL_ROLLBACK_ACK','SE_REPL_SLOW_SECONDARY_THROTTLE','THREADPOOL')
3285 GROUP BY wait_type
3286 HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
3287 AND SUM([wait_time_ms]) > 60000;
3288 END;
3289
3290 IF NOT EXISTS ( SELECT 1
3291 FROM #SkipChecks
3292 WHERE DatabaseName IS NULL AND CheckID = 121 )
3293 BEGIN
3294
3295 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 121) WITH NOWAIT;
3296
3297 INSERT INTO #BlitzResults
3298 ( CheckID ,
3299 Priority ,
3300 FindingsGroup ,
3301 Finding ,
3302 URL ,
3303 Details
3304 )
3305 SELECT 121 AS CheckID ,
3306 50 AS Priority ,
3307 'Performance' AS FindingGroup ,
3308 'Poison Wait Detected: Serializable Locking' AS Finding ,
3309 'https://BrentOzar.com/go/serializable' AS URL ,
3310 CONVERT(VARCHAR(10), (SUM([wait_time_ms]) / 1000) / 86400) + ':' + CONVERT(VARCHAR(20), DATEADD(s, (SUM([wait_time_ms]) / 1000), 0), 108) + ' of LCK_M_R% waits have been recorded. This wait often indicates killer performance problems.'
3311 FROM sys.[dm_os_wait_stats]
3312 WHERE wait_type IN ('LCK_M_RS_S', 'LCK_M_RS_U', 'LCK_M_RIn_NL','LCK_M_RIn_S', 'LCK_M_RIn_U','LCK_M_RIn_X', 'LCK_M_RX_S', 'LCK_M_RX_U','LCK_M_RX_X')
3313 HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
3314 AND SUM([wait_time_ms]) > 60000;
3315 END;
3316
3317
3318 IF NOT EXISTS ( SELECT 1
3319 FROM #SkipChecks
3320 WHERE DatabaseName IS NULL AND CheckID = 111 )
3321 BEGIN
3322
3323 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 111) WITH NOWAIT;
3324
3325 INSERT INTO #BlitzResults
3326 ( CheckID ,
3327 Priority ,
3328 FindingsGroup ,
3329 Finding ,
3330 DatabaseName ,
3331 URL ,
3332 Details
3333 )
3334 SELECT 111 AS CheckID ,
3335 50 AS Priority ,
3336 'Reliability' AS FindingGroup ,
3337 'Possibly Broken Log Shipping' AS Finding ,
3338 d.[name] ,
3339 'https://BrentOzar.com/go/shipping' AS URL ,
3340 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.'
3341 FROM [master].sys.databases d
3342 INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id
3343 AND dm.mirroring_role IS NULL
3344 WHERE ( d.[state] = 1
3345 OR (d.[state] = 0 AND d.[is_in_standby] = 1) )
3346 AND NOT EXISTS(SELECT * FROM msdb.dbo.restorehistory rh
3347 INNER JOIN msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id
3348 WHERE d.[name] COLLATE SQL_Latin1_General_CP1_CI_AS = rh.destination_database_name COLLATE SQL_Latin1_General_CP1_CI_AS
3349 AND rh.restore_date >= DATEADD(dd, -2, GETDATE()));
3350
3351 END;
3352
3353 IF NOT EXISTS ( SELECT 1
3354 FROM #SkipChecks
3355 WHERE DatabaseName IS NULL AND CheckID = 112 )
3356 AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'change_tracking_databases')
3357 BEGIN
3358
3359 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 112) WITH NOWAIT;
3360
3361 SET @StringToExecute = 'INSERT INTO #BlitzResults
3362 (CheckID,
3363 Priority,
3364 FindingsGroup,
3365 Finding,
3366 DatabaseName,
3367 URL,
3368 Details)
3369 SELECT 112 AS CheckID,
3370 100 AS Priority,
3371 ''Performance'' AS FindingsGroup,
3372 ''Change Tracking Enabled'' AS Finding,
3373 d.[name],
3374 ''https://BrentOzar.com/go/tracking'' AS URL,
3375 ( 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)';
3376
3377 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
3378 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
3379
3380 EXECUTE(@StringToExecute);
3381 END;
3382
3383 IF NOT EXISTS ( SELECT 1
3384 FROM #SkipChecks
3385 WHERE DatabaseName IS NULL AND CheckID = 116 )
3386 AND EXISTS (SELECT * FROM msdb.sys.all_columns WHERE name = 'compressed_backup_size')
3387 BEGIN
3388
3389 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 116) WITH NOWAIT
3390
3391 SET @StringToExecute = 'INSERT INTO #BlitzResults
3392 ( CheckID ,
3393 Priority ,
3394 FindingsGroup ,
3395 Finding ,
3396 URL ,
3397 Details
3398 )
3399 SELECT 116 AS CheckID ,
3400 200 AS Priority ,
3401 ''Informational'' AS FindingGroup ,
3402 ''Backup Compression Default Off'' AS Finding ,
3403 ''https://BrentOzar.com/go/backup'' AS URL ,
3404 ''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.''
3405 FROM sys.configurations
3406 WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0
3407 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);';
3408
3409 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
3410 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
3411
3412 EXECUTE(@StringToExecute);
3413 END;
3414
3415 IF NOT EXISTS ( SELECT 1
3416 FROM #SkipChecks
3417 WHERE DatabaseName IS NULL AND CheckID = 117 )
3418 AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_exec_query_resource_semaphores')
3419 BEGIN
3420
3421 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 117) WITH NOWAIT;
3422
3423 SET @StringToExecute = 'IF 0 < (SELECT SUM([forced_grant_count]) FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL)
3424 INSERT INTO #BlitzResults
3425 (CheckID,
3426 Priority,
3427 FindingsGroup,
3428 Finding,
3429 URL,
3430 Details)
3431 SELECT 117 AS CheckID,
3432 100 AS Priority,
3433 ''Performance'' AS FindingsGroup,
3434 ''Memory Pressure Affecting Queries'' AS Finding,
3435 ''https://BrentOzar.com/go/grants'' AS URL,
3436 CAST(SUM(forced_grant_count) AS NVARCHAR(100)) + '' forced grants reported in the DMV sys.dm_exec_query_resource_semaphores, indicating memory pressure has affected query runtimes.''
3437 FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL OPTION (RECOMPILE);';
3438
3439 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
3440 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
3441
3442 EXECUTE(@StringToExecute);
3443 END;
3444
3445 IF NOT EXISTS ( SELECT 1
3446 FROM #SkipChecks
3447 WHERE DatabaseName IS NULL AND CheckID = 124 )
3448 BEGIN
3449
3450 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 124) WITH NOWAIT;
3451
3452 INSERT INTO #BlitzResults
3453 (CheckID,
3454 Priority,
3455 FindingsGroup,
3456 Finding,
3457 URL,
3458 Details)
3459 SELECT 124,
3460 150,
3461 'Performance',
3462 'Deadlocks Happening Daily',
3463 'https://BrentOzar.com/go/deadlocks',
3464 CAST(CAST(p.cntr_value / @DaysUptime AS BIGINT) AS NVARCHAR(100)) + ' average deadlocks per day. To find them, run sp_BlitzLock.' AS Details
3465 FROM sys.dm_os_performance_counters p
3466 INNER JOIN sys.databases d ON d.name = 'tempdb'
3467 WHERE RTRIM(p.counter_name) = 'Number of Deadlocks/sec'
3468 AND RTRIM(p.instance_name) = '_Total'
3469 AND p.cntr_value > 0
3470 AND (1.0 * p.cntr_value / NULLIF(datediff(DD,create_date,CURRENT_TIMESTAMP),0)) > 10;
3471 END;
3472
3473 IF DATEADD(mi, -15, GETDATE()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time)
3474 AND NOT EXISTS ( SELECT 1
3475 FROM #SkipChecks
3476 WHERE DatabaseName IS NULL AND CheckID = 125 )
3477 BEGIN
3478
3479 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 125) WITH NOWAIT;
3480
3481 DECLARE @user_perm_sql NVARCHAR(MAX) = N'';
3482 DECLARE @user_perm_gb_out DECIMAL(38,2);
3483
3484 IF @ProductVersionMajor >= 11
3485
3486 BEGIN
3487
3488 SET @user_perm_sql += N'
3489 SELECT @user_perm_gb = CASE WHEN (pages_kb / 128.0 / 1024.) >= 2.
3490 THEN CONVERT(DECIMAL(38, 2), (pages_kb / 128.0 / 1024.))
3491 ELSE NULL
3492 END
3493 FROM sys.dm_os_memory_clerks
3494 WHERE type = ''USERSTORE_TOKENPERM''
3495 AND name = ''TokenAndPermUserStore''
3496 ';
3497
3498 END
3499
3500 IF @ProductVersionMajor < 11
3501
3502 BEGIN
3503 SET @user_perm_sql += N'
3504 SELECT @user_perm_gb = CASE WHEN ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.) >= 2.
3505 THEN CONVERT(DECIMAL(38, 2), ((single_pages_kb + multi_pages_kb) / 1024.0 / 1024.))
3506 ELSE NULL
3507 END
3508 FROM sys.dm_os_memory_clerks
3509 WHERE type = ''USERSTORE_TOKENPERM''
3510 AND name = ''TokenAndPermUserStore''
3511 ';
3512
3513 END
3514
3515 EXEC sys.sp_executesql @user_perm_sql,
3516 N'@user_perm_gb DECIMAL(38,2) OUTPUT',
3517 @user_perm_gb = @user_perm_gb_out OUTPUT
3518
3519 INSERT INTO #BlitzResults
3520 (CheckID,
3521 Priority,
3522 FindingsGroup,
3523 Finding,
3524 URL,
3525 Details)
3526 SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'https://BrentOzar.com/askbrent/plan-cache-erased-recently/',
3527 'The oldest query in the plan cache was created at ' + CAST(creation_time AS NVARCHAR(50))
3528 + CASE WHEN @user_perm_gb_out IS NULL
3529 THEN '. Someone ran DBCC FREEPROCCACHE, restarted SQL Server, or it is under horrific memory pressure.'
3530 ELSE '. You also have ' + CONVERT(NVARCHAR(20), @user_perm_gb_out) + ' GB of USERSTORE_TOKENPERM, which could indicate unusual memory consumption.'
3531 END
3532 FROM sys.dm_exec_query_stats WITH (NOLOCK)
3533 ORDER BY creation_time;
3534 END;
3535
3536 IF EXISTS (SELECT * FROM sys.configurations WHERE name = 'priority boost' AND (value = 1 OR value_in_use = 1))
3537 AND NOT EXISTS ( SELECT 1
3538 FROM #SkipChecks
3539 WHERE DatabaseName IS NULL AND CheckID = 126 )
3540 BEGIN
3541
3542 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 126) WITH NOWAIT;
3543
3544 INSERT INTO #BlitzResults
3545 (CheckID,
3546 Priority,
3547 FindingsGroup,
3548 Finding,
3549 URL,
3550 Details)
3551 VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'https://BrentOzar.com/go/priorityboost/',
3552 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.');
3553 END;
3554
3555 IF NOT EXISTS ( SELECT 1
3556 FROM #SkipChecks
3557 WHERE DatabaseName IS NULL AND CheckID = 128 )
3558 AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
3559 BEGIN
3560
3561 IF (@ProductVersionMajor = 15 AND @ProductVersionMinor < 2000) OR
3562 (@ProductVersionMajor = 14 AND @ProductVersionMinor < 1000) OR
3563 (@ProductVersionMajor = 13 AND @ProductVersionMinor < 5026) OR
3564 (@ProductVersionMajor = 12 AND @ProductVersionMinor < 6024) OR
3565 (@ProductVersionMajor = 11 AND @ProductVersionMinor < 7001) OR
3566 (@ProductVersionMajor = 10.5 /*AND @ProductVersionMinor < 6000*/) OR
3567 (@ProductVersionMajor = 10 /*AND @ProductVersionMinor < 6000*/) OR
3568 (@ProductVersionMajor = 9 /*AND @ProductVersionMinor <= 5000*/)
3569 BEGIN
3570
3571 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 128) WITH NOWAIT;
3572
3573 INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
3574 VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'https://BrentOzar.com/go/unsupported',
3575 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) +
3576 CASE WHEN @ProductVersionMajor >= 11 THEN
3577 '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.'
3578 ELSE ' is no longer supported by Microsoft. You should be making plans to upgrade to a modern version of SQL Server.' END);
3579 END;
3580
3581 END;
3582
3583 /* Reliability - Dangerous Build of SQL Server (Corruption) */
3584 IF NOT EXISTS ( SELECT 1
3585 FROM #SkipChecks
3586 WHERE DatabaseName IS NULL AND CheckID = 129 )
3587 AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
3588 BEGIN
3589 IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3436) OR
3590 (@ProductVersionMajor = 11 AND @ProductVersionMinor = 5058) OR
3591 (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2342)
3592 BEGIN
3593
3594 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 129) WITH NOWAIT;
3595
3596 INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
3597 VALUES(129, 20, 'Reliability', 'Dangerous Build of SQL Server (Corruption)', 'http://sqlperformance.com/2014/06/sql-indexes/hotfix-sql-2012-rebuilds',
3598 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.');
3599 END;
3600
3601 END;
3602
3603 /* Reliability - Dangerous Build of SQL Server (Security) */
3604 IF NOT EXISTS ( SELECT 1
3605 FROM #SkipChecks
3606 WHERE DatabaseName IS NULL AND CheckID = 157 )
3607 AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
3608 BEGIN
3609 IF (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5500 AND @ProductVersionMinor <= 5512) OR
3610 (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5750 AND @ProductVersionMinor <= 5867) OR
3611 (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4000 AND @ProductVersionMinor <= 4017) OR
3612 (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4251 AND @ProductVersionMinor <= 4319) OR
3613 (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3129) OR
3614 (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3300 AND @ProductVersionMinor <= 3447) OR
3615 (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2253) OR
3616 (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2300 AND @ProductVersionMinor <= 2370)
3617 BEGIN
3618
3619 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 157) WITH NOWAIT;
3620
3621 INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
3622 VALUES(157, 20, 'Reliability', 'Dangerous Build of SQL Server (Security)', 'https://technet.microsoft.com/en-us/library/security/MS14-044',
3623 'There are dangerous known bugs with version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + '. Check the URL for details and apply the right service pack or hotfix.');
3624 END;
3625
3626 END;
3627
3628 /* Check if SQL 2016 Standard Edition but not SP1 */
3629 IF NOT EXISTS ( SELECT 1
3630 FROM #SkipChecks
3631 WHERE DatabaseName IS NULL AND CheckID = 189 )
3632 AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
3633 BEGIN
3634 IF (@ProductVersionMajor = 13 AND @ProductVersionMinor < 4001 AND @@VERSION LIKE '%Standard Edition%')
3635 BEGIN
3636
3637 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 189) WITH NOWAIT;
3638
3639 INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
3640 VALUES(189, 100, 'Features', 'Missing Features', 'https://blogs.msdn.microsoft.com/sqlreleaseservices/sql-server-2016-service-pack-1-sp1-released/',
3641 'SQL 2016 Standard Edition is being used but not Service Pack 1. Check the URL for a list of Enterprise Features that are included in Standard Edition as of SP1.');
3642 END;
3643
3644 END;
3645
3646 /* Check if SQL 2017 but not CU3 */
3647 IF NOT EXISTS ( SELECT 1
3648 FROM #SkipChecks
3649 WHERE DatabaseName IS NULL AND CheckID = 216 )
3650 AND SERVERPROPERTY('EngineEdition') <> 8 /* Azure Managed Instances */
3651 BEGIN
3652 IF (@ProductVersionMajor = 14 AND @ProductVersionMinor < 3015)
3653 BEGIN
3654
3655 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT;
3656
3657 INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
3658 VALUES(216, 100, 'Features', 'Missing Features', 'https://support.microsoft.com/en-us/help/4041814',
3659 'SQL 2017 is being used but not Cumulative Update 3. We''d recommend patching to take advantage of increased analytics when running BlitzCache.');
3660 END;
3661
3662 END;
3663
3664 /* Cumulative Update Available */
3665 IF NOT EXISTS ( SELECT 1
3666 FROM #SkipChecks
3667 WHERE DatabaseName IS NULL AND CheckID = 217 )
3668 AND SERVERPROPERTY('EngineEdition') NOT IN (5,8) /* Azure Managed Instances and Azure SQL DB*/
3669 AND EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'SqlServerVersions' AND TABLE_TYPE = 'BASE TABLE')
3670 AND NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (128, 129, 157, 189, 216)) /* Other version checks */
3671 BEGIN
3672 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 217) WITH NOWAIT;
3673
3674 INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
3675 SELECT TOP 1 217, 100, 'Reliability', 'Cumulative Update Available', COALESCE(v.Url, 'https://SQLServerUpdates.com/'),
3676 v.MinorVersionName + ' was released on ' + CAST(CONVERT(DATETIME, v.ReleaseDate, 112) AS VARCHAR(100))
3677 FROM dbo.SqlServerVersions v
3678 WHERE v.MajorVersionNumber = @ProductVersionMajor
3679 AND v.MinorVersionNumber > @ProductVersionMinor
3680 ORDER BY v.MinorVersionNumber DESC;
3681 END;
3682
3683 /* Performance - High Memory Use for In-Memory OLTP (Hekaton) */
3684 IF NOT EXISTS ( SELECT 1
3685 FROM #SkipChecks
3686 WHERE DatabaseName IS NULL AND CheckID = 145 )
3687 AND EXISTS ( SELECT *
3688 FROM sys.all_objects o
3689 WHERE o.name = 'dm_db_xtp_table_memory_stats' )
3690 BEGIN
3691
3692 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 145) WITH NOWAIT;
3693
3694 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
3695 SELECT 145 AS CheckID,
3696 10 AS Priority,
3697 ''Performance'' AS FindingsGroup,
3698 ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding,
3699 ''https://BrentOzar.com/go/hekaton'' AS URL,
3700 CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton). Microsoft recommends having 2X your Hekaton table space available in memory just for Hekaton, with a max of 250GB of in-memory data regardless of your server memory capacity.'' AS Details
3701 FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP''
3702 WHERE c.name = ''max server memory (MB)''
3703 GROUP BY c.value_in_use
3704 HAVING CAST(value_in_use AS DECIMAL(38,2)) * .25 < SUM(mem.pages_kb / 1024.0)
3705 OR SUM(mem.pages_kb / 1024.0) > 250000 OPTION (RECOMPILE)';
3706
3707 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
3708 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
3709
3710 EXECUTE(@StringToExecute);
3711 END;
3712
3713 /* Performance - In-Memory OLTP (Hekaton) In Use */
3714 IF NOT EXISTS ( SELECT 1
3715 FROM #SkipChecks
3716 WHERE DatabaseName IS NULL AND CheckID = 146 )
3717 AND EXISTS ( SELECT *
3718 FROM sys.all_objects o
3719 WHERE o.name = 'dm_db_xtp_table_memory_stats' )
3720 BEGIN
3721
3722 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 146) WITH NOWAIT;
3723
3724 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
3725 SELECT 146 AS CheckID,
3726 200 AS Priority,
3727 ''Performance'' AS FindingsGroup,
3728 ''In-Memory OLTP (Hekaton) In Use'' AS Finding,
3729 ''https://BrentOzar.com/go/hekaton'' AS URL,
3730 CAST(CAST((SUM(mem.pages_kb / 1024.0) / CAST(value_in_use AS INT) * 100) AS INT) AS NVARCHAR(100)) + ''% of your '' + CAST(CAST((CAST(value_in_use AS DECIMAL(38,1)) / 1024) AS MONEY) AS NVARCHAR(100)) + ''GB of your max server memory is being used for in-memory OLTP tables (Hekaton).'' AS Details
3731 FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP''
3732 WHERE c.name = ''max server memory (MB)''
3733 GROUP BY c.value_in_use
3734 HAVING SUM(mem.pages_kb / 1024.0) > 10 OPTION (RECOMPILE)';
3735
3736 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
3737 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
3738
3739 EXECUTE(@StringToExecute);
3740 END;
3741
3742 /* In-Memory OLTP (Hekaton) - Transaction Errors */
3743 IF NOT EXISTS ( SELECT 1
3744 FROM #SkipChecks
3745 WHERE DatabaseName IS NULL AND CheckID = 147 )
3746 AND EXISTS ( SELECT *
3747 FROM sys.all_objects o
3748 WHERE o.name = 'dm_xtp_transaction_stats' )
3749 BEGIN
3750
3751 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 147) WITH NOWAIT
3752
3753 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
3754 SELECT 147 AS CheckID,
3755 100 AS Priority,
3756 ''In-Memory OLTP (Hekaton)'' AS FindingsGroup,
3757 ''Transaction Errors'' AS Finding,
3758 ''https://BrentOzar.com/go/hekaton'' AS URL,
3759 ''Since restart: '' + CAST(validation_failures AS NVARCHAR(100)) + '' validation failures, '' + CAST(dependencies_failed AS NVARCHAR(100)) + '' dependency failures, '' + CAST(write_conflicts AS NVARCHAR(100)) + '' write conflicts, '' + CAST(unique_constraint_violations AS NVARCHAR(100)) + '' unique constraint violations.'' AS Details
3760 FROM sys.dm_xtp_transaction_stats
3761 WHERE validation_failures <> 0
3762 OR dependencies_failed <> 0
3763 OR write_conflicts <> 0
3764 OR unique_constraint_violations <> 0 OPTION (RECOMPILE);';
3765
3766 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
3767 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
3768
3769 EXECUTE(@StringToExecute);
3770 END;
3771
3772 /* Reliability - Database Files on Network File Shares */
3773 IF NOT EXISTS ( SELECT 1
3774 FROM #SkipChecks
3775 WHERE DatabaseName IS NULL AND CheckID = 148 )
3776 BEGIN
3777
3778 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 148) WITH NOWAIT;
3779
3780 INSERT INTO #BlitzResults
3781 ( CheckID ,
3782 DatabaseName ,
3783 Priority ,
3784 FindingsGroup ,
3785 Finding ,
3786 URL ,
3787 Details
3788 )
3789 SELECT DISTINCT 148 AS CheckID ,
3790 d.[name] AS DatabaseName ,
3791 170 AS Priority ,
3792 'Reliability' AS FindingsGroup ,
3793 'Database Files on Network File Shares' AS Finding ,
3794 'https://BrentOzar.com/go/nas' AS URL ,
3795 ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details
3796 FROM sys.databases d
3797 INNER JOIN sys.master_files mf ON d.database_id = mf.database_id
3798 WHERE mf.physical_name LIKE '\\%'
3799 AND d.name NOT IN ( SELECT DISTINCT
3800 DatabaseName
3801 FROM #SkipChecks
3802 WHERE CheckID IS NULL OR CheckID = 148);
3803 END;
3804
3805 /* Reliability - Database Files Stored in Azure */
3806 IF NOT EXISTS ( SELECT 1
3807 FROM #SkipChecks
3808 WHERE DatabaseName IS NULL AND CheckID = 149 )
3809 BEGIN
3810
3811 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 149) WITH NOWAIT;
3812
3813 INSERT INTO #BlitzResults
3814 ( CheckID ,
3815 DatabaseName ,
3816 Priority ,
3817 FindingsGroup ,
3818 Finding ,
3819 URL ,
3820 Details
3821 )
3822 SELECT DISTINCT 149 AS CheckID ,
3823 d.[name] AS DatabaseName ,
3824 170 AS Priority ,
3825 'Reliability' AS FindingsGroup ,
3826 'Database Files Stored in Azure' AS Finding ,
3827 'https://BrentOzar.com/go/azurefiles' AS URL ,
3828 ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details
3829 FROM sys.databases d
3830 INNER JOIN sys.master_files mf ON d.database_id = mf.database_id
3831 WHERE mf.physical_name LIKE 'http://%'
3832 AND d.name NOT IN ( SELECT DISTINCT
3833 DatabaseName
3834 FROM #SkipChecks
3835 WHERE CheckID IS NULL OR CheckID = 149);
3836 END;
3837
3838 /* Reliability - Errors Logged Recently in the Default Trace */
3839
3840 /* First, let's check that there aren't any issues with the trace files */
3841 BEGIN TRY
3842
3843 INSERT INTO #fnTraceGettable
3844 ( TextData ,
3845 DatabaseName ,
3846 EventClass ,
3847 Severity ,
3848 StartTime ,
3849 EndTime ,
3850 Duration ,
3851 NTUserName ,
3852 NTDomainName ,
3853 HostName ,
3854 ApplicationName ,
3855 LoginName ,
3856 DBUserName
3857 )
3858 SELECT TOP 20000
3859 CONVERT(NVARCHAR(4000),t.TextData) ,
3860 t.DatabaseName ,
3861 t.EventClass ,
3862 t.Severity ,
3863 t.StartTime ,
3864 t.EndTime ,
3865 t.Duration ,
3866 t.NTUserName ,
3867 t.NTDomainName ,
3868 t.HostName ,
3869 t.ApplicationName ,
3870 t.LoginName ,
3871 t.DBUserName
3872 FROM sys.fn_trace_gettable(@base_tracefilename, DEFAULT) t
3873 WHERE
3874 (
3875 t.EventClass = 22
3876 AND t.Severity >= 17
3877 AND t.StartTime > DATEADD(dd, -30, GETDATE())
3878 )
3879 OR
3880 (
3881 t.EventClass IN (92, 93)
3882 AND t.StartTime > DATEADD(dd, -30, GETDATE())
3883 AND t.Duration > 15000000
3884 )
3885 OR
3886 (
3887 t.EventClass IN (94, 95, 116)
3888 )
3889
3890 SET @TraceFileIssue = 0
3891
3892 END TRY
3893 BEGIN CATCH
3894
3895 SET @TraceFileIssue = 1
3896
3897 END CATCH
3898
3899 IF @TraceFileIssue = 1
3900 BEGIN
3901 IF NOT EXISTS ( SELECT 1
3902 FROM #SkipChecks
3903 WHERE DatabaseName IS NULL AND CheckID = 199 )
3904
3905 INSERT INTO #BlitzResults
3906 ( CheckID ,
3907 DatabaseName ,
3908 Priority ,
3909 FindingsGroup ,
3910 Finding ,
3911 URL ,
3912 Details
3913 )
3914 SELECT
3915 '199' AS CheckID ,
3916 '' AS DatabaseName ,
3917 50 AS Priority ,
3918 'Reliability' AS FindingsGroup ,
3919 'There Is An Error With The Default Trace' AS Finding ,
3920 'https://BrentOzar.com/go/defaulttrace' AS URL ,
3921 'Somebody has been messing with your trace files. Check the files are present at ' + @base_tracefilename AS Details
3922 END
3923
3924 IF NOT EXISTS ( SELECT 1
3925 FROM #SkipChecks
3926 WHERE DatabaseName IS NULL AND CheckID = 150 )
3927 AND @base_tracefilename IS NOT NULL
3928 AND @TraceFileIssue = 0
3929 BEGIN
3930
3931 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 150) WITH NOWAIT;
3932
3933 INSERT INTO #BlitzResults
3934 ( CheckID ,
3935 DatabaseName ,
3936 Priority ,
3937 FindingsGroup ,
3938 Finding ,
3939 URL ,
3940 Details
3941 )
3942 SELECT DISTINCT 150 AS CheckID ,
3943 t.DatabaseName,
3944 50 AS Priority ,
3945 'Reliability' AS FindingsGroup ,
3946 'Errors Logged Recently in the Default Trace' AS Finding ,
3947 'https://BrentOzar.com/go/defaulttrace' AS URL ,
3948 CAST(t.TextData AS NVARCHAR(4000)) AS Details
3949 FROM #fnTraceGettable t
3950 WHERE t.EventClass = 22
3951 /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */
3952 --AND t.Severity >= 17
3953 --AND t.StartTime > DATEADD(dd, -30, GETDATE());
3954 END;
3955
3956 /* Performance - File Growths Slow */
3957 IF NOT EXISTS ( SELECT 1
3958 FROM #SkipChecks
3959 WHERE DatabaseName IS NULL AND CheckID = 151 )
3960 AND @base_tracefilename IS NOT NULL
3961 AND @TraceFileIssue = 0
3962 BEGIN
3963
3964 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 151) WITH NOWAIT;
3965
3966 INSERT INTO #BlitzResults
3967 ( CheckID ,
3968 DatabaseName ,
3969 Priority ,
3970 FindingsGroup ,
3971 Finding ,
3972 URL ,
3973 Details
3974 )
3975 SELECT DISTINCT 151 AS CheckID ,
3976 t.DatabaseName,
3977 50 AS Priority ,
3978 'Performance' AS FindingsGroup ,
3979 'File Growths Slow' AS Finding ,
3980 'https://BrentOzar.com/go/filegrowth' AS URL ,
3981 CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting file autogrowth to a smaller increment.' AS Details
3982 FROM #fnTraceGettable t
3983 WHERE t.EventClass IN (92, 93)
3984 /* Removed these as they're unnecessary, we filter this when inserting data into #fnTraceGettable */
3985 --AND t.StartTime > DATEADD(dd, -30, GETDATE())
3986 --AND t.Duration > 15000000
3987 GROUP BY t.DatabaseName
3988 HAVING COUNT(*) > 1;
3989 END;
3990
3991 /* Performance - Many Plans for One Query */
3992 IF NOT EXISTS ( SELECT 1
3993 FROM #SkipChecks
3994 WHERE DatabaseName IS NULL AND CheckID = 160 )
3995 AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'query_hash')
3996 BEGIN
3997
3998 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 160) WITH NOWAIT;
3999
4000 SET @StringToExecute = N'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
4001 SELECT TOP 1 160 AS CheckID,
4002 100 AS Priority,
4003 ''Performance'' AS FindingsGroup,
4004 ''Many Plans for One Query'' AS Finding,
4005 ''https://BrentOzar.com/go/parameterization'' AS URL,
4006 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
4007 FROM sys.dm_exec_query_stats qs
4008 CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa
4009 WHERE pa.attribute = ''dbid''
4010 GROUP BY qs.query_hash, pa.value
4011 HAVING COUNT(DISTINCT plan_handle) > ';
4012
4013 IF 50 > (SELECT COUNT(*) FROM sys.databases)
4014 SET @StringToExecute = @StringToExecute + N' 50 ';
4015 ELSE
4016 SELECT @StringToExecute = @StringToExecute + CAST(COUNT(*) * 2 AS NVARCHAR(50)) FROM sys.databases;
4017
4018 SET @StringToExecute = @StringToExecute + N' ORDER BY COUNT(DISTINCT plan_handle) DESC OPTION (RECOMPILE);';
4019
4020 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
4021 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
4022
4023 EXECUTE(@StringToExecute);
4024 END;
4025
4026 /* Performance - High Number of Cached Plans */
4027 IF NOT EXISTS ( SELECT 1
4028 FROM #SkipChecks
4029 WHERE DatabaseName IS NULL AND CheckID = 161 )
4030 BEGIN
4031
4032 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 161) WITH NOWAIT;
4033
4034 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
4035 SELECT TOP 1 161 AS CheckID,
4036 100 AS Priority,
4037 ''Performance'' AS FindingsGroup,
4038 ''High Number of Cached Plans'' AS Finding,
4039 ''https://BrentOzar.com/go/planlimits'' AS URL,
4040 ''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
4041 FROM sys.dm_os_memory_cache_hash_tables ht
4042 INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type
4043 where ht.name IN ( ''SQL Plans'' , ''Object Plans'' , ''Bound Trees'' )
4044 AND cc.entries_count >= (3 * ht.buckets_count) OPTION (RECOMPILE)';
4045
4046 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
4047 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
4048
4049 EXECUTE(@StringToExecute);
4050 END;
4051
4052 /* Performance - Too Much Free Memory */
4053 IF NOT EXISTS ( SELECT 1
4054 FROM #SkipChecks
4055 WHERE DatabaseName IS NULL AND CheckID = 165 )
4056 BEGIN
4057
4058 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 165) WITH NOWAIT;
4059
4060 INSERT INTO #BlitzResults
4061 (CheckID,
4062 Priority,
4063 FindingsGroup,
4064 Finding,
4065 URL,
4066 Details)
4067 SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'https://BrentOzar.com/go/freememory',
4068 CAST((CAST(cFree.cntr_value AS BIGINT) / 1024 / 1024 ) AS NVARCHAR(100)) + N'GB of free memory inside SQL Server''s buffer pool, which is ' + CAST((CAST(cTotal.cntr_value AS BIGINT) / 1024 / 1024) AS NVARCHAR(100)) + N'GB. You would think lots of free memory would be good, but check out the URL for more information.' AS Details
4069 FROM sys.dm_os_performance_counters cFree
4070 INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%'
4071 AND cTotal.counter_name = N'Total Server Memory (KB) '
4072 WHERE cFree.object_name LIKE N'%Memory Manager%'
4073 AND cFree.counter_name = N'Free Memory (KB) '
4074 AND CAST(cTotal.cntr_value AS BIGINT) > 20480000000
4075 AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT)
4076 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%';
4077
4078 END;
4079
4080 /* Outdated sp_Blitz - sp_Blitz is Over 6 Months Old */
4081 IF NOT EXISTS ( SELECT 1
4082 FROM #SkipChecks
4083 WHERE DatabaseName IS NULL AND CheckID = 155 )
4084 AND DATEDIFF(MM, @VersionDate, GETDATE()) > 6
4085 BEGIN
4086
4087 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 155) WITH NOWAIT;
4088
4089 INSERT INTO #BlitzResults
4090 ( CheckID ,
4091 Priority ,
4092 FindingsGroup ,
4093 Finding ,
4094 URL ,
4095 Details
4096 )
4097 SELECT 155 AS CheckID ,
4098 0 AS Priority ,
4099 'Outdated sp_Blitz' AS FindingsGroup ,
4100 'sp_Blitz is Over 6 Months Old' AS Finding ,
4101 'http://FirstResponderKit.org/' AS URL ,
4102 '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;
4103 END;
4104
4105 /* Populate a list of database defaults. I'm doing this kind of oddly -
4106 it reads like a lot of work, but this way it compiles & runs on all
4107 versions of SQL Server.
4108 */
4109
4110 IF @Debug IN (1, 2) RAISERROR('Generating database defaults.', 0, 1) WITH NOWAIT;
4111
4112 INSERT INTO #DatabaseDefaults
4113 SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4114 FROM sys.all_columns
4115 WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases');
4116 INSERT INTO #DatabaseDefaults
4117 SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4118 FROM sys.all_columns
4119 WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases');
4120 INSERT INTO #DatabaseDefaults
4121 SELECT 'is_read_committed_snapshot_on',
4122 CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 1 ELSE 0 END, /* RCSI is always enabled in Azure SQL DB per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
4123 133, 210, CASE WHEN SERVERPROPERTY('EngineEdition') = 5 THEN 'Read Committed Snapshot Isolation Disabled' ELSE 'Read Committed Snapshot Isolation Enabled' END, 'https://BrentOzar.com/go/dbdefaults', NULL
4124 FROM sys.all_columns
4125 WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases');
4126 INSERT INTO #DatabaseDefaults
4127 SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4128 FROM sys.all_columns
4129 WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases');
4130 INSERT INTO #DatabaseDefaults
4131 SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4132 FROM sys.all_columns
4133 WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases');
4134 INSERT INTO #DatabaseDefaults
4135 SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4136 FROM sys.all_columns
4137 WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases');
4138 INSERT INTO #DatabaseDefaults
4139 SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4140 FROM sys.all_columns
4141 WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases');
4142 INSERT INTO #DatabaseDefaults
4143 SELECT 'is_broker_enabled', 0, 230, 210, 'Broker Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4144 FROM sys.all_columns
4145 WHERE name = 'is_broker_enabled' AND object_id = OBJECT_ID('sys.databases');
4146 INSERT INTO #DatabaseDefaults
4147 SELECT 'is_honor_broker_priority_on', 0, 231, 210, 'Honor Broker Priority Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4148 FROM sys.all_columns
4149 WHERE name = 'is_honor_broker_priority_on' AND object_id = OBJECT_ID('sys.databases');
4150 INSERT INTO #DatabaseDefaults
4151 SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4152 FROM sys.all_columns
4153 WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases');
4154 /* Not alerting for this since we actually want it and we have a separate check for it:
4155 INSERT INTO #DatabaseDefaults
4156 SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4157 FROM sys.all_columns
4158 WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases');
4159 */
4160 INSERT INTO #DatabaseDefaults
4161 SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4162 FROM sys.all_columns
4163 WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases');
4164 INSERT INTO #DatabaseDefaults
4165 SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4166 FROM sys.all_columns
4167 WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases');
4168 INSERT INTO #DatabaseDefaults
4169 SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'https://BrentOzar.com/go/dbdefaults', NULL
4170 FROM sys.all_columns
4171 WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases');
4172 INSERT INTO #DatabaseDefaults
4173 SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4174 FROM sys.all_columns
4175 WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases');
4176 INSERT INTO #DatabaseDefaults
4177 SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'https://BrentOzar.com/go/dbdefaults', NULL
4178 FROM sys.all_columns
4179 WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases')
4180 AND SERVERPROPERTY('EngineEdition') <> 8; /* Hekaton is always enabled in Managed Instances per https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/1919 */
4181
4182 DECLARE DatabaseDefaultsLoop CURSOR FOR
4183 SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details
4184 FROM #DatabaseDefaults;
4185
4186 OPEN DatabaseDefaultsLoop;
4187 FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails;
4188 WHILE @@FETCH_STATUS = 0
4189 BEGIN
4190
4191 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, @CurrentCheckID) WITH NOWAIT;
4192
4193 /* Target Recovery Time (142) can be either 0 or 60 due to a number of bugs */
4194 IF @CurrentCheckID = 142
4195 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
4196 SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + '''
4197 FROM sys.databases d
4198 WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] NOT IN (0, 60) OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);';
4199 ELSE
4200 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
4201 SELECT ' + CAST(@CurrentCheckID AS NVARCHAR(200)) + ', d.[name], ' + CAST(@CurrentPriority AS NVARCHAR(200)) + ', ''Non-Default Database Config'', ''' + @CurrentFinding + ''',''' + @CurrentURL + ''',''' + COALESCE(@CurrentDetails, 'This database setting is not the default.') + '''
4202 FROM sys.databases d
4203 WHERE d.database_id > 4 AND d.state <> 1 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL) OPTION (RECOMPILE);';
4204
4205 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
4206 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
4207
4208 EXEC (@StringToExecute);
4209
4210 FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails;
4211 END;
4212
4213 CLOSE DatabaseDefaultsLoop;
4214 DEALLOCATE DatabaseDefaultsLoop;
4215
4216
4217/*This checks to see if Agent is Offline*/
4218IF @ProductVersionMajor >= 10
4219 AND NOT EXISTS ( SELECT 1
4220 FROM #SkipChecks
4221 WHERE DatabaseName IS NULL AND CheckID = 167 )
4222 BEGIN
4223 IF EXISTS ( SELECT 1
4224 FROM sys.all_objects
4225 WHERE name = 'dm_server_services' )
4226 BEGIN
4227
4228 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 167) WITH NOWAIT;
4229
4230 INSERT INTO [#BlitzResults]
4231 ( [CheckID] ,
4232 [Priority] ,
4233 [FindingsGroup] ,
4234 [Finding] ,
4235 [URL] ,
4236 [Details] )
4237
4238 SELECT
4239 167 AS [CheckID] ,
4240 250 AS [Priority] ,
4241 'Server Info' AS [FindingsGroup] ,
4242 'Agent is Currently Offline' AS [Finding] ,
4243 '' AS [URL] ,
4244 ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.'
4245 ) AS [Details]
4246 FROM
4247 [sys].[dm_server_services]
4248 WHERE [status_desc] <> 'Running'
4249 AND [servicename] LIKE 'SQL Server Agent%'
4250 AND CAST(SERVERPROPERTY('Edition') AS VARCHAR(1000)) NOT LIKE '%xpress%';
4251
4252 END;
4253 END;
4254
4255/*This checks to see if the Full Text thingy is offline*/
4256IF @ProductVersionMajor >= 10
4257 AND NOT EXISTS ( SELECT 1
4258 FROM #SkipChecks
4259 WHERE DatabaseName IS NULL AND CheckID = 168 )
4260 BEGIN
4261 IF EXISTS ( SELECT 1
4262 FROM sys.all_objects
4263 WHERE name = 'dm_server_services' )
4264 BEGIN
4265
4266 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 168) WITH NOWAIT;
4267
4268 INSERT INTO [#BlitzResults]
4269 ( [CheckID] ,
4270 [Priority] ,
4271 [FindingsGroup] ,
4272 [Finding] ,
4273 [URL] ,
4274 [Details] )
4275
4276 SELECT
4277 168 AS [CheckID] ,
4278 250 AS [Priority] ,
4279 'Server Info' AS [FindingsGroup] ,
4280 'Full-text Filter Daemon Launcher is Currently Offline' AS [Finding] ,
4281 '' AS [URL] ,
4282 ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.'
4283 ) AS [Details]
4284 FROM
4285 [sys].[dm_server_services]
4286 WHERE [status_desc] <> 'Running'
4287 AND [servicename] LIKE 'SQL Full-text Filter Daemon Launcher%';
4288
4289 END;
4290 END;
4291
4292/*This checks which service account SQL Server is running as.*/
4293IF @ProductVersionMajor >= 10
4294 AND NOT EXISTS ( SELECT 1
4295 FROM #SkipChecks
4296 WHERE DatabaseName IS NULL AND CheckID = 169 )
4297
4298 BEGIN
4299 IF EXISTS ( SELECT 1
4300 FROM sys.all_objects
4301 WHERE name = 'dm_server_services' )
4302 BEGIN
4303
4304 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 169) WITH NOWAIT;
4305
4306 INSERT INTO [#BlitzResults]
4307 ( [CheckID] ,
4308 [Priority] ,
4309 [FindingsGroup] ,
4310 [Finding] ,
4311 [URL] ,
4312 [Details] )
4313
4314 SELECT
4315 169 AS [CheckID] ,
4316 250 AS [Priority] ,
4317 'Informational' AS [FindingsGroup] ,
4318 'SQL Server is running under an NT Service account' AS [Finding] ,
4319 'https://BrentOzar.com/go/setup' AS [URL] ,
4320 ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.'
4321 ) AS [Details]
4322 FROM
4323 [sys].[dm_server_services]
4324 WHERE [service_account] LIKE 'NT Service%'
4325 AND [servicename] LIKE 'SQL Server%'
4326 AND [servicename] NOT LIKE 'SQL Server Agent%';
4327
4328 END;
4329 END;
4330
4331/*This checks which service account SQL Agent is running as.*/
4332IF @ProductVersionMajor >= 10
4333 AND NOT EXISTS ( SELECT 1
4334 FROM #SkipChecks
4335 WHERE DatabaseName IS NULL AND CheckID = 170 )
4336
4337 BEGIN
4338 IF EXISTS ( SELECT 1
4339 FROM sys.all_objects
4340 WHERE name = 'dm_server_services' )
4341 BEGIN
4342
4343 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 170) WITH NOWAIT;
4344
4345 INSERT INTO [#BlitzResults]
4346 ( [CheckID] ,
4347 [Priority] ,
4348 [FindingsGroup] ,
4349 [Finding] ,
4350 [URL] ,
4351 [Details] )
4352
4353 SELECT
4354 170 AS [CheckID] ,
4355 250 AS [Priority] ,
4356 'Informational' AS [FindingsGroup] ,
4357 'SQL Server Agent is running under an NT Service account' AS [Finding] ,
4358 'https://BrentOzar.com/go/setup' AS [URL] ,
4359 ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.'
4360 ) AS [Details]
4361 FROM
4362 [sys].[dm_server_services]
4363 WHERE [service_account] LIKE 'NT Service%'
4364 AND [servicename] LIKE 'SQL Server Agent%';
4365
4366 END;
4367 END;
4368
4369/*This checks that First Responder Kit is consistent.
4370It assumes that all the objects of the kit resides in the same database, the one in which this SP is stored
4371It also is ready to check for installation in another schema.
4372*/
4373IF(
4374 NOT EXISTS (
4375 SELECT 1
4376 FROM #SkipChecks
4377 WHERE DatabaseName IS NULL AND CheckID = 226
4378 )
4379)
4380BEGIN
4381
4382 IF @Debug IN (1, 2) RAISERROR('Running check with id %d',0,1,2000);
4383
4384 SET @spBlitzFullName = QUOTENAME(DB_NAME()) + '.' +QUOTENAME(OBJECT_SCHEMA_NAME(@@PROCID)) + '.' + QUOTENAME(OBJECT_NAME(@@PROCID));
4385 SET @BlitzIsOutdatedComparedToOthers = 0;
4386 SET @tsql = NULL;
4387 SET @VersionCheckModeExistsTSQL = NULL;
4388 SET @BlitzProcDbName = DB_NAME();
4389 SET @ExecRet = NULL;
4390 SET @InnerExecRet = NULL;
4391 SET @TmpCnt = NULL;
4392
4393 SET @PreviousComponentName = NULL;
4394 SET @PreviousComponentFullPath = NULL;
4395 SET @CurrentStatementId = NULL;
4396 SET @CurrentComponentSchema = NULL;
4397 SET @CurrentComponentName = NULL;
4398 SET @CurrentComponentType = NULL;
4399 SET @CurrentComponentVersionDate = NULL;
4400 SET @CurrentComponentFullName = NULL;
4401 SET @CurrentComponentMandatory = NULL;
4402 SET @MaximumVersionDate = NULL;
4403
4404 SET @StatementCheckName = NULL;
4405 SET @StatementOutputsCounter = NULL;
4406 SET @OutputCounterExpectedValue = NULL;
4407 SET @StatementOutputsExecRet = NULL;
4408 SET @StatementOutputsDateTime = NULL;
4409
4410 SET @CurrentComponentMandatoryCheckOK = NULL;
4411 SET @CurrentComponentVersionCheckModeOK = NULL;
4412
4413 SET @canExitLoop = 0;
4414 SET @frkIsConsistent = 0;
4415
4416
4417 SET @tsql = 'USE ' + QUOTENAME(@BlitzProcDbName) + ';' + @crlf +
4418 'WITH FRKComponents (' + @crlf +
4419 ' ObjectName,' + @crlf +
4420 ' ObjectType,' + @crlf +
4421 ' MandatoryComponent' + @crlf +
4422 ')' + @crlf +
4423 'AS (' + @crlf +
4424 ' SELECT ''sp_AllNightLog'',''P'' ,0' + @crlf +
4425 ' UNION ALL' + @crlf +
4426 ' SELECT ''sp_AllNightLog_Setup'', ''P'',0' + @crlf +
4427 ' UNION ALL ' + @crlf +
4428 ' SELECT ''sp_Blitz'',''P'',0' + @crlf +
4429 ' UNION ALL ' + @crlf +
4430 ' SELECT ''sp_BlitzBackups'',''P'',0' + @crlf +
4431 ' UNION ALL ' + @crlf +
4432 ' SELECT ''sp_BlitzCache'',''P'',0' + @crlf +
4433 ' UNION ALL ' + @crlf +
4434 ' SELECT ''sp_BlitzFirst'',''P'',0' + @crlf +
4435 ' UNION ALL' + @crlf +
4436 ' SELECT ''sp_BlitzIndex'',''P'',0' + @crlf +
4437 ' UNION ALL ' + @crlf +
4438 ' SELECT ''sp_BlitzLock'',''P'',0' + @crlf +
4439 ' UNION ALL ' + @crlf +
4440 ' SELECT ''sp_BlitzQueryStore'',''P'',0' + @crlf +
4441 ' UNION ALL ' + @crlf +
4442 ' SELECT ''sp_BlitzWho'',''P'',0' + @crlf +
4443 ' UNION ALL ' + @crlf +
4444 ' SELECT ''sp_DatabaseRestore'',''P'',0' + @crlf +
4445 ' UNION ALL ' + @crlf +
4446 ' SELECT ''sp_ineachdb'',''P'',0' + @crlf +
4447 ' UNION ALL' + @crlf +
4448 ' SELECT ''SqlServerVersions'',''U'',0' + @crlf +
4449 ')' + @crlf +
4450 'INSERT INTO #FRKObjects (' + @crlf +
4451 ' DatabaseName,ObjectSchemaName,ObjectName, ObjectType,MandatoryComponent' + @crlf +
4452 ')' + @crlf +
4453 'SELECT DB_NAME(),SCHEMA_NAME(o.schema_id), c.ObjectName,c.ObjectType,c.MandatoryComponent' + @crlf +
4454 'FROM ' + @crlf +
4455 ' FRKComponents c' + @crlf +
4456 'LEFT JOIN ' + @crlf +
4457 ' sys.objects o' + @crlf +
4458 'ON c.ObjectName = o.[name]' + @crlf +
4459 'AND c.ObjectType = o.[type]' + @crlf +
4460 --'WHERE o.schema_id IS NOT NULL' + @crlf +
4461 ';'
4462 ;
4463
4464 EXEC @ExecRet = sp_executesql @tsql ;
4465
4466 -- TODO: add check for statement success
4467
4468 -- TODO: based on SP requirements and presence (SchemaName is not null) ==> update MandatoryComponent column
4469
4470 -- Filling #StatementsToRun4FRKVersionCheck
4471 INSERT INTO #StatementsToRun4FRKVersionCheck (
4472 CheckName,StatementText,SubjectName,SubjectFullPath, StatementOutputsCounter,OutputCounterExpectedValue,StatementOutputsExecRet,StatementOutputsDateTime
4473 )
4474 SELECT
4475 'Mandatory',
4476 'SELECT @cnt = COUNT(*) FROM #FRKObjects WHERE ObjectSchemaName IS NULL AND ObjectName = ''' + ObjectName + ''' AND MandatoryComponent = 1;',
4477 ObjectName,
4478 QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName),
4479 1,
4480 0,
4481 0,
4482 0
4483 FROM #FRKObjects
4484 UNION ALL
4485 SELECT
4486 'VersionCheckMode',
4487 'SELECT @cnt = COUNT(*) FROM ' +
4488 QUOTENAME(DatabaseName) + '.sys.all_parameters ' +
4489 'where object_id = OBJECT_ID(''' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ''') AND [name] = ''@VersionCheckMode'';',
4490 ObjectName,
4491 QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName),
4492 1,
4493 1,
4494 0,
4495 0
4496 FROM #FRKObjects
4497 WHERE ObjectType = 'P'
4498 AND ObjectSchemaName IS NOT NULL
4499 UNION ALL
4500 SELECT
4501 'VersionCheck',
4502 'EXEC @ExecRet = ' + QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName) + ' @VersionCheckMode = 1 , @VersionDate = @ObjDate OUTPUT;',
4503 ObjectName,
4504 QUOTENAME(DatabaseName) + '.' + QUOTENAME(ObjectSchemaName) + '.' + QUOTENAME(ObjectName),
4505 0,
4506 0,
4507 1,
4508 1
4509 FROM #FRKObjects
4510 WHERE ObjectType = 'P'
4511 AND ObjectSchemaName IS NOT NULL
4512 ;
4513 IF(@Debug in (1,2))
4514 BEGIN
4515 SELECT *
4516 FROM #StatementsToRun4FRKVersionCheck ORDER BY SubjectName,SubjectFullPath,StatementId -- in case of schema change ;
4517 END;
4518
4519
4520 -- loop on queries...
4521 WHILE(@canExitLoop = 0)
4522 BEGIN
4523 SET @CurrentStatementId = NULL;
4524
4525 SELECT TOP 1
4526 @StatementCheckName = CheckName,
4527 @CurrentStatementId = StatementId ,
4528 @CurrentComponentName = SubjectName,
4529 @CurrentComponentFullName = SubjectFullPath,
4530 @tsql = StatementText,
4531 @StatementOutputsCounter = StatementOutputsCounter,
4532 @OutputCounterExpectedValue = OutputCounterExpectedValue ,
4533 @StatementOutputsExecRet = StatementOutputsExecRet,
4534 @StatementOutputsDateTime = StatementOutputsDateTime
4535 FROM #StatementsToRun4FRKVersionCheck
4536 ORDER BY SubjectName, SubjectFullPath,StatementId /* in case of schema change */
4537 ;
4538
4539 -- loop exit condition
4540 IF(@CurrentStatementId IS NULL)
4541 BEGIN
4542 BREAK;
4543 END;
4544
4545 IF @Debug IN (1, 2) RAISERROR(' Statement: %s',0,1,@tsql);
4546
4547 -- we start a new component
4548 IF(@PreviousComponentName IS NULL OR
4549 (@PreviousComponentName IS NOT NULL AND @PreviousComponentName <> @CurrentComponentName) OR
4550 (@PreviousComponentName IS NOT NULL AND @PreviousComponentName = @CurrentComponentName AND @PreviousComponentFullPath <> @CurrentComponentFullName)
4551 )
4552 BEGIN
4553 -- reset variables
4554 SET @CurrentComponentMandatoryCheckOK = 0;
4555 SET @CurrentComponentVersionCheckModeOK = 0;
4556 SET @PreviousComponentName = @CurrentComponentName;
4557 SET @PreviousComponentFullPath = @CurrentComponentFullName ;
4558 END;
4559
4560 IF(@StatementCheckName NOT IN ('Mandatory','VersionCheckMode','VersionCheck'))
4561 BEGIN
4562 INSERT INTO #BlitzResults(
4563 CheckID ,
4564 Priority ,
4565 FindingsGroup ,
4566 Finding ,
4567 URL ,
4568 Details
4569 )
4570 SELECT
4571 226 AS CheckID ,
4572 253 AS Priority ,
4573 'First Responder Kit' AS FindingsGroup ,
4574 'Version Check Failed (code generator changed)' AS Finding ,
4575 'http://FirstResponderKit.org' AS URL ,
4576 'Download an updated First Responder Kit. Your version check failed because a change has been made to the version check code generator.' + @crlf +
4577 'Error: No handler for check with name "' + ISNULL(@StatementCheckName,'') + '"' AS Details
4578 ;
4579
4580 -- we will stop the test because it's possible to get the same message for other components
4581 SET @canExitLoop = 1;
4582 CONTINUE;
4583 END;
4584
4585 IF(@StatementCheckName = 'Mandatory')
4586 BEGIN
4587 -- outputs counter
4588 EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT;
4589
4590 IF(@ExecRet <> 0)
4591 BEGIN
4592
4593 INSERT INTO #BlitzResults(
4594 CheckID ,
4595 Priority ,
4596 FindingsGroup ,
4597 Finding ,
4598 URL ,
4599 Details
4600 )
4601 SELECT
4602 226 AS CheckID ,
4603 253 AS Priority ,
4604 'First Responder Kit' AS FindingsGroup ,
4605 'Version Check Failed (dynamic query failure)' AS Finding ,
4606 'http://FirstResponderKit.org' AS URL ,
4607 'Download an updated First Responder Kit. Your version check failed due to dynamic query failure.' + @crlf +
4608 'Error: following query failed at execution (check if component [' + ISNULL(@CurrentComponentName,@CurrentComponentName) + '] is mandatory and missing)' + @crlf +
4609 @tsql AS Details
4610 ;
4611
4612 -- we will stop the test because it's possible to get the same message for other components
4613 SET @canExitLoop = 1;
4614 CONTINUE;
4615 END;
4616
4617 IF(@TmpCnt <> @OutputCounterExpectedValue)
4618 BEGIN
4619 INSERT INTO #BlitzResults(
4620 CheckID ,
4621 Priority ,
4622 FindingsGroup ,
4623 Finding ,
4624 URL ,
4625 Details
4626 )
4627 SELECT
4628 227 AS CheckID ,
4629 253 AS Priority ,
4630 'First Responder Kit' AS FindingsGroup ,
4631 'Component Missing: ' + @CurrentComponentName AS Finding ,
4632 'http://FirstResponderKit.org' AS URL ,
4633 'Download an updated version of the First Responder Kit to install it.' AS Details
4634 ;
4635
4636 -- as it's missing, no value for SubjectFullPath
4637 DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectName = @CurrentComponentName ;
4638 CONTINUE;
4639 END;
4640
4641 SET @CurrentComponentMandatoryCheckOK = 1;
4642 END;
4643
4644 IF(@StatementCheckName = 'VersionCheckMode')
4645 BEGIN
4646 IF(@CurrentComponentMandatoryCheckOK = 0)
4647 BEGIN
4648 INSERT INTO #BlitzResults(
4649 CheckID ,
4650 Priority ,
4651 FindingsGroup ,
4652 Finding ,
4653 URL ,
4654 Details
4655 )
4656 SELECT
4657 226 AS CheckID ,
4658 253 AS Priority ,
4659 'First Responder Kit' AS FindingsGroup ,
4660 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding ,
4661 'http://FirstResponderKit.org' AS URL ,
4662 'Download an updated First Responder Kit. Version check failed because "Mandatory" check has not been completed before for current component' + @crlf +
4663 'Error: version check mode happenned before "Mandatory" check for component called "' + @CurrentComponentFullName + '"'
4664 ;
4665
4666 -- we will stop the test because it's possible to get the same message for other components
4667 SET @canExitLoop = 1;
4668 CONTINUE;
4669 END;
4670
4671 -- outputs counter
4672 EXEC @ExecRet = sp_executesql @tsql, N'@cnt INT OUTPUT',@cnt = @TmpCnt OUTPUT;
4673
4674 IF(@ExecRet <> 0)
4675 BEGIN
4676 INSERT INTO #BlitzResults(
4677 CheckID ,
4678 Priority ,
4679 FindingsGroup ,
4680 Finding ,
4681 URL ,
4682 Details
4683 )
4684 SELECT
4685 226 AS CheckID ,
4686 253 AS Priority ,
4687 'First Responder Kit' AS FindingsGroup ,
4688 'Version Check Failed (dynamic query failure)' AS Finding ,
4689 'http://FirstResponderKit.org' AS URL ,
4690 'Download an updated First Responder Kit. Version check failed because a change has been made to the code generator.' + @crlf +
4691 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] can run in VersionCheckMode)' + @crlf +
4692 @tsql AS Details
4693 ;
4694
4695 -- we will stop the test because it's possible to get the same message for other components
4696 SET @canExitLoop = 1;
4697 CONTINUE;
4698 END;
4699
4700 IF(@TmpCnt <> @OutputCounterExpectedValue)
4701 BEGIN
4702 INSERT INTO #BlitzResults(
4703 CheckID ,
4704 Priority ,
4705 FindingsGroup ,
4706 Finding ,
4707 URL ,
4708 Details
4709 )
4710 SELECT
4711 228 AS CheckID ,
4712 253 AS Priority ,
4713 'First Responder Kit' AS FindingsGroup ,
4714 'Component Outdated: ' + @CurrentComponentFullName AS Finding ,
4715 'http://FirstResponderKit.org' AS URL ,
4716 'Download an updated First Responder Kit. Component ' + @CurrentComponentFullName + ' is not at the minimum version required to run this procedure' + @crlf +
4717 'VersionCheckMode has been introduced in component version date after "20190320". This means its version is lower than or equal to that date.' AS Details;
4718 ;
4719
4720 DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ;
4721 CONTINUE;
4722 END;
4723
4724 SET @CurrentComponentVersionCheckModeOK = 1;
4725 END;
4726
4727 IF(@StatementCheckName = 'VersionCheck')
4728 BEGIN
4729 IF(@CurrentComponentMandatoryCheckOK = 0 OR @CurrentComponentVersionCheckModeOK = 0)
4730 BEGIN
4731 INSERT INTO #BlitzResults(
4732 CheckID ,
4733 Priority ,
4734 FindingsGroup ,
4735 Finding ,
4736 URL ,
4737 Details
4738 )
4739 SELECT
4740 226 AS CheckID ,
4741 253 AS Priority ,
4742 'First Responder Kit' AS FindingsGroup ,
4743 'Version Check Failed (unexpectedly modified checks ordering)' AS Finding ,
4744 'http://FirstResponderKit.org' AS URL ,
4745 'Download an updated First Responder Kit. Version check failed because "VersionCheckMode" check has not been completed before for component called "' + @CurrentComponentFullName + '"' + @crlf +
4746 'Error: VersionCheck happenned before "VersionCheckMode" check for component called "' + @CurrentComponentFullName + '"'
4747 ;
4748
4749 -- we will stop the test because it's possible to get the same message for other components
4750 SET @canExitLoop = 1;
4751 CONTINUE;
4752 END;
4753
4754 EXEC @ExecRet = sp_executesql @tsql , N'@ExecRet INT OUTPUT, @ObjDate DATETIME OUTPUT', @ExecRet = @InnerExecRet OUTPUT, @ObjDate = @CurrentComponentVersionDate OUTPUT;
4755
4756 IF(@ExecRet <> 0)
4757 BEGIN
4758 INSERT INTO #BlitzResults(
4759 CheckID ,
4760 Priority ,
4761 FindingsGroup ,
4762 Finding ,
4763 URL ,
4764 Details
4765 )
4766 SELECT
4767 226 AS CheckID ,
4768 253 AS Priority ,
4769 'First Responder Kit' AS FindingsGroup ,
4770 'Version Check Failed (dynamic query failure)' AS Finding ,
4771 'http://FirstResponderKit.org' AS URL ,
4772 'Download an updated First Responder Kit. The version check failed because a change has been made to the code generator.' + @crlf +
4773 'Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf +
4774 @tsql AS Details
4775 ;
4776
4777 -- we will stop the test because it's possible to get the same message for other components
4778 SET @canExitLoop = 1;
4779 CONTINUE;
4780 END;
4781
4782
4783 IF(@InnerExecRet <> 0)
4784 BEGIN
4785 INSERT INTO #BlitzResults(
4786 CheckID ,
4787 Priority ,
4788 FindingsGroup ,
4789 Finding ,
4790 URL ,
4791 Details
4792 )
4793 SELECT
4794 226 AS CheckID ,
4795 253 AS Priority ,
4796 'First Responder Kit' AS FindingsGroup ,
4797 'Version Check Failed (Failed dynamic SP call to ' + @CurrentComponentFullName + ')' AS Finding ,
4798 'http://FirstResponderKit.org' AS URL ,
4799 'Download an updated First Responder Kit. Error: following query failed at execution (check if component [' + @CurrentComponentFullName + '] is at the expected version)' + @crlf +
4800 'Return code: ' + CONVERT(VARCHAR(10),@InnerExecRet) + @crlf +
4801 'T-SQL Query: ' + @crlf +
4802 @tsql AS Details
4803 ;
4804
4805 -- advance to next component
4806 DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ;
4807 CONTINUE;
4808 END;
4809
4810 IF(@CurrentComponentVersionDate < @VersionDate)
4811 BEGIN
4812
4813 INSERT INTO #BlitzResults(
4814 CheckID ,
4815 Priority ,
4816 FindingsGroup ,
4817 Finding ,
4818 URL ,
4819 Details
4820 )
4821 SELECT
4822 228 AS CheckID ,
4823 253 AS Priority ,
4824 'First Responder Kit' AS FindingsGroup ,
4825 'Component Outdated: ' + @CurrentComponentFullName AS Finding ,
4826 'http://FirstResponderKit.org' AS URL ,
4827 'Download and install the latest First Responder Kit - you''re running some older code, and it doesn''t get better with age.' AS Details
4828 ;
4829
4830 RAISERROR('Component %s is outdated',10,1,@CurrentComponentFullName);
4831 -- advance to next component
4832 DELETE FROM #StatementsToRun4FRKVersionCheck WHERE SubjectFullPath = @CurrentComponentFullName ;
4833 CONTINUE;
4834 END;
4835
4836 ELSE IF(@CurrentComponentVersionDate > @VersionDate AND @BlitzIsOutdatedComparedToOthers = 0)
4837 BEGIN
4838 SET @BlitzIsOutdatedComparedToOthers = 1;
4839 RAISERROR('Procedure %s is outdated',10,1,@spBlitzFullName);
4840 IF(@MaximumVersionDate IS NULL OR @MaximumVersionDate < @CurrentComponentVersionDate)
4841 BEGIN
4842 SET @MaximumVersionDate = @CurrentComponentVersionDate;
4843 END;
4844 END;
4845 /* Kept for debug purpose:
4846 ELSE
4847 BEGIN
4848 INSERT INTO #BlitzResults(
4849 CheckID ,
4850 Priority ,
4851 FindingsGroup ,
4852 Finding ,
4853 URL ,
4854 Details
4855 )
4856 SELECT
4857 2000 AS CheckID ,
4858 250 AS Priority ,
4859 'Informational' AS FindingsGroup ,
4860 'First Responder kit component ' + @CurrentComponentFullName + ' is at the expected version' AS Finding ,
4861 'https://www.BrentOzar.com/blitz/' AS URL ,
4862 'Version date is: ' + CONVERT(VARCHAR(32),@CurrentComponentVersionDate,121) AS Details
4863 ;
4864 END;
4865 */
4866 END;
4867
4868 -- could be performed differently to minimize computation
4869 DELETE FROM #StatementsToRun4FRKVersionCheck WHERE StatementId = @CurrentStatementId ;
4870 END;
4871END;
4872
4873
4874/*This counts memory dumps and gives min and max date of in view*/
4875IF @ProductVersionMajor >= 10
4876 AND NOT (@ProductVersionMajor = 10.5 AND @ProductVersionMinor < 4297) /* Skip due to crash bug: https://support.microsoft.com/en-us/help/2908087 */
4877 AND NOT EXISTS ( SELECT 1
4878 FROM #SkipChecks
4879 WHERE DatabaseName IS NULL AND CheckID = 171 )
4880 BEGIN
4881 IF EXISTS ( SELECT 1
4882 FROM sys.all_objects
4883 WHERE name = 'dm_server_memory_dumps' )
4884 BEGIN
4885 IF 5 <= (SELECT COUNT(*) FROM [sys].[dm_server_memory_dumps] WHERE [creation_time] >= DATEADD(YEAR, -1, GETDATE()))
4886
4887 BEGIN
4888
4889 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 171) WITH NOWAIT;
4890
4891 INSERT INTO [#BlitzResults]
4892 ( [CheckID] ,
4893 [Priority] ,
4894 [FindingsGroup] ,
4895 [Finding] ,
4896 [URL] ,
4897 [Details] )
4898
4899 SELECT
4900 171 AS [CheckID] ,
4901 20 AS [Priority] ,
4902 'Reliability' AS [FindingsGroup] ,
4903 'Memory Dumps Have Occurred' AS [Finding] ,
4904 'https://BrentOzar.com/go/dump' AS [URL] ,
4905 ( 'That ain''t good. I''ve had ' +
4906 CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' +
4907 CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) +
4908 ' and ' +
4909 CAST(CAST(MAX([creation_time]) AS DATETIME) AS VARCHAR(100)) +
4910 '!'
4911 ) AS [Details]
4912 FROM
4913 [sys].[dm_server_memory_dumps]
4914 WHERE [creation_time] >= DATEADD(year, -1, GETDATE());
4915
4916 END;
4917 END;
4918 END;
4919
4920/*Checks to see if you're on Developer or Evaluation*/
4921 IF NOT EXISTS ( SELECT 1
4922 FROM #SkipChecks
4923 WHERE DatabaseName IS NULL AND CheckID = 173 )
4924 BEGIN
4925
4926 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 173) WITH NOWAIT;
4927
4928 INSERT INTO [#BlitzResults]
4929 ( [CheckID] ,
4930 [Priority] ,
4931 [FindingsGroup] ,
4932 [Finding] ,
4933 [URL] ,
4934 [Details] )
4935
4936 SELECT
4937 173 AS [CheckID] ,
4938 200 AS [Priority] ,
4939 'Licensing' AS [FindingsGroup] ,
4940 'Non-Production License' AS [Finding] ,
4941 'https://BrentOzar.com/go/licensing' AS [URL] ,
4942 ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' +
4943 CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) +
4944 ' the good folks at Microsoft might get upset with you. Better start counting those cores.'
4945 ) AS [Details]
4946 WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Developer%'
4947 OR CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Evaluation%';
4948
4949 END;
4950
4951/*Checks to see if Buffer Pool Extensions are in use*/
4952 IF @ProductVersionMajor >= 12
4953 AND NOT EXISTS ( SELECT 1
4954 FROM #SkipChecks
4955 WHERE DatabaseName IS NULL AND CheckID = 174 )
4956 BEGIN
4957
4958 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 174) WITH NOWAIT;
4959
4960 INSERT INTO [#BlitzResults]
4961 ( [CheckID] ,
4962 [Priority] ,
4963 [FindingsGroup] ,
4964 [Finding] ,
4965 [URL] ,
4966 [Details] )
4967
4968 SELECT
4969 174 AS [CheckID] ,
4970 200 AS [Priority] ,
4971 'Performance' AS [FindingsGroup] ,
4972 'Buffer Pool Extensions Enabled' AS [Finding] ,
4973 'https://BrentOzar.com/go/bpe' AS [URL] ,
4974 ( 'You have Buffer Pool Extensions enabled, and one lives here: ' +
4975 [path] +
4976 '. It''s currently ' +
4977 CASE WHEN [current_size_in_kb] / 1024. / 1024. > 0
4978 THEN CAST([current_size_in_kb] / 1024. / 1024. AS VARCHAR(100))
4979 + ' GB'
4980 ELSE CAST([current_size_in_kb] / 1024. AS VARCHAR(100))
4981 + ' MB'
4982 END +
4983 '. Did you know that BPEs only provide single threaded access 8KB (one page) at a time?'
4984 ) AS [Details]
4985 FROM sys.dm_os_buffer_pool_extension_configuration
4986 WHERE [state_description] <> 'BUFFER POOL EXTENSION DISABLED';
4987
4988 END;
4989
4990/*Check for too many tempdb files*/
4991 IF NOT EXISTS ( SELECT 1
4992 FROM #SkipChecks
4993 WHERE DatabaseName IS NULL AND CheckID = 175 )
4994 BEGIN
4995
4996 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 175) WITH NOWAIT;
4997
4998 INSERT INTO #BlitzResults
4999 ( CheckID ,
5000 DatabaseName ,
5001 Priority ,
5002 FindingsGroup ,
5003 Finding ,
5004 URL ,
5005 Details
5006 )
5007 SELECT DISTINCT
5008 175 AS CheckID ,
5009 'TempDB' AS DatabaseName ,
5010 170 AS Priority ,
5011 'File Configuration' AS FindingsGroup ,
5012 'TempDB Has >16 Data Files' AS Finding ,
5013 'https://BrentOzar.com/go/tempdb' AS URL ,
5014 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + '. Did you forget to terminate a loop somewhere?' AS Details
5015 FROM sys.[master_files] AS [mf]
5016 WHERE [mf].[database_id] = 2 AND [mf].[type] = 0
5017 HAVING COUNT_BIG(*) > 16;
5018 END;
5019
5020 IF NOT EXISTS ( SELECT 1
5021 FROM #SkipChecks
5022 WHERE DatabaseName IS NULL AND CheckID = 176 )
5023 BEGIN
5024
5025 IF EXISTS ( SELECT 1
5026 FROM sys.all_objects
5027 WHERE name = 'dm_xe_sessions' )
5028
5029 BEGIN
5030
5031 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 176) WITH NOWAIT;
5032
5033 INSERT INTO #BlitzResults
5034 ( CheckID ,
5035 DatabaseName ,
5036 Priority ,
5037 FindingsGroup ,
5038 Finding ,
5039 URL ,
5040 Details
5041 )
5042 SELECT DISTINCT
5043 176 AS CheckID ,
5044 '' AS DatabaseName ,
5045 200 AS Priority ,
5046 'Monitoring' AS FindingsGroup ,
5047 'Extended Events Hyperextension' AS Finding ,
5048 'https://BrentOzar.com/go/xe' AS URL ,
5049 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR(30)) + ' Extended Events sessions running. You sure you meant to do that?' AS Details
5050 FROM sys.dm_xe_sessions
5051 WHERE [name] NOT IN
5052 ( 'AlwaysOn_health',
5053 'system_health',
5054 'telemetry_xevents',
5055 'sp_server_diagnostics',
5056 'sp_server_diagnostics session',
5057 'hkenginexesession' )
5058 AND name NOT LIKE '%$A%'
5059 HAVING COUNT_BIG(*) >= 2;
5060 END;
5061 END;
5062
5063 /*Harmful startup parameter*/
5064 IF NOT EXISTS ( SELECT 1
5065 FROM #SkipChecks
5066 WHERE DatabaseName IS NULL AND CheckID = 177 )
5067 BEGIN
5068
5069 IF EXISTS ( SELECT 1
5070 FROM sys.all_objects
5071 WHERE name = 'dm_server_registry' )
5072
5073 BEGIN
5074
5075 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 177) WITH NOWAIT;
5076
5077 INSERT INTO #BlitzResults
5078 ( CheckID ,
5079 DatabaseName ,
5080 Priority ,
5081 FindingsGroup ,
5082 Finding ,
5083 URL ,
5084 Details
5085 )
5086 SELECT DISTINCT
5087 177 AS CheckID ,
5088 '' AS DatabaseName ,
5089 5 AS Priority ,
5090 'Monitoring' AS FindingsGroup ,
5091 'Disabled Internal Monitoring Features' AS Finding ,
5092 'https://msdn.microsoft.com/en-us/library/ms190737.aspx' AS URL ,
5093 'You have -x as a startup parameter. You should head to the URL and read more about what it does to your system.' AS Details
5094 FROM
5095 [sys].[dm_server_registry] AS [dsr]
5096 WHERE
5097 [dsr].[registry_key] LIKE N'%MSSQLServer\Parameters'
5098 AND [dsr].[value_data] = '-x';;
5099 END;
5100 END;
5101
5102
5103 /* Reliability - Dangerous Third Party Modules - 179 */
5104 IF NOT EXISTS ( SELECT 1
5105 FROM #SkipChecks
5106 WHERE DatabaseName IS NULL AND CheckID = 179 )
5107 BEGIN
5108
5109 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 179) WITH NOWAIT;
5110
5111 INSERT INTO [#BlitzResults]
5112 ( [CheckID] ,
5113 [Priority] ,
5114 [FindingsGroup] ,
5115 [Finding] ,
5116 [URL] ,
5117 [Details] )
5118
5119 SELECT
5120 179 AS [CheckID] ,
5121 5 AS [Priority] ,
5122 'Reliability' AS [FindingsGroup] ,
5123 'Dangerous Third Party Modules' AS [Finding] ,
5124 'https://support.microsoft.com/en-us/kb/2033238' AS [URL] ,
5125 ( COALESCE(company, '') + ' - ' + COALESCE(description, '') + ' - ' + COALESCE(name, '') + ' - suspected dangerous third party module is installed.') AS [Details]
5126 FROM sys.dm_os_loaded_modules
5127 WHERE UPPER(name) LIKE UPPER('%\ENTAPI.DLL') /* McAfee VirusScan Enterprise */
5128 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 */
5129 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 */
5130 OR UPPER(name) LIKE UPPER('%\PIOLEDB.DLL') OR UPPER(name) LIKE UPPER('%\PISDK.DLL'); /* OSISoft PI data access */
5131
5132 END;
5133
5134 /*Find shrink database tasks*/
5135
5136 IF NOT EXISTS ( SELECT 1
5137 FROM #SkipChecks
5138 WHERE DatabaseName IS NULL AND CheckID = 180 )
5139 AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */
5140 BEGIN
5141
5142 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 180) WITH NOWAIT;
5143
5144 WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts])
5145 ,[maintenance_plan_steps] AS (
5146 SELECT [name]
5147 , [id] -- ID required to link maintenace plan with jobs and jobhistory (sp_Blitz Issue #776)
5148 , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml]
5149 FROM [msdb].[dbo].[sysssispackages]
5150 WHERE [packagetype] = 6
5151 )
5152 INSERT INTO [#BlitzResults]
5153 ( [CheckID] ,
5154 [Priority] ,
5155 [FindingsGroup] ,
5156 [Finding] ,
5157 [URL] ,
5158 [Details] )
5159 SELECT
5160 180 AS [CheckID] ,
5161 -- sp_Blitz Issue #776
5162 -- Job has history and was executed in the last 30 days
5163 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
5164 100
5165 ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled)
5166 200
5167 END AS Priority,
5168 'Performance' AS [FindingsGroup] ,
5169 'Shrink Database Step In Maintenance Plan' AS [Finding] ,
5170 'https://BrentOzar.com/go/autoshrink' AS [URL] ,
5171 'The maintenance plan ' + [mps].[name] + ' has a step to shrink databases in it. Shrinking databases is as outdated as maintenance plans.'
5172 + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS [Details]
5173 FROM [maintenance_plan_steps] [mps]
5174 CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c])
5175 join msdb.dbo.sysmaintplan_subplans as sms
5176 on mps.id = sms.plan_id
5177 JOIN msdb.dbo.sysjobs j
5178 on sms.job_id = j.job_id
5179 LEFT OUTER JOIN msdb.dbo.sysjobsteps AS step
5180 ON j.job_id = step.job_id
5181 LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc
5182 ON j.job_id = sjsc.job_id
5183 LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc
5184 ON sjsc.schedule_id = ssc.schedule_id
5185 AND sjsc.job_id = j.job_id
5186 LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh
5187 ON j.job_id = sjh.job_id
5188 AND step.step_id = sjh.step_id
5189 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
5190 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
5191 WHERE [c].[value]('(@dts:ObjectName)', 'VARCHAR(128)') = 'Shrink Database Task';
5192
5193 END;
5194
5195 /*Find repetitive maintenance tasks*/
5196 IF NOT EXISTS ( SELECT 1
5197 FROM #SkipChecks
5198 WHERE DatabaseName IS NULL AND CheckID = 181 )
5199 AND CONVERT(VARCHAR(128), SERVERPROPERTY ('productversion')) LIKE '1%' /* Only run on 2008+ */
5200 BEGIN
5201 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 181) WITH NOWAIT;
5202
5203 WITH XMLNAMESPACES ('www.microsoft.com/SqlServer/Dts' AS [dts])
5204 ,[maintenance_plan_steps] AS (
5205 SELECT [name]
5206 , CAST(CAST([packagedata] AS VARBINARY(MAX)) AS XML) AS [maintenance_plan_xml]
5207 FROM [msdb].[dbo].[sysssispackages]
5208 WHERE [packagetype] = 6
5209 ), [maintenance_plan_table] AS (
5210 SELECT [mps].[name]
5211 ,[c].[value]('(@dts:ObjectName)', 'NVARCHAR(128)') AS [step_name]
5212 FROM [maintenance_plan_steps] [mps]
5213 CROSS APPLY [maintenance_plan_xml].[nodes]('//dts:Executables/dts:Executable') [t]([c])
5214 ), [mp_steps_pretty] AS (SELECT DISTINCT [m1].[name] ,
5215 STUFF((SELECT N', ' + [m2].[step_name] FROM [maintenance_plan_table] AS [m2] WHERE [m1].[name] = [m2].[name]
5216 FOR XML PATH(N'')), 1, 2, N'') AS [maintenance_plan_steps]
5217 FROM [maintenance_plan_table] AS [m1])
5218
5219 INSERT INTO [#BlitzResults]
5220 ( [CheckID] ,
5221 [Priority] ,
5222 [FindingsGroup] ,
5223 [Finding] ,
5224 [URL] ,
5225 [Details] )
5226
5227 SELECT
5228 181 AS [CheckID] ,
5229 100 AS [Priority] ,
5230 'Performance' AS [FindingsGroup] ,
5231 'Repetitive Steps In Maintenance Plans' AS [Finding] ,
5232 'https://ola.hallengren.com/' AS [URL] ,
5233 'The maintenance plan ' + [m].[name] + ' is doing repetitive work on indexes and statistics. Perhaps it''s time to try something more modern?' AS [Details]
5234 FROM [mp_steps_pretty] m
5235 WHERE m.[maintenance_plan_steps] LIKE '%Rebuild%Reorganize%'
5236 OR m.[maintenance_plan_steps] LIKE '%Rebuild%Update%';
5237
5238 END;
5239
5240
5241 /* Reliability - No Failover Cluster Nodes Available - 184 */
5242 IF NOT EXISTS ( SELECT 1
5243 FROM #SkipChecks
5244 WHERE DatabaseName IS NULL AND CheckID = 184 )
5245 AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '10%'
5246 AND CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128)) NOT LIKE '9%'
5247 BEGIN
5248 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT;
5249
5250 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
5251 SELECT TOP 1
5252 184 AS CheckID ,
5253 20 AS Priority ,
5254 ''Reliability'' AS FindingsGroup ,
5255 ''No Failover Cluster Nodes Available'' AS Finding ,
5256 ''https://BrentOzar.com/go/node'' AS URL ,
5257 ''There are no failover cluster nodes available if the active node fails'' AS Details
5258 FROM (
5259 SELECT SUM(CASE WHEN [status] = 0 AND [is_current_owner] = 0 THEN 1 ELSE 0 END) AS [available_nodes]
5260 FROM sys.dm_os_cluster_nodes
5261 ) a
5262 WHERE [available_nodes] < 1 OPTION (RECOMPILE)';
5263
5264 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
5265 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
5266
5267 EXECUTE(@StringToExecute);
5268 END;
5269
5270 /* Reliability - TempDB File Error */
5271 IF NOT EXISTS ( SELECT 1
5272 FROM #SkipChecks
5273 WHERE DatabaseName IS NULL AND CheckID = 191 )
5274 AND (SELECT COUNT(*) FROM sys.master_files WHERE database_id = 2) <> (SELECT COUNT(*) FROM tempdb.sys.database_files)
5275 BEGIN
5276
5277 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 191) WITH NOWAIT
5278
5279 INSERT INTO [#BlitzResults]
5280 ( [CheckID] ,
5281 [Priority] ,
5282 [FindingsGroup] ,
5283 [Finding] ,
5284 [URL] ,
5285 [Details] )
5286
5287 SELECT
5288 191 AS [CheckID] ,
5289 50 AS [Priority] ,
5290 'Reliability' AS [FindingsGroup] ,
5291 'TempDB File Error' AS [Finding] ,
5292 'https://BrentOzar.com/go/tempdboops' AS [URL] ,
5293 'Mismatch between the number of TempDB files in sys.master_files versus tempdb.sys.database_files' AS [Details];
5294 END;
5295
5296/*Perf - Odd number of cores in a socket*/
5297 IF NOT EXISTS ( SELECT 1
5298 FROM #SkipChecks
5299 WHERE DatabaseName IS NULL
5300 AND CheckID = 198 )
5301 AND EXISTS ( SELECT 1
5302 FROM sys.dm_os_schedulers
5303 WHERE is_online = 1
5304 AND scheduler_id < 255
5305 AND parent_node_id < 64
5306 GROUP BY parent_node_id,
5307 is_online
5308 HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1 )
5309 BEGIN
5310
5311 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 198) WITH NOWAIT
5312
5313 INSERT INTO #BlitzResults
5314 (
5315 CheckID,
5316 DatabaseName,
5317 Priority,
5318 FindingsGroup,
5319 Finding,
5320 URL,
5321 Details
5322 )
5323 SELECT 198 AS CheckID,
5324 NULL AS DatabaseName,
5325 10 AS Priority,
5326 'Performance' AS FindingsGroup,
5327 'CPU w/Odd Number of Cores' AS Finding,
5328 'https://BrentOzar.com/go/oddity' AS URL,
5329 'Node ' + CONVERT(VARCHAR(10), parent_node_id) + ' has ' + CONVERT(VARCHAR(10), COUNT(cpu_id))
5330 + CASE WHEN COUNT(cpu_id) = 1 THEN ' core assigned to it. This is a really bad NUMA configuration.'
5331 ELSE ' cores assigned to it. This is a really bad NUMA configuration.'
5332 END AS Details
5333 FROM sys.dm_os_schedulers
5334 WHERE is_online = 1
5335 AND scheduler_id < 255
5336 AND parent_node_id < 64
5337 AND EXISTS (
5338 SELECT 1
5339 FROM ( SELECT memory_node_id, SUM(online_scheduler_count) AS schedulers
5340 FROM sys.dm_os_nodes
5341 WHERE memory_node_id < 64
5342 GROUP BY memory_node_id ) AS nodes
5343 HAVING MIN(nodes.schedulers) <> MAX(nodes.schedulers)
5344 )
5345 GROUP BY parent_node_id,
5346 is_online
5347 HAVING ( COUNT(cpu_id) + 2 ) % 2 = 1;
5348
5349 END;
5350
5351/*Begin: checking default trace for odd DBCC activity*/
5352
5353 --Grab relevant event data
5354 IF @TraceFileIssue = 0
5355 BEGIN
5356 SELECT UPPER(
5357 REPLACE(
5358 SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData), 0,
5359 ISNULL(
5360 NULLIF(
5361 CHARINDEX('(', CONVERT(NVARCHAR(MAX), t.TextData)),
5362 0),
5363 LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )) --This replaces everything up to an open paren, if one exists.
5364 , SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData),
5365 ISNULL(
5366 NULLIF(
5367 CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData))
5368 , 0),
5369 LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1),
5370 LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 )
5371 , '') --This replaces any optional WITH clause to a DBCC command, like tableresults.
5372 ) AS [dbcc_event_trunc_upper],
5373 UPPER(
5374 REPLACE(
5375 CONVERT(NVARCHAR(MAX), t.TextData), SUBSTRING(CONVERT(NVARCHAR(MAX), t.TextData),
5376 ISNULL(
5377 NULLIF(
5378 CHARINDEX(' WITH ',CONVERT(NVARCHAR(MAX), t.TextData))
5379 , 0),
5380 LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1),
5381 LEN(CONVERT(NVARCHAR(MAX), t.TextData)) + 1 ), '')) AS [dbcc_event_full_upper],
5382 MIN(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS min_start_time,
5383 MAX(t.StartTime) OVER (PARTITION BY CONVERT(NVARCHAR(128), t.TextData)) AS max_start_time,
5384 t.NTUserName AS [nt_user_name],
5385 t.NTDomainName AS [nt_domain_name],
5386 t.HostName AS [host_name],
5387 t.ApplicationName AS [application_name],
5388 t.LoginName [login_name],
5389 t.DBUserName AS [db_user_name]
5390 INTO #dbcc_events_from_trace
5391 FROM #fnTraceGettable AS t
5392 WHERE t.EventClass = 116
5393 OPTION(RECOMPILE)
5394 END;
5395
5396 /*Overall count of DBCC events excluding silly stuff*/
5397 IF NOT EXISTS ( SELECT 1
5398 FROM #SkipChecks
5399 WHERE DatabaseName IS NULL AND CheckID = 203 )
5400 AND @TraceFileIssue = 0
5401 BEGIN
5402
5403 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 203) WITH NOWAIT
5404
5405 INSERT INTO [#BlitzResults]
5406 ( [CheckID] ,
5407 [Priority] ,
5408 [FindingsGroup] ,
5409 [Finding] ,
5410 [URL] ,
5411 [Details] )
5412 SELECT 203 AS CheckID ,
5413 50 AS Priority ,
5414 'DBCC Events' AS FindingsGroup ,
5415 'Overall Events' AS Finding ,
5416 'https://www.BrentOzar.com/go/dbcc' AS URL ,
5417 CAST(COUNT(*) AS NVARCHAR(100)) + ' DBCC events have taken place between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) +
5418 '. This does not include CHECKDB and other usually benign DBCC events.'
5419 AS Details
5420 FROM #dbcc_events_from_trace d
5421 /* This WHERE clause below looks horrible, but it's because users can run stuff like
5422 DBCC LOGINFO
5423 with lots of spaces (or carriage returns, or comments) in between the DBCC and the
5424 command they're trying to run. See Github issues 1062, 1074, 1075.
5425 */
5426 WHERE d.dbcc_event_full_upper NOT LIKE '%DBCC%ADDINSTANCE%'
5427 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%AUTOPILOT%'
5428 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKALLOC%'
5429 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCATALOG%'
5430 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKCONSTRAINTS%'
5431 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKDB%'
5432 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKFILEGROUP%'
5433 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKIDENT%'
5434 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKPRIMARYFILE%'
5435 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CHECKTABLE%'
5436 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%CLEANTABLE%'
5437 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%DBINFO%'
5438 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%ERRORLOG%'
5439 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INCREMENTINSTANCE%'
5440 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%INPUTBUFFER%'
5441 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%LOGINFO%'
5442 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%OPENTRAN%'
5443 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SETINSTANCE%'
5444 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOWFILESTATS%'
5445 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SHOW_STATISTICS%'
5446 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%NETSTATS%'
5447 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%SQLPERF%LOGSPACE%'
5448 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEON%'
5449 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACEOFF%'
5450 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%TRACESTATUS%'
5451 AND d.dbcc_event_full_upper NOT LIKE '%DBCC%USEROPTIONS%'
5452 AND d.application_name NOT LIKE 'Critical Care(R) Collector'
5453 AND d.application_name NOT LIKE '%Red Gate Software Ltd SQL Prompt%'
5454 AND d.application_name NOT LIKE '%Spotlight Diagnostic Server%'
5455 AND d.application_name NOT LIKE '%SQL Diagnostic Manager%'
5456 AND d.application_name NOT LIKE 'SQL Server Checkup%'
5457 AND d.application_name NOT LIKE '%Sentry%'
5458 AND d.application_name NOT LIKE '%LiteSpeed%'
5459 AND d.application_name NOT LIKE '%SQL Monitor - Monitoring%'
5460
5461
5462 HAVING COUNT(*) > 0;
5463
5464 END;
5465
5466 /*Check for someone running drop clean buffers*/
5467 IF NOT EXISTS ( SELECT 1
5468 FROM #SkipChecks
5469 WHERE DatabaseName IS NULL AND CheckID = 207 )
5470 AND @TraceFileIssue = 0
5471 BEGIN
5472
5473 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 207) WITH NOWAIT
5474
5475 INSERT INTO [#BlitzResults]
5476 ( [CheckID] ,
5477 [Priority] ,
5478 [FindingsGroup] ,
5479 [Finding] ,
5480 [URL] ,
5481 [Details] )
5482 SELECT 207 AS CheckID ,
5483 10 AS Priority ,
5484 'Performance' AS FindingsGroup ,
5485 'DBCC DROPCLEANBUFFERS Ran Recently' AS Finding ,
5486 '' AS URL ,
5487 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC DROPCLEANBUFFERS ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) +
5488 '. If this is a production box, know that you''re clearing all data out of memory when this happens. What kind of monster would do that?'
5489 AS Details
5490 FROM #dbcc_events_from_trace d
5491 WHERE d.dbcc_event_full_upper = N'DBCC DROPCLEANBUFFERS'
5492 GROUP BY COALESCE(d.nt_user_name, d.login_name)
5493 HAVING COUNT(*) > 0;
5494
5495 END;
5496
5497 /*Check for someone running free proc cache*/
5498 IF NOT EXISTS ( SELECT 1
5499 FROM #SkipChecks
5500 WHERE DatabaseName IS NULL AND CheckID = 208 )
5501 AND @TraceFileIssue = 0
5502 BEGIN
5503
5504 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 208) WITH NOWAIT
5505
5506 INSERT INTO [#BlitzResults]
5507 ( [CheckID] ,
5508 [Priority] ,
5509 [FindingsGroup] ,
5510 [Finding] ,
5511 [URL] ,
5512 [Details] )
5513 SELECT 208 AS CheckID ,
5514 10 AS Priority ,
5515 'DBCC Events' AS FindingsGroup ,
5516 'DBCC FREEPROCCACHE Ran Recently' AS Finding ,
5517 '' AS URL ,
5518 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC FREEPROCCACHE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) +
5519 '. This has bad idea jeans written all over its butt, like most other bad idea jeans.'
5520 AS Details
5521 FROM #dbcc_events_from_trace d
5522 WHERE d.dbcc_event_full_upper = N'DBCC FREEPROCCACHE'
5523 GROUP BY COALESCE(d.nt_user_name, d.login_name)
5524 HAVING COUNT(*) > 0;
5525
5526 END;
5527
5528 /*Check for someone clearing wait stats*/
5529 IF NOT EXISTS ( SELECT 1
5530 FROM #SkipChecks
5531 WHERE DatabaseName IS NULL AND CheckID = 205 )
5532 AND @TraceFileIssue = 0
5533 BEGIN
5534
5535 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 205) WITH NOWAIT
5536
5537 INSERT INTO [#BlitzResults]
5538 ( [CheckID] ,
5539 [Priority] ,
5540 [FindingsGroup] ,
5541 [Finding] ,
5542 [URL] ,
5543 [Details] )
5544 SELECT 205 AS CheckID ,
5545 50 AS Priority ,
5546 'Performance' AS FindingsGroup ,
5547 'Wait Stats Cleared Recently' AS Finding ,
5548 '' AS URL ,
5549 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR) ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) +
5550 '. Why are you clearing wait stats? What are you hiding?'
5551 AS Details
5552 FROM #dbcc_events_from_trace d
5553 WHERE d.dbcc_event_full_upper = N'DBCC SQLPERF(''SYS.DM_OS_WAIT_STATS'',CLEAR)'
5554 GROUP BY COALESCE(d.nt_user_name, d.login_name)
5555 HAVING COUNT(*) > 0;
5556
5557 END;
5558
5559 /*Check for someone writing to pages. Yeah, right?*/
5560 IF NOT EXISTS ( SELECT 1
5561 FROM #SkipChecks
5562 WHERE DatabaseName IS NULL AND CheckID = 209 )
5563 AND @TraceFileIssue = 0
5564 BEGIN
5565
5566 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 209) WITH NOWAIT
5567
5568 INSERT INTO [#BlitzResults]
5569 ( [CheckID] ,
5570 [Priority] ,
5571 [FindingsGroup] ,
5572 [Finding] ,
5573 [URL] ,
5574 [Details] )
5575 SELECT 209 AS CheckID ,
5576 50 AS Priority ,
5577 'Reliability' AS FindingsGroup ,
5578 'DBCC WRITEPAGE Used Recently' AS Finding ,
5579 '' AS URL ,
5580 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run DBCC WRITEPAGE ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) +
5581 '. So, uh, are they trying to fix corruption, or cause corruption?'
5582 AS Details
5583 FROM #dbcc_events_from_trace d
5584 WHERE d.dbcc_event_trunc_upper = N'DBCC WRITEPAGE'
5585 GROUP BY COALESCE(d.nt_user_name, d.login_name)
5586 HAVING COUNT(*) > 0;
5587
5588 END;
5589
5590 IF NOT EXISTS ( SELECT 1
5591 FROM #SkipChecks
5592 WHERE DatabaseName IS NULL AND CheckID = 210 )
5593 AND @TraceFileIssue = 0
5594 BEGIN
5595
5596 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 210) WITH NOWAIT
5597
5598 INSERT INTO [#BlitzResults]
5599 ( [CheckID] ,
5600 [Priority] ,
5601 [FindingsGroup] ,
5602 [Finding] ,
5603 [URL] ,
5604 [Details] )
5605
5606 SELECT 210 AS CheckID ,
5607 10 AS Priority ,
5608 'Performance' AS FindingsGroup ,
5609 'DBCC SHRINK% Ran Recently' AS Finding ,
5610 '' AS URL ,
5611 'The user ' + COALESCE(d.nt_user_name, d.login_name) + ' has run file shrinks ' + CAST(COUNT(*) AS NVARCHAR(100)) + ' times between ' + CONVERT(NVARCHAR(30), MIN(d.min_start_time)) + ' and ' + CONVERT(NVARCHAR(30), MAX(d.max_start_time)) +
5612 '. So, uh, are they trying cause bad performance on purpose?'
5613 AS Details
5614 FROM #dbcc_events_from_trace d
5615 WHERE d.dbcc_event_trunc_upper LIKE N'DBCC SHRINK%'
5616 GROUP BY COALESCE(d.nt_user_name, d.login_name)
5617 HAVING COUNT(*) > 0;
5618
5619 END;
5620
5621/*End: checking default trace for odd DBCC activity*/
5622
5623 /*Begin check for autoshrink events*/
5624
5625 IF NOT EXISTS ( SELECT 1
5626 FROM #SkipChecks
5627 WHERE DatabaseName IS NULL AND CheckID = 206 )
5628 AND @TraceFileIssue = 0
5629 BEGIN
5630
5631 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 206) WITH NOWAIT
5632
5633 INSERT INTO [#BlitzResults]
5634 ( [CheckID] ,
5635 [Priority] ,
5636 [FindingsGroup] ,
5637 [Finding] ,
5638 [URL] ,
5639 [Details] )
5640
5641 SELECT 206 AS CheckID ,
5642 10 AS Priority ,
5643 'Performance' AS FindingsGroup ,
5644 'Auto-Shrink Ran Recently' AS Finding ,
5645 '' AS URL ,
5646 N'The database ' + QUOTENAME(t.DatabaseName) + N' has had '
5647 + CONVERT(NVARCHAR(10), COUNT(*))
5648 + N' auto shrink events between '
5649 + CONVERT(NVARCHAR(30), MIN(t.StartTime)) + ' and ' + CONVERT(NVARCHAR(30), MAX(t.StartTime))
5650 + ' that lasted on average '
5651 + CONVERT(NVARCHAR(10), AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime)))
5652 + ' seconds.' AS Details
5653 FROM #fnTraceGettable AS t
5654 WHERE t.EventClass IN (94, 95)
5655 GROUP BY t.DatabaseName
5656 HAVING AVG(DATEDIFF(SECOND, t.StartTime, t.EndTime)) > 5;
5657
5658 END;
5659
5660 IF NOT EXISTS ( SELECT 1
5661 FROM #SkipChecks
5662 WHERE DatabaseName IS NULL AND CheckID = 215 )
5663 AND @TraceFileIssue = 0
5664 AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'database_id' AND object_id = OBJECT_ID('sys.dm_exec_sessions'))
5665 BEGIN
5666
5667 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 215) WITH NOWAIT
5668
5669 SET @StringToExecute = 'INSERT INTO [#BlitzResults]
5670 ( [CheckID] ,
5671 [Priority] ,
5672 [FindingsGroup] ,
5673 [Finding] ,
5674 [DatabaseName] ,
5675 [URL] ,
5676 [Details] )
5677
5678 SELECT 215 AS CheckID ,
5679 100 AS Priority ,
5680 ''Performance'' AS FindingsGroup ,
5681 ''Implicit Transactions'' AS Finding ,
5682 DB_NAME(s.database_id) AS DatabaseName,
5683 ''https://www.brentozar.com/go/ImplicitTransactions/'' AS URL ,
5684 N''The database '' +
5685 DB_NAME(s.database_id)
5686 + '' has ''
5687 + CONVERT(NVARCHAR(20), COUNT_BIG(*))
5688 + '' open implicit transactions with an oldest begin time of ''
5689 + CONVERT(NVARCHAR(30), MIN(tat.transaction_begin_time))
5690 + '' Run sp_BlitzWho and check the is_implicit_transaction column to see the culprits.'' AS details
5691 FROM sys.dm_tran_active_transactions AS tat
5692 LEFT JOIN sys.dm_tran_session_transactions AS tst
5693 ON tst.transaction_id = tat.transaction_id
5694 LEFT JOIN sys.dm_exec_sessions AS s
5695 ON s.session_id = tst.session_id
5696 WHERE tat.name = ''implicit_transaction''
5697 GROUP BY DB_NAME(s.database_id), transaction_type, transaction_state;';
5698
5699
5700 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
5701 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
5702
5703 EXECUTE(@StringToExecute);
5704
5705
5706
5707 END;
5708
5709
5710 IF NOT EXISTS ( SELECT 1
5711 FROM #SkipChecks
5712 WHERE DatabaseName IS NULL AND CheckID = 221 )
5713 BEGIN
5714
5715 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT;
5716
5717 WITH reboot_airhorn
5718 AS
5719 (
5720 SELECT create_date
5721 FROM sys.databases
5722 WHERE database_id = 2
5723 UNION ALL
5724 SELECT CAST(DATEADD(SECOND, ( ms_ticks / 1000 ) * ( -1 ), GETDATE()) AS DATETIME)
5725 FROM sys.dm_os_sys_info
5726 )
5727 INSERT INTO #BlitzResults
5728 ( CheckID ,
5729 Priority ,
5730 FindingsGroup ,
5731 Finding ,
5732 URL ,
5733 Details
5734 )
5735 SELECT 221 AS CheckID,
5736 10 AS Priority,
5737 'Reliability' AS FindingsGroup,
5738 'Server restarted in last 24 hours' AS Finding,
5739 '' AS URL,
5740 'Surprise! Your server was last restarted on: ' + CONVERT(VARCHAR(30), MAX(reboot_airhorn.create_date)) AS details
5741 FROM reboot_airhorn
5742 HAVING MAX(reboot_airhorn.create_date) >= DATEADD(HOUR, -24, GETDATE());
5743
5744
5745 END;
5746
5747 IF NOT EXISTS ( SELECT 1
5748 FROM #SkipChecks
5749 WHERE DatabaseName IS NULL AND CheckID = 229 )
5750 AND CAST(SERVERPROPERTY('Edition') AS NVARCHAR(4000)) LIKE '%Evaluation%'
5751 BEGIN
5752
5753 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 216) WITH NOWAIT;
5754
5755 INSERT INTO #BlitzResults
5756 ( CheckID ,
5757 Priority ,
5758 FindingsGroup ,
5759 Finding ,
5760 URL ,
5761 Details
5762 )
5763 SELECT 229 AS CheckID,
5764 1 AS Priority,
5765 'Reliability' AS FindingsGroup,
5766 'Evaluation Edition' AS Finding,
5767 'https://www.BrentOzar.com/go/workgroup' AS URL,
5768 'This server will stop working on: ' + CAST(CONVERT(DATETIME, DATEADD(DD, 180, create_date), 102) AS VARCHAR(100)) AS details
5769 FROM sys.server_principals
5770 WHERE sid = 0x010100000000000512000000;
5771
5772 END;
5773
5774
5775 IF @CheckUserDatabaseObjects = 1
5776 BEGIN
5777
5778 IF @Debug IN (1, 2) RAISERROR('Starting @CheckUserDatabaseObjects section.', 0, 1) WITH NOWAIT
5779
5780 /*
5781 But what if you need to run a query in every individual database?
5782 Check out CheckID 99 below. Yes, it uses sp_MSforeachdb, and no,
5783 we're not happy about that. sp_MSforeachdb is known to have a lot
5784 of issues, like skipping databases sometimes. However, this is the
5785 only built-in option that we have. If you're writing your own code
5786 for database maintenance, consider Aaron Bertrand's alternative:
5787 http://www.mssqltips.com/sqlservertip/2201/making-a-more-reliable-and-flexible-spmsforeachdb/
5788 We don't include that as part of sp_Blitz, of course, because
5789 copying and distributing copyrighted code from others without their
5790 written permission isn't a good idea.
5791 */
5792 IF NOT EXISTS ( SELECT 1
5793 FROM #SkipChecks
5794 WHERE DatabaseName IS NULL AND CheckID = 99 )
5795 BEGIN
5796
5797 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 99) WITH NOWAIT;
5798
5799 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS (SELECT * FROM sys.tables WITH (NOLOCK) WHERE name = ''sysmergepublications'' ) IF EXISTS ( SELECT * FROM sysmergepublications WITH (NOLOCK) WHERE retention = 0) INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 99, DB_NAME(), 110, ''Performance'', ''Infinite merge replication metadata retention period'', ''https://BrentOzar.com/go/merge'', (''The ['' + DB_NAME() + ''] database has merge replication metadata retention period set to infinite - this can be the case of significant performance issues.'')';
5800 END;
5801 /*
5802 Note that by using sp_MSforeachdb, we're running the query in all
5803 databases. We're not checking #SkipChecks here for each database to
5804 see if we should run the check in this database. That means we may
5805 still run a skipped check if it involves sp_MSforeachdb. We just
5806 don't output those results in the last step.
5807 */
5808
5809 IF NOT EXISTS ( SELECT 1
5810 FROM #SkipChecks
5811 WHERE DatabaseName IS NULL AND CheckID = 163 )
5812 AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options')
5813 BEGIN
5814 /* --TOURSTOP03-- */
5815
5816 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 163) WITH NOWAIT;
5817
5818 EXEC dbo.sp_MSforeachdb 'USE [?];
5819 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
5820 INSERT INTO #BlitzResults
5821 (CheckID,
5822 DatabaseName,
5823 Priority,
5824 FindingsGroup,
5825 Finding,
5826 URL,
5827 Details)
5828 SELECT TOP 1 163,
5829 N''?'',
5830 200,
5831 ''Performance'',
5832 ''Query Store Disabled'',
5833 ''https://BrentOzar.com/go/querystore'',
5834 (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'')
5835 FROM [?].sys.database_query_store_options WHERE desired_state = 0
5836 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''DWConfiguration'', ''DWDiagnostics'', ''DWQueue'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE)';
5837 END;
5838
5839
5840 IF @ProductVersionMajor >= 13 AND @ProductVersionMinor < 2149 --CU1 has the fix in it
5841 AND NOT EXISTS ( SELECT 1
5842 FROM #SkipChecks
5843 WHERE DatabaseName IS NULL AND CheckID = 182 )
5844 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%'
5845 AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%'
5846 BEGIN
5847
5848 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 182) WITH NOWAIT;
5849
5850 SET @StringToExecute = 'INSERT INTO #BlitzResults
5851 (CheckID,
5852 DatabaseName,
5853 Priority,
5854 FindingsGroup,
5855 Finding,
5856 URL,
5857 Details)
5858 SELECT TOP 1
5859 182,
5860 ''Server'',
5861 20,
5862 ''Reliability'',
5863 ''Query Store Cleanup Disabled'',
5864 ''https://BrentOzar.com/go/cleanup'',
5865 (''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/'')
5866 FROM sys.databases AS d
5867 WHERE d.is_query_store_on = 1 OPTION (RECOMPILE);';
5868
5869 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
5870 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
5871
5872 EXECUTE(@StringToExecute);
5873 END;
5874
5875 IF NOT EXISTS ( SELECT 1
5876 FROM #SkipChecks
5877 WHERE DatabaseName IS NULL AND CheckID = 41 )
5878 BEGIN
5879
5880 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 41) WITH NOWAIT;
5881
5882 EXEC dbo.sp_MSforeachdb 'use [?];
5883 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
5884 INSERT INTO #BlitzResults
5885 (CheckID,
5886 DatabaseName,
5887 Priority,
5888 FindingsGroup,
5889 Finding,
5890 URL,
5891 Details)
5892 SELECT 41,
5893 N''?'',
5894 170,
5895 ''File Configuration'',
5896 ''Multiple Log Files on One Drive'',
5897 ''https://BrentOzar.com/go/manylogs'',
5898 (''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.'')
5899 FROM [?].sys.database_files WHERE type_desc = ''LOG''
5900 AND N''?'' <> ''[tempdb]''
5901 GROUP BY LEFT(physical_name, 1)
5902 HAVING COUNT(*) > 1 OPTION (RECOMPILE);';
5903 END;
5904
5905 IF NOT EXISTS ( SELECT 1
5906 FROM #SkipChecks
5907 WHERE DatabaseName IS NULL AND CheckID = 42 )
5908 BEGIN
5909
5910 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 42) WITH NOWAIT;
5911
5912 EXEC dbo.sp_MSforeachdb 'use [?];
5913 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
5914 INSERT INTO #BlitzResults
5915 (CheckID,
5916 DatabaseName,
5917 Priority,
5918 FindingsGroup,
5919 Finding,
5920 URL,
5921 Details)
5922 SELECT DISTINCT 42,
5923 N''?'',
5924 170,
5925 ''File Configuration'',
5926 ''Uneven File Growth Settings in One Filegroup'',
5927 ''https://BrentOzar.com/go/grow'',
5928 (''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.'')
5929 FROM [?].sys.database_files
5930 WHERE type_desc = ''ROWS''
5931 GROUP BY data_space_id
5932 HAVING COUNT(DISTINCT growth) > 1 OR COUNT(DISTINCT is_percent_growth) > 1 OPTION (RECOMPILE);';
5933 END;
5934
5935 IF NOT EXISTS ( SELECT 1
5936 FROM #SkipChecks
5937 WHERE DatabaseName IS NULL AND CheckID = 82 )
5938 BEGIN
5939
5940 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 82) WITH NOWAIT;
5941
5942 EXEC sp_MSforeachdb 'use [?];
5943 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
5944 INSERT INTO #BlitzResults
5945 (CheckID,
5946 DatabaseName,
5947 Priority,
5948 FindingsGroup,
5949 Finding,
5950 URL, Details)
5951 SELECT DISTINCT 82 AS CheckID,
5952 N''?'' as DatabaseName,
5953 170 AS Priority,
5954 ''File Configuration'' AS FindingsGroup,
5955 ''File growth set to percent'',
5956 ''https://BrentOzar.com/go/percentgrowth'' AS URL,
5957 ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CONVERT(NVARCHAR(10), CONVERT(NUMERIC(38, 2), (f.size / 128.) / 1024.)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.''
5958 FROM [?].sys.database_files f
5959 WHERE is_percent_growth = 1 and size > 128000 OPTION (RECOMPILE);';
5960 END;
5961
5962 /* addition by Henrik Staun Poulsen, Stovi Software */
5963 IF NOT EXISTS ( SELECT 1
5964 FROM #SkipChecks
5965 WHERE DatabaseName IS NULL AND CheckID = 158 )
5966 BEGIN
5967
5968 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 158) WITH NOWAIT;
5969
5970 EXEC sp_MSforeachdb 'use [?];
5971 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
5972 INSERT INTO #BlitzResults
5973 (CheckID,
5974 DatabaseName,
5975 Priority,
5976 FindingsGroup,
5977 Finding,
5978 URL, Details)
5979 SELECT DISTINCT 158 AS CheckID,
5980 N''?'' as DatabaseName,
5981 170 AS Priority,
5982 ''File Configuration'' AS FindingsGroup,
5983 ''File growth set to 1MB'',
5984 ''https://BrentOzar.com/go/percentgrowth'' AS URL,
5985 ''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.''
5986 FROM [?].sys.database_files f
5987 WHERE is_percent_growth = 0 and growth=128 and size > 128000 OPTION (RECOMPILE);';
5988 END;
5989
5990 IF NOT EXISTS ( SELECT 1
5991 FROM #SkipChecks
5992 WHERE DatabaseName IS NULL AND CheckID = 33 )
5993 BEGIN
5994 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
5995 AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
5996 AND @SkipBlockingChecks = 0
5997 BEGIN
5998
5999 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 33) WITH NOWAIT;
6000
6001 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6002 INSERT INTO #BlitzResults
6003 (CheckID,
6004 DatabaseName,
6005 Priority,
6006 FindingsGroup,
6007 Finding,
6008 URL,
6009 Details)
6010 SELECT DISTINCT 33,
6011 db_name(),
6012 200,
6013 ''Licensing'',
6014 ''Enterprise Edition Features In Use'',
6015 ''https://BrentOzar.com/go/ee'',
6016 (''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.'')
6017 FROM [?].sys.dm_db_persisted_sku_features OPTION (RECOMPILE);';
6018 END;
6019 END;
6020
6021 IF NOT EXISTS ( SELECT 1
6022 FROM #SkipChecks
6023 WHERE DatabaseName IS NULL AND CheckID = 19 )
6024 BEGIN
6025 /* Method 1: Check sys.databases parameters */
6026
6027 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 19) WITH NOWAIT;
6028
6029 INSERT INTO #BlitzResults
6030 ( CheckID ,
6031 DatabaseName ,
6032 Priority ,
6033 FindingsGroup ,
6034 Finding ,
6035 URL ,
6036 Details
6037 )
6038
6039 SELECT 19 AS CheckID ,
6040 [name] AS DatabaseName ,
6041 200 AS Priority ,
6042 'Informational' AS FindingsGroup ,
6043 'Replication In Use' AS Finding ,
6044 'https://BrentOzar.com/go/repl' AS URL ,
6045 ( 'Database [' + [name]
6046 + '] is a replication publisher, subscriber, or distributor.' ) AS Details
6047 FROM sys.databases
6048 WHERE name NOT IN ( SELECT DISTINCT
6049 DatabaseName
6050 FROM #SkipChecks
6051 WHERE CheckID IS NULL OR CheckID = 19)
6052 AND is_published = 1
6053 OR is_subscribed = 1
6054 OR is_merge_published = 1
6055 OR is_distributor = 1;
6056
6057 /* Method B: check subscribers for MSreplication_objects tables */
6058 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6059 INSERT INTO #BlitzResults
6060 (CheckID,
6061 DatabaseName,
6062 Priority,
6063 FindingsGroup,
6064 Finding,
6065 URL,
6066 Details)
6067 SELECT DISTINCT 19,
6068 db_name(),
6069 200,
6070 ''Informational'',
6071 ''Replication In Use'',
6072 ''https://BrentOzar.com/go/repl'',
6073 (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'')
6074 FROM [?].sys.tables
6075 WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master'' OPTION (RECOMPILE)';
6076
6077 END;
6078
6079 IF NOT EXISTS ( SELECT 1
6080 FROM #SkipChecks
6081 WHERE DatabaseName IS NULL AND CheckID = 32 )
6082 BEGIN
6083
6084 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 32) WITH NOWAIT;
6085
6086 EXEC dbo.sp_MSforeachdb 'USE [?];
6087 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6088 INSERT INTO #BlitzResults
6089 (CheckID,
6090 DatabaseName,
6091 Priority,
6092 FindingsGroup,
6093 Finding,
6094 URL,
6095 Details)
6096 SELECT 32,
6097 N''?'',
6098 150,
6099 ''Performance'',
6100 ''Triggers on Tables'',
6101 ''https://BrentOzar.com/go/trig'',
6102 (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'')
6103 FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id
6104 INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer''
6105 HAVING SUM(1) > 0 OPTION (RECOMPILE)';
6106 END;
6107
6108 IF NOT EXISTS ( SELECT 1
6109 FROM #SkipChecks
6110 WHERE DatabaseName IS NULL AND CheckID = 38 )
6111 BEGIN
6112
6113 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 38) WITH NOWAIT;
6114
6115 EXEC dbo.sp_MSforeachdb 'USE [?];
6116 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6117 INSERT INTO #BlitzResults
6118 (CheckID,
6119 DatabaseName,
6120 Priority,
6121 FindingsGroup,
6122 Finding,
6123 URL,
6124 Details)
6125 SELECT DISTINCT 38,
6126 N''?'',
6127 110,
6128 ''Performance'',
6129 ''Active Tables Without Clustered Indexes'',
6130 ''https://BrentOzar.com/go/heaps'',
6131 (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that are being actively queried.'')
6132 FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id
6133 INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id
6134 INNER JOIN sys.databases sd ON sd.name = N''?''
6135 LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id
6136 WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NOT NULL
6137 AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)';
6138 END;
6139
6140 IF NOT EXISTS ( SELECT 1
6141 FROM #SkipChecks
6142 WHERE DatabaseName IS NULL AND CheckID = 164 )
6143 AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'fn_validate_plan_guide')
6144 BEGIN
6145
6146 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 164) WITH NOWAIT;
6147
6148 EXEC dbo.sp_MSforeachdb 'USE [?];
6149 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6150 INSERT INTO #BlitzResults
6151 (CheckID,
6152 DatabaseName,
6153 Priority,
6154 FindingsGroup,
6155 Finding,
6156 URL,
6157 Details)
6158 SELECT DISTINCT 164,
6159 N''?'',
6160 20,
6161 ''Reliability'',
6162 ''Plan Guides Failing'',
6163 ''https://BrentOzar.com/go/misguided'',
6164 (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'')
6165 FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id) OPTION (RECOMPILE)';
6166 END;
6167
6168 IF NOT EXISTS ( SELECT 1
6169 FROM #SkipChecks
6170 WHERE DatabaseName IS NULL AND CheckID = 39 )
6171 BEGIN
6172
6173 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 39) WITH NOWAIT;
6174
6175 EXEC dbo.sp_MSforeachdb 'USE [?];
6176 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6177 INSERT INTO #BlitzResults
6178 (CheckID,
6179 DatabaseName,
6180 Priority,
6181 FindingsGroup,
6182 Finding,
6183 URL,
6184 Details)
6185 SELECT DISTINCT 39,
6186 N''?'',
6187 150,
6188 ''Performance'',
6189 ''Inactive Tables Without Clustered Indexes'',
6190 ''https://BrentOzar.com/go/heaps'',
6191 (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that have not been queried since the last restart. These may be backup tables carelessly left behind.'')
6192 FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id
6193 INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id
6194 INNER JOIN sys.databases sd ON sd.name = N''?''
6195 LEFT OUTER JOIN [?].sys.dm_db_index_usage_stats ius ON i.object_id = ius.object_id AND i.index_id = ius.index_id AND ius.database_id = sd.database_id
6196 WHERE i.type_desc = ''HEAP'' AND COALESCE(NULLIF(ius.user_seeks,0), NULLIF(ius.user_scans,0), NULLIF(ius.user_lookups,0), NULLIF(ius.user_updates,0)) IS NULL
6197 AND sd.name <> ''tempdb'' AND sd.name <> ''DWDiagnostics'' AND o.is_ms_shipped = 0 AND o.type <> ''S'' OPTION (RECOMPILE)';
6198 END;
6199
6200 IF NOT EXISTS ( SELECT 1
6201 FROM #SkipChecks
6202 WHERE DatabaseName IS NULL AND CheckID = 46 )
6203 BEGIN
6204
6205 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 46) WITH NOWAIT;
6206
6207 EXEC dbo.sp_MSforeachdb 'USE [?];
6208 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6209 INSERT INTO #BlitzResults
6210 (CheckID,
6211 DatabaseName,
6212 Priority,
6213 FindingsGroup,
6214 Finding,
6215 URL,
6216 Details)
6217 SELECT 46,
6218 N''?'',
6219 150,
6220 ''Performance'',
6221 ''Leftover Fake Indexes From Wizards'',
6222 ''https://BrentOzar.com/go/hypo'',
6223 (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is a leftover hypothetical index from the Index Tuning Wizard or Database Tuning Advisor. This index is not actually helping performance and should be removed.'')
6224 from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
6225 WHERE i.is_hypothetical = 1 OPTION (RECOMPILE);';
6226 END;
6227
6228 IF NOT EXISTS ( SELECT 1
6229 FROM #SkipChecks
6230 WHERE DatabaseName IS NULL AND CheckID = 47 )
6231 BEGIN
6232
6233 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 47) WITH NOWAIT;
6234
6235 EXEC dbo.sp_MSforeachdb 'USE [?];
6236 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6237 INSERT INTO #BlitzResults
6238 (CheckID,
6239 DatabaseName,
6240 Priority,
6241 FindingsGroup,
6242 Finding,
6243 URL,
6244 Details)
6245 SELECT 47,
6246 N''?'',
6247 100,
6248 ''Performance'',
6249 ''Indexes Disabled'',
6250 ''https://BrentOzar.com/go/ixoff'',
6251 (''The index ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is disabled. This index is not actually helping performance and should either be enabled or removed.'')
6252 from [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
6253 WHERE i.is_disabled = 1 OPTION (RECOMPILE);';
6254 END;
6255
6256 IF NOT EXISTS ( SELECT 1
6257 FROM #SkipChecks
6258 WHERE DatabaseName IS NULL AND CheckID = 48 )
6259 BEGIN
6260
6261 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 48) WITH NOWAIT;
6262
6263 EXEC dbo.sp_MSforeachdb 'USE [?];
6264 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6265 INSERT INTO #BlitzResults
6266 (CheckID,
6267 DatabaseName,
6268 Priority,
6269 FindingsGroup,
6270 Finding,
6271 URL,
6272 Details)
6273 SELECT DISTINCT 48,
6274 N''?'',
6275 150,
6276 ''Performance'',
6277 ''Foreign Keys Not Trusted'',
6278 ''https://BrentOzar.com/go/trust'',
6279 (''The ['' + DB_NAME() + ''] database has foreign keys that were probably disabled, data was changed, and then the key was enabled again. Simply enabling the key is not enough for the optimizer to use this key - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'')
6280 from [?].sys.foreign_keys i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
6281 WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 AND N''?'' NOT IN (''master'', ''model'', ''msdb'', ''ReportServer'', ''ReportServerTempDB'') OPTION (RECOMPILE);';
6282 END;
6283
6284 IF NOT EXISTS ( SELECT 1
6285 FROM #SkipChecks
6286 WHERE DatabaseName IS NULL AND CheckID = 56 )
6287 BEGIN
6288
6289 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 56) WITH NOWAIT;
6290
6291 EXEC dbo.sp_MSforeachdb 'USE [?];
6292 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6293 INSERT INTO #BlitzResults
6294 (CheckID,
6295 DatabaseName,
6296 Priority,
6297 FindingsGroup,
6298 Finding,
6299 URL,
6300 Details)
6301 SELECT 56,
6302 N''?'',
6303 150,
6304 ''Performance'',
6305 ''Check Constraint Not Trusted'',
6306 ''https://BrentOzar.com/go/trust'',
6307 (''The check constraint ['' + DB_NAME() + ''].['' + s.name + ''].['' + o.name + ''].['' + i.name + ''] is not trusted - meaning, it was disabled, data was changed, and then the constraint was enabled again. Simply enabling the constraint is not enough for the optimizer to use this constraint - we have to alter the table using the WITH CHECK CHECK CONSTRAINT parameter.'')
6308 from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id
6309 INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
6310 WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0 OPTION (RECOMPILE);';
6311 END;
6312
6313 IF NOT EXISTS ( SELECT 1
6314 FROM #SkipChecks
6315 WHERE DatabaseName IS NULL AND CheckID = 95 )
6316 BEGIN
6317 IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
6318 AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
6319 BEGIN
6320
6321 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 95) WITH NOWAIT;
6322
6323 EXEC dbo.sp_MSforeachdb 'USE [?];
6324 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6325 INSERT INTO #BlitzResults
6326 (CheckID,
6327 DatabaseName,
6328 Priority,
6329 FindingsGroup,
6330 Finding,
6331 URL,
6332 Details)
6333 SELECT TOP 1 95 AS CheckID,
6334 N''?'' as DatabaseName,
6335 110 AS Priority,
6336 ''Performance'' AS FindingsGroup,
6337 ''Plan Guides Enabled'' AS Finding,
6338 ''https://BrentOzar.com/go/guides'' AS URL,
6339 (''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
6340 FROM [?].sys.plan_guides WHERE is_disabled = 0 OPTION (RECOMPILE);';
6341 END;
6342 END;
6343
6344 IF NOT EXISTS ( SELECT 1
6345 FROM #SkipChecks
6346 WHERE DatabaseName IS NULL AND CheckID = 60 )
6347 BEGIN
6348
6349 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 60) WITH NOWAIT;
6350
6351 EXEC sp_MSforeachdb 'USE [?];
6352 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6353 INSERT INTO #BlitzResults
6354 (CheckID,
6355 DatabaseName,
6356 Priority,
6357 FindingsGroup,
6358 Finding,
6359 URL,
6360 Details)
6361 SELECT 60 AS CheckID,
6362 N''?'' as DatabaseName,
6363 100 AS Priority,
6364 ''Performance'' AS FindingsGroup,
6365 ''Fill Factor Changed'',
6366 ''https://BrentOzar.com/go/fillfactor'' AS URL,
6367 ''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' objects with fill factor = '' + CAST(fill_factor AS NVARCHAR(5)) + ''%. This can cause memory and storage performance problems, but may also prevent page splits.''
6368 FROM [?].sys.indexes
6369 WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0
6370 GROUP BY fill_factor OPTION (RECOMPILE);';
6371 END;
6372
6373 IF NOT EXISTS ( SELECT 1
6374 FROM #SkipChecks
6375 WHERE DatabaseName IS NULL AND CheckID = 78 )
6376 BEGIN
6377
6378 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 78) WITH NOWAIT;
6379
6380 EXECUTE master.sys.sp_MSforeachdb 'USE [?];
6381 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6382 INSERT INTO #Recompile
6383 SELECT DISTINCT DBName = DB_Name(), SPName = SO.name, SM.is_recompiled, ISR.SPECIFIC_SCHEMA
6384 FROM sys.sql_modules AS SM
6385 LEFT OUTER JOIN master.sys.databases AS sDB ON SM.object_id = DB_id()
6386 LEFT OUTER JOIN dbo.sysobjects AS SO ON SM.object_id = SO.id and type = ''P''
6387 LEFT OUTER JOIN INFORMATION_SCHEMA.ROUTINES AS ISR on ISR.Routine_Name = SO.name AND ISR.SPECIFIC_CATALOG = DB_Name()
6388 WHERE SM.is_recompiled=1 OPTION (RECOMPILE); /* oh the rich irony of recompile here */
6389 ';
6390 INSERT INTO #BlitzResults
6391 (Priority,
6392 FindingsGroup,
6393 Finding,
6394 DatabaseName,
6395 URL,
6396 Details,
6397 CheckID)
6398 SELECT [Priority] = '100',
6399 FindingsGroup = 'Performance',
6400 Finding = 'Stored Procedure WITH RECOMPILE',
6401 DatabaseName = DBName,
6402 URL = 'https://BrentOzar.com/go/recompile',
6403 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.',
6404 CheckID = '78'
6405 FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AllNightLog%' AND ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%';
6406 DROP TABLE #Recompile;
6407 END;
6408
6409 IF NOT EXISTS ( SELECT 1
6410 FROM #SkipChecks
6411 WHERE DatabaseName IS NULL AND CheckID = 86 )
6412 BEGIN
6413
6414 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 86) WITH NOWAIT;
6415
6416 EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 230, ''Security'', ''Elevated Permissions on a Database'', ''https://BrentOzar.com/go/elevated'', (''In ['' + DB_NAME() + ''], user ['' + u.name + ''] has the role ['' + g.name + '']. This user can perform tasks beyond just reading and writing data.'') FROM (SELECT memberuid = convert(int, member_principal_id), groupuid = convert(int, role_principal_id) FROM [?].sys.database_role_members) m inner join [?].dbo.sysusers u on m.memberuid = u.uid inner join sysusers g on m.groupuid = g.uid where u.name <> ''dbo'' and g.name in (''db_owner'' , ''db_accessadmin'' , ''db_securityadmin'' , ''db_ddladmin'') OPTION (RECOMPILE);';
6417 END;
6418
6419 /*Check for non-aligned indexes in partioned databases*/
6420
6421 IF NOT EXISTS ( SELECT 1
6422 FROM #SkipChecks
6423 WHERE DatabaseName IS NULL AND CheckID = 72 )
6424 BEGIN
6425
6426 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 72) WITH NOWAIT;
6427
6428 EXEC dbo.sp_MSforeachdb 'USE [?];
6429 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6430 insert into #partdb(dbname, objectname, type_desc)
6431 SELECT distinct db_name(DB_ID()) as DBName,o.name Object_Name,ds.type_desc
6432 FROM sys.objects AS o JOIN sys.indexes AS i ON o.object_id = i.object_id
6433 JOIN sys.data_spaces ds on ds.data_space_id = i.data_space_id
6434 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()
6435 WHERE o.type = ''u''
6436 -- Clustered and Non-Clustered indexes
6437 AND i.type IN (1, 2)
6438 AND o.object_id in
6439 (
6440 SELECT a.object_id from
6441 (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
6442 GROUP BY ob.object_id, ds.type_desc ) a group by a.object_id having COUNT (*) > 1
6443 ) OPTION (RECOMPILE);';
6444 INSERT INTO #BlitzResults
6445 ( CheckID ,
6446 DatabaseName ,
6447 Priority ,
6448 FindingsGroup ,
6449 Finding ,
6450 URL ,
6451 Details
6452 )
6453 SELECT DISTINCT
6454 72 AS CheckID ,
6455 dbname AS DatabaseName ,
6456 100 AS Priority ,
6457 'Performance' AS FindingsGroup ,
6458 'The partitioned database ' + dbname
6459 + ' may have non-aligned indexes' AS Finding ,
6460 'https://BrentOzar.com/go/aligned' AS URL ,
6461 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details
6462 FROM #partdb
6463 WHERE dbname IS NOT NULL
6464 AND dbname NOT IN ( SELECT DISTINCT
6465 DatabaseName
6466 FROM #SkipChecks
6467 WHERE CheckID IS NULL OR CheckID = 72);
6468 DROP TABLE #partdb;
6469 END;
6470
6471 IF NOT EXISTS ( SELECT 1
6472 FROM #SkipChecks
6473 WHERE DatabaseName IS NULL AND CheckID = 113 )
6474 BEGIN
6475
6476 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 113) WITH NOWAIT;
6477
6478 EXEC dbo.sp_MSforeachdb 'USE [?];
6479 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6480 INSERT INTO #BlitzResults
6481 (CheckID,
6482 DatabaseName,
6483 Priority,
6484 FindingsGroup,
6485 Finding,
6486 URL,
6487 Details)
6488 SELECT DISTINCT 113,
6489 N''?'',
6490 50,
6491 ''Reliability'',
6492 ''Full Text Indexes Not Updating'',
6493 ''https://BrentOzar.com/go/fulltext'',
6494 (''At least one full text index in this database has not been crawled in the last week.'')
6495 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);';
6496 END;
6497
6498 IF NOT EXISTS ( SELECT 1
6499 FROM #SkipChecks
6500 WHERE DatabaseName IS NULL AND CheckID = 115 )
6501 BEGIN
6502
6503 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 115) WITH NOWAIT;
6504
6505 EXEC dbo.sp_MSforeachdb 'USE [?];
6506 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6507 INSERT INTO #BlitzResults
6508 (CheckID,
6509 DatabaseName,
6510 Priority,
6511 FindingsGroup,
6512 Finding,
6513 URL,
6514 Details)
6515 SELECT 115,
6516 N''?'',
6517 110,
6518 ''Performance'',
6519 ''Parallelism Rocket Surgery'',
6520 ''https://BrentOzar.com/go/makeparallel'',
6521 (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'')
6522 from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION'' OPTION (RECOMPILE);';
6523 END;
6524
6525 IF NOT EXISTS ( SELECT 1
6526 FROM #SkipChecks
6527 WHERE DatabaseName IS NULL AND CheckID = 122 )
6528 BEGIN
6529
6530 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 122) WITH NOWAIT;
6531
6532 /* SQL Server 2012 and newer uses temporary stats for Availability Groups, and those show up as user-created */
6533 IF EXISTS (SELECT *
6534 FROM sys.all_columns c
6535 INNER JOIN sys.all_objects o ON c.object_id = o.object_id
6536 WHERE c.name = 'is_temporary' AND o.name = 'stats')
6537
6538 EXEC dbo.sp_MSforeachdb 'USE [?];
6539 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6540 INSERT INTO #BlitzResults
6541 (CheckID,
6542 DatabaseName,
6543 Priority,
6544 FindingsGroup,
6545 Finding,
6546 URL,
6547 Details)
6548 SELECT TOP 1 122,
6549 N''?'',
6550 200,
6551 ''Performance'',
6552 ''User-Created Statistics In Place'',
6553 ''https://BrentOzar.com/go/userstats'',
6554 (''['' + 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.'')
6555 from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0
6556 HAVING SUM(1) > 0 OPTION (RECOMPILE);';
6557
6558 ELSE
6559 EXEC dbo.sp_MSforeachdb 'USE [?];
6560 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6561 INSERT INTO #BlitzResults
6562 (CheckID,
6563 DatabaseName,
6564 Priority,
6565 FindingsGroup,
6566 Finding,
6567 URL,
6568 Details)
6569 SELECT 122,
6570 N''?'',
6571 200,
6572 ''Performance'',
6573 ''User-Created Statistics In Place'',
6574 ''https://BrentOzar.com/go/userstats'',
6575 (''['' + 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.'')
6576 from [?].sys.stats WHERE user_created = 1
6577 HAVING SUM(1) > 0 OPTION (RECOMPILE);';
6578
6579 END; /* IF NOT EXISTS ( SELECT 1 */
6580
6581 /*Check for high VLF count: this will omit any database snapshots*/
6582
6583 IF NOT EXISTS ( SELECT 1
6584 FROM #SkipChecks
6585 WHERE DatabaseName IS NULL AND CheckID = 69 )
6586 BEGIN
6587 IF @ProductVersionMajor >= 11
6588
6589 BEGIN
6590
6591 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (2012 version of Log Info).', 0, 1, 69) WITH NOWAIT;
6592
6593 EXEC sp_MSforeachdb N'USE [?];
6594 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6595 INSERT INTO #LogInfo2012
6596 EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS'';
6597 IF @@ROWCOUNT > 999
6598 BEGIN
6599 INSERT INTO #BlitzResults
6600 ( CheckID
6601 ,DatabaseName
6602 ,Priority
6603 ,FindingsGroup
6604 ,Finding
6605 ,URL
6606 ,Details)
6607 SELECT 69
6608 ,DB_NAME()
6609 ,170
6610 ,''File Configuration''
6611 ,''High VLF Count''
6612 ,''https://BrentOzar.com/go/vlf''
6613 ,''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.''
6614 FROM #LogInfo2012
6615 WHERE EXISTS (SELECT name FROM master.sys.databases
6616 WHERE source_database_id is null) OPTION (RECOMPILE);
6617 END
6618 TRUNCATE TABLE #LogInfo2012;';
6619 DROP TABLE #LogInfo2012;
6620 END;
6621 ELSE
6622 BEGIN
6623
6624 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] (pre-2012 version of Log Info).', 0, 1, 69) WITH NOWAIT;
6625
6626 EXEC sp_MSforeachdb N'USE [?];
6627 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6628 INSERT INTO #LogInfo
6629 EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS'';
6630 IF @@ROWCOUNT > 999
6631 BEGIN
6632 INSERT INTO #BlitzResults
6633 ( CheckID
6634 ,DatabaseName
6635 ,Priority
6636 ,FindingsGroup
6637 ,Finding
6638 ,URL
6639 ,Details)
6640 SELECT 69
6641 ,DB_NAME()
6642 ,170
6643 ,''File Configuration''
6644 ,''High VLF Count''
6645 ,''https://BrentOzar.com/go/vlf''
6646 ,''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.''
6647 FROM #LogInfo
6648 WHERE EXISTS (SELECT name FROM master.sys.databases
6649 WHERE source_database_id is null) OPTION (RECOMPILE);
6650 END
6651 TRUNCATE TABLE #LogInfo;';
6652 DROP TABLE #LogInfo;
6653 END;
6654 END;
6655
6656 IF NOT EXISTS ( SELECT 1
6657 FROM #SkipChecks
6658 WHERE DatabaseName IS NULL AND CheckID = 80 )
6659 BEGIN
6660
6661 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 80) WITH NOWAIT;
6662
6663 EXEC dbo.sp_MSforeachdb 'USE [?];
6664 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6665 INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
6666 SELECT DISTINCT 80, DB_NAME(), 170, ''Reliability'', ''Max File Size Set'', ''https://BrentOzar.com/go/maxsize'',
6667 (''The ['' + DB_NAME() + ''] database file '' + df.name + '' has a max file size set to ''
6668 + CAST(CAST(df.max_size AS BIGINT) * 8 / 1024 AS VARCHAR(100))
6669 + ''MB. If it runs out of space, the database will stop working even though there may be drive space available.'')
6670 FROM sys.database_files df
6671 WHERE 0 = (SELECT is_read_only FROM sys.databases WHERE name = ''?'')
6672 AND df.max_size <> 268435456
6673 AND df.max_size <> -1
6674 AND df.type <> 2
6675 AND df.growth > 0
6676 AND df.name <> ''DWDiagnostics'' OPTION (RECOMPILE);';
6677
6678 DELETE br
6679 FROM #BlitzResults br
6680 INNER JOIN #SkipChecks sc ON sc.CheckID = 80 AND br.DatabaseName = sc.DatabaseName;
6681 END;
6682
6683
6684 /* Check if columnstore indexes are in use - for Github issue #615 */
6685 IF NOT EXISTS ( SELECT 1
6686 FROM #SkipChecks
6687 WHERE DatabaseName IS NULL AND CheckID = 74 ) /* Trace flags */
6688 BEGIN
6689 TRUNCATE TABLE #TemporaryDatabaseResults;
6690
6691 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT;
6692
6693 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; IF EXISTS(SELECT * FROM sys.indexes WHERE type IN (5,6)) INSERT INTO #TemporaryDatabaseResults (DatabaseName, Finding) VALUES (DB_NAME(), ''Yup'') OPTION (RECOMPILE);';
6694 IF EXISTS (SELECT * FROM #TemporaryDatabaseResults) SET @ColumnStoreIndexesInUse = 1;
6695 END;
6696
6697 /* Non-Default Database Scoped Config - Github issue #598 */
6698 IF EXISTS ( SELECT * FROM sys.all_objects WHERE [name] = 'database_scoped_configurations' )
6699 BEGIN
6700
6701 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d] through [%d].', 0, 1, 194, 197) WITH NOWAIT;
6702
6703 INSERT INTO #DatabaseScopedConfigurationDefaults (configuration_id, [name], default_value, default_value_for_secondary, CheckID)
6704 SELECT 1, 'MAXDOP', 0, NULL, 194
6705 UNION ALL
6706 SELECT 2, 'LEGACY_CARDINALITY_ESTIMATION', 0, NULL, 195
6707 UNION ALL
6708 SELECT 3, 'PARAMETER_SNIFFING', 1, NULL, 196
6709 UNION ALL
6710 SELECT 4, 'QUERY_OPTIMIZER_HOTFIXES', 0, NULL, 197;
6711 EXEC dbo.sp_MSforeachdb 'USE [?]; SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
6712 SELECT def1.CheckID, DB_NAME(), 210, ''Non-Default Database Scoped Config'', dsc.[name], ''https://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''))
6713 FROM [?].sys.database_scoped_configurations dsc
6714 INNER JOIN #DatabaseScopedConfigurationDefaults def1 ON dsc.configuration_id = def1.configuration_id
6715 LEFT OUTER JOIN #DatabaseScopedConfigurationDefaults def ON dsc.configuration_id = def.configuration_id AND (dsc.value = def.default_value OR dsc.value IS NULL) AND (dsc.value_for_secondary = def.default_value_for_secondary OR dsc.value_for_secondary IS NULL)
6716 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())
6717 WHERE def.configuration_id IS NULL AND sk.CheckID IS NULL ORDER BY 1
6718 OPTION (RECOMPILE);';
6719 END;
6720
6721 /* Check 218 - Show me the dodgy SET Options */
6722 IF NOT EXISTS (
6723 SELECT 1
6724 FROM #SkipChecks
6725 WHERE DatabaseName IS NULL
6726 AND CheckID = 218
6727 )
6728 BEGIN
6729 IF @Debug IN (1,2)
6730 BEGIN
6731 RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT;
6732 END
6733
6734 EXECUTE sp_MSforeachdb 'USE [?];
6735 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6736 INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
6737 SELECT 218 AS CheckID
6738 ,''?'' AS DatabaseName
6739 ,150 AS Priority
6740 ,''Performance'' AS FindingsGroup
6741 ,''Objects created with dangerous SET Options'' AS Finding
6742 ,''https://BrentOzar.com/go/badset'' AS URL
6743 ,''The '' + QUOTENAME(DB_NAME())
6744 + '' database has '' + CONVERT(VARCHAR(20),COUNT(1))
6745 + '' objects that were created with dangerous ANSI_NULL or QUOTED_IDENTIFIER options.''
6746 + '' These objects can break when using filtered indexes, indexed views''
6747 + '' and other advanced SQL features.'' AS Details
6748 FROM sys.sql_modules sm
6749 JOIN sys.objects o ON o.[object_id] = sm.[object_id]
6750 AND (
6751 sm.uses_ansi_nulls <> 1
6752 OR sm.uses_quoted_identifier <> 1
6753 )
6754 AND o.is_ms_shipped = 0
6755 HAVING COUNT(1) > 0;';
6756 END; --of Check 218.
6757
6758 /* Check 225 - Reliability - Resumable Index Operation Paused */
6759 IF NOT EXISTS (
6760 SELECT 1
6761 FROM #SkipChecks
6762 WHERE DatabaseName IS NULL
6763 AND CheckID = 225
6764 )
6765 AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'index_resumable_operations')
6766 BEGIN
6767 IF @Debug IN (1,2)
6768 BEGIN
6769 RAISERROR ('Running CheckId [%d].',0,1,218) WITH NOWAIT;
6770 END
6771
6772 EXECUTE sp_MSforeachdb 'USE [?];
6773 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6774 INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
6775 SELECT 225 AS CheckID
6776 ,''?'' AS DatabaseName
6777 ,200 AS Priority
6778 ,''Reliability'' AS FindingsGroup
6779 ,''Resumable Index Operation Paused'' AS Finding
6780 ,''https://BrentOzar.com/go/resumable'' AS URL
6781 ,iro.state_desc + N'' since '' + CONVERT(NVARCHAR(50), last_pause_time, 120) + '', ''
6782 + CAST(iro.percent_complete AS NVARCHAR(20)) + ''% complete: ''
6783 + CAST(iro.sql_text AS NVARCHAR(1000)) AS Details
6784 FROM sys.index_resumable_operations iro
6785 JOIN sys.objects o ON iro.[object_id] = o.[object_id]
6786 WHERE iro.state <> 0;';
6787 END; --of Check 225.
6788
6789 --/* Check 220 - Statistics Without Histograms */
6790 --IF NOT EXISTS (
6791 -- SELECT 1
6792 -- FROM #SkipChecks
6793 -- WHERE DatabaseName IS NULL
6794 -- AND CheckID = 220
6795 -- )
6796 -- AND EXISTS (SELECT * FROM sys.all_objects WHERE name = 'dm_db_stats_histogram')
6797 --BEGIN
6798 -- IF @Debug IN (1,2)
6799 -- BEGIN
6800 -- RAISERROR ('Running CheckId [%d].',0,1,220) WITH NOWAIT;
6801 -- END
6802
6803 -- EXECUTE sp_MSforeachdb 'USE [?];
6804 -- SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
6805 -- INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
6806 -- SELECT 220 AS CheckID
6807 -- ,DB_NAME() AS DatabaseName
6808 -- ,110 AS Priority
6809 -- ,''Performance'' AS FindingsGroup
6810 -- ,''Statistics Without Histograms'' AS Finding
6811 -- ,''https://BrentOzar.com/go/brokenstats'' AS URL
6812 -- ,CAST(COUNT(DISTINCT o.object_id) AS VARCHAR(100)) + '' tables have statistics that have not been updated since the database was restored or upgraded,''
6813 -- + '' and have no data in their histogram. See the More Info URL for a script to update them. '' AS Details
6814 -- FROM sys.all_objects o
6815 -- INNER JOIN sys.stats s ON o.object_id = s.object_id AND s.has_filter = 0
6816 -- OUTER APPLY sys.dm_db_stats_histogram(o.object_id, s.stats_id) h
6817 -- WHERE o.is_ms_shipped = 0 AND o.type_desc = ''USER_TABLE''
6818 -- AND h.object_id IS NULL
6819 -- AND 0 < (SELECT SUM(row_count) FROM sys.dm_db_partition_stats ps WHERE ps.object_id = o.object_id)
6820 -- AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'')
6821 -- HAVING COUNT(DISTINCT o.object_id) > 0;';
6822 --END; --of Check 220.
6823
6824
6825 END; /* IF @CheckUserDatabaseObjects = 1 */
6826
6827 IF @CheckProcedureCache = 1
6828
6829 BEGIN
6830
6831 IF @Debug IN (1, 2) RAISERROR('Begin checking procedure cache', 0, 1) WITH NOWAIT;
6832
6833 BEGIN
6834
6835 IF NOT EXISTS ( SELECT 1
6836 FROM #SkipChecks
6837 WHERE DatabaseName IS NULL AND CheckID = 35 )
6838 BEGIN
6839
6840 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 35) WITH NOWAIT;
6841
6842 INSERT INTO #BlitzResults
6843 ( CheckID ,
6844 Priority ,
6845 FindingsGroup ,
6846 Finding ,
6847 URL ,
6848 Details
6849 )
6850 SELECT 35 AS CheckID ,
6851 100 AS Priority ,
6852 'Performance' AS FindingsGroup ,
6853 'Single-Use Plans in Procedure Cache' AS Finding ,
6854 'https://BrentOzar.com/go/single' AS URL ,
6855 ( CAST(COUNT(*) AS VARCHAR(10))
6856 + ' 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
6857 FROM sys.dm_exec_cached_plans AS cp
6858 WHERE cp.usecounts = 1
6859 AND cp.objtype = 'Adhoc'
6860 AND EXISTS ( SELECT
6861 1
6862 FROM sys.configurations
6863 WHERE
6864 name = 'optimize for ad hoc workloads'
6865 AND value_in_use = 0 )
6866 HAVING COUNT(*) > 1;
6867 END;
6868
6869 /* Set up the cache tables. Different on 2005 since it doesn't support query_hash, query_plan_hash. */
6870 IF @@VERSION LIKE '%Microsoft SQL Server 2005%'
6871 BEGIN
6872 IF @CheckProcedureCacheFilter = 'CPU'
6873 OR @CheckProcedureCacheFilter IS NULL
6874 BEGIN
6875 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])
6876 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]
6877 FROM sys.dm_exec_query_stats qs
6878 ORDER BY qs.total_worker_time DESC)
6879 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])
6880 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]
6881 FROM queries qs
6882 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
6883 WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
6884 EXECUTE(@StringToExecute);
6885 END;
6886
6887 IF @CheckProcedureCacheFilter = 'Reads'
6888 OR @CheckProcedureCacheFilter IS NULL
6889 BEGIN
6890 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])
6891 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]
6892 FROM sys.dm_exec_query_stats qs
6893 ORDER BY qs.total_logical_reads DESC)
6894 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])
6895 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]
6896 FROM queries qs
6897 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
6898 WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
6899 EXECUTE(@StringToExecute);
6900 END;
6901
6902 IF @CheckProcedureCacheFilter = 'ExecCount'
6903 OR @CheckProcedureCacheFilter IS NULL
6904 BEGIN
6905 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])
6906 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]
6907 FROM sys.dm_exec_query_stats qs
6908 ORDER BY qs.execution_count DESC)
6909 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])
6910 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]
6911 FROM queries qs
6912 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
6913 WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
6914 EXECUTE(@StringToExecute);
6915 END;
6916
6917 IF @CheckProcedureCacheFilter = 'Duration'
6918 OR @CheckProcedureCacheFilter IS NULL
6919 BEGIN
6920 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])
6921 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]
6922 FROM sys.dm_exec_query_stats qs
6923 ORDER BY qs.total_elapsed_time DESC)
6924 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])
6925 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]
6926 FROM queries qs
6927 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
6928 WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
6929 EXECUTE(@StringToExecute);
6930 END;
6931
6932 END;
6933 IF @ProductVersionMajor >= 10
6934 BEGIN
6935 IF @CheckProcedureCacheFilter = 'CPU'
6936 OR @CheckProcedureCacheFilter IS NULL
6937 BEGIN
6938 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])
6939 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]
6940 FROM sys.dm_exec_query_stats qs
6941 ORDER BY qs.total_worker_time DESC)
6942 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])
6943 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]
6944 FROM queries qs
6945 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
6946 WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
6947 EXECUTE(@StringToExecute);
6948 END;
6949
6950 IF @CheckProcedureCacheFilter = 'Reads'
6951 OR @CheckProcedureCacheFilter IS NULL
6952 BEGIN
6953 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])
6954 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]
6955 FROM sys.dm_exec_query_stats qs
6956 ORDER BY qs.total_logical_reads DESC)
6957 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])
6958 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]
6959 FROM queries qs
6960 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
6961 WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
6962 EXECUTE(@StringToExecute);
6963 END;
6964
6965 IF @CheckProcedureCacheFilter = 'ExecCount'
6966 OR @CheckProcedureCacheFilter IS NULL
6967 BEGIN
6968 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])
6969 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]
6970 FROM sys.dm_exec_query_stats qs
6971 ORDER BY qs.execution_count DESC)
6972 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])
6973 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]
6974 FROM queries qs
6975 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
6976 WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
6977 EXECUTE(@StringToExecute);
6978 END;
6979
6980 IF @CheckProcedureCacheFilter = 'Duration'
6981 OR @CheckProcedureCacheFilter IS NULL
6982 BEGIN
6983 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])
6984 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]
6985 FROM sys.dm_exec_query_stats qs
6986 ORDER BY qs.total_elapsed_time DESC)
6987 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])
6988 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]
6989 FROM queries qs
6990 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
6991 WHERE qsCaught.sql_handle IS NULL OPTION (RECOMPILE);';
6992 EXECUTE(@StringToExecute);
6993 END;
6994
6995 /* Populate the query_plan_filtered field. Only works in 2005SP2+, but we're just doing it in 2008 to be safe. */
6996 UPDATE #dm_exec_query_stats
6997 SET query_plan_filtered = qp.query_plan
6998 FROM #dm_exec_query_stats qs
6999 CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle,
7000 qs.statement_start_offset,
7001 qs.statement_end_offset)
7002 AS qp;
7003
7004 END;
7005
7006 /* Populate the additional query_plan, text, and text_filtered fields */
7007 UPDATE #dm_exec_query_stats
7008 SET query_plan = qp.query_plan ,
7009 [text] = st.[text] ,
7010 text_filtered = SUBSTRING(st.text,
7011 ( qs.statement_start_offset
7012 / 2 ) + 1,
7013 ( ( CASE qs.statement_end_offset
7014 WHEN -1
7015 THEN DATALENGTH(st.text)
7016 ELSE qs.statement_end_offset
7017 END
7018 - qs.statement_start_offset )
7019 / 2 ) + 1)
7020 FROM #dm_exec_query_stats qs
7021 CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
7022 CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle)
7023 AS qp;
7024
7025 /* Dump instances of our own script. We're not trying to tune ourselves. */
7026 DELETE #dm_exec_query_stats
7027 WHERE text LIKE '%sp_Blitz%'
7028 OR text LIKE '%#BlitzResults%';
7029
7030 /* Look for implicit conversions */
7031
7032 IF NOT EXISTS ( SELECT 1
7033 FROM #SkipChecks
7034 WHERE DatabaseName IS NULL AND CheckID = 63 )
7035 BEGIN
7036
7037 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 63) WITH NOWAIT;
7038
7039 INSERT INTO #BlitzResults
7040 ( CheckID ,
7041 Priority ,
7042 FindingsGroup ,
7043 Finding ,
7044 URL ,
7045 Details ,
7046 QueryPlan ,
7047 QueryPlanFiltered
7048 )
7049 SELECT 63 AS CheckID ,
7050 120 AS Priority ,
7051 'Query Plans' AS FindingsGroup ,
7052 'Implicit Conversion' AS Finding ,
7053 'https://BrentOzar.com/go/implicit' AS URL ,
7054 ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details ,
7055 qs.query_plan ,
7056 qs.query_plan_filtered
7057 FROM #dm_exec_query_stats qs
7058 WHERE COALESCE(qs.query_plan_filtered,
7059 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%CONVERT_IMPLICIT%'
7060 AND COALESCE(qs.query_plan_filtered,
7061 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%PhysicalOp="Index Scan"%';
7062 END;
7063
7064 IF NOT EXISTS ( SELECT 1
7065 FROM #SkipChecks
7066 WHERE DatabaseName IS NULL AND CheckID = 64 )
7067 BEGIN
7068
7069 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 64) WITH NOWAIT;
7070
7071 INSERT INTO #BlitzResults
7072 ( CheckID ,
7073 Priority ,
7074 FindingsGroup ,
7075 Finding ,
7076 URL ,
7077 Details ,
7078 QueryPlan ,
7079 QueryPlanFiltered
7080 )
7081 SELECT 64 AS CheckID ,
7082 120 AS Priority ,
7083 'Query Plans' AS FindingsGroup ,
7084 'Implicit Conversion Affecting Cardinality' AS Finding ,
7085 'https://BrentOzar.com/go/implicit' AS URL ,
7086 ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details ,
7087 qs.query_plan ,
7088 qs.query_plan_filtered
7089 FROM #dm_exec_query_stats qs
7090 WHERE COALESCE(qs.query_plan_filtered,
7091 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<PlanAffectingConvert ConvertIssue="Cardinality Estimate" Expression="CONVERT_IMPLICIT%';
7092 END;
7093
7094 /* @cms4j, 29.11.2013: Look for RID or Key Lookups */
7095 IF NOT EXISTS ( SELECT 1
7096 FROM #SkipChecks
7097 WHERE DatabaseName IS NULL AND CheckID = 118 )
7098 BEGIN
7099
7100 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 118) WITH NOWAIT;
7101
7102 INSERT INTO #BlitzResults
7103 ( CheckID ,
7104 Priority ,
7105 FindingsGroup ,
7106 Finding ,
7107 URL ,
7108 Details ,
7109 QueryPlan ,
7110 QueryPlanFiltered
7111 )
7112 SELECT 118 AS CheckID ,
7113 120 AS Priority ,
7114 'Query Plans' AS FindingsGroup ,
7115 'RID or Key Lookups' AS Finding ,
7116 'https://BrentOzar.com/go/lookup' AS URL ,
7117 'One of the top resource-intensive queries contains RID or Key Lookups. Try to avoid them by creating covering indexes.' AS Details ,
7118 qs.query_plan ,
7119 qs.query_plan_filtered
7120 FROM #dm_exec_query_stats qs
7121 WHERE COALESCE(qs.query_plan_filtered,
7122 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%Lookup="1"%';
7123 END; /* @cms4j, 29.11.2013: Look for RID or Key Lookups */
7124
7125 /* Look for missing indexes */
7126 IF NOT EXISTS ( SELECT 1
7127 FROM #SkipChecks
7128 WHERE DatabaseName IS NULL AND CheckID = 65 )
7129 BEGIN
7130
7131 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 65) WITH NOWAIT;
7132
7133 INSERT INTO #BlitzResults
7134 ( CheckID ,
7135 Priority ,
7136 FindingsGroup ,
7137 Finding ,
7138 URL ,
7139 Details ,
7140 QueryPlan ,
7141 QueryPlanFiltered
7142 )
7143 SELECT 65 AS CheckID ,
7144 120 AS Priority ,
7145 'Query Plans' AS FindingsGroup ,
7146 'Missing Index' AS Finding ,
7147 'https://BrentOzar.com/go/missingindex' AS URL ,
7148 ( 'One of the top resource-intensive queries may be dramatically improved by adding an index.' ) AS Details ,
7149 qs.query_plan ,
7150 qs.query_plan_filtered
7151 FROM #dm_exec_query_stats qs
7152 WHERE COALESCE(qs.query_plan_filtered,
7153 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%MissingIndexGroup%';
7154 END;
7155
7156 /* Look for cursors */
7157 IF NOT EXISTS ( SELECT 1
7158 FROM #SkipChecks
7159 WHERE DatabaseName IS NULL AND CheckID = 66 )
7160 BEGIN
7161
7162 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 66) WITH NOWAIT;
7163
7164 INSERT INTO #BlitzResults
7165 ( CheckID ,
7166 Priority ,
7167 FindingsGroup ,
7168 Finding ,
7169 URL ,
7170 Details ,
7171 QueryPlan ,
7172 QueryPlanFiltered
7173 )
7174 SELECT 66 AS CheckID ,
7175 120 AS Priority ,
7176 'Query Plans' AS FindingsGroup ,
7177 'Cursor' AS Finding ,
7178 'https://BrentOzar.com/go/cursor' AS URL ,
7179 ( 'One of the top resource-intensive queries is using a cursor.' ) AS Details ,
7180 qs.query_plan ,
7181 qs.query_plan_filtered
7182 FROM #dm_exec_query_stats qs
7183 WHERE COALESCE(qs.query_plan_filtered,
7184 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<StmtCursor%';
7185 END;
7186
7187 /* Look for scalar user-defined functions */
7188
7189 IF NOT EXISTS ( SELECT 1
7190 FROM #SkipChecks
7191 WHERE DatabaseName IS NULL AND CheckID = 67 )
7192 BEGIN
7193
7194 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 67) WITH NOWAIT;
7195
7196 INSERT INTO #BlitzResults
7197 ( CheckID ,
7198 Priority ,
7199 FindingsGroup ,
7200 Finding ,
7201 URL ,
7202 Details ,
7203 QueryPlan ,
7204 QueryPlanFiltered
7205 )
7206 SELECT 67 AS CheckID ,
7207 120 AS Priority ,
7208 'Query Plans' AS FindingsGroup ,
7209 'Scalar UDFs' AS Finding ,
7210 'https://BrentOzar.com/go/functions' AS URL ,
7211 ( 'One of the top resource-intensive queries is using a user-defined scalar function that may inhibit parallelism.' ) AS Details ,
7212 qs.query_plan ,
7213 qs.query_plan_filtered
7214 FROM #dm_exec_query_stats qs
7215 WHERE COALESCE(qs.query_plan_filtered,
7216 CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<UserDefinedFunction%';
7217 END;
7218
7219 END; /* IF @CheckProcedureCache = 1 */
7220 END;
7221
7222 /*Check to see if the HA endpoint account is set at the same as the SQL Server Service Account*/
7223 IF @ProductVersionMajor >= 10
7224 AND NOT EXISTS ( SELECT 1
7225 FROM #SkipChecks
7226 WHERE DatabaseName IS NULL AND CheckID = 187 )
7227
7228 IF SERVERPROPERTY('IsHadrEnabled') = 1
7229 BEGIN
7230
7231 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 187) WITH NOWAIT;
7232
7233 INSERT INTO [#BlitzResults]
7234 ( [CheckID] ,
7235 [Priority] ,
7236 [FindingsGroup] ,
7237 [Finding] ,
7238 [URL] ,
7239 [Details] )
7240 SELECT
7241 187 AS [CheckID] ,
7242 230 AS [Priority] ,
7243 'Security' AS [FindingsGroup] ,
7244 'Endpoints Owned by Users' AS [Finding] ,
7245 'https://BrentOzar.com/go/owners' AS [URL] ,
7246 ( 'Endpoint ' + ep.[name] + ' is owned by ' + SUSER_NAME(ep.principal_id) + '. If the endpoint owner login is disabled or not available due to Active Directory problems, the high availability will stop working.'
7247 ) AS [Details]
7248 FROM sys.database_mirroring_endpoints ep
7249 LEFT OUTER JOIN sys.dm_server_services s ON SUSER_NAME(ep.principal_id) = s.service_account
7250 WHERE s.service_account IS NULL AND ep.principal_id <> 1;
7251 END;
7252
7253 /*Check for the last good DBCC CHECKDB date */
7254 IF NOT EXISTS ( SELECT 1
7255 FROM #SkipChecks
7256 WHERE DatabaseName IS NULL AND CheckID = 68 )
7257 BEGIN
7258
7259 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 68) WITH NOWAIT;
7260
7261 EXEC sp_MSforeachdb N'USE [?];
7262 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
7263 INSERT #DBCCs
7264 (ParentObject,
7265 Object,
7266 Field,
7267 Value)
7268 EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS'');
7269 UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL OPTION (RECOMPILE);';
7270
7271 WITH DB2
7272 AS ( SELECT DISTINCT
7273 Field ,
7274 Value ,
7275 DbName
7276 FROM #DBCCs
7277 INNER JOIN sys.databases d ON #DBCCs.DbName = d.name
7278 WHERE Field = 'dbi_dbccLastKnownGood'
7279 AND d.create_date < DATEADD(dd, -14, GETDATE())
7280 )
7281 INSERT INTO #BlitzResults
7282 ( CheckID ,
7283 DatabaseName ,
7284 Priority ,
7285 FindingsGroup ,
7286 Finding ,
7287 URL ,
7288 Details
7289 )
7290 SELECT 68 AS CheckID ,
7291 DB2.DbName AS DatabaseName ,
7292 1 AS PRIORITY ,
7293 'Reliability' AS FindingsGroup ,
7294 'Last good DBCC CHECKDB over 2 weeks old' AS Finding ,
7295 'https://BrentOzar.com/go/checkdb' AS URL ,
7296 'Last successful CHECKDB: '
7297 + CASE DB2.Value
7298 WHEN '1900-01-01 00:00:00.000'
7299 THEN ' never.'
7300 ELSE DB2.Value
7301 END AS Details
7302 FROM DB2
7303 WHERE DB2.DbName <> 'tempdb'
7304 AND DB2.DbName NOT IN ( SELECT DISTINCT
7305 DatabaseName
7306 FROM
7307 #SkipChecks
7308 WHERE CheckID IS NULL OR CheckID = 68)
7309 AND DB2.DbName NOT IN ( SELECT name
7310 FROM sys.databases
7311 WHERE is_read_only = 1)
7312 AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD,
7313 -14,
7314 CURRENT_TIMESTAMP);
7315 END;
7316
7317 /*Verify that the servername is set */
7318 IF NOT EXISTS ( SELECT 1
7319 FROM #SkipChecks
7320 WHERE DatabaseName IS NULL AND CheckID = 70 )
7321 BEGIN
7322 IF @@SERVERNAME IS NULL
7323 BEGIN
7324
7325 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT;
7326
7327 INSERT INTO #BlitzResults
7328 ( CheckID ,
7329 Priority ,
7330 FindingsGroup ,
7331 Finding ,
7332 URL ,
7333 Details
7334 )
7335 SELECT 70 AS CheckID ,
7336 200 AS Priority ,
7337 'Informational' AS FindingsGroup ,
7338 '@@Servername Not Set' AS Finding ,
7339 'https://BrentOzar.com/go/servername' AS URL ,
7340 '@@Servername variable is null. You can fix it by executing: "sp_addserver ''<LocalServerName>'', local"' AS Details;
7341 END;
7342
7343 IF /* @@SERVERNAME IS set */
7344 (@@SERVERNAME IS NOT NULL
7345 AND
7346 /* not a named instance */
7347 CHARINDEX(CHAR(92),CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))) = 0
7348 AND
7349 /* not clustered, when computername may be different than the servername */
7350 SERVERPROPERTY('IsClustered') = 0
7351 AND
7352 /* @@SERVERNAME is different than the computer name */
7353 @@SERVERNAME <> CAST(ISNULL(SERVERPROPERTY('ComputerNamePhysicalNetBIOS'),@@SERVERNAME) AS NVARCHAR(128)) )
7354 BEGIN
7355
7356 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 70) WITH NOWAIT;
7357
7358 INSERT INTO #BlitzResults
7359 ( CheckID ,
7360 Priority ,
7361 FindingsGroup ,
7362 Finding ,
7363 URL ,
7364 Details
7365 )
7366 SELECT 70 AS CheckID ,
7367 200 AS Priority ,
7368 'Configuration' AS FindingsGroup ,
7369 '@@Servername Not Correct' AS Finding ,
7370 'https://BrentOzar.com/go/servername' AS URL ,
7371 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details;
7372 END;
7373
7374 END;
7375 /*Check to see if a failsafe operator has been configured*/
7376 IF NOT EXISTS ( SELECT 1
7377 FROM #SkipChecks
7378 WHERE DatabaseName IS NULL AND CheckID = 73 )
7379 BEGIN
7380
7381 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 73) WITH NOWAIT;
7382
7383 DECLARE @AlertInfo TABLE
7384 (
7385 FailSafeOperator NVARCHAR(255) ,
7386 NotificationMethod INT ,
7387 ForwardingServer NVARCHAR(255) ,
7388 ForwardingSeverity INT ,
7389 PagerToTemplate NVARCHAR(255) ,
7390 PagerCCTemplate NVARCHAR(255) ,
7391 PagerSubjectTemplate NVARCHAR(255) ,
7392 PagerSendSubjectOnly NVARCHAR(255) ,
7393 ForwardAlways INT
7394 );
7395 INSERT INTO @AlertInfo
7396 EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0;
7397 INSERT INTO #BlitzResults
7398 ( CheckID ,
7399 Priority ,
7400 FindingsGroup ,
7401 Finding ,
7402 URL ,
7403 Details
7404 )
7405 SELECT 73 AS CheckID ,
7406 200 AS Priority ,
7407 'Monitoring' AS FindingsGroup ,
7408 'No Failsafe Operator Configured' AS Finding ,
7409 'https://BrentOzar.com/go/failsafe' AS URL ,
7410 ( '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
7411 FROM @AlertInfo
7412 WHERE FailSafeOperator IS NULL;
7413 END;
7414
7415 /*Identify globally enabled trace flags*/
7416 IF NOT EXISTS ( SELECT 1
7417 FROM #SkipChecks
7418 WHERE DatabaseName IS NULL AND CheckID = 74 )
7419 BEGIN
7420
7421 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 74) WITH NOWAIT;
7422
7423 INSERT INTO #TraceStatus
7424 EXEC ( ' DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'
7425 );
7426 INSERT INTO #BlitzResults
7427 ( CheckID ,
7428 Priority ,
7429 FindingsGroup ,
7430 Finding ,
7431 URL ,
7432 Details
7433 )
7434 SELECT 74 AS CheckID ,
7435 200 AS Priority ,
7436 'Informational' AS FindingsGroup ,
7437 'TraceFlag On' AS Finding ,
7438 CASE WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN 'https://support.microsoft.com/en-us/kb/3210239'
7439 ELSE'https://www.BrentOzar.com/go/traceflags/' END AS URL ,
7440 'Trace flag ' +
7441 CASE WHEN [T].[TraceFlag] = '2330' THEN ' 2330 enabled globally. Using this trace Flag disables missing index requests!'
7442 WHEN [T].[TraceFlag] = '1211' THEN ' 1211 enabled globally. Using this Trace Flag disables lock escalation when you least expect it. No Bueno!'
7443 WHEN [T].[TraceFlag] = '1224' THEN ' 1224 enabled globally. Using this Trace Flag disables lock escalation based on the number of locks being taken. You shouldn''t have done that, Dave.'
7444 WHEN [T].[TraceFlag] = '652' THEN ' 652 enabled globally. Using this Trace Flag disables pre-fetching during index scans. If you hate slow queries, you should turn that off.'
7445 WHEN [T].[TraceFlag] = '661' THEN ' 661 enabled globally. Using this Trace Flag disables ghost record removal. Who you gonna call? No one, turn that thing off.'
7446 WHEN [T].[TraceFlag] = '1806' THEN ' 1806 enabled globally. Using this Trace Flag disables Instant File Initialization. I question your sanity.'
7447 WHEN [T].[TraceFlag] = '3505' THEN ' 3505 enabled globally. Using this Trace Flag disables Checkpoints. Probably not the wisest idea.'
7448 WHEN [T].[TraceFlag] = '8649' THEN ' 8649 enabled globally. Using this Trace Flag drops cost threshold for parallelism down to 0. I hope this is a dev server.'
7449 WHEN [T].[TraceFlag] = '834' AND @ColumnStoreIndexesInUse = 1 THEN ' 834 is enabled globally. Using this Trace Flag with Columnstore Indexes is not a great idea.'
7450 WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) LIKE N'%Express%') THEN ' 8017 is enabled globally, which is the default for express edition.'
7451 WHEN [T].[TraceFlag] = '8017' AND (CAST(SERVERPROPERTY('Edition') AS NVARCHAR(1000)) NOT LIKE N'%Express%') THEN ' 8017 is enabled globally. Using this Trace Flag disables creation schedulers for all logical processors. Not good.'
7452 ELSE [T].[TraceFlag] + ' is enabled globally.' END
7453 AS Details
7454 FROM #TraceStatus T;
7455 END;
7456
7457 /* High CMEMTHREAD waits that could need trace flag 8048.
7458 This check has to be run AFTER the globally enabled trace flag check,
7459 since it uses the #TraceStatus table to know if flags are enabled.
7460 */
7461 IF @ProductVersionMajor >= 11 AND NOT EXISTS ( SELECT 1
7462 FROM #SkipChecks
7463 WHERE DatabaseName IS NULL AND CheckID = 162 )
7464 BEGIN
7465
7466 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 162) WITH NOWAIT;
7467
7468 INSERT INTO #BlitzResults
7469 ( CheckID ,
7470 Priority ,
7471 FindingsGroup ,
7472 Finding ,
7473 URL ,
7474 Details
7475 )
7476 SELECT 162 AS CheckID ,
7477 50 AS Priority ,
7478 'Performance' AS FindingGroup ,
7479 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding ,
7480 'https://BrentOzar.com/go/poison' AS URL ,
7481 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'
7482 + CASE WHEN ts.status = 1 THEN ' despite enabling trace flag 8048 already.'
7483 ELSE '. In servers with over 8 cores per NUMA node, when CMEMTHREAD waits are a bottleneck, trace flag 8048 may be needed.'
7484 END
7485 FROM sys.dm_os_nodes n
7486 INNER JOIN sys.[dm_os_wait_stats] w ON w.wait_type = 'CMEMTHREAD'
7487 LEFT OUTER JOIN #TraceStatus ts ON ts.TraceFlag = 8048 AND ts.status = 1
7488 WHERE n.node_id = 0 AND n.online_scheduler_count >= 8
7489 AND EXISTS (SELECT * FROM sys.dm_os_nodes WHERE node_id > 0 AND node_state_desc NOT LIKE '%DAC')
7490 GROUP BY w.wait_type, ts.status
7491 HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
7492 AND SUM([wait_time_ms]) > 60000;
7493 END;
7494
7495
7496 /*Check for transaction log file larger than data file */
7497 IF NOT EXISTS ( SELECT 1
7498 FROM #SkipChecks
7499 WHERE DatabaseName IS NULL AND CheckID = 75 )
7500 BEGIN
7501
7502 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 75) WITH NOWAIT;
7503
7504 INSERT INTO #BlitzResults
7505 ( CheckID ,
7506 DatabaseName ,
7507 Priority ,
7508 FindingsGroup ,
7509 Finding ,
7510 URL ,
7511 Details
7512 )
7513 SELECT 75 AS CheckID ,
7514 DB_NAME(a.database_id) ,
7515 50 AS Priority ,
7516 'Reliability' AS FindingsGroup ,
7517 'Transaction Log Larger than Data File' AS Finding ,
7518 'https://BrentOzar.com/go/biglog' AS URL ,
7519 'The database [' + DB_NAME(a.database_id)
7520 + '] 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
7521 FROM sys.master_files a
7522 WHERE a.type = 1
7523 AND DB_NAME(a.database_id) NOT IN (
7524 SELECT DISTINCT
7525 DatabaseName
7526 FROM #SkipChecks
7527 WHERE CheckID = 75 OR CheckID IS NULL)
7528 AND a.size > 125000 /* Size is measured in pages here, so this gets us log files over 1GB. */
7529 AND a.size > ( SELECT SUM(CAST(b.size AS BIGINT))
7530 FROM sys.master_files b
7531 WHERE a.database_id = b.database_id
7532 AND b.type = 0
7533 )
7534 AND a.database_id IN (
7535 SELECT database_id
7536 FROM sys.databases
7537 WHERE source_database_id IS NULL );
7538 END;
7539
7540 /*Check for collation conflicts between user databases and tempdb */
7541 IF NOT EXISTS ( SELECT 1
7542 FROM #SkipChecks
7543 WHERE DatabaseName IS NULL AND CheckID = 76 )
7544 BEGIN
7545
7546 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 76) WITH NOWAIT;
7547
7548 INSERT INTO #BlitzResults
7549 ( CheckID ,
7550 DatabaseName ,
7551 Priority ,
7552 FindingsGroup ,
7553 Finding ,
7554 URL ,
7555 Details
7556 )
7557 SELECT 76 AS CheckID ,
7558 name AS DatabaseName ,
7559 200 AS Priority ,
7560 'Informational' AS FindingsGroup ,
7561 'Collation is ' + collation_name AS Finding ,
7562 'https://BrentOzar.com/go/collate' AS URL ,
7563 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details
7564 FROM sys.databases
7565 WHERE name NOT IN ( 'master', 'model', 'msdb')
7566 AND name NOT LIKE 'ReportServer%'
7567 AND name NOT IN ( SELECT DISTINCT
7568 DatabaseName
7569 FROM #SkipChecks
7570 WHERE CheckID IS NULL OR CheckID = 76)
7571 AND collation_name <> ( SELECT
7572 collation_name
7573 FROM
7574 sys.databases
7575 WHERE
7576 name = 'tempdb'
7577 );
7578 END;
7579
7580 IF NOT EXISTS ( SELECT 1
7581 FROM #SkipChecks
7582 WHERE DatabaseName IS NULL AND CheckID = 77 )
7583 BEGIN
7584
7585 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 77) WITH NOWAIT;
7586
7587 INSERT INTO #BlitzResults
7588 ( CheckID ,
7589 DatabaseName ,
7590 Priority ,
7591 FindingsGroup ,
7592 Finding ,
7593 URL ,
7594 Details
7595 )
7596 SELECT 77 AS CheckID ,
7597 dSnap.[name] AS DatabaseName ,
7598 50 AS Priority ,
7599 'Reliability' AS FindingsGroup ,
7600 'Database Snapshot Online' AS Finding ,
7601 'https://BrentOzar.com/go/snapshot' AS URL ,
7602 'Database [' + dSnap.[name]
7603 + '] is a snapshot of ['
7604 + dOriginal.[name]
7605 + ']. Make sure you have enough drive space to maintain the snapshot as the original database grows.' AS Details
7606 FROM sys.databases dSnap
7607 INNER JOIN sys.databases dOriginal ON dSnap.source_database_id = dOriginal.database_id
7608 AND dSnap.name NOT IN (
7609 SELECT DISTINCT DatabaseName
7610 FROM #SkipChecks
7611 WHERE CheckID = 77 OR CheckID IS NULL);
7612 END;
7613
7614 IF NOT EXISTS ( SELECT 1
7615 FROM #SkipChecks
7616 WHERE DatabaseName IS NULL AND CheckID = 79 )
7617 BEGIN
7618
7619 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 79) WITH NOWAIT;
7620
7621 INSERT INTO #BlitzResults
7622 ( CheckID ,
7623 Priority ,
7624 FindingsGroup ,
7625 Finding ,
7626 URL ,
7627 Details
7628 )
7629 SELECT 79 AS CheckID ,
7630 -- sp_Blitz Issue #776
7631 -- Job has history and was executed in the last 30 days OR Job is enabled AND Job Schedule is enabled
7632 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
7633 100
7634 ELSE -- no job history (implicit) AND job not run in the past 30 days AND (Job disabled OR Job Schedule disabled)
7635 200
7636 END AS Priority,
7637 'Performance' AS FindingsGroup ,
7638 'Shrink Database Job' AS Finding ,
7639 'https://BrentOzar.com/go/autoshrink' AS URL ,
7640 'In the [' + j.[name] + '] job, step ['
7641 + step.[step_name]
7642 + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.'
7643 + CASE WHEN COALESCE(ssc.name,'0') != '0' THEN + ' (Schedule: [' + ssc.name + '])' ELSE + '' END AS Details
7644 FROM msdb.dbo.sysjobs j
7645 INNER JOIN msdb.dbo.sysjobsteps step ON j.job_id = step.job_id
7646 LEFT OUTER JOIN msdb.dbo.sysjobschedules AS sjsc
7647 ON j.job_id = sjsc.job_id
7648 LEFT OUTER JOIN msdb.dbo.sysschedules AS ssc
7649 ON sjsc.schedule_id = ssc.schedule_id
7650 AND sjsc.job_id = j.job_id
7651 LEFT OUTER JOIN msdb.dbo.sysjobhistory AS sjh
7652 ON j.job_id = sjh.job_id
7653 AND step.step_id = sjh.step_id
7654 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
7655 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
7656 WHERE step.command LIKE N'%SHRINKDATABASE%'
7657 OR step.command LIKE N'%SHRINKFILE%';
7658 END;
7659
7660 IF NOT EXISTS ( SELECT 1
7661 FROM #SkipChecks
7662 WHERE DatabaseName IS NULL AND CheckID = 81 )
7663 BEGIN
7664
7665 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 81) WITH NOWAIT;
7666
7667 INSERT INTO #BlitzResults
7668 ( CheckID ,
7669 Priority ,
7670 FindingsGroup ,
7671 Finding ,
7672 URL ,
7673 Details
7674 )
7675 SELECT 81 AS CheckID ,
7676 200 AS Priority ,
7677 'Non-Active Server Config' AS FindingsGroup ,
7678 cr.name AS Finding ,
7679 'https://www.BrentOzar.com/blitz/sp_configure/' AS URL ,
7680 ( 'This sp_configure option isn''t running under its set value. Its set value is '
7681 + CAST(cr.[value] AS VARCHAR(100))
7682 + ' and its running value is '
7683 + CAST(cr.value_in_use AS VARCHAR(100))
7684 + '. When someone does a RECONFIGURE or restarts the instance, this setting will start taking effect.' ) AS Details
7685 FROM sys.configurations cr
7686 WHERE cr.value <> cr.value_in_use
7687 AND NOT (cr.name = 'min server memory (MB)' AND cr.value IN (0,16) AND cr.value_in_use IN (0,16));
7688 END;
7689
7690 IF NOT EXISTS ( SELECT 1
7691 FROM #SkipChecks
7692 WHERE DatabaseName IS NULL AND CheckID = 123 )
7693 BEGIN
7694
7695 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 123) WITH NOWAIT;
7696
7697 INSERT INTO #BlitzResults
7698 ( CheckID ,
7699 Priority ,
7700 FindingsGroup ,
7701 Finding ,
7702 URL ,
7703 Details
7704 )
7705 SELECT TOP 1 123 AS CheckID ,
7706 200 AS Priority ,
7707 'Informational' AS FindingsGroup ,
7708 'Agent Jobs Starting Simultaneously' AS Finding ,
7709 'https://BrentOzar.com/go/busyagent/' AS URL ,
7710 ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details
7711 FROM msdb.dbo.sysjobactivity
7712 WHERE start_execution_date > DATEADD(dd, -14, GETDATE())
7713 GROUP BY start_execution_date HAVING COUNT(*) > 1;
7714 END;
7715
7716 IF @CheckServerInfo = 1
7717 BEGIN
7718
7719/*This checks Windows version. It would be better if Microsoft gave everything a separate build number, but whatever.*/
7720IF @ProductVersionMajor >= 10
7721 AND NOT EXISTS ( SELECT 1
7722 FROM #SkipChecks
7723 WHERE DatabaseName IS NULL AND CheckID = 172 )
7724 BEGIN
7725 -- sys.dm_os_host_info includes both Windows and Linux info
7726 IF EXISTS (SELECT 1
7727 FROM sys.all_objects
7728 WHERE name = 'dm_os_host_info' )
7729 BEGIN
7730
7731 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT;
7732
7733 INSERT INTO [#BlitzResults]
7734 ( [CheckID] ,
7735 [Priority] ,
7736 [FindingsGroup] ,
7737 [Finding] ,
7738 [URL] ,
7739 [Details] )
7740
7741 SELECT
7742 172 AS [CheckID] ,
7743 250 AS [Priority] ,
7744 'Server Info' AS [FindingsGroup] ,
7745 'Operating System Version' AS [Finding] ,
7746 ( CASE WHEN @IsWindowsOperatingSystem = 1
7747 THEN 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions'
7748 ELSE 'https://en.wikipedia.org/wiki/List_of_Linux_distributions'
7749 END
7750 ) AS [URL] ,
7751 ( CASE
7752 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))
7753 WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] = '5' THEN 'You''re running a really old version: Windows 2000, version ' + CAST([ohi].[host_release] AS VARCHAR(5))
7754 WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] > '5' AND [ohi].[host_release] < '6' THEN 'You''re running a really old version: ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
7755 WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] >= '6' AND [ohi].[host_release] <= '6.1' THEN 'You''re running a pretty old version: Windows: ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
7756 WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] = '6.2' THEN 'You''re running a rather modern version of Windows: ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
7757 WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] = '6.3' THEN 'You''re running a pretty modern version of Windows: ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
7758 WHEN [ohi].[host_platform] = 'Windows' AND [ohi].[host_release] > '6.3' THEN 'Hot dog! You''re living in the future! You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(50)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
7759 ELSE 'You''re running ' + CAST([ohi].[host_distribution] AS VARCHAR(35)) + ', version ' + CAST([ohi].[host_release] AS VARCHAR(5))
7760 END
7761 ) AS [Details]
7762 FROM [sys].[dm_os_host_info] [ohi];
7763 END;
7764 ELSE
7765 BEGIN
7766 -- Otherwise, stick with Windows-only detection
7767
7768 IF EXISTS ( SELECT 1
7769 FROM sys.all_objects
7770 WHERE name = 'dm_os_windows_info' )
7771
7772 BEGIN
7773
7774 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 172) WITH NOWAIT;
7775
7776 INSERT INTO [#BlitzResults]
7777 ( [CheckID] ,
7778 [Priority] ,
7779 [FindingsGroup] ,
7780 [Finding] ,
7781 [URL] ,
7782 [Details] )
7783
7784 SELECT
7785 172 AS [CheckID] ,
7786 250 AS [Priority] ,
7787 'Server Info' AS [FindingsGroup] ,
7788 'Windows Version' AS [Finding] ,
7789 'https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions' AS [URL] ,
7790 ( CASE
7791 WHEN [owi].[windows_release] = '5' THEN 'You''re running a really old version: Windows 2000, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
7792 WHEN [owi].[windows_release] > '5' AND [owi].[windows_release] < '6' THEN 'You''re running a really old version: Windows Server 2003/2003R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
7793 WHEN [owi].[windows_release] >= '6' AND [owi].[windows_release] <= '6.1' THEN 'You''re running a pretty old version: Windows: Server 2008/2008R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
7794 WHEN [owi].[windows_release] = '6.2' THEN 'You''re running a rather modern version of Windows: Server 2012 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
7795 WHEN [owi].[windows_release] = '6.3' THEN 'You''re running a pretty modern version of Windows: Server 2012R2 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
7796 WHEN [owi].[windows_release] = '10.0' THEN 'You''re running a pretty modern version of Windows: Server 2016 era, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
7797 ELSE 'Hot dog! You''re living in the future! You''re running version ' + CAST([owi].[windows_release] AS VARCHAR(5))
7798 END
7799 ) AS [Details]
7800 FROM [sys].[dm_os_windows_info] [owi];
7801
7802 END;
7803 END;
7804 END;
7805
7806/*
7807This check hits the dm_os_process_memory system view
7808to see if locked_page_allocations_kb is > 0,
7809which could indicate that locked pages in memory is enabled.
7810*/
7811IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1
7812 FROM #SkipChecks
7813 WHERE DatabaseName IS NULL AND CheckID = 166 )
7814 BEGIN
7815
7816 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT;
7817
7818 INSERT INTO [#BlitzResults]
7819 ( [CheckID] ,
7820 [Priority] ,
7821 [FindingsGroup] ,
7822 [Finding] ,
7823 [URL] ,
7824 [Details] )
7825 SELECT
7826 166 AS [CheckID] ,
7827 250 AS [Priority] ,
7828 'Server Info' AS [FindingsGroup] ,
7829 'Locked Pages In Memory Enabled' AS [Finding] ,
7830 'https://BrentOzar.com/go/lpim' AS [URL] ,
7831 ( 'You currently have '
7832 + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0
7833 THEN CAST([dopm].[locked_page_allocations_kb] / 1024 / 1024 AS VARCHAR(100))
7834 + ' GB'
7835 ELSE CAST([dopm].[locked_page_allocations_kb] / 1024 AS VARCHAR(100))
7836 + ' MB'
7837 END + ' of pages locked in memory.' ) AS [Details]
7838 FROM
7839 [sys].[dm_os_process_memory] AS [dopm]
7840 WHERE
7841 [dopm].[locked_page_allocations_kb] > 0;
7842 END;
7843
7844 /* Server Info - Locked Pages In Memory Enabled - Check 166 - SQL Server 2016 SP1 and newer */
7845 IF NOT EXISTS ( SELECT 1
7846 FROM #SkipChecks
7847 WHERE DatabaseName IS NULL AND CheckID = 166 )
7848 AND EXISTS ( SELECT *
7849 FROM sys.all_objects o
7850 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
7851 WHERE o.name = 'dm_os_sys_info'
7852 AND c.name = 'sql_memory_model' )
7853 BEGIN
7854
7855 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 166) WITH NOWAIT;
7856
7857 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
7858 SELECT 166 AS CheckID ,
7859 250 AS Priority ,
7860 ''Server Info'' AS FindingsGroup ,
7861 ''Memory Model Unconventional'' AS Finding ,
7862 ''https://BrentOzar.com/go/lpim'' AS URL ,
7863 ''Memory Model: '' + CAST(sql_memory_model_desc AS NVARCHAR(100))
7864 FROM sys.dm_os_sys_info WHERE sql_memory_model <> 1 OPTION (RECOMPILE);';
7865
7866 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
7867 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
7868
7869 EXECUTE(@StringToExecute);
7870 END;
7871
7872 /*
7873 Starting with SQL Server 2014 SP2, Instant File Initialization
7874 is logged in the SQL Server Error Log.
7875 */
7876 IF NOT EXISTS ( SELECT 1
7877 FROM #SkipChecks
7878 WHERE DatabaseName IS NULL AND CheckID = 184 )
7879 AND (@ProductVersionMajor >= 13) OR (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 5000)
7880 BEGIN
7881
7882 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 184) WITH NOWAIT;
7883
7884 INSERT INTO #ErrorLog
7885 EXEC sys.xp_readerrorlog 0, 1, N'Database Instant File Initialization: enabled';
7886
7887 IF @@ROWCOUNT > 0
7888 INSERT INTO #BlitzResults
7889 ( CheckID ,
7890 [Priority] ,
7891 FindingsGroup ,
7892 Finding ,
7893 URL ,
7894 Details
7895 )
7896 SELECT
7897 193 AS [CheckID] ,
7898 250 AS [Priority] ,
7899 'Server Info' AS [FindingsGroup] ,
7900 'Instant File Initialization Enabled' AS [Finding] ,
7901 'https://BrentOzar.com/go/instant' AS [URL] ,
7902 'The service account has the Perform Volume Maintenance Tasks permission.';
7903 END;
7904
7905 /* Server Info - Instant File Initialization Not Enabled - Check 192 - SQL Server 2016 SP1 and newer */
7906 IF NOT EXISTS ( SELECT 1
7907 FROM #SkipChecks
7908 WHERE DatabaseName IS NULL AND CheckID = 192 )
7909 AND EXISTS ( SELECT *
7910 FROM sys.all_objects o
7911 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
7912 WHERE o.name = 'dm_server_services'
7913 AND c.name = 'instant_file_initialization_enabled' )
7914 BEGIN
7915
7916 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 192) WITH NOWAIT;
7917
7918 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
7919 SELECT 192 AS CheckID ,
7920 50 AS Priority ,
7921 ''Server Info'' AS FindingsGroup ,
7922 ''Instant File Initialization Not Enabled'' AS Finding ,
7923 ''https://BrentOzar.com/go/instant'' AS URL ,
7924 ''Consider enabling IFI for faster restores and data file growths.''
7925 FROM sys.dm_server_services WHERE instant_file_initialization_enabled <> ''Y'' AND filename LIKE ''%sqlservr.exe%'' OPTION (RECOMPILE);';
7926
7927 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
7928 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
7929
7930 EXECUTE(@StringToExecute);
7931 END;
7932
7933 IF NOT EXISTS ( SELECT 1
7934 FROM #SkipChecks
7935 WHERE DatabaseName IS NULL AND CheckID = 130 )
7936 BEGIN
7937
7938 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 130) WITH NOWAIT;
7939
7940 INSERT INTO #BlitzResults
7941 ( CheckID ,
7942 Priority ,
7943 FindingsGroup ,
7944 Finding ,
7945 URL ,
7946 Details
7947 )
7948 SELECT 130 AS CheckID ,
7949 250 AS Priority ,
7950 'Server Info' AS FindingsGroup ,
7951 'Server Name' AS Finding ,
7952 'https://BrentOzar.com/go/servername' AS URL ,
7953 @@SERVERNAME AS Details
7954 WHERE @@SERVERNAME IS NOT NULL;
7955 END;
7956
7957 IF NOT EXISTS ( SELECT 1
7958 FROM #SkipChecks
7959 WHERE DatabaseName IS NULL AND CheckID = 83 )
7960 BEGIN
7961 IF EXISTS ( SELECT *
7962 FROM sys.all_objects
7963 WHERE name = 'dm_server_services' )
7964 BEGIN
7965
7966 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 83) WITH NOWAIT;
7967
7968 -- DATETIMEOFFSET and DATETIME have different minimum values, so there's
7969 -- a small workaround here to force 1753-01-01 if the minimum is detected
7970 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
7971 SELECT 83 AS CheckID ,
7972 250 AS Priority ,
7973 ''Server Info'' AS FindingsGroup ,
7974 ''Services'' AS Finding ,
7975 '''' AS URL ,
7976 N''Service: '' + servicename + N'' runs under service account '' + service_account + N''. Last startup time: '' + COALESCE(CAST(CASE WHEN YEAR(last_startup_time) <= 1753 THEN CAST(''17530101'' as datetime) ELSE CAST(last_startup_time AS DATETIME) END AS VARCHAR(50)), ''not shown.'') + ''. Startup type: '' + startup_type_desc + N'', currently '' + status_desc + ''.''
7977 FROM sys.dm_server_services OPTION (RECOMPILE);';
7978
7979 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
7980 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
7981
7982 EXECUTE(@StringToExecute);
7983 END;
7984 END;
7985
7986 /* Check 84 - SQL Server 2012 */
7987 IF NOT EXISTS ( SELECT 1
7988 FROM #SkipChecks
7989 WHERE DatabaseName IS NULL AND CheckID = 84 )
7990 BEGIN
7991 IF EXISTS ( SELECT *
7992 FROM sys.all_objects o
7993 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
7994 WHERE o.name = 'dm_os_sys_info'
7995 AND c.name = 'physical_memory_kb' )
7996 BEGIN
7997
7998 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT;
7999
8000 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
8001 SELECT 84 AS CheckID ,
8002 250 AS Priority ,
8003 ''Server Info'' AS FindingsGroup ,
8004 ''Hardware'' AS Finding ,
8005 '''' AS URL ,
8006 ''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.''
8007 FROM sys.dm_os_sys_info OPTION (RECOMPILE);';
8008
8009 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
8010 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
8011
8012 EXECUTE(@StringToExecute);
8013 END;
8014
8015 /* Check 84 - SQL Server 2008 */
8016 IF EXISTS ( SELECT *
8017 FROM sys.all_objects o
8018 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
8019 WHERE o.name = 'dm_os_sys_info'
8020 AND c.name = 'physical_memory_in_bytes' )
8021 BEGIN
8022
8023 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 84) WITH NOWAIT;
8024
8025 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
8026 SELECT 84 AS CheckID ,
8027 250 AS Priority ,
8028 ''Server Info'' AS FindingsGroup ,
8029 ''Hardware'' AS Finding ,
8030 '''' AS URL ,
8031 ''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.''
8032 FROM sys.dm_os_sys_info OPTION (RECOMPILE);';
8033
8034 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
8035 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
8036
8037 EXECUTE(@StringToExecute);
8038 END;
8039 END;
8040
8041 IF NOT EXISTS ( SELECT 1
8042 FROM #SkipChecks
8043 WHERE DatabaseName IS NULL AND CheckID = 85 )
8044 BEGIN
8045
8046 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 85) WITH NOWAIT;
8047
8048 INSERT INTO #BlitzResults
8049 ( CheckID ,
8050 Priority ,
8051 FindingsGroup ,
8052 Finding ,
8053 URL ,
8054 Details
8055 )
8056 SELECT 85 AS CheckID ,
8057 250 AS Priority ,
8058 'Server Info' AS FindingsGroup ,
8059 'SQL Server Service' AS Finding ,
8060 '' AS URL ,
8061 N'Version: '
8062 + CAST(SERVERPROPERTY('productversion') AS NVARCHAR(100))
8063 + N'. Patch Level: '
8064 + CAST(SERVERPROPERTY('productlevel') AS NVARCHAR(100))
8065 + CASE WHEN SERVERPROPERTY('ProductUpdateLevel') IS NULL
8066 THEN N''
8067 ELSE N'. Cumulative Update: '
8068 + CAST(SERVERPROPERTY('ProductUpdateLevel') AS NVARCHAR(100))
8069 END
8070 + N'. Edition: '
8071 + CAST(SERVERPROPERTY('edition') AS VARCHAR(100))
8072 + N'. Availability Groups Enabled: '
8073 + CAST(COALESCE(SERVERPROPERTY('IsHadrEnabled'),
8074 0) AS VARCHAR(100))
8075 + N'. Availability Groups Manager Status: '
8076 + CAST(COALESCE(SERVERPROPERTY('HadrManagerStatus'),
8077 0) AS VARCHAR(100));
8078 END;
8079
8080 IF NOT EXISTS ( SELECT 1
8081 FROM #SkipChecks
8082 WHERE DatabaseName IS NULL AND CheckID = 88 )
8083 BEGIN
8084
8085 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 88) WITH NOWAIT;
8086
8087 INSERT INTO #BlitzResults
8088 ( CheckID ,
8089 Priority ,
8090 FindingsGroup ,
8091 Finding ,
8092 URL ,
8093 Details
8094 )
8095 SELECT 88 AS CheckID ,
8096 250 AS Priority ,
8097 'Server Info' AS FindingsGroup ,
8098 'SQL Server Last Restart' AS Finding ,
8099 '' AS URL ,
8100 CAST(create_date AS VARCHAR(100))
8101 FROM sys.databases
8102 WHERE database_id = 2;
8103 END;
8104
8105 IF NOT EXISTS ( SELECT 1
8106 FROM #SkipChecks
8107 WHERE DatabaseName IS NULL AND CheckID = 91 )
8108 BEGIN
8109
8110 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 91) WITH NOWAIT;
8111
8112 INSERT INTO #BlitzResults
8113 ( CheckID ,
8114 Priority ,
8115 FindingsGroup ,
8116 Finding ,
8117 URL ,
8118 Details
8119 )
8120 SELECT 91 AS CheckID ,
8121 250 AS Priority ,
8122 'Server Info' AS FindingsGroup ,
8123 'Server Last Restart' AS Finding ,
8124 '' AS URL ,
8125 CAST(DATEADD(SECOND, (ms_ticks/1000)*(-1), GETDATE()) AS nvarchar(25))
8126 FROM sys.dm_os_sys_info;
8127 END;
8128
8129 IF NOT EXISTS ( SELECT 1
8130 FROM #SkipChecks
8131 WHERE DatabaseName IS NULL AND CheckID = 92 )
8132 BEGIN
8133
8134 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 92) WITH NOWAIT;
8135
8136 INSERT INTO #driveInfo
8137 ( drive, SIZE )
8138 EXEC master..xp_fixeddrives;
8139
8140 INSERT INTO #BlitzResults
8141 ( CheckID ,
8142 Priority ,
8143 FindingsGroup ,
8144 Finding ,
8145 URL ,
8146 Details
8147 )
8148 SELECT 92 AS CheckID ,
8149 250 AS Priority ,
8150 'Server Info' AS FindingsGroup ,
8151 'Drive ' + i.drive + ' Space' AS Finding ,
8152 '' AS URL ,
8153 CAST(i.SIZE AS VARCHAR(30))
8154 + 'MB free on ' + i.drive
8155 + ' drive' AS Details
8156 FROM #driveInfo AS i;
8157 DROP TABLE #driveInfo;
8158 END;
8159
8160 IF NOT EXISTS ( SELECT 1
8161 FROM #SkipChecks
8162 WHERE DatabaseName IS NULL AND CheckID = 103 )
8163 AND EXISTS ( SELECT *
8164 FROM sys.all_objects o
8165 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
8166 WHERE o.name = 'dm_os_sys_info'
8167 AND c.name = 'virtual_machine_type_desc' )
8168 BEGIN
8169
8170 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 103) WITH NOWAIT;
8171
8172 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
8173 SELECT 103 AS CheckID,
8174 250 AS Priority,
8175 ''Server Info'' AS FindingsGroup,
8176 ''Virtual Server'' AS Finding,
8177 ''https://BrentOzar.com/go/virtual'' AS URL,
8178 ''Type: ('' + virtual_machine_type_desc + '')'' AS Details
8179 FROM sys.dm_os_sys_info
8180 WHERE virtual_machine_type <> 0 OPTION (RECOMPILE);';
8181
8182 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
8183 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
8184
8185 EXECUTE(@StringToExecute);
8186 END;
8187
8188 IF NOT EXISTS ( SELECT 1
8189 FROM #SkipChecks
8190 WHERE DatabaseName IS NULL AND CheckID = 214 )
8191 AND EXISTS ( SELECT *
8192 FROM sys.all_objects o
8193 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
8194 WHERE o.name = 'dm_os_sys_info'
8195 AND c.name = 'container_type_desc' )
8196 BEGIN
8197
8198 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 214) WITH NOWAIT;
8199
8200 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
8201 SELECT 214 AS CheckID,
8202 250 AS Priority,
8203 ''Server Info'' AS FindingsGroup,
8204 ''Container'' AS Finding,
8205 ''https://BrentOzar.com/go/virtual'' AS URL,
8206 ''Type: ('' + container_type_desc + '')'' AS Details
8207 FROM sys.dm_os_sys_info
8208 WHERE container_type_desc <> ''NONE'' OPTION (RECOMPILE);';
8209
8210 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
8211 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
8212
8213 EXECUTE(@StringToExecute);
8214 END;
8215
8216 IF NOT EXISTS ( SELECT 1
8217 FROM #SkipChecks
8218 WHERE DatabaseName IS NULL AND CheckID = 114 )
8219 AND EXISTS ( SELECT *
8220 FROM sys.all_objects o
8221 WHERE o.name = 'dm_os_memory_nodes' )
8222 AND EXISTS ( SELECT *
8223 FROM sys.all_objects o
8224 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
8225 WHERE o.name = 'dm_os_nodes'
8226 AND c.name = 'processor_group' )
8227 BEGIN
8228
8229 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 114) WITH NOWAIT;
8230
8231 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
8232 SELECT 114 AS CheckID ,
8233 250 AS Priority ,
8234 ''Server Info'' AS FindingsGroup ,
8235 ''Hardware - NUMA Config'' AS Finding ,
8236 '''' AS URL ,
8237 ''Node: '' + CAST(n.node_id AS NVARCHAR(10)) + '' State: '' + node_state_desc
8238 + '' Online schedulers: '' + CAST(n.online_scheduler_count AS NVARCHAR(10)) + '' Offline schedulers: '' + CAST(oac.offline_schedulers AS VARCHAR(100)) + '' Processor Group: '' + CAST(n.processor_group AS NVARCHAR(10))
8239 + '' Memory node: '' + CAST(n.memory_node_id AS NVARCHAR(10)) + '' Memory VAS Reserved GB: '' + CAST(CAST((m.virtual_address_space_reserved_kb / 1024.0 / 1024) AS INT) AS NVARCHAR(100))
8240 FROM sys.dm_os_nodes n
8241 INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id
8242 OUTER APPLY (SELECT
8243 COUNT(*) AS [offline_schedulers]
8244 FROM sys.dm_os_schedulers dos
8245 WHERE n.node_id = dos.parent_node_id
8246 AND dos.status = ''VISIBLE OFFLINE''
8247 ) oac
8248 WHERE n.node_state_desc NOT LIKE ''%DAC%''
8249 ORDER BY n.node_id OPTION (RECOMPILE);';
8250
8251 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
8252 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
8253
8254 EXECUTE(@StringToExecute);
8255 END;
8256
8257
8258 IF NOT EXISTS ( SELECT 1
8259 FROM #SkipChecks
8260 WHERE DatabaseName IS NULL AND CheckID = 211 )
8261 BEGIN
8262
8263 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 211) WITH NOWAIT;
8264
8265 DECLARE @outval VARCHAR(36);
8266 /* Get power plan if set by group policy [Git Hub Issue #1620] */
8267 EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE',
8268 @key = 'SOFTWARE\Policies\Microsoft\Power\PowerSettings',
8269 @value_name = 'ActivePowerScheme',
8270 @value = @outval OUTPUT;
8271
8272 IF @outval IS NULL /* If power plan was not set by group policy, get local value [Git Hub Issue #1620]*/
8273 EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE',
8274 @key = 'SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes',
8275 @value_name = 'ActivePowerScheme',
8276 @value = @outval OUTPUT;
8277
8278 DECLARE @cpu_speed_mhz int,
8279 @cpu_speed_ghz decimal(18,2);
8280
8281 EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE',
8282 @key = 'HARDWARE\DESCRIPTION\System\CentralProcessor\0',
8283 @value_name = '~MHz',
8284 @value = @cpu_speed_mhz OUTPUT;
8285
8286 SELECT @cpu_speed_ghz = CAST(CAST(@cpu_speed_mhz AS DECIMAL) / 1000 AS DECIMAL(18,2));
8287
8288 INSERT INTO #BlitzResults
8289 ( CheckID ,
8290 Priority ,
8291 FindingsGroup ,
8292 Finding ,
8293 URL ,
8294 Details
8295 )
8296 SELECT 211 AS CheckId,
8297 250 AS Priority,
8298 'Server Info' AS FindingsGroup,
8299 'Power Plan' AS Finding,
8300 'https://www.brentozar.com/blitz/power-mode/' AS URL,
8301 'Your server has '
8302 + CAST(@cpu_speed_ghz as VARCHAR(4))
8303 + 'GHz CPUs, and is in '
8304 + CASE @outval
8305 WHEN 'a1841308-3541-4fab-bc81-f71556f20b4a'
8306 THEN 'power saving mode -- are you sure this is a production SQL Server?'
8307 WHEN '381b4222-f694-41f0-9685-ff5bb260df2e'
8308 THEN 'balanced power mode -- Uh... you want your CPUs to run at full speed, right?'
8309 WHEN '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'
8310 THEN 'high performance power mode'
8311 WHEN 'e9a42b02-d5df-448d-aa00-03f14749eb61'
8312 THEN 'ultimate performance power mode'
8313 ELSE 'an unknown power mode.'
8314 END AS Details
8315
8316 END;
8317
8318 IF NOT EXISTS ( SELECT 1
8319 FROM #SkipChecks
8320 WHERE DatabaseName IS NULL AND CheckID = 212 )
8321 BEGIN
8322
8323 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 212) WITH NOWAIT;
8324
8325 INSERT INTO #Instances (Instance_Number, Instance_Name, Data_Field)
8326 EXEC master.sys.xp_regread @rootkey = 'HKEY_LOCAL_MACHINE',
8327 @key = 'SOFTWARE\Microsoft\Microsoft SQL Server',
8328 @value_name = 'InstalledInstances'
8329
8330 IF (SELECT COUNT(*) FROM #Instances) > 1
8331 BEGIN
8332
8333 DECLARE @InstanceCount NVARCHAR(MAX)
8334 SELECT @InstanceCount = COUNT(*) FROM #Instances
8335
8336 INSERT INTO #BlitzResults
8337 (
8338 CheckID ,
8339 Priority ,
8340 FindingsGroup ,
8341 Finding ,
8342 URL ,
8343 Details
8344 )
8345 SELECT
8346 212 AS CheckId ,
8347 250 AS Priority ,
8348 'Server Info' AS FindingsGroup ,
8349 'Instance Stacking' AS Finding ,
8350 'https://www.brentozar.com/go/babygotstacked/' AS URL ,
8351 'Your Server has ' + @InstanceCount + ' Instances of SQL Server installed. More than one is usually a bad idea. Read the URL for more info.'
8352 END;
8353 END;
8354
8355 IF NOT EXISTS ( SELECT 1
8356 FROM #SkipChecks
8357 WHERE DatabaseName IS NULL AND CheckID = 106 )
8358 AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1
8359 AND DATALENGTH( COALESCE( @base_tracefilename, '' ) ) > DATALENGTH('.TRC')
8360 AND @TraceFileIssue = 0
8361 BEGIN
8362
8363 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 106) WITH NOWAIT;
8364
8365 INSERT INTO #BlitzResults
8366 ( CheckID ,
8367 Priority ,
8368 FindingsGroup ,
8369 Finding ,
8370 URL ,
8371 Details
8372 )
8373 SELECT
8374 106 AS CheckID
8375 ,250 AS Priority
8376 ,'Server Info' AS FindingsGroup
8377 ,'Default Trace Contents' AS Finding
8378 ,'https://BrentOzar.com/go/trace' AS URL
8379 ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as VARCHAR(30))+' hours of data'
8380 +' between '+cast(Min(StartTime) as VARCHAR(30))+' and '+cast(GETDATE()as VARCHAR(30))
8381 +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx)
8382 ) as Details
8383 FROM ::fn_trace_gettable( @base_tracefilename, default )
8384 WHERE EventClass BETWEEN 65500 and 65600;
8385 END; /* CheckID 106 */
8386
8387 IF NOT EXISTS ( SELECT 1
8388 FROM #SkipChecks
8389 WHERE DatabaseName IS NULL AND CheckID = 152 )
8390 BEGIN
8391 IF EXISTS (SELECT * FROM sys.dm_os_wait_stats ws
8392 LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type
8393 WHERE wait_time_ms > .1 * @CpuMsSinceWaitsCleared AND waiting_tasks_count > 0
8394 AND i.wait_type IS NULL)
8395 BEGIN
8396 /* Check for waits that have had more than 10% of the server's wait time */
8397
8398 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 152) WITH NOWAIT;
8399
8400 WITH os(wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms)
8401 AS
8402 (SELECT ws.wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms
8403 FROM sys.dm_os_wait_stats ws
8404 LEFT OUTER JOIN #IgnorableWaits i ON ws.wait_type = i.wait_type
8405 WHERE i.wait_type IS NULL
8406 AND wait_time_ms > .1 * @CpuMsSinceWaitsCleared
8407 AND waiting_tasks_count > 0)
8408 INSERT INTO #BlitzResults
8409 ( CheckID ,
8410 Priority ,
8411 FindingsGroup ,
8412 Finding ,
8413 URL ,
8414 Details
8415 )
8416 SELECT TOP 9
8417 152 AS CheckID
8418 ,240 AS Priority
8419 ,'Wait Stats' AS FindingsGroup
8420 , CAST(ROW_NUMBER() OVER(ORDER BY os.wait_time_ms DESC) AS NVARCHAR(10)) + N' - ' + os.wait_type AS Finding
8421 ,'https://www.sqlskills.com/help/waits/' + LOWER(os.wait_type) + '/' AS URL
8422 , 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, ' +
8423 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, ' +
8424 /* CAST(CAST(
8425 100.* SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type)
8426 / (1. * SUM(os.wait_time_ms) OVER () )
8427 AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% of waits, ' + */
8428 CAST(CAST(
8429 100. * SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type)
8430 / (1. * SUM(os.wait_time_ms) OVER ())
8431 AS NUMERIC(18,1)) AS NVARCHAR(40)) + N'% signal wait, ' +
8432 CAST(SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS NVARCHAR(40)) + N' waiting tasks, ' +
8433 CAST(CASE WHEN SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) > 0
8434 THEN
8435 CAST(
8436 SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type)
8437 / (1. * SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type))
8438 AS NUMERIC(18,1))
8439 ELSE 0 END AS NVARCHAR(40)) + N' ms average wait time.'
8440 FROM os
8441 ORDER BY SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) DESC;
8442 END; /* IF EXISTS (SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0 AND waiting_tasks_count > 0) */
8443
8444 /* If no waits were found, add a note about that */
8445 IF NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (107, 108, 109, 121, 152, 162))
8446 BEGIN
8447
8448 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 153) WITH NOWAIT;
8449
8450 INSERT INTO #BlitzResults
8451 ( CheckID ,
8452 Priority ,
8453 FindingsGroup ,
8454 Finding ,
8455 URL ,
8456 Details
8457 )
8458 VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'https://BrentOzar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.');
8459 END;
8460 END; /* CheckID 152 */
8461
8462 /* CheckID 222 - Server Info - Azure Managed Instance */
8463 IF NOT EXISTS ( SELECT 1
8464 FROM #SkipChecks
8465 WHERE DatabaseName IS NULL AND CheckID = 222 )
8466 AND 4 = ( SELECT COUNT(*)
8467 FROM sys.all_objects o
8468 INNER JOIN sys.all_columns c ON o.object_id = c.object_id
8469 WHERE o.name = 'dm_os_job_object'
8470 AND c.name IN ('cpu_rate', 'memory_limit_mb', 'process_memory_limit_mb', 'workingset_limit_mb' ))
8471 BEGIN
8472
8473 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 222) WITH NOWAIT;
8474
8475 SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
8476 SELECT 222 AS CheckID ,
8477 250 AS Priority ,
8478 ''Server Info'' AS FindingsGroup ,
8479 ''Azure Managed Instance'' AS Finding ,
8480 ''https://www.BrenOzar.com/go/azurevm'' AS URL ,
8481 ''cpu_rate: '' + CAST(COALESCE(cpu_rate, 0) AS VARCHAR(20)) +
8482 '', memory_limit_mb: '' + CAST(COALESCE(memory_limit_mb, 0) AS NVARCHAR(20)) +
8483 '', process_memory_limit_mb: '' + CAST(COALESCE(process_memory_limit_mb, 0) AS NVARCHAR(20)) +
8484 '', workingset_limit_mb: '' + CAST(COALESCE(workingset_limit_mb, 0) AS NVARCHAR(20))
8485 FROM sys.dm_os_job_object OPTION (RECOMPILE);';
8486
8487 IF @Debug = 2 AND @StringToExecute IS NOT NULL PRINT @StringToExecute;
8488 IF @Debug = 2 AND @StringToExecute IS NULL PRINT '@StringToExecute has gone NULL, for some reason.';
8489
8490 EXECUTE(@StringToExecute);
8491 END;
8492
8493 /* CheckID 224 - Performance - SSRS/SSAS/SSIS Installed */
8494 IF NOT EXISTS ( SELECT 1
8495 FROM #SkipChecks
8496 WHERE DatabaseName IS NULL AND CheckID = 224 )
8497 BEGIN
8498
8499 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 224) WITH NOWAIT;
8500
8501 IF (SELECT value_in_use FROM sys.configurations WHERE [name] = 'xp_cmdshell') = 1
8502 BEGIN
8503
8504 IF OBJECT_ID('tempdb..#services') IS NOT NULL DROP TABLE #services;
8505 CREATE TABLE #services (cmdshell_output varchar(max));
8506
8507 INSERT INTO #services
8508 EXEC xp_cmdshell 'net start'
8509
8510 IF EXISTS (SELECT 1
8511 FROM #services
8512 WHERE cmdshell_output LIKE '%SQL Server Reporting Services%'
8513 OR cmdshell_output LIKE '%SQL Server Integration Services%'
8514 OR cmdshell_output LIKE '%SQL Server Analysis Services%')
8515 BEGIN
8516 INSERT INTO #BlitzResults
8517 ( CheckID ,
8518 Priority ,
8519 FindingsGroup ,
8520 Finding ,
8521 URL ,
8522 Details
8523 )
8524 SELECT
8525 224 AS CheckID
8526 ,200 AS Priority
8527 ,'Performance' AS FindingsGroup
8528 ,'SSAS/SSIS/SSRS Installed' AS Finding
8529 ,'https://www.BrentOzar.com/go/services' AS URL
8530 ,'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
8531
8532 END;
8533
8534 END;
8535 END;
8536
8537 /* CheckID 232 - Server Info - Data Size */
8538 IF NOT EXISTS ( SELECT 1
8539 FROM #SkipChecks
8540 WHERE DatabaseName IS NULL AND CheckID = 232 )
8541 BEGIN
8542
8543 IF @Debug IN (1, 2) RAISERROR('Running CheckId [%d].', 0, 1, 232) WITH NOWAIT;
8544
8545 IF OBJECT_ID('tempdb..#MasterFiles') IS NOT NULL
8546 DROP TABLE #MasterFiles;
8547 CREATE TABLE #MasterFiles (database_id INT, file_id INT, type_desc NVARCHAR(50), name NVARCHAR(255), physical_name NVARCHAR(255), size BIGINT);
8548 /* Azure SQL Database doesn't have sys.master_files, so we have to build our own. */
8549 IF ((SERVERPROPERTY('Edition')) = 'SQL Azure'
8550 AND (OBJECT_ID('sys.master_files') IS NULL))
8551 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;';
8552 ELSE
8553 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;';
8554 EXEC(@StringToExecute);
8555
8556
8557 INSERT INTO #BlitzResults
8558 ( CheckID ,
8559 Priority ,
8560 FindingsGroup ,
8561 Finding ,
8562 URL ,
8563 Details
8564 )
8565 SELECT 232 AS CheckID
8566 ,250 AS Priority
8567 ,'Server Info' AS FindingsGroup
8568 ,'Data Size' AS Finding
8569 ,'' AS URL
8570 ,CAST(COUNT(DISTINCT database_id) AS NVARCHAR(100)) + N' databases, ' + CAST(CAST(SUM (CAST(size AS BIGINT)*8./1024./1024.) AS MONEY) AS VARCHAR(100)) + ' GB total file size' as Details
8571 FROM #MasterFiles
8572 WHERE database_id > 4;
8573
8574 END;
8575
8576
8577 END; /* IF @CheckServerInfo = 1 */
8578 END; /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */
8579
8580 /* Delete priorites they wanted to skip. */
8581 IF @IgnorePrioritiesAbove IS NOT NULL
8582 DELETE #BlitzResults
8583 WHERE [Priority] > @IgnorePrioritiesAbove AND CheckID <> -1;
8584
8585 IF @IgnorePrioritiesBelow IS NOT NULL
8586 DELETE #BlitzResults
8587 WHERE [Priority] < @IgnorePrioritiesBelow AND CheckID <> -1;
8588
8589 /* Delete checks they wanted to skip. */
8590 IF @SkipChecksTable IS NOT NULL
8591 BEGIN
8592 DELETE FROM #BlitzResults
8593 WHERE DatabaseName IN ( SELECT DatabaseName
8594 FROM #SkipChecks
8595 WHERE CheckID IS NULL
8596 AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')));
8597 DELETE FROM #BlitzResults
8598 WHERE CheckID IN ( SELECT CheckID
8599 FROM #SkipChecks
8600 WHERE DatabaseName IS NULL
8601 AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')));
8602 DELETE r FROM #BlitzResults r
8603 INNER JOIN #SkipChecks c ON r.DatabaseName = c.DatabaseName and r.CheckID = c.CheckID
8604 AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'));
8605 END;
8606
8607 /* Add summary mode */
8608 IF @SummaryMode > 0
8609 BEGIN
8610 UPDATE #BlitzResults
8611 SET Finding = br.Finding + ' (' + CAST(brTotals.recs AS NVARCHAR(20)) + ')'
8612 FROM #BlitzResults br
8613 INNER JOIN (SELECT FindingsGroup, Finding, Priority, COUNT(*) AS recs FROM #BlitzResults GROUP BY FindingsGroup, Finding, Priority) brTotals ON br.FindingsGroup = brTotals.FindingsGroup AND br.Finding = brTotals.Finding AND br.Priority = brTotals.Priority
8614 WHERE brTotals.recs > 1;
8615
8616 DELETE br
8617 FROM #BlitzResults br
8618 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);
8619
8620 END;
8621
8622 /* Add credits for the nice folks who put so much time into building and maintaining this for free: */
8623
8624 INSERT INTO #BlitzResults
8625 ( CheckID ,
8626 Priority ,
8627 FindingsGroup ,
8628 Finding ,
8629 URL ,
8630 Details
8631 )
8632 VALUES ( -1 ,
8633 255 ,
8634 'Thanks!' ,
8635 'From Your Community Volunteers' ,
8636 'http://FirstResponderKit.org' ,
8637 'We hope you found this tool useful.'
8638 );
8639
8640 INSERT INTO #BlitzResults
8641 ( CheckID ,
8642 Priority ,
8643 FindingsGroup ,
8644 Finding ,
8645 URL ,
8646 Details
8647
8648 )
8649 VALUES ( -1 ,
8650 0 ,
8651 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)),
8652 'SQL Server First Responder Kit' ,
8653 'http://FirstResponderKit.org/' ,
8654 'To get help or add your own contributions, join us at http://FirstResponderKit.org.'
8655
8656 );
8657
8658 INSERT INTO #BlitzResults
8659 ( CheckID ,
8660 Priority ,
8661 FindingsGroup ,
8662 Finding ,
8663 URL ,
8664 Details
8665
8666 )
8667 SELECT 156 ,
8668 254 ,
8669 'Rundate' ,
8670 GETDATE() ,
8671 'http://FirstResponderKit.org/' ,
8672 'Captain''s log: stardate something and something...';
8673
8674 IF @EmailRecipients IS NOT NULL
8675 BEGIN
8676
8677 IF @Debug IN (1, 2) RAISERROR('Sending an email.', 0, 1) WITH NOWAIT;
8678
8679 /* Database mail won't work off a local temp table. I'm not happy about this hacky workaround either. */
8680 IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults;
8681 SELECT * INTO ##BlitzResults FROM #BlitzResults;
8682 SET @query_result_separator = char(9);
8683 SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup, Finding, Details; SET NOCOUNT OFF;';
8684 SET @EmailSubject = 'sp_Blitz Results for ' + @@SERVERNAME;
8685 SET @EmailBody = 'sp_Blitz ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. http://FirstResponderKit.org';
8686 IF @EmailProfile IS NULL
8687 EXEC msdb.dbo.sp_send_dbmail
8688 @recipients = @EmailRecipients,
8689 @subject = @EmailSubject,
8690 @body = @EmailBody,
8691 @query_attachment_filename = 'sp_Blitz-Results.csv',
8692 @attach_query_result_as_file = 1,
8693 @query_result_header = 1,
8694 @query_result_width = 32767,
8695 @append_query_error = 1,
8696 @query_result_no_padding = 1,
8697 @query_result_separator = @query_result_separator,
8698 @query = @StringToExecute;
8699 ELSE
8700 EXEC msdb.dbo.sp_send_dbmail
8701 @profile_name = @EmailProfile,
8702 @recipients = @EmailRecipients,
8703 @subject = @EmailSubject,
8704 @body = @EmailBody,
8705 @query_attachment_filename = 'sp_Blitz-Results.csv',
8706 @attach_query_result_as_file = 1,
8707 @query_result_header = 1,
8708 @query_result_width = 32767,
8709 @append_query_error = 1,
8710 @query_result_no_padding = 1,
8711 @query_result_separator = @query_result_separator,
8712 @query = @StringToExecute;
8713 IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults;
8714 END;
8715
8716 /* Checks if @OutputServerName is populated with a valid linked server, and that the database name specified is valid */
8717 DECLARE @ValidOutputServer BIT;
8718 DECLARE @ValidOutputLocation BIT;
8719 DECLARE @LinkedServerDBCheck NVARCHAR(2000);
8720 DECLARE @ValidLinkedServerDB INT;
8721 DECLARE @tmpdbchk table (cnt int);
8722 IF @OutputServerName IS NOT NULL
8723 BEGIN
8724
8725 IF @Debug IN (1, 2) RAISERROR('Outputting to a remote server.', 0, 1) WITH NOWAIT;
8726
8727 IF EXISTS (SELECT server_id FROM sys.servers WHERE QUOTENAME([name]) = @OutputServerName)
8728 BEGIN
8729 SET @LinkedServerDBCheck = 'SELECT 1 WHERE EXISTS (SELECT * FROM '+@OutputServerName+'.master.sys.databases WHERE QUOTENAME([name]) = '''+@OutputDatabaseName+''')';
8730 INSERT INTO @tmpdbchk EXEC sys.sp_executesql @LinkedServerDBCheck;
8731 SET @ValidLinkedServerDB = (SELECT COUNT(*) FROM @tmpdbchk);
8732 IF (@ValidLinkedServerDB > 0)
8733 BEGIN
8734 SET @ValidOutputServer = 1;
8735 SET @ValidOutputLocation = 1;
8736 END;
8737 ELSE
8738 RAISERROR('The specified database was not found on the output server', 16, 0);
8739 END;
8740 ELSE
8741 BEGIN
8742 RAISERROR('The specified output server was not found', 16, 0);
8743 END;
8744 END;
8745 ELSE
8746 BEGIN
8747 IF @OutputDatabaseName IS NOT NULL
8748 AND @OutputSchemaName IS NOT NULL
8749 AND @OutputTableName IS NOT NULL
8750 AND EXISTS ( SELECT *
8751 FROM sys.databases
8752 WHERE QUOTENAME([name]) = @OutputDatabaseName)
8753 BEGIN
8754 SET @ValidOutputLocation = 1;
8755 END;
8756 ELSE IF @OutputDatabaseName IS NOT NULL
8757 AND @OutputSchemaName IS NOT NULL
8758 AND @OutputTableName IS NOT NULL
8759 AND NOT EXISTS ( SELECT *
8760 FROM sys.databases
8761 WHERE QUOTENAME([name]) = @OutputDatabaseName)
8762 BEGIN
8763 RAISERROR('The specified output database was not found on this server', 16, 0);
8764 END;
8765 ELSE
8766 BEGIN
8767 SET @ValidOutputLocation = 0;
8768 END;
8769 END;
8770
8771 /* @OutputTableName lets us export the results to a permanent table */
8772 IF @ValidOutputLocation = 1
8773 BEGIN
8774 SET @StringToExecute = 'USE '
8775 + @OutputDatabaseName
8776 + '; IF EXISTS(SELECT * FROM '
8777 + @OutputDatabaseName
8778 + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
8779 + @OutputSchemaName
8780 + ''') AND NOT EXISTS (SELECT * FROM '
8781 + @OutputDatabaseName
8782 + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
8783 + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = '''
8784 + @OutputTableName + ''') CREATE TABLE '
8785 + @OutputSchemaName + '.'
8786 + @OutputTableName
8787 + ' (ID INT IDENTITY(1,1) NOT NULL,
8788 ServerName NVARCHAR(128),
8789 CheckDate DATETIMEOFFSET,
8790 Priority TINYINT ,
8791 FindingsGroup VARCHAR(50) ,
8792 Finding VARCHAR(200) ,
8793 DatabaseName NVARCHAR(128),
8794 URL VARCHAR(200) ,
8795 Details NVARCHAR(4000) ,
8796 QueryPlan [XML] NULL ,
8797 QueryPlanFiltered [NVARCHAR](MAX) NULL,
8798 CheckID INT ,
8799 CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));';
8800 IF @ValidOutputServer = 1
8801 BEGIN
8802 SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputSchemaName+'''',''''''+@OutputSchemaName+'''''');
8803 SET @StringToExecute = REPLACE(@StringToExecute,''''+@OutputTableName+'''',''''''+@OutputTableName+'''''');
8804 SET @StringToExecute = REPLACE(@StringToExecute,'[XML]','[NVARCHAR](MAX)');
8805 EXEC('EXEC('''+@StringToExecute+''') AT ' + @OutputServerName);
8806 END;
8807 ELSE
8808 BEGIN
8809 EXEC(@StringToExecute);
8810 END;
8811 IF @ValidOutputServer = 1
8812 BEGIN
8813 SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
8814 + @OutputServerName + '.'
8815 + @OutputDatabaseName
8816 + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
8817 + @OutputSchemaName + ''') INSERT '
8818 + @OutputServerName + '.'
8819 + @OutputDatabaseName + '.'
8820 + @OutputSchemaName + '.'
8821 + @OutputTableName
8822 + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
8823 + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
8824 + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, CAST(QueryPlan AS NVARCHAR(MAX)), QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details';
8825
8826 EXEC(@StringToExecute);
8827 END;
8828 ELSE
8829 BEGIN
8830 SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
8831 + @OutputDatabaseName
8832 + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
8833 + @OutputSchemaName + ''') INSERT '
8834 + @OutputDatabaseName + '.'
8835 + @OutputSchemaName + '.'
8836 + @OutputTableName
8837 + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
8838 + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
8839 + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details';
8840
8841 EXEC(@StringToExecute);
8842 END;
8843 END;
8844 ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##')
8845 BEGIN
8846 IF @ValidOutputServer = 1
8847 BEGIN
8848 RAISERROR('Due to the nature of temporary tables, outputting to a linked server requires a permanent table.', 16, 0);
8849 END;
8850 ELSE
8851 BEGIN
8852 SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..'
8853 + @OutputTableName
8854 + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';'
8855 + 'CREATE TABLE '
8856 + @OutputTableName
8857 + ' (ID INT IDENTITY(1,1) NOT NULL,
8858 ServerName NVARCHAR(128),
8859 CheckDate DATETIMEOFFSET,
8860 Priority TINYINT ,
8861 FindingsGroup VARCHAR(50) ,
8862 Finding VARCHAR(200) ,
8863 DatabaseName NVARCHAR(128),
8864 URL VARCHAR(200) ,
8865 Details NVARCHAR(4000) ,
8866 QueryPlan [XML] NULL ,
8867 QueryPlanFiltered [NVARCHAR](MAX) NULL,
8868 CheckID INT ,
8869 CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
8870 + ' INSERT '
8871 + @OutputTableName
8872 + ' (ServerName, CheckDate, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
8873 + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
8874 + ''', SYSDATETIMEOFFSET(), CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details';
8875
8876 EXEC(@StringToExecute);
8877 END;
8878 END;
8879 ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#')
8880 BEGIN
8881 RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0);
8882 END;
8883
8884 DECLARE @separator AS VARCHAR(1);
8885 IF @OutputType = 'RSV'
8886 SET @separator = CHAR(31);
8887 ELSE
8888 SET @separator = ',';
8889
8890 IF @OutputType = 'COUNT'
8891 BEGIN
8892 SELECT COUNT(*) AS Warnings
8893 FROM #BlitzResults;
8894 END;
8895 ELSE
8896 IF @OutputType IN ( 'CSV', 'RSV' )
8897 BEGIN
8898
8899 SELECT Result = CAST([Priority] AS NVARCHAR(100))
8900 + @separator + CAST(CheckID AS NVARCHAR(100))
8901 + @separator + COALESCE([FindingsGroup],
8902 '(N/A)') + @separator
8903 + COALESCE([Finding], '(N/A)') + @separator
8904 + COALESCE(DatabaseName, '(N/A)') + @separator
8905 + COALESCE([URL], '(N/A)') + @separator
8906 + COALESCE([Details], '(N/A)')
8907 FROM #BlitzResults
8908 ORDER BY Priority ,
8909 FindingsGroup ,
8910 Finding ,
8911 DatabaseName ,
8912 Details;
8913 END;
8914 ELSE IF @OutputXMLasNVARCHAR = 1 AND @OutputType <> 'NONE'
8915 BEGIN
8916 SELECT [Priority] ,
8917 [FindingsGroup] ,
8918 [Finding] ,
8919 [DatabaseName] ,
8920 [URL] ,
8921 [Details] ,
8922 CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan,
8923 [QueryPlanFiltered] ,
8924 CheckID
8925 FROM #BlitzResults
8926 ORDER BY Priority ,
8927 FindingsGroup ,
8928 Finding ,
8929 DatabaseName ,
8930 Details;
8931 END;
8932 ELSE IF @OutputType = 'MARKDOWN'
8933 BEGIN
8934 WITH Results AS (SELECT row_number() OVER (ORDER BY Priority, FindingsGroup, Finding, DatabaseName, Details) AS rownum, *
8935 FROM #BlitzResults
8936 WHERE Priority > 0 AND Priority < 255 AND FindingsGroup IS NOT NULL AND Finding IS NOT NULL
8937 AND FindingsGroup <> 'Security' /* Specifically excluding security checks for public exports */)
8938 SELECT
8939 CASE
8940 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
8941 ELSE N''
8942 END
8943 + CASE WHEN r.Finding <> COALESCE(rPrior.Finding,N'') AND r.Finding <> rNext.Finding THEN N'- ' + COALESCE(r.Finding,N'') + N' ' + COALESCE(r.DatabaseName, N'') + N' - ' + COALESCE(r.Details,N'') + @crlf
8944 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
8945 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
8946 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
8947 END + @crlf
8948 FROM Results r
8949 LEFT OUTER JOIN Results rPrior ON r.rownum = rPrior.rownum + 1
8950 LEFT OUTER JOIN Results rNext ON r.rownum = rNext.rownum - 1
8951 ORDER BY r.rownum FOR XML PATH(N'');
8952 END;
8953 ELSE IF @OutputType = 'XML'
8954 BEGIN
8955 /* --TOURSTOP05-- */
8956 SELECT [Priority] ,
8957 [FindingsGroup] ,
8958 [Finding] ,
8959 [DatabaseName] ,
8960 [URL] ,
8961 [Details] ,
8962 [QueryPlanFiltered] ,
8963 CheckID
8964 FROM #BlitzResults
8965 ORDER BY Priority ,
8966 FindingsGroup ,
8967 Finding ,
8968 DatabaseName ,
8969 Details
8970 FOR XML PATH('Result'), ROOT('sp_Blitz_Output');
8971 END;
8972 ELSE IF @OutputType <> 'NONE'
8973 BEGIN
8974 /* --TOURSTOP05-- */
8975 SELECT [Priority] ,
8976 [FindingsGroup] ,
8977 [Finding] ,
8978 [DatabaseName] ,
8979 [URL] ,
8980 [Details] ,
8981 [QueryPlan] ,
8982 [QueryPlanFiltered] ,
8983 CheckID
8984 FROM #BlitzResults
8985 ORDER BY Priority ,
8986 FindingsGroup ,
8987 Finding ,
8988 DatabaseName ,
8989 Details;
8990 END;
8991
8992 DROP TABLE #BlitzResults;
8993
8994 IF @OutputProcedureCache = 1
8995 AND @CheckProcedureCache = 1
8996 SELECT TOP 20
8997 total_worker_time / execution_count AS AvgCPU ,
8998 total_worker_time AS TotalCPU ,
8999 CAST(ROUND(100.00 * total_worker_time
9000 / ( SELECT SUM(total_worker_time)
9001 FROM sys.dm_exec_query_stats
9002 ), 2) AS MONEY) AS PercentCPU ,
9003 total_elapsed_time / execution_count AS AvgDuration ,
9004 total_elapsed_time AS TotalDuration ,
9005 CAST(ROUND(100.00 * total_elapsed_time
9006 / ( SELECT SUM(total_elapsed_time)
9007 FROM sys.dm_exec_query_stats
9008 ), 2) AS MONEY) AS PercentDuration ,
9009 total_logical_reads / execution_count AS AvgReads ,
9010 total_logical_reads AS TotalReads ,
9011 CAST(ROUND(100.00 * total_logical_reads
9012 / ( SELECT SUM(total_logical_reads)
9013 FROM sys.dm_exec_query_stats
9014 ), 2) AS MONEY) AS PercentReads ,
9015 execution_count ,
9016 CAST(ROUND(100.00 * execution_count
9017 / ( SELECT SUM(execution_count)
9018 FROM sys.dm_exec_query_stats
9019 ), 2) AS MONEY) AS PercentExecutions ,
9020 CASE WHEN DATEDIFF(mi, creation_time,
9021 qs.last_execution_time) = 0 THEN 0
9022 ELSE CAST(( 1.00 * execution_count / DATEDIFF(mi,
9023 creation_time,
9024 qs.last_execution_time) ) AS MONEY)
9025 END AS executions_per_minute ,
9026 qs.creation_time AS plan_creation_time ,
9027 qs.last_execution_time ,
9028 text ,
9029 text_filtered ,
9030 query_plan ,
9031 query_plan_filtered ,
9032 sql_handle ,
9033 query_hash ,
9034 plan_handle ,
9035 query_plan_hash
9036 FROM #dm_exec_query_stats qs
9037 ORDER BY CASE UPPER(@CheckProcedureCacheFilter)
9038 WHEN 'CPU' THEN total_worker_time
9039 WHEN 'READS' THEN total_logical_reads
9040 WHEN 'EXECCOUNT' THEN execution_count
9041 WHEN 'DURATION' THEN total_elapsed_time
9042 ELSE total_worker_time
9043 END DESC;
9044
9045 END; /* ELSE -- IF @OutputType = 'SCHEMA' */
9046
9047 /*
9048 Reset the Nmumeric_RoundAbort session state back to enabled if it was disabled earlier.
9049 See Github issue #2302 for more info.
9050 */
9051 IF @NeedToTurnNumericRoundabortBackOn = 1
9052 SET NUMERIC_ROUNDABORT ON;
9053
9054 SET NOCOUNT OFF;
9055GO
9056
9057/*
9058--Sample execution call with the most common parameters:
9059EXEC [dbo].[sp_Blitz]
9060 @CheckUserDatabaseObjects = 1 ,
9061 @CheckProcedureCache = 0 ,
9062 @OutputType = 'TABLE' ,
9063 @OutputProcedureCache = 0 ,
9064 @CheckProcedureCacheFilter = NULL,
9065 @CheckServerInfo = 1
9066*/