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