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