Advertisement
paraffin

sp_blitz

Feb 25th, 2016
317
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 255.59 KB | None | 0 0
  1. USE [master];
  2. GO
  3.  
  4. IF EXISTS(SELECT * FROM sys.databases WHERE compatibility_level < 90)
  5. RAISERROR ('sp_Blitz cannot be installed when databases are still in 2000 compatibility mode. For information: http://BrentOzar.com/blitz/', 10,1) WITH LOG, NOWAIT;
  6. GO
  7.  
  8. IF OBJECT_ID('dbo.sp_Blitz') IS NULL
  9. EXEC ('CREATE PROCEDURE dbo.sp_Blitz AS RETURN 0;')
  10. GO
  11.  
  12. ALTER PROCEDURE [dbo].[sp_Blitz]
  13. @CheckUserDatabaseObjects TINYINT = 1 ,
  14. @CheckProcedureCache TINYINT = 0 ,
  15. @OutputType VARCHAR(20) = 'TABLE' ,
  16. @OutputProcedureCache TINYINT = 0 ,
  17. @CheckProcedureCacheFilter VARCHAR(10) = NULL ,
  18. @CheckServerInfo TINYINT = 0 ,
  19. @SkipChecksServer NVARCHAR(256) = NULL ,
  20. @SkipChecksDatabase NVARCHAR(256) = NULL ,
  21. @SkipChecksSchema NVARCHAR(256) = NULL ,
  22. @SkipChecksTable NVARCHAR(256) = NULL ,
  23. @IgnorePrioritiesBelow INT = NULL ,
  24. @IgnorePrioritiesAbove INT = NULL ,
  25. @OutputDatabaseName NVARCHAR(128) = NULL ,
  26. @OutputSchemaName NVARCHAR(256) = NULL ,
  27. @OutputTableName NVARCHAR(256) = NULL ,
  28. @OutputXMLasNVARCHAR TINYINT = 0 ,
  29. @EmailRecipients VARCHAR(MAX) = NULL ,
  30. @EmailProfile sysname = NULL ,
  31. @SummaryMode TINYINT = 0 ,
  32. @Help TINYINT = 0 ,
  33. @Version INT = NULL OUTPUT,
  34. @VersionDate DATETIME = NULL OUTPUT
  35. AS
  36. SET NOCOUNT ON;
  37. SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
  38. SELECT @Version = 48, @VersionDate = '20160320'
  39.  
  40. IF @Help = 1 PRINT '
  41. /*
  42. sp_Blitz (TM) v48 - 2016/03/20
  43.  
  44. (C) 2016, Brent Ozar Unlimited.
  45. See http://BrentOzar.com/go/eula for the End User Licensing Agreement.
  46.  
  47. To learn more, visit http://www.BrentOzar.com/blitz where you can download
  48. new versions for free, watch training videos on how it works, get more info on
  49. the findings, and more.
  50.  
  51. To request a feature or change: http://support.brentozar.com/
  52. To contribute code: http://www.brentozar.com/contributing-code/
  53.  
  54. Known limitations of this version:
  55. - No support for SQL Server 2000 or compatibility mode 80.
  56. - If a database name has a question mark in it, some tests will fail. Gotta
  57. love that unsupported sp_MSforeachdb.
  58. - If you have offline databases, sp_Blitz fails the first time you run it,
  59. but does work the second time. (Hoo, boy, this will be fun to debug.)
  60.  
  61. Unknown limitations of this version:
  62. - None. (If we knew them, they would be known. Duh.)
  63.  
  64. Changes in v48 - 2016/03/20
  65. - Julie Citro massively improved the check for stored procedures with a
  66. recompile hint in them. (78)
  67. - These new checks brought to you by Erik Darling:
  68. - Multiple Extended Events sessions running (176)
  69. - Startup parameter -x in use, disabling monitoring (177)
  70. - TempDB has >16 data files (175)
  71. - Along with some changes:
  72. - Improved full text repopulation check to ignore change_tracking_state_desc AUTO (113)
  73. - DBCC CHECKDB checks now ignore TempDB (68)
  74. - Poison wait times are now reported in Days:Hours:Minutes, and are only
  75. reported if they exceed 60 seconds (instead of 5)
  76. - The missing log backups alert now includes the log file size.
  77. - Trace flag alerts now include more details about dangerous trace flags.
  78. - Some per-database checks were running even when @CheckUserDatabaseObjects
  79. was set to 0. (Performance tuning sp_Blitz for thousands of databases.)
  80. - The high number of cached plans warning (161) reported the wrong limit for cached
  81. plans in the detail message.
  82.  
  83. Changes in v47 - 2016/03/12
  84. - These new changes brought to you by Erik Darling:
  85. - Locked pages in memory (check 166)
  86. - Agent currently offline (167)
  87. - Full text daemon offline (168)
  88. - SQL Server running as NT Service rather than an AD account (169)
  89. - Agent running as NT Service rather than an AD account (170)
  90. - Memory dumps in the last year (171)
  91. - Windows version (172)
  92. - Dev or Evaluation Edition in use (173)
  93. - Buffer Pool Extension enabled (174)
  94.  
  95. Changes in v46 - 2016/01/06
  96. - Thomas Franz suggested that the severity level be 10, not 20, when sp_Blitz
  97. fails to install due to a SQL 2000 compat level issue.
  98.  
  99. Changes in v45 - 2016/01/06
  100. - Oops! Fixed a SQL 2005 compatibility bug.
  101.  
  102. Changes in v44 - 2016/01/05
  103. - Reorganized and reprioritized a lot of the checks.
  104. - Added check 165 for too much free memory. (Sounds like a good thing, but
  105. turns out it can indicate queries with inaccurately huge memory grants.)
  106. - Tobias Ortmann reported a bug in missing new default configs in SQL 2014.
  107. - Upped "slow storage" warnings to 200ms for reads, 100ms for writes, and
  108. only throw warnings on files with more than 100,000 reads or writes.
  109. Yes, those thresholds are horrifically high, but that is the point.
  110. - "Old compat level" warning now only fires for level 90 and older, not
  111. a lower compat level than model. With the new Cardinality Estimator, we
  112. have seen a lot of people running at an older-than-model level, and that
  113. is fine - it should not generate an "old" warning.
  114. - Bug fixes.
  115.  
  116. Changes in v43 - December 28, 2015
  117. - Ken Nelson identified a bug in checks 97 & 154 for the skip-checks table.
  118. - Removed not-really-useful checks for endpoints (9), extraneous backups
  119. for ReportServerTempDB (127), Agent XPs enabled (1006), Database Mail
  120. XPs enabled (1015).
  121. - Backup compression warning (116) now checks not just the default config
  122. setting, but also to see if uncompressed full backups have been done in
  123. the last 2 weeks.
  124. - Default file growth size checks for 1MB or percent growths (82, 158) now
  125. include the full file name and size in the details column, and only alert
  126. for files over 1GB in size.
  127. - Log file larger than data files check (75) now reports the log file size
  128. in the details column.
  129. - Tables in msdb check (28) now ignores Database Tuning Advisor tables.
  130. - Untrusted foreign keys check (48) now ignores master, model, msdb,
  131. ReportServer, ReportServerTempDB.
  132. - User-created stats check (122) now reports the number of user-created
  133. stats in the details column.
  134. - Bug fixes.
  135.  
  136. Changes in v42 - September 7, 2015
  137. - Added check 163 for SQL Server 2016 databases with Query Store disabled.
  138. - Added a few ignorable waits.
  139. - Do not say no-significant-waits-found if we detected poison waits.
  140. - Stop people from trying to install it in SQL Server 2000 compat mode.
  141. - Bug fixes.
  142.  
  143. Changes in v41 - June 18, 2015
  144. - Added check 162 for CMEMTHREAD waits on servers with >= 8 logical
  145. processors per NUMA node.
  146. - Added check 159 for NUMA nodes reporting dangerously low memory in
  147. sys.dm_os_nodes.
  148. - Added check 161 for a high number of cached plans per KB 3026083.
  149. - Fixed a bug in the SkipChecks routines. Reported by Kevin Collins.
  150. - Backup-to-same-drive-as-databases check (93) now includes the number of
  151. backups that were done so you can tell if it was a one-off problem, or if
  152. all backups are going to the wrong place.
  153. - Bug fixes and improvements.
  154.  
  155. For prior changes, see: http://www.BrentOzar.com/blitz/changelog/
  156.  
  157.  
  158. Parameter explanations:
  159.  
  160. @CheckUserDatabaseObjects 1=review user databases for triggers, heaps, etc. Takes more time for more databases and objects.
  161. @CheckServerInfo 1=show server info like CPUs, memory, virtualization
  162. @CheckProcedureCache 1=top 20-50 resource-intensive cache plans and analyze them for common performance issues.
  163. @OutputProcedureCache 1=output the top 20-50 resource-intensive plans even if they did not trigger an alarm
  164. @CheckProcedureCacheFilter ''CPU'' | ''Reads'' | ''Duration'' | ''ExecCount''
  165. @OutputType ''TABLE''=table | ''COUNT''=row with number found | ''SCHEMA''=version and field list | ''NONE'' = none
  166. @IgnorePrioritiesBelow 100=ignore priorities below 100
  167. @IgnorePrioritiesAbove 100=ignore priorities above 100
  168. For the rest of the parameters, see http://www.brentozar.com/blitz/documentation for details.
  169.  
  170.  
  171. */'
  172. ELSE IF @OutputType = 'SCHEMA'
  173. BEGIN
  174. SELECT @Version AS Version,
  175. 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'
  176.  
  177. END
  178. ELSE /* IF @OutputType = 'SCHEMA' */
  179. BEGIN
  180.  
  181. /*
  182. We start by creating #BlitzResults. It's a temp table that will store all of
  183. the results from our checks. Throughout the rest of this stored procedure,
  184. we're running a series of checks looking for dangerous things inside the SQL
  185. Server. When we find a problem, we insert rows into #BlitzResults. At the
  186. end, we return these results to the end user.
  187.  
  188. #BlitzResults has a CheckID field, but there's no Check table. As we do
  189. checks, we insert data into this table, and we manually put in the CheckID.
  190. We (Brent Ozar Unlimited) maintain a list of the checks by ID#. You can
  191. download that from http://www.BrentOzar.com/blitz/documentation/ - you'll
  192. see why it can help shortly.
  193. */
  194. DECLARE @StringToExecute NVARCHAR(4000)
  195. ,@curr_tracefilename NVARCHAR(500)
  196. ,@base_tracefilename NVARCHAR(500)
  197. ,@indx int
  198. ,@query_result_separator CHAR(1)
  199. ,@EmailSubject NVARCHAR(255)
  200. ,@EmailBody NVARCHAR(MAX)
  201. ,@EmailAttachmentFilename NVARCHAR(255)
  202. ,@ProductVersion NVARCHAR(128)
  203. ,@ProductVersionMajor DECIMAL(10,2)
  204. ,@ProductVersionMinor DECIMAL(10,2)
  205. ,@CurrentName NVARCHAR(128)
  206. ,@CurrentDefaultValue NVARCHAR(200)
  207. ,@CurrentCheckID INT
  208. ,@CurrentPriority INT
  209. ,@CurrentFinding VARCHAR(200)
  210. ,@CurrentURL VARCHAR(200)
  211. ,@CurrentDetails NVARCHAR(4000)
  212. ,@MSSinceStartup DECIMAL(38,0)
  213. ,@CPUMSsinceStartup DECIMAL(38,0);
  214.  
  215. IF OBJECT_ID('tempdb..#BlitzResults') IS NOT NULL
  216. DROP TABLE #BlitzResults;
  217. CREATE TABLE #BlitzResults
  218. (
  219. ID INT IDENTITY(1, 1) ,
  220. CheckID INT ,
  221. DatabaseName NVARCHAR(128) ,
  222. Priority TINYINT ,
  223. FindingsGroup VARCHAR(50) ,
  224. Finding VARCHAR(200) ,
  225. URL VARCHAR(200) ,
  226. Details NVARCHAR(4000) ,
  227. QueryPlan [XML] NULL ,
  228. QueryPlanFiltered [NVARCHAR](MAX) NULL
  229. );
  230.  
  231. /*
  232. You can build your own table with a list of checks to skip. For example, you
  233. might have some databases that you don't care about, or some checks you don't
  234. want to run. Then, when you run sp_Blitz, you can specify these parameters:
  235. @SkipChecksDatabase = 'DBAtools',
  236. @SkipChecksSchema = 'dbo',
  237. @SkipChecksTable = 'BlitzChecksToSkip'
  238. Pass in the database, schema, and table that contains the list of checks you
  239. want to skip. This part of the code checks those parameters, gets the list,
  240. and then saves those in a temp table. As we run each check, we'll see if we
  241. need to skip it.
  242.  
  243. Really anal-retentive users will note that the @SkipChecksServer parameter is
  244. not used. YET. We added that parameter in so that we could avoid changing the
  245. stored proc's surface area (interface) later.
  246. */
  247. IF OBJECT_ID('tempdb..#SkipChecks') IS NOT NULL
  248. DROP TABLE #SkipChecks;
  249. CREATE TABLE #SkipChecks
  250. (
  251. DatabaseName NVARCHAR(128) ,
  252. CheckID INT ,
  253. ServerName NVARCHAR(128)
  254. );
  255. CREATE CLUSTERED INDEX IX_CheckID_DatabaseName ON #SkipChecks(CheckID, DatabaseName);
  256.  
  257. IF @SkipChecksTable IS NOT NULL
  258. AND @SkipChecksSchema IS NOT NULL
  259. AND @SkipChecksDatabase IS NOT NULL
  260. BEGIN
  261. SET @StringToExecute = 'INSERT INTO #SkipChecks(DatabaseName, CheckID, ServerName )
  262. SELECT DISTINCT DatabaseName, CheckID, ServerName
  263. FROM ' + QUOTENAME(@SkipChecksDatabase) + '.' + QUOTENAME(@SkipChecksSchema) + '.' + QUOTENAME(@SkipChecksTable)
  264. + ' WHERE ServerName IS NULL OR ServerName = SERVERPROPERTY(''ServerName'');'
  265. EXEC(@StringToExecute)
  266. END
  267.  
  268. IF NOT EXISTS ( SELECT 1
  269. FROM #SkipChecks
  270. WHERE DatabaseName IS NULL AND CheckID = 106 )
  271. AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1
  272. BEGIN
  273. select @curr_tracefilename = [path] from sys.traces where is_default = 1 ;
  274. set @curr_tracefilename = reverse(@curr_tracefilename);
  275. select @indx = patindex('%\%', @curr_tracefilename) ;
  276. set @curr_tracefilename = reverse(@curr_tracefilename) ;
  277. set @base_tracefilename = left( @curr_tracefilename,len(@curr_tracefilename) - @indx) + '\log.trc' ;
  278. END
  279.  
  280.  
  281. /*
  282. That's the end of the SkipChecks stuff.
  283. The next several tables are used by various checks later.
  284. */
  285. IF OBJECT_ID('tempdb..#ConfigurationDefaults') IS NOT NULL
  286. DROP TABLE #ConfigurationDefaults;
  287. CREATE TABLE #ConfigurationDefaults
  288. (
  289. name NVARCHAR(128) ,
  290. DefaultValue BIGINT,
  291. CheckID INT
  292. );
  293.  
  294. IF OBJECT_ID ('tempdb..#Recompile') IS NOT NULL
  295. DROP TABLE #Recompile;
  296. CREATE TABLE #Recompile(
  297. DBName varchar(200),
  298. ProcName varchar(300),
  299. RecompileFlag varchar(1),
  300. SPSchema varchar(50)
  301. );
  302.  
  303. IF OBJECT_ID('tempdb..#DatabaseDefaults') IS NOT NULL
  304. DROP TABLE #DatabaseDefaults;
  305. CREATE TABLE #DatabaseDefaults
  306. (
  307. name NVARCHAR(128) ,
  308. DefaultValue NVARCHAR(200),
  309. CheckID INT,
  310. Priority INT,
  311. Finding VARCHAR(200),
  312. URL VARCHAR(200),
  313. Details NVARCHAR(4000)
  314. );
  315.  
  316.  
  317.  
  318. IF OBJECT_ID('tempdb..#DBCCs') IS NOT NULL
  319. DROP TABLE #DBCCs;
  320. CREATE TABLE #DBCCs
  321. (
  322. ID INT IDENTITY(1, 1)
  323. PRIMARY KEY ,
  324. ParentObject VARCHAR(255) ,
  325. Object VARCHAR(255) ,
  326. Field VARCHAR(255) ,
  327. Value VARCHAR(255) ,
  328. DbName NVARCHAR(128) NULL
  329. )
  330.  
  331.  
  332. IF OBJECT_ID('tempdb..#LogInfo2012') IS NOT NULL
  333. DROP TABLE #LogInfo2012;
  334. CREATE TABLE #LogInfo2012
  335. (
  336. recoveryunitid INT ,
  337. FileID SMALLINT ,
  338. FileSize BIGINT ,
  339. StartOffset BIGINT ,
  340. FSeqNo BIGINT ,
  341. [Status] TINYINT ,
  342. Parity TINYINT ,
  343. CreateLSN NUMERIC(38)
  344. );
  345.  
  346. IF OBJECT_ID('tempdb..#LogInfo') IS NOT NULL
  347. DROP TABLE #LogInfo;
  348. CREATE TABLE #LogInfo
  349. (
  350. FileID SMALLINT ,
  351. FileSize BIGINT ,
  352. StartOffset BIGINT ,
  353. FSeqNo BIGINT ,
  354. [Status] TINYINT ,
  355. Parity TINYINT ,
  356. CreateLSN NUMERIC(38)
  357. );
  358.  
  359. IF OBJECT_ID('tempdb..#partdb') IS NOT NULL
  360. DROP TABLE #partdb;
  361. CREATE TABLE #partdb
  362. (
  363. dbname NVARCHAR(128) ,
  364. objectname NVARCHAR(200) ,
  365. type_desc NVARCHAR(128)
  366. )
  367.  
  368. IF OBJECT_ID('tempdb..#TraceStatus') IS NOT NULL
  369. DROP TABLE #TraceStatus;
  370. CREATE TABLE #TraceStatus
  371. (
  372. TraceFlag VARCHAR(10) ,
  373. status BIT ,
  374. Global BIT ,
  375. Session BIT
  376. );
  377.  
  378. IF OBJECT_ID('tempdb..#driveInfo') IS NOT NULL
  379. DROP TABLE #driveInfo;
  380. CREATE TABLE #driveInfo
  381. (
  382. drive NVARCHAR ,
  383. SIZE DECIMAL(18, 2)
  384. )
  385.  
  386.  
  387. IF OBJECT_ID('tempdb..#dm_exec_query_stats') IS NOT NULL
  388. DROP TABLE #dm_exec_query_stats;
  389. CREATE TABLE #dm_exec_query_stats
  390. (
  391. [id] [int] NOT NULL
  392. IDENTITY(1, 1) ,
  393. [sql_handle] [varbinary](64) NOT NULL ,
  394. [statement_start_offset] [int] NOT NULL ,
  395. [statement_end_offset] [int] NOT NULL ,
  396. [plan_generation_num] [bigint] NOT NULL ,
  397. [plan_handle] [varbinary](64) NOT NULL ,
  398. [creation_time] [datetime] NOT NULL ,
  399. [last_execution_time] [datetime] NOT NULL ,
  400. [execution_count] [bigint] NOT NULL ,
  401. [total_worker_time] [bigint] NOT NULL ,
  402. [last_worker_time] [bigint] NOT NULL ,
  403. [min_worker_time] [bigint] NOT NULL ,
  404. [max_worker_time] [bigint] NOT NULL ,
  405. [total_physical_reads] [bigint] NOT NULL ,
  406. [last_physical_reads] [bigint] NOT NULL ,
  407. [min_physical_reads] [bigint] NOT NULL ,
  408. [max_physical_reads] [bigint] NOT NULL ,
  409. [total_logical_writes] [bigint] NOT NULL ,
  410. [last_logical_writes] [bigint] NOT NULL ,
  411. [min_logical_writes] [bigint] NOT NULL ,
  412. [max_logical_writes] [bigint] NOT NULL ,
  413. [total_logical_reads] [bigint] NOT NULL ,
  414. [last_logical_reads] [bigint] NOT NULL ,
  415. [min_logical_reads] [bigint] NOT NULL ,
  416. [max_logical_reads] [bigint] NOT NULL ,
  417. [total_clr_time] [bigint] NOT NULL ,
  418. [last_clr_time] [bigint] NOT NULL ,
  419. [min_clr_time] [bigint] NOT NULL ,
  420. [max_clr_time] [bigint] NOT NULL ,
  421. [total_elapsed_time] [bigint] NOT NULL ,
  422. [last_elapsed_time] [bigint] NOT NULL ,
  423. [min_elapsed_time] [bigint] NOT NULL ,
  424. [max_elapsed_time] [bigint] NOT NULL ,
  425. [query_hash] [binary](8) NULL ,
  426. [query_plan_hash] [binary](8) NULL ,
  427. [query_plan] [xml] NULL ,
  428. [query_plan_filtered] [nvarchar](MAX) NULL ,
  429. [text] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS
  430. NULL ,
  431. [text_filtered] [nvarchar](MAX) COLLATE SQL_Latin1_General_CP1_CI_AS
  432. NULL
  433. )
  434.  
  435. /* Used for the default trace checks. */
  436. DECLARE @TracePath NVARCHAR(256);
  437. SELECT @TracePath=CAST(value as NVARCHAR(256))
  438. FROM sys.fn_trace_getinfo(1)
  439. WHERE traceid=1 AND property=2;
  440.  
  441. SELECT @MSSinceStartup = DATEDIFF(MINUTE, create_date, CURRENT_TIMESTAMP)
  442. FROM sys.databases
  443. WHERE name='tempdb';
  444.  
  445. SET @MSSinceStartup = @MSSinceStartup * 60000;
  446.  
  447. SELECT @CPUMSsinceStartup = @MSSinceStartup * cpu_count
  448. FROM sys.dm_os_sys_info;
  449.  
  450.  
  451. /* If we're outputting CSV, don't bother checking the plan cache because we cannot export plans. */
  452. IF @OutputType = 'CSV'
  453. SET @CheckProcedureCache = 0;
  454.  
  455. /* Sanitize our inputs */
  456. SELECT
  457. @OutputDatabaseName = QUOTENAME(@OutputDatabaseName),
  458. @OutputSchemaName = QUOTENAME(@OutputSchemaName),
  459. @OutputTableName = QUOTENAME(@OutputTableName)
  460.  
  461. /* Get the major and minor build numbers */
  462. SET @ProductVersion = CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(128));
  463. SELECT @ProductVersionMajor = SUBSTRING(@ProductVersion, 1,CHARINDEX('.', @ProductVersion) + 1 ),
  464. @ProductVersionMinor = PARSENAME(CONVERT(varchar(32), @ProductVersion), 2)
  465.  
  466.  
  467. /*
  468. Whew! we're finally done with the setup, and we can start doing checks.
  469. First, let's make sure we're actually supposed to do checks on this server.
  470. The user could have passed in a SkipChecks table that specified to skip ALL
  471. checks on this server, so let's check for that:
  472. */
  473. IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName
  474. FROM #SkipChecks
  475. WHERE DatabaseName IS NULL
  476. AND CheckID IS NULL ) )
  477. OR ( @SkipChecksTable IS NULL )
  478. )
  479. BEGIN
  480.  
  481. /*
  482. Our very first check! We'll put more comments in this one just to
  483. explain exactly how it works. First, we check to see if we're
  484. supposed to skip CheckID 1 (that's the check we're working on.)
  485. */
  486. IF NOT EXISTS ( SELECT 1
  487. FROM #SkipChecks
  488. WHERE DatabaseName IS NULL AND CheckID = 1 )
  489. BEGIN
  490.  
  491. /*
  492. Below, we check master.sys.databases looking for databases
  493. that haven't had a backup in the last week. If we find any,
  494. we insert them into #BlitzResults, the temp table that
  495. tracks our server's problems. Note that if the check does
  496. NOT find any problems, we don't save that. We're only
  497. saving the problems, not the successful checks.
  498. */
  499. INSERT INTO #BlitzResults
  500. ( CheckID ,
  501. DatabaseName ,
  502. Priority ,
  503. FindingsGroup ,
  504. Finding ,
  505. URL ,
  506. Details
  507. )
  508. SELECT 1 AS CheckID ,
  509. d.[name] AS DatabaseName ,
  510. 1 AS Priority ,
  511. 'Backup' AS FindingsGroup ,
  512. 'Backups Not Performed Recently' AS Finding ,
  513. 'http://BrentOzar.com/go/nobak' AS URL ,
  514. 'Database ' + d.Name + ' last backed up: '
  515. + COALESCE(CAST(MAX(b.backup_finish_date) AS VARCHAR(25)),'never') AS Details
  516. FROM master.sys.databases d
  517. 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
  518. AND b.type = 'D'
  519. AND b.server_name = SERVERPROPERTY('ServerName') /*Backupset ran on current server */
  520. WHERE d.database_id <> 2 /* Bonus points if you know what that means */
  521. AND d.state NOT IN(1, 6, 10) /* Not currently offline or restoring, like log shipping databases */
  522. AND d.is_in_standby = 0 /* Not a log shipping target database */
  523. AND d.source_database_id IS NULL /* Excludes database snapshots */
  524. AND d.name NOT IN ( SELECT DISTINCT
  525. DatabaseName
  526. FROM #SkipChecks
  527. WHERE CheckID IS NULL )
  528. /*
  529. The above NOT IN filters out the databases we're not supposed to check.
  530. */
  531. GROUP BY d.name
  532. HAVING MAX(b.backup_finish_date) <= DATEADD(dd,
  533. -7, GETDATE())
  534. OR MAX(b.backup_finish_date) IS NULL;
  535. /*
  536. And there you have it. The rest of this stored procedure works the same
  537. way: it asks:
  538. - Should I skip this check?
  539. - If not, do I find problems?
  540. - Insert the results into #BlitzResults
  541. */
  542.  
  543. END
  544.  
  545. /*
  546. And that's the end of CheckID #1.
  547.  
  548. CheckID #2 is a little simpler because it only involves one query, and it's
  549. more typical for queries that people contribute. But keep reading, because
  550. the next check gets more complex again.
  551. */
  552.  
  553. IF NOT EXISTS ( SELECT 1
  554. FROM #SkipChecks
  555. WHERE DatabaseName IS NULL AND CheckID = 2 )
  556. BEGIN
  557. INSERT INTO #BlitzResults
  558. ( CheckID ,
  559. DatabaseName ,
  560. Priority ,
  561. FindingsGroup ,
  562. Finding ,
  563. URL ,
  564. Details
  565. )
  566. SELECT DISTINCT
  567. 2 AS CheckID ,
  568. d.name AS DatabaseName ,
  569. 1 AS Priority ,
  570. 'Backup' AS FindingsGroup ,
  571. 'Full Recovery Mode w/o Log Backups' AS Finding ,
  572. 'http://BrentOzar.com/go/biglogs' AS URL ,
  573. ( 'The ' + CAST(CAST((SELECT (([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) + 'MB log file has not been backed up in the last week.' ) AS Details
  574. FROM master.sys.databases d
  575. WHERE d.recovery_model IN ( 1, 2 )
  576. AND d.database_id NOT IN ( 2, 3 )
  577. AND d.source_database_id IS NULL
  578. AND d.state <> 1 /* Not currently restoring, like log shipping databases */
  579. AND d.is_in_standby = 0 /* Not a log shipping target database */
  580. AND d.source_database_id IS NULL /* Excludes database snapshots */
  581. AND d.name NOT IN ( SELECT DISTINCT
  582. DatabaseName
  583. FROM #SkipChecks
  584. WHERE CheckID IS NULL )
  585. AND NOT EXISTS ( SELECT *
  586. FROM msdb.dbo.backupset b
  587. WHERE d.name COLLATE SQL_Latin1_General_CP1_CI_AS = b.database_name COLLATE SQL_Latin1_General_CP1_CI_AS
  588. AND b.type = 'L'
  589. AND b.backup_finish_date >= DATEADD(dd,
  590. -7, GETDATE()) );
  591. END
  592.  
  593.  
  594. /*
  595. Next up, we've got CheckID 8. (These don't have to go in order.) This one
  596. won't work on SQL Server 2005 because it relies on a new DMV that didn't
  597. exist prior to SQL Server 2008. This means we have to check the SQL Server
  598. version first, then build a dynamic string with the query we want to run:
  599. */
  600.  
  601. IF NOT EXISTS ( SELECT 1
  602. FROM #SkipChecks
  603. WHERE DatabaseName IS NULL AND CheckID = 8 )
  604. BEGIN
  605. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  606. AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
  607. BEGIN
  608. SET @StringToExecute = 'INSERT INTO #BlitzResults
  609. (CheckID, Priority,
  610. FindingsGroup,
  611. Finding, URL,
  612. Details)
  613. SELECT 8 AS CheckID,
  614. 190 AS Priority,
  615. ''Security'' AS FindingsGroup,
  616. ''Server Audits Running'' AS Finding,
  617. ''http://BrentOzar.com/go/audits'' AS URL,
  618. (''SQL Server built-in audit functionality is being used by server audit: '' + [name]) AS Details FROM sys.dm_server_audit_status'
  619. EXECUTE(@StringToExecute)
  620. END;
  621. END
  622.  
  623. /*
  624. But what if you need to run a query in every individual database?
  625. Hop down to the @CheckUserDatabaseObjects section.
  626.  
  627. And that's the basic idea! You can read through the rest of the
  628. checks if you like - some more exciting stuff happens closer to the
  629. end of the stored proc, where we start doing things like checking
  630. the plan cache, but those aren't as cleanly commented.
  631.  
  632. If you'd like to contribute your own check, use one of the check
  633. formats shown above and email it to Help@BrentOzar.com. You don't
  634. have to pick a CheckID or a link - we'll take care of that when we
  635. test and publish the code. Thanks!
  636. */
  637.  
  638.  
  639. IF NOT EXISTS ( SELECT 1
  640. FROM #SkipChecks
  641. WHERE DatabaseName IS NULL AND CheckID = 93 )
  642. BEGIN
  643. INSERT INTO #BlitzResults
  644. ( CheckID ,
  645. Priority ,
  646. FindingsGroup ,
  647. Finding ,
  648. URL ,
  649. Details
  650. )
  651. SELECT
  652. 93 AS CheckID ,
  653. 1 AS Priority ,
  654. 'Backup' AS FindingsGroup ,
  655. 'Backing Up to Same Drive Where Databases Reside' AS Finding ,
  656. 'http://BrentOzar.com/go/backup' AS URL ,
  657. CAST(COUNT(1) AS VARCHAR(50)) + ' backups done on drive '
  658. + UPPER(LEFT(bmf.physical_device_name, 3))
  659. + ' in the last two weeks, where database files also live. This represents a serious risk if that array fails.' Details
  660. FROM msdb.dbo.backupmediafamily AS bmf
  661. INNER JOIN msdb.dbo.backupset AS bs ON bmf.media_set_id = bs.media_set_id
  662. AND bs.backup_start_date >= ( DATEADD(dd,
  663. -14, GETDATE()) )
  664. WHERE UPPER(LEFT(bmf.physical_device_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3)) IN (
  665. SELECT DISTINCT
  666. UPPER(LEFT(mf.physical_name COLLATE SQL_Latin1_General_CP1_CI_AS, 3))
  667. FROM sys.master_files AS mf )
  668. GROUP BY UPPER(LEFT(bmf.physical_device_name, 3))
  669. END
  670.  
  671.  
  672. IF NOT EXISTS ( SELECT 1
  673. FROM #SkipChecks
  674. WHERE DatabaseName IS NULL AND CheckID = 119 )
  675. AND EXISTS ( SELECT *
  676. FROM sys.all_objects o
  677. WHERE o.name = 'dm_database_encryption_keys' )
  678. BEGIN
  679. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, DatabaseName, URL, Details)
  680. SELECT 119 AS CheckID,
  681. 1 AS Priority,
  682. ''Backup'' AS FindingsGroup,
  683. ''TDE Certificate Not Backed Up Recently'' AS Finding,
  684. db_name(dek.database_id) AS DatabaseName,
  685. ''http://BrentOzar.com/go/tde'' AS URL,
  686. ''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
  687. FROM sys.certificates c INNER JOIN sys.dm_database_encryption_keys dek ON c.thumbprint = dek.encryptor_thumbprint
  688. WHERE pvt_key_last_backup_date IS NULL OR pvt_key_last_backup_date <= DATEADD(dd, -30, GETDATE())';
  689. EXECUTE(@StringToExecute);
  690. END
  691.  
  692.  
  693. IF NOT EXISTS ( SELECT 1
  694. FROM #SkipChecks
  695. WHERE DatabaseName IS NULL AND CheckID = 3 )
  696. BEGIN
  697. INSERT INTO #BlitzResults
  698. ( CheckID ,
  699. DatabaseName ,
  700. Priority ,
  701. FindingsGroup ,
  702. Finding ,
  703. URL ,
  704. Details
  705. )
  706. SELECT TOP 1
  707. 3 AS CheckID ,
  708. 'msdb' ,
  709. 200 AS Priority ,
  710. 'Backup' AS FindingsGroup ,
  711. 'MSDB Backup History Not Purged' AS Finding ,
  712. 'http://BrentOzar.com/go/history' AS URL ,
  713. ( 'Database backup history retained back to '
  714. + CAST(bs.backup_start_date AS VARCHAR(20)) ) AS Details
  715. FROM msdb.dbo.backupset bs
  716. WHERE bs.backup_start_date <= DATEADD(dd, -60,
  717. GETDATE())
  718. ORDER BY backup_set_id ASC;
  719. END
  720.  
  721. IF NOT EXISTS ( SELECT 1
  722. FROM #SkipChecks
  723. WHERE DatabaseName IS NULL AND CheckID = 4 )
  724. BEGIN
  725. INSERT INTO #BlitzResults
  726. ( CheckID ,
  727. Priority ,
  728. FindingsGroup ,
  729. Finding ,
  730. URL ,
  731. Details
  732. )
  733. SELECT 4 AS CheckID ,
  734. 10 AS Priority ,
  735. 'Security' AS FindingsGroup ,
  736. 'Sysadmins' AS Finding ,
  737. 'http://BrentOzar.com/go/sa' AS URL ,
  738. ( 'Login [' + l.name
  739. + '] is a sysadmin - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' ) AS Details
  740. FROM master.sys.syslogins l
  741. WHERE l.sysadmin = 1
  742. AND l.name <> SUSER_SNAME(0x01)
  743. AND l.denylogin = 0
  744. AND l.name NOT LIKE 'NT SERVICE\%';
  745. END
  746.  
  747. IF NOT EXISTS ( SELECT 1
  748. FROM #SkipChecks
  749. WHERE DatabaseName IS NULL AND CheckID = 5 )
  750. BEGIN
  751. INSERT INTO #BlitzResults
  752. ( CheckID ,
  753. Priority ,
  754. FindingsGroup ,
  755. Finding ,
  756. URL ,
  757. Details
  758. )
  759. SELECT 5 AS CheckID ,
  760. 10 AS Priority ,
  761. 'Security' AS FindingsGroup ,
  762. 'Security Admins' AS Finding ,
  763. 'http://BrentOzar.com/go/sa' AS URL ,
  764. ( 'Login [' + l.name
  765. + '] 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
  766. FROM master.sys.syslogins l
  767. WHERE l.securityadmin = 1
  768. AND l.name <> SUSER_SNAME(0x01)
  769. AND l.denylogin = 0;
  770. END
  771.  
  772. IF NOT EXISTS ( SELECT 1
  773. FROM #SkipChecks
  774. WHERE DatabaseName IS NULL AND CheckID = 104 )
  775. BEGIN
  776. INSERT INTO #BlitzResults
  777. ( [CheckID] ,
  778. [Priority] ,
  779. [FindingsGroup] ,
  780. [Finding] ,
  781. [URL] ,
  782. [Details]
  783. )
  784. SELECT 104 AS [CheckID] ,
  785. 10 AS [Priority] ,
  786. 'Security' AS [FindingsGroup] ,
  787. 'Login Can Control Server' AS [Finding] ,
  788. 'http://BrentOzar.com/go/sa' AS [URL] ,
  789. 'Login [' + pri.[name]
  790. + '] has the CONTROL SERVER permission - meaning they can do absolutely anything in SQL Server, including dropping databases or hiding their tracks.' AS [Details]
  791. FROM sys.server_principals AS pri
  792. WHERE pri.[principal_id] IN (
  793. SELECT p.[grantee_principal_id]
  794. FROM sys.server_permissions AS p
  795. WHERE p.[state] IN ( 'G', 'W' )
  796. AND p.[class] = 100
  797. AND p.[type] = 'CL' )
  798. AND pri.[name] NOT LIKE '##%##'
  799. END
  800.  
  801. IF NOT EXISTS ( SELECT 1
  802. FROM #SkipChecks
  803. WHERE DatabaseName IS NULL AND CheckID = 6 )
  804. BEGIN
  805. INSERT INTO #BlitzResults
  806. ( CheckID ,
  807. Priority ,
  808. FindingsGroup ,
  809. Finding ,
  810. URL ,
  811. Details
  812. )
  813. SELECT 6 AS CheckID ,
  814. 200 AS Priority ,
  815. 'Security' AS FindingsGroup ,
  816. 'Jobs Owned By Users' AS Finding ,
  817. 'http://BrentOzar.com/go/owners' AS URL ,
  818. ( 'Job [' + j.name + '] is owned by ['
  819. + SUSER_SNAME(j.owner_sid)
  820. + '] - meaning if their login is disabled or not available due to Active Directory problems, the job will stop working.' ) AS Details
  821. FROM msdb.dbo.sysjobs j
  822. WHERE j.enabled = 1
  823. AND SUSER_SNAME(j.owner_sid) <> SUSER_SNAME(0x01);
  824. END
  825.  
  826.  
  827. IF NOT EXISTS ( SELECT 1
  828. FROM #SkipChecks
  829. WHERE DatabaseName IS NULL AND CheckID = 7 )
  830. BEGIN
  831. INSERT INTO #BlitzResults
  832. ( CheckID ,
  833. Priority ,
  834. FindingsGroup ,
  835. Finding ,
  836. URL ,
  837. Details
  838. )
  839. SELECT 7 AS CheckID ,
  840. 10 AS Priority ,
  841. 'Security' AS FindingsGroup ,
  842. 'Stored Procedure Runs at Startup' AS Finding ,
  843. 'http://BrentOzar.com/go/startup' AS URL ,
  844. ( 'Stored procedure [master].['
  845. + r.SPECIFIC_SCHEMA + '].['
  846. + r.SPECIFIC_NAME
  847. + '] 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
  848. FROM master.INFORMATION_SCHEMA.ROUTINES r
  849. WHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME),
  850. 'ExecIsStartup') = 1;
  851. END
  852.  
  853.  
  854. IF NOT EXISTS ( SELECT 1
  855. FROM #SkipChecks
  856. WHERE DatabaseName IS NULL AND CheckID = 10 )
  857. BEGIN
  858. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  859. AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
  860. BEGIN
  861. SET @StringToExecute = 'INSERT INTO #BlitzResults
  862. (CheckID,
  863. Priority,
  864. FindingsGroup,
  865. Finding,
  866. URL,
  867. Details)
  868. SELECT 10 AS CheckID,
  869. 100 AS Priority,
  870. ''Performance'' AS FindingsGroup,
  871. ''Resource Governor Enabled'' AS Finding,
  872. ''http://BrentOzar.com/go/rg'' AS URL,
  873. (''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'
  874. EXECUTE(@StringToExecute)
  875. END;
  876. END
  877.  
  878.  
  879. IF NOT EXISTS ( SELECT 1
  880. FROM #SkipChecks
  881. WHERE DatabaseName IS NULL AND CheckID = 11 )
  882. BEGIN
  883. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  884. BEGIN
  885. SET @StringToExecute = 'INSERT INTO #BlitzResults
  886. (CheckID,
  887. Priority,
  888. FindingsGroup,
  889. Finding,
  890. URL,
  891. Details)
  892. SELECT 11 AS CheckID,
  893. 100 AS Priority,
  894. ''Performance'' AS FindingsGroup,
  895. ''Server Triggers Enabled'' AS Finding,
  896. ''http://BrentOzar.com/go/logontriggers/'' AS URL,
  897. (''Server Trigger ['' + [name] ++ ''] is enabled, so it runs every time someone logs in. 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'
  898. EXECUTE(@StringToExecute)
  899. END;
  900. END
  901.  
  902.  
  903. IF NOT EXISTS ( SELECT 1
  904. FROM #SkipChecks
  905. WHERE DatabaseName IS NULL AND CheckID = 12 )
  906. BEGIN
  907. INSERT INTO #BlitzResults
  908. ( CheckID ,
  909. DatabaseName ,
  910. Priority ,
  911. FindingsGroup ,
  912. Finding ,
  913. URL ,
  914. Details
  915. )
  916. SELECT 12 AS CheckID ,
  917. [name] AS DatabaseName ,
  918. 10 AS Priority ,
  919. 'Performance' AS FindingsGroup ,
  920. 'Auto-Close Enabled' AS Finding ,
  921. 'http://BrentOzar.com/go/autoclose' AS URL ,
  922. ( 'Database [' + [name]
  923. + '] has auto-close enabled. This setting can dramatically decrease performance.' ) AS Details
  924. FROM sys.databases
  925. WHERE is_auto_close_on = 1
  926. AND name NOT IN ( SELECT DISTINCT
  927. DatabaseName
  928. FROM #SkipChecks
  929. WHERE CheckID IS NULL)
  930. END
  931.  
  932.  
  933. IF NOT EXISTS ( SELECT 1
  934. FROM #SkipChecks
  935. WHERE DatabaseName IS NULL AND CheckID = 13 )
  936. BEGIN
  937. INSERT INTO #BlitzResults
  938. ( CheckID ,
  939. DatabaseName ,
  940. Priority ,
  941. FindingsGroup ,
  942. Finding ,
  943. URL ,
  944. Details
  945. )
  946. SELECT 13 AS CheckID ,
  947. [name] AS DatabaseName ,
  948. 10 AS Priority ,
  949. 'Performance' AS FindingsGroup ,
  950. 'Auto-Shrink Enabled' AS Finding ,
  951. 'http://BrentOzar.com/go/autoshrink' AS URL ,
  952. ( 'Database [' + [name]
  953. + '] has auto-shrink enabled. This setting can dramatically decrease performance.' ) AS Details
  954. FROM sys.databases
  955. WHERE is_auto_shrink_on = 1
  956. AND name NOT IN ( SELECT DISTINCT
  957. DatabaseName
  958. FROM #SkipChecks
  959. WHERE CheckID IS NULL);
  960. END
  961.  
  962.  
  963. IF NOT EXISTS ( SELECT 1
  964. FROM #SkipChecks
  965. WHERE DatabaseName IS NULL AND CheckID = 14 )
  966. BEGIN
  967. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  968. BEGIN
  969. SET @StringToExecute = 'INSERT INTO #BlitzResults
  970. (CheckID,
  971. DatabaseName,
  972. Priority,
  973. FindingsGroup,
  974. Finding,
  975. URL,
  976. Details)
  977. SELECT 14 AS CheckID,
  978. [name] as DatabaseName,
  979. 50 AS Priority,
  980. ''Reliability'' AS FindingsGroup,
  981. ''Page Verification Not Optimal'' AS Finding,
  982. ''http://BrentOzar.com/go/torn'' AS URL,
  983. (''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
  984. FROM sys.databases
  985. WHERE page_verify_option < 2
  986. AND name <> ''tempdb''
  987. and name not in (select distinct DatabaseName from #SkipChecks)'
  988. EXECUTE(@StringToExecute)
  989. END;
  990. END
  991.  
  992.  
  993. IF NOT EXISTS ( SELECT 1
  994. FROM #SkipChecks
  995. WHERE DatabaseName IS NULL AND CheckID = 15 )
  996. BEGIN
  997. INSERT INTO #BlitzResults
  998. ( CheckID ,
  999. DatabaseName ,
  1000. Priority ,
  1001. FindingsGroup ,
  1002. Finding ,
  1003. URL ,
  1004. Details
  1005. )
  1006. SELECT 15 AS CheckID ,
  1007. [name] AS DatabaseName ,
  1008. 110 AS Priority ,
  1009. 'Performance' AS FindingsGroup ,
  1010. 'Auto-Create Stats Disabled' AS Finding ,
  1011. 'http://BrentOzar.com/go/acs' AS URL ,
  1012. ( 'Database [' + [name]
  1013. + '] 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
  1014. FROM sys.databases
  1015. WHERE is_auto_create_stats_on = 0
  1016. AND name NOT IN ( SELECT DISTINCT
  1017. DatabaseName
  1018. FROM #SkipChecks
  1019. WHERE CheckID IS NULL)
  1020. END
  1021.  
  1022. IF NOT EXISTS ( SELECT 1
  1023. FROM #SkipChecks
  1024. WHERE DatabaseName IS NULL AND CheckID = 16 )
  1025. BEGIN
  1026. INSERT INTO #BlitzResults
  1027. ( CheckID ,
  1028. DatabaseName ,
  1029. Priority ,
  1030. FindingsGroup ,
  1031. Finding ,
  1032. URL ,
  1033. Details
  1034. )
  1035. SELECT 16 AS CheckID ,
  1036. [name] AS DatabaseName ,
  1037. 110 AS Priority ,
  1038. 'Performance' AS FindingsGroup ,
  1039. 'Auto-Update Stats Disabled' AS Finding ,
  1040. 'http://BrentOzar.com/go/aus' AS URL ,
  1041. ( 'Database [' + [name]
  1042. + '] 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
  1043. FROM sys.databases
  1044. WHERE is_auto_update_stats_on = 0
  1045. AND name NOT IN ( SELECT DISTINCT
  1046. DatabaseName
  1047. FROM #SkipChecks
  1048. WHERE CheckID IS NULL)
  1049. END
  1050.  
  1051.  
  1052. IF NOT EXISTS ( SELECT 1
  1053. FROM #SkipChecks
  1054. WHERE DatabaseName IS NULL AND CheckID = 17 )
  1055. BEGIN
  1056. INSERT INTO #BlitzResults
  1057. ( CheckID ,
  1058. DatabaseName ,
  1059. Priority ,
  1060. FindingsGroup ,
  1061. Finding ,
  1062. URL ,
  1063. Details
  1064. )
  1065. SELECT 17 AS CheckID ,
  1066. [name] AS DatabaseName ,
  1067. 150 AS Priority ,
  1068. 'Performance' AS FindingsGroup ,
  1069. 'Stats Updated Asynchronously' AS Finding ,
  1070. 'http://BrentOzar.com/go/asyncstats' AS URL ,
  1071. ( 'Database [' + [name]
  1072. + '] 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
  1073. FROM sys.databases
  1074. WHERE is_auto_update_stats_async_on = 1
  1075. AND name NOT IN ( SELECT DISTINCT
  1076. DatabaseName
  1077. FROM #SkipChecks
  1078. WHERE CheckID IS NULL)
  1079. END
  1080.  
  1081.  
  1082. IF NOT EXISTS ( SELECT 1
  1083. FROM #SkipChecks
  1084. WHERE DatabaseName IS NULL AND CheckID = 18 )
  1085. BEGIN
  1086. INSERT INTO #BlitzResults
  1087. ( CheckID ,
  1088. DatabaseName ,
  1089. Priority ,
  1090. FindingsGroup ,
  1091. Finding ,
  1092. URL ,
  1093. Details
  1094. )
  1095. SELECT 18 AS CheckID ,
  1096. [name] AS DatabaseName ,
  1097. 150 AS Priority ,
  1098. 'Performance' AS FindingsGroup ,
  1099. 'Forced Parameterization On' AS Finding ,
  1100. 'http://BrentOzar.com/go/forced' AS URL ,
  1101. ( 'Database [' + [name]
  1102. + '] 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
  1103. FROM sys.databases
  1104. WHERE is_parameterization_forced = 1
  1105. AND name NOT IN ( SELECT DatabaseName
  1106. FROM #SkipChecks
  1107. WHERE CheckID IS NULL)
  1108. END
  1109.  
  1110.  
  1111. IF NOT EXISTS ( SELECT 1
  1112. FROM #SkipChecks
  1113. WHERE DatabaseName IS NULL AND CheckID = 20 )
  1114. BEGIN
  1115. INSERT INTO #BlitzResults
  1116. ( CheckID ,
  1117. DatabaseName ,
  1118. Priority ,
  1119. FindingsGroup ,
  1120. Finding ,
  1121. URL ,
  1122. Details
  1123. )
  1124. SELECT 20 AS CheckID ,
  1125. [name] AS DatabaseName ,
  1126. 200 AS Priority ,
  1127. 'Informational' AS FindingsGroup ,
  1128. 'Date Correlation On' AS Finding ,
  1129. 'http://BrentOzar.com/go/corr' AS URL ,
  1130. ( 'Database [' + [name]
  1131. + '] 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
  1132. FROM sys.databases
  1133. WHERE is_date_correlation_on = 1
  1134. AND name NOT IN ( SELECT DISTINCT
  1135. DatabaseName
  1136. FROM #SkipChecks
  1137. WHERE CheckID IS NULL)
  1138. END
  1139.  
  1140.  
  1141. IF NOT EXISTS ( SELECT 1
  1142. FROM #SkipChecks
  1143. WHERE DatabaseName IS NULL AND CheckID = 21 )
  1144. BEGIN
  1145. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  1146. AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
  1147. BEGIN
  1148. SET @StringToExecute = 'INSERT INTO #BlitzResults
  1149. (CheckID,
  1150. DatabaseName,
  1151. Priority,
  1152. FindingsGroup,
  1153. Finding,
  1154. URL,
  1155. Details)
  1156. SELECT 21 AS CheckID,
  1157. [name] as DatabaseName,
  1158. 200 AS Priority,
  1159. ''Informational'' AS FindingsGroup,
  1160. ''Database Encrypted'' AS Finding,
  1161. ''http://BrentOzar.com/go/tde'' AS URL,
  1162. (''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
  1163. FROM sys.databases
  1164. WHERE is_encrypted = 1
  1165. and name not in (select distinct DatabaseName from #SkipChecks)'
  1166. EXECUTE(@StringToExecute)
  1167. END;
  1168. END
  1169.  
  1170. /*
  1171. Believe it or not, SQL Server doesn't track the default values
  1172. for sp_configure options! We'll make our own list here.
  1173. */
  1174. INSERT INTO #ConfigurationDefaults
  1175. VALUES ( 'access check cache bucket count', 0, 1001 );
  1176. INSERT INTO #ConfigurationDefaults
  1177. VALUES ( 'access check cache quota', 0, 1002 );
  1178. INSERT INTO #ConfigurationDefaults
  1179. VALUES ( 'Ad Hoc Distributed Queries', 0, 1003 );
  1180. INSERT INTO #ConfigurationDefaults
  1181. VALUES ( 'affinity I/O mask', 0, 1004 );
  1182. INSERT INTO #ConfigurationDefaults
  1183. VALUES ( 'affinity mask', 0, 1005 );
  1184. INSERT INTO #ConfigurationDefaults
  1185. VALUES ( 'affinity64 mask', 0, 1066 );
  1186. INSERT INTO #ConfigurationDefaults
  1187. VALUES ( 'affinity64 I/O mask', 0, 1067 );
  1188. INSERT INTO #ConfigurationDefaults
  1189. VALUES ( 'Agent XPs', 0, 1071 );
  1190. INSERT INTO #ConfigurationDefaults
  1191. VALUES ( 'allow updates', 0, 1007 );
  1192. INSERT INTO #ConfigurationDefaults
  1193. VALUES ( 'awe enabled', 0, 1008 );
  1194. INSERT INTO #ConfigurationDefaults
  1195. VALUES ( 'backup checksum default', 0, 1070 );
  1196. INSERT INTO #ConfigurationDefaults
  1197. VALUES ( 'backup compression default', 0, 1073 );
  1198. INSERT INTO #ConfigurationDefaults
  1199. VALUES ( 'blocked process threshold', 0, 1009 );
  1200. INSERT INTO #ConfigurationDefaults
  1201. VALUES ( 'blocked process threshold (s)', 0, 1009 );
  1202. INSERT INTO #ConfigurationDefaults
  1203. VALUES ( 'c2 audit mode', 0, 1010 );
  1204. INSERT INTO #ConfigurationDefaults
  1205. VALUES ( 'clr enabled', 0, 1011 );
  1206. INSERT INTO #ConfigurationDefaults
  1207. VALUES ( 'common criteria compliance enabled', 0, 1074 );
  1208. INSERT INTO #ConfigurationDefaults
  1209. VALUES ( 'contained database authentication', 0, 1068 );
  1210. INSERT INTO #ConfigurationDefaults
  1211. VALUES ( 'cost threshold for parallelism', 5, 1012 );
  1212. INSERT INTO #ConfigurationDefaults
  1213. VALUES ( 'cross db ownership chaining', 0, 1013 );
  1214. INSERT INTO #ConfigurationDefaults
  1215. VALUES ( 'cursor threshold', -1, 1014 );
  1216. INSERT INTO #ConfigurationDefaults
  1217. VALUES ( 'Database Mail XPs', 0, 1072 );
  1218. INSERT INTO #ConfigurationDefaults
  1219. VALUES ( 'default full-text language', 1033, 1016 );
  1220. INSERT INTO #ConfigurationDefaults
  1221. VALUES ( 'default language', 0, 1017 );
  1222. INSERT INTO #ConfigurationDefaults
  1223. VALUES ( 'default trace enabled', 1, 1018 );
  1224. INSERT INTO #ConfigurationDefaults
  1225. VALUES ( 'disallow results from triggers', 0, 1019 );
  1226. INSERT INTO #ConfigurationDefaults
  1227. VALUES ( 'EKM provider enabled', 0, 1075 );
  1228. INSERT INTO #ConfigurationDefaults
  1229. VALUES ( 'filestream access level', 0, 1076 );
  1230. INSERT INTO #ConfigurationDefaults
  1231. VALUES ( 'fill factor (%)', 0, 1020 );
  1232. INSERT INTO #ConfigurationDefaults
  1233. VALUES ( 'ft crawl bandwidth (max)', 100, 1021 );
  1234. INSERT INTO #ConfigurationDefaults
  1235. VALUES ( 'ft crawl bandwidth (min)', 0, 1022 );
  1236. INSERT INTO #ConfigurationDefaults
  1237. VALUES ( 'ft notify bandwidth (max)', 100, 1023 );
  1238. INSERT INTO #ConfigurationDefaults
  1239. VALUES ( 'ft notify bandwidth (min)', 0, 1024 );
  1240. INSERT INTO #ConfigurationDefaults
  1241. VALUES ( 'index create memory (KB)', 0, 1025 );
  1242. INSERT INTO #ConfigurationDefaults
  1243. VALUES ( 'in-doubt xact resolution', 0, 1026 );
  1244. INSERT INTO #ConfigurationDefaults
  1245. VALUES ( 'lightweight pooling', 0, 1027 );
  1246. INSERT INTO #ConfigurationDefaults
  1247. VALUES ( 'locks', 0, 1028 );
  1248. INSERT INTO #ConfigurationDefaults
  1249. VALUES ( 'max degree of parallelism', 0, 1029 );
  1250. INSERT INTO #ConfigurationDefaults
  1251. VALUES ( 'max full-text crawl range', 4, 1030 );
  1252. INSERT INTO #ConfigurationDefaults
  1253. VALUES ( 'max server memory (MB)', 2147483647, 1031 );
  1254. INSERT INTO #ConfigurationDefaults
  1255. VALUES ( 'max text repl size (B)', 65536, 1032 );
  1256. INSERT INTO #ConfigurationDefaults
  1257. VALUES ( 'max worker threads', 0, 1033 );
  1258. INSERT INTO #ConfigurationDefaults
  1259. VALUES ( 'media retention', 0, 1034 );
  1260. INSERT INTO #ConfigurationDefaults
  1261. VALUES ( 'min memory per query (KB)', 1024, 1035 );
  1262. /* Accepting both 0 and 16 below because both have been seen in the wild as defaults. */
  1263. IF EXISTS ( SELECT *
  1264. FROM sys.configurations
  1265. WHERE name = 'min server memory (MB)'
  1266. AND value_in_use IN ( 0, 16 ) )
  1267. INSERT INTO #ConfigurationDefaults
  1268. SELECT 'min server memory (MB)' ,
  1269. CAST(value_in_use AS BIGINT), 1036
  1270. FROM sys.configurations
  1271. WHERE name = 'min server memory (MB)'
  1272. ELSE
  1273. INSERT INTO #ConfigurationDefaults
  1274. VALUES ( 'min server memory (MB)', 0, 1036 );
  1275. INSERT INTO #ConfigurationDefaults
  1276. VALUES ( 'nested triggers', 1, 1037 );
  1277. INSERT INTO #ConfigurationDefaults
  1278. VALUES ( 'network packet size (B)', 4096, 1038 );
  1279. INSERT INTO #ConfigurationDefaults
  1280. VALUES ( 'Ole Automation Procedures', 0, 1039 );
  1281. INSERT INTO #ConfigurationDefaults
  1282. VALUES ( 'open objects', 0, 1040 );
  1283. INSERT INTO #ConfigurationDefaults
  1284. VALUES ( 'optimize for ad hoc workloads', 0, 1041 );
  1285. INSERT INTO #ConfigurationDefaults
  1286. VALUES ( 'PH timeout (s)', 60, 1042 );
  1287. INSERT INTO #ConfigurationDefaults
  1288. VALUES ( 'precompute rank', 0, 1043 );
  1289. INSERT INTO #ConfigurationDefaults
  1290. VALUES ( 'priority boost', 0, 1044 );
  1291. INSERT INTO #ConfigurationDefaults
  1292. VALUES ( 'query governor cost limit', 0, 1045 );
  1293. INSERT INTO #ConfigurationDefaults
  1294. VALUES ( 'query wait (s)', -1, 1046 );
  1295. INSERT INTO #ConfigurationDefaults
  1296. VALUES ( 'recovery interval (min)', 0, 1047 );
  1297. INSERT INTO #ConfigurationDefaults
  1298. VALUES ( 'remote access', 1, 1048 );
  1299. INSERT INTO #ConfigurationDefaults
  1300. VALUES ( 'remote admin connections', 0, 1049 );
  1301. /* SQL Server 2012 changes a configuration default */
  1302. IF @@VERSION LIKE '%Microsoft SQL Server 2005%'
  1303. OR @@VERSION LIKE '%Microsoft SQL Server 2008%'
  1304. BEGIN
  1305. INSERT INTO #ConfigurationDefaults
  1306. VALUES ( 'remote login timeout (s)', 20, 1069 );
  1307. END
  1308. ELSE
  1309. BEGIN
  1310. INSERT INTO #ConfigurationDefaults
  1311. VALUES ( 'remote login timeout (s)', 10, 1069 );
  1312. END
  1313. INSERT INTO #ConfigurationDefaults
  1314. VALUES ( 'remote proc trans', 0, 1050 );
  1315. INSERT INTO #ConfigurationDefaults
  1316. VALUES ( 'remote query timeout (s)', 600, 1051 );
  1317. INSERT INTO #ConfigurationDefaults
  1318. VALUES ( 'Replication XPs', 0, 1052 );
  1319. INSERT INTO #ConfigurationDefaults
  1320. VALUES ( 'RPC parameter data validation', 0, 1053 );
  1321. INSERT INTO #ConfigurationDefaults
  1322. VALUES ( 'scan for startup procs', 0, 1054 );
  1323. INSERT INTO #ConfigurationDefaults
  1324. VALUES ( 'server trigger recursion', 1, 1055 );
  1325. INSERT INTO #ConfigurationDefaults
  1326. VALUES ( 'set working set size', 0, 1056 );
  1327. INSERT INTO #ConfigurationDefaults
  1328. VALUES ( 'show advanced options', 0, 1057 );
  1329. INSERT INTO #ConfigurationDefaults
  1330. VALUES ( 'SMO and DMO XPs', 1, 1058 );
  1331. INSERT INTO #ConfigurationDefaults
  1332. VALUES ( 'SQL Mail XPs', 0, 1059 );
  1333. INSERT INTO #ConfigurationDefaults
  1334. VALUES ( 'transform noise words', 0, 1060 );
  1335. INSERT INTO #ConfigurationDefaults
  1336. VALUES ( 'two digit year cutoff', 2049, 1061 );
  1337. INSERT INTO #ConfigurationDefaults
  1338. VALUES ( 'user connections', 0, 1062 );
  1339. INSERT INTO #ConfigurationDefaults
  1340. VALUES ( 'user options', 0, 1063 );
  1341. INSERT INTO #ConfigurationDefaults
  1342. VALUES ( 'Web Assistant Procedures', 0, 1064 );
  1343. INSERT INTO #ConfigurationDefaults
  1344. VALUES ( 'xp_cmdshell', 0, 1065 );
  1345.  
  1346.  
  1347. IF NOT EXISTS ( SELECT 1
  1348. FROM #SkipChecks
  1349. WHERE DatabaseName IS NULL AND CheckID = 22 )
  1350. BEGIN
  1351. INSERT INTO #BlitzResults
  1352. ( CheckID ,
  1353. Priority ,
  1354. FindingsGroup ,
  1355. Finding ,
  1356. URL ,
  1357. Details
  1358. )
  1359. SELECT cd.CheckID ,
  1360. 200 AS Priority ,
  1361. 'Non-Default Server Config' AS FindingsGroup ,
  1362. cr.name AS Finding ,
  1363. 'http://BrentOzar.com/go/conf' AS URL ,
  1364. ( 'This sp_configure option has been changed. Its default value is '
  1365. + COALESCE(CAST(cd.[DefaultValue] AS VARCHAR(100)),
  1366. '(unknown)')
  1367. + ' and it has been set to '
  1368. + CAST(cr.value_in_use AS VARCHAR(100))
  1369. + '.' ) AS Details
  1370. FROM sys.configurations cr
  1371. INNER JOIN #ConfigurationDefaults cd ON cd.name = cr.name
  1372. LEFT OUTER JOIN #ConfigurationDefaults cdUsed ON cdUsed.name = cr.name
  1373. AND cdUsed.DefaultValue = cr.value_in_use
  1374. WHERE cdUsed.name IS NULL;
  1375. END
  1376.  
  1377.  
  1378. IF NOT EXISTS ( SELECT 1
  1379. FROM #SkipChecks
  1380. WHERE DatabaseName IS NULL AND CheckID = 24 )
  1381. BEGIN
  1382. INSERT INTO #BlitzResults
  1383. ( CheckID ,
  1384. DatabaseName ,
  1385. Priority ,
  1386. FindingsGroup ,
  1387. Finding ,
  1388. URL ,
  1389. Details
  1390. )
  1391. SELECT DISTINCT
  1392. 24 AS CheckID ,
  1393. DB_NAME(database_id) AS DatabaseName ,
  1394. 170 AS Priority ,
  1395. 'File Configuration' AS FindingsGroup ,
  1396. 'System Database on C Drive' AS Finding ,
  1397. 'http://BrentOzar.com/go/cdrive' AS URL ,
  1398. ( 'The ' + DB_NAME(database_id)
  1399. + ' 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
  1400. FROM sys.master_files
  1401. WHERE UPPER(LEFT(physical_name, 1)) = 'C'
  1402. AND DB_NAME(database_id) IN ( 'master',
  1403. 'model', 'msdb' );
  1404. END
  1405.  
  1406.  
  1407. IF NOT EXISTS ( SELECT 1
  1408. FROM #SkipChecks
  1409. WHERE DatabaseName IS NULL AND CheckID = 25 )
  1410. BEGIN
  1411. INSERT INTO #BlitzResults
  1412. ( CheckID ,
  1413. DatabaseName ,
  1414. Priority ,
  1415. FindingsGroup ,
  1416. Finding ,
  1417. URL ,
  1418. Details
  1419. )
  1420. SELECT TOP 1
  1421. 25 AS CheckID ,
  1422. 'tempdb' ,
  1423. 170 AS Priority ,
  1424. 'File Configuration' AS FindingsGroup ,
  1425. 'TempDB on C Drive' AS Finding ,
  1426. 'http://BrentOzar.com/go/cdrive' AS URL ,
  1427. CASE WHEN growth > 0
  1428. 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.' )
  1429. 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.' )
  1430. END AS Details
  1431. FROM sys.master_files
  1432. WHERE UPPER(LEFT(physical_name, 1)) = 'C'
  1433. AND DB_NAME(database_id) = 'tempdb';
  1434. END
  1435.  
  1436.  
  1437. IF NOT EXISTS ( SELECT 1
  1438. FROM #SkipChecks
  1439. WHERE DatabaseName IS NULL AND CheckID = 26 )
  1440. BEGIN
  1441. INSERT INTO #BlitzResults
  1442. ( CheckID ,
  1443. DatabaseName ,
  1444. Priority ,
  1445. FindingsGroup ,
  1446. Finding ,
  1447. URL ,
  1448. Details
  1449. )
  1450. SELECT DISTINCT
  1451. 26 AS CheckID ,
  1452. DB_NAME(database_id) AS DatabaseName ,
  1453. 20 AS Priority ,
  1454. 'Reliability' AS FindingsGroup ,
  1455. 'User Databases on C Drive' AS Finding ,
  1456. 'http://BrentOzar.com/go/cdrive' AS URL ,
  1457. ( 'The ' + DB_NAME(database_id)
  1458. + ' 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
  1459. FROM sys.master_files
  1460. WHERE UPPER(LEFT(physical_name, 1)) = 'C'
  1461. AND DB_NAME(database_id) NOT IN ( 'master',
  1462. 'model', 'msdb',
  1463. 'tempdb' )
  1464. AND DB_NAME(database_id) NOT IN (
  1465. SELECT DISTINCT
  1466. DatabaseName
  1467. FROM #SkipChecks )
  1468. END
  1469.  
  1470.  
  1471. IF NOT EXISTS ( SELECT 1
  1472. FROM #SkipChecks
  1473. WHERE DatabaseName IS NULL AND CheckID = 27 )
  1474. BEGIN
  1475. INSERT INTO #BlitzResults
  1476. ( CheckID ,
  1477. DatabaseName ,
  1478. Priority ,
  1479. FindingsGroup ,
  1480. Finding ,
  1481. URL ,
  1482. Details
  1483. )
  1484. SELECT 27 AS CheckID ,
  1485. 'master' AS DatabaseName ,
  1486. 200 AS Priority ,
  1487. 'Informational' AS FindingsGroup ,
  1488. 'Tables in the Master Database' AS Finding ,
  1489. 'http://BrentOzar.com/go/mastuser' AS URL ,
  1490. ( 'The ' + name
  1491. + ' table in the master database was created by end users on '
  1492. + CAST(create_date AS VARCHAR(20))
  1493. + '. Tables in the master database may not be restored in the event of a disaster.' ) AS Details
  1494. FROM master.sys.tables
  1495. WHERE is_ms_shipped = 0;
  1496. END
  1497.  
  1498.  
  1499. IF NOT EXISTS ( SELECT 1
  1500. FROM #SkipChecks
  1501. WHERE DatabaseName IS NULL AND CheckID = 28 )
  1502. BEGIN
  1503. INSERT INTO #BlitzResults
  1504. ( CheckID ,
  1505. Priority ,
  1506. FindingsGroup ,
  1507. Finding ,
  1508. URL ,
  1509. Details
  1510. )
  1511. SELECT 28 AS CheckID ,
  1512. 200 AS Priority ,
  1513. 'Informational' AS FindingsGroup ,
  1514. 'Tables in the MSDB Database' AS Finding ,
  1515. 'http://BrentOzar.com/go/msdbuser' AS URL ,
  1516. ( 'The ' + name
  1517. + ' table in the msdb database was created by end users on '
  1518. + CAST(create_date AS VARCHAR(20))
  1519. + '. Tables in the msdb database may not be restored in the event of a disaster.' ) AS Details
  1520. FROM msdb.sys.tables
  1521. WHERE is_ms_shipped = 0 AND name NOT LIKE '%DTA_%';
  1522. END
  1523.  
  1524.  
  1525. IF NOT EXISTS ( SELECT 1
  1526. FROM #SkipChecks
  1527. WHERE DatabaseName IS NULL AND CheckID = 29 )
  1528. BEGIN
  1529. INSERT INTO #BlitzResults
  1530. ( CheckID ,
  1531. Priority ,
  1532. FindingsGroup ,
  1533. Finding ,
  1534. URL ,
  1535. Details
  1536. )
  1537. SELECT 29 AS CheckID ,
  1538. 200 AS Priority ,
  1539. 'Informational' AS FindingsGroup ,
  1540. 'Tables in the Model Database' AS Finding ,
  1541. 'http://BrentOzar.com/go/model' AS URL ,
  1542. ( 'The ' + name
  1543. + ' table in the model database was created by end users on '
  1544. + CAST(create_date AS VARCHAR(20))
  1545. + '. Tables in the model database are automatically copied into all new databases.' ) AS Details
  1546. FROM model.sys.tables
  1547. WHERE is_ms_shipped = 0;
  1548. END
  1549.  
  1550.  
  1551. IF NOT EXISTS ( SELECT 1
  1552. FROM #SkipChecks
  1553. WHERE DatabaseName IS NULL AND CheckID = 30 )
  1554. BEGIN
  1555. IF ( SELECT COUNT(*)
  1556. FROM msdb.dbo.sysalerts
  1557. WHERE severity BETWEEN 19 AND 25
  1558. ) < 7
  1559. INSERT INTO #BlitzResults
  1560. ( CheckID ,
  1561. Priority ,
  1562. FindingsGroup ,
  1563. Finding ,
  1564. URL ,
  1565. Details
  1566. )
  1567. SELECT 30 AS CheckID ,
  1568. 200 AS Priority ,
  1569. 'Monitoring' AS FindingsGroup ,
  1570. 'Not All Alerts Configured' AS Finding ,
  1571. 'http://BrentOzar.com/go/alert' AS URL ,
  1572. ( '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;
  1573. END
  1574.  
  1575.  
  1576.  
  1577. IF NOT EXISTS ( SELECT 1
  1578. FROM #SkipChecks
  1579. WHERE DatabaseName IS NULL AND CheckID = 59 )
  1580. BEGIN
  1581. IF EXISTS ( SELECT *
  1582. FROM msdb.dbo.sysalerts
  1583. WHERE enabled = 1
  1584. AND COALESCE(has_notification, 0) = 0
  1585. AND (job_id IS NULL OR job_id = 0x))
  1586. INSERT INTO #BlitzResults
  1587. ( CheckID ,
  1588. Priority ,
  1589. FindingsGroup ,
  1590. Finding ,
  1591. URL ,
  1592. Details
  1593. )
  1594. SELECT 59 AS CheckID ,
  1595. 200 AS Priority ,
  1596. 'Monitoring' AS FindingsGroup ,
  1597. 'Alerts Configured without Follow Up' AS Finding ,
  1598. 'http://BrentOzar.com/go/alert' AS URL ,
  1599. ( '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;
  1600. END
  1601.  
  1602. IF NOT EXISTS ( SELECT 1
  1603. FROM #SkipChecks
  1604. WHERE DatabaseName IS NULL AND CheckID = 96 )
  1605. BEGIN
  1606. IF NOT EXISTS ( SELECT *
  1607. FROM msdb.dbo.sysalerts
  1608. WHERE message_id IN ( 823, 824, 825 ) )
  1609. INSERT INTO #BlitzResults
  1610. ( CheckID ,
  1611. Priority ,
  1612. FindingsGroup ,
  1613. Finding ,
  1614. URL ,
  1615. Details
  1616. )
  1617. SELECT 96 AS CheckID ,
  1618. 200 AS Priority ,
  1619. 'Monitoring' AS FindingsGroup ,
  1620. 'No Alerts for Corruption' AS Finding ,
  1621. 'http://BrentOzar.com/go/alert' AS URL ,
  1622. ( '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;
  1623. END
  1624.  
  1625.  
  1626. IF NOT EXISTS ( SELECT 1
  1627. FROM #SkipChecks
  1628. WHERE DatabaseName IS NULL AND CheckID = 61 )
  1629. BEGIN
  1630. IF NOT EXISTS ( SELECT *
  1631. FROM msdb.dbo.sysalerts
  1632. WHERE severity BETWEEN 19 AND 25 )
  1633. INSERT INTO #BlitzResults
  1634. ( CheckID ,
  1635. Priority ,
  1636. FindingsGroup ,
  1637. Finding ,
  1638. URL ,
  1639. Details
  1640. )
  1641. SELECT 61 AS CheckID ,
  1642. 200 AS Priority ,
  1643. 'Monitoring' AS FindingsGroup ,
  1644. 'No Alerts for Sev 19-25' AS Finding ,
  1645. 'http://BrentOzar.com/go/alert' AS URL ,
  1646. ( '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;
  1647. END
  1648.  
  1649. --check for disabled alerts
  1650. IF NOT EXISTS ( SELECT 1
  1651. FROM #SkipChecks
  1652. WHERE DatabaseName IS NULL AND CheckID = 98 )
  1653. BEGIN
  1654. IF EXISTS ( SELECT name
  1655. FROM msdb.dbo.sysalerts
  1656. WHERE enabled = 0 )
  1657. INSERT INTO #BlitzResults
  1658. ( CheckID ,
  1659. Priority ,
  1660. FindingsGroup ,
  1661. Finding ,
  1662. URL ,
  1663. Details
  1664. )
  1665. SELECT 98 AS CheckID ,
  1666. 200 AS Priority ,
  1667. 'Monitoring' AS FindingsGroup ,
  1668. 'Alerts Disabled' AS Finding ,
  1669. 'http://www.BrentOzar.com/go/alerts/' AS URL ,
  1670. ( 'The following Alert is disabled, please review and enable if desired: '
  1671. + name ) AS Details
  1672. FROM msdb.dbo.sysalerts
  1673. WHERE enabled = 0
  1674. END
  1675.  
  1676.  
  1677. IF NOT EXISTS ( SELECT 1
  1678. FROM #SkipChecks
  1679. WHERE DatabaseName IS NULL AND CheckID = 31 )
  1680. BEGIN
  1681. IF NOT EXISTS ( SELECT *
  1682. FROM msdb.dbo.sysoperators
  1683. WHERE enabled = 1 )
  1684. INSERT INTO #BlitzResults
  1685. ( CheckID ,
  1686. Priority ,
  1687. FindingsGroup ,
  1688. Finding ,
  1689. URL ,
  1690. Details
  1691. )
  1692. SELECT 31 AS CheckID ,
  1693. 200 AS Priority ,
  1694. 'Monitoring' AS FindingsGroup ,
  1695. 'No Operators Configured/Enabled' AS Finding ,
  1696. 'http://BrentOzar.com/go/op' AS URL ,
  1697. ( '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;
  1698. END
  1699.  
  1700.  
  1701.  
  1702. IF NOT EXISTS ( SELECT 1
  1703. FROM #SkipChecks
  1704. WHERE DatabaseName IS NULL AND CheckID = 34 )
  1705. BEGIN
  1706. IF EXISTS ( SELECT *
  1707. FROM sys.all_objects
  1708. WHERE name = 'dm_db_mirroring_auto_page_repair' )
  1709. BEGIN
  1710. SET @StringToExecute = 'INSERT INTO #BlitzResults
  1711. (CheckID,
  1712. DatabaseName,
  1713. Priority,
  1714. FindingsGroup,
  1715. Finding,
  1716. URL,
  1717. Details)
  1718. SELECT DISTINCT
  1719. 34 AS CheckID ,
  1720. db.name ,
  1721. 1 AS Priority ,
  1722. ''Corruption'' AS FindingsGroup ,
  1723. ''Database Corruption Detected'' AS Finding ,
  1724. ''http://BrentOzar.com/go/repair'' AS URL ,
  1725. ( ''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
  1726. FROM (SELECT rp2.database_id, rp2.modification_time
  1727. FROM sys.dm_db_mirroring_auto_page_repair rp2
  1728. WHERE rp2.[database_id] not in (
  1729. SELECT db2.[database_id]
  1730. FROM sys.databases as db2
  1731. WHERE db2.[state] = 1
  1732. ) ) as rp
  1733. INNER JOIN master.sys.databases db ON rp.database_id = db.database_id
  1734. WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) ;'
  1735. EXECUTE(@StringToExecute)
  1736. END;
  1737. END
  1738.  
  1739. IF NOT EXISTS ( SELECT 1
  1740. FROM #SkipChecks
  1741. WHERE DatabaseName IS NULL AND CheckID = 89 )
  1742. BEGIN
  1743. IF EXISTS ( SELECT *
  1744. FROM sys.all_objects
  1745. WHERE name = 'dm_hadr_auto_page_repair' )
  1746. BEGIN
  1747. SET @StringToExecute = 'INSERT INTO #BlitzResults
  1748. (CheckID,
  1749. DatabaseName,
  1750. Priority,
  1751. FindingsGroup,
  1752. Finding,
  1753. URL,
  1754. Details)
  1755. SELECT DISTINCT
  1756. 89 AS CheckID ,
  1757. db.name ,
  1758. 1 AS Priority ,
  1759. ''Corruption'' AS FindingsGroup ,
  1760. ''Database Corruption Detected'' AS Finding ,
  1761. ''http://BrentOzar.com/go/repair'' AS URL ,
  1762. ( ''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
  1763. FROM sys.dm_hadr_auto_page_repair rp
  1764. INNER JOIN master.sys.databases db ON rp.database_id = db.database_id
  1765. WHERE rp.modification_time >= DATEADD(dd, -30, GETDATE()) ;'
  1766. EXECUTE(@StringToExecute)
  1767. END;
  1768. END
  1769.  
  1770.  
  1771. IF NOT EXISTS ( SELECT 1
  1772. FROM #SkipChecks
  1773. WHERE DatabaseName IS NULL AND CheckID = 90 )
  1774. BEGIN
  1775. IF EXISTS ( SELECT *
  1776. FROM msdb.sys.all_objects
  1777. WHERE name = 'suspect_pages' )
  1778. BEGIN
  1779. SET @StringToExecute = 'INSERT INTO #BlitzResults
  1780. (CheckID,
  1781. DatabaseName,
  1782. Priority,
  1783. FindingsGroup,
  1784. Finding,
  1785. URL,
  1786. Details)
  1787. SELECT DISTINCT
  1788. 90 AS CheckID ,
  1789. db.name ,
  1790. 1 AS Priority ,
  1791. ''Corruption'' AS FindingsGroup ,
  1792. ''Database Corruption Detected'' AS Finding ,
  1793. ''http://BrentOzar.com/go/repair'' AS URL ,
  1794. ( ''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
  1795. FROM msdb.dbo.suspect_pages sp
  1796. INNER JOIN master.sys.databases db ON sp.database_id = db.database_id
  1797. WHERE sp.last_update_date >= DATEADD(dd, -30, GETDATE()) ;'
  1798. EXECUTE(@StringToExecute)
  1799. END;
  1800. END
  1801.  
  1802.  
  1803. IF NOT EXISTS ( SELECT 1
  1804. FROM #SkipChecks
  1805. WHERE DatabaseName IS NULL AND CheckID = 36 )
  1806. BEGIN
  1807. INSERT INTO #BlitzResults
  1808. ( CheckID ,
  1809. Priority ,
  1810. FindingsGroup ,
  1811. Finding ,
  1812. URL ,
  1813. Details
  1814. )
  1815. SELECT DISTINCT
  1816. 36 AS CheckID ,
  1817. 150 AS Priority ,
  1818. 'Performance' AS FindingsGroup ,
  1819. 'Slow Storage Reads on Drive '
  1820. + UPPER(LEFT(mf.physical_name, 1)) AS Finding ,
  1821. 'http://BrentOzar.com/go/slow' AS URL ,
  1822. '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
  1823. FROM sys.dm_io_virtual_file_stats(NULL, NULL)
  1824. AS fs
  1825. INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id
  1826. AND fs.[file_id] = mf.[file_id]
  1827. WHERE ( io_stall_read_ms / ( 1.0 + num_of_reads ) ) > 200
  1828. AND num_of_reads > 100000;
  1829. END
  1830.  
  1831. IF NOT EXISTS ( SELECT 1
  1832. FROM #SkipChecks
  1833. WHERE DatabaseName IS NULL AND CheckID = 37 )
  1834. BEGIN
  1835. INSERT INTO #BlitzResults
  1836. ( CheckID ,
  1837. Priority ,
  1838. FindingsGroup ,
  1839. Finding ,
  1840. URL ,
  1841. Details
  1842. )
  1843. SELECT DISTINCT
  1844. 37 AS CheckID ,
  1845. 150 AS Priority ,
  1846. 'Performance' AS FindingsGroup ,
  1847. 'Slow Storage Writes on Drive '
  1848. + UPPER(LEFT(mf.physical_name, 1)) AS Finding ,
  1849. 'http://BrentOzar.com/go/slow' AS URL ,
  1850. '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
  1851. FROM sys.dm_io_virtual_file_stats(NULL, NULL)
  1852. AS fs
  1853. INNER JOIN sys.master_files AS mf ON fs.database_id = mf.database_id
  1854. AND fs.[file_id] = mf.[file_id]
  1855. WHERE ( io_stall_write_ms / ( 1.0
  1856. + num_of_writes ) ) > 100
  1857. AND num_of_writes > 100000;
  1858. END
  1859.  
  1860. IF NOT EXISTS ( SELECT 1
  1861. FROM #SkipChecks
  1862. WHERE DatabaseName IS NULL AND CheckID = 40 )
  1863. BEGIN
  1864. IF ( SELECT COUNT(*)
  1865. FROM tempdb.sys.database_files
  1866. WHERE type_desc = 'ROWS'
  1867. ) = 1
  1868. BEGIN
  1869. INSERT INTO #BlitzResults
  1870. ( CheckID ,
  1871. DatabaseName ,
  1872. Priority ,
  1873. FindingsGroup ,
  1874. Finding ,
  1875. URL ,
  1876. Details
  1877. )
  1878. VALUES ( 40 ,
  1879. 'tempdb' ,
  1880. 170 ,
  1881. 'File Configuration' ,
  1882. 'TempDB Only Has 1 Data File' ,
  1883. 'http://BrentOzar.com/go/tempdb' ,
  1884. 'TempDB is only configured with one data file. More data files are usually required to alleviate SGAM contention.'
  1885. );
  1886. END;
  1887. END
  1888.  
  1889.  
  1890. IF NOT EXISTS ( SELECT 1
  1891. FROM #SkipChecks
  1892. WHERE DatabaseName IS NULL AND CheckID = 44 )
  1893. BEGIN
  1894. INSERT INTO #BlitzResults
  1895. ( CheckID ,
  1896. Priority ,
  1897. FindingsGroup ,
  1898. Finding ,
  1899. URL ,
  1900. Details
  1901. )
  1902. SELECT 44 AS CheckID ,
  1903. 150 AS Priority ,
  1904. 'Performance' AS FindingsGroup ,
  1905. 'Queries Forcing Order Hints' AS Finding ,
  1906. 'http://BrentOzar.com/go/hints' AS URL ,
  1907. CAST(occurrence AS VARCHAR(10))
  1908. + ' 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
  1909. FROM sys.dm_exec_query_optimizer_info
  1910. WHERE counter = 'order hint'
  1911. AND occurrence > 1000
  1912. END
  1913.  
  1914. IF NOT EXISTS ( SELECT 1
  1915. FROM #SkipChecks
  1916. WHERE DatabaseName IS NULL AND CheckID = 45 )
  1917. BEGIN
  1918. INSERT INTO #BlitzResults
  1919. ( CheckID ,
  1920. Priority ,
  1921. FindingsGroup ,
  1922. Finding ,
  1923. URL ,
  1924. Details
  1925. )
  1926. SELECT 45 AS CheckID ,
  1927. 150 AS Priority ,
  1928. 'Performance' AS FindingsGroup ,
  1929. 'Queries Forcing Join Hints' AS Finding ,
  1930. 'http://BrentOzar.com/go/hints' AS URL ,
  1931. CAST(occurrence AS VARCHAR(10))
  1932. + ' 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
  1933. FROM sys.dm_exec_query_optimizer_info
  1934. WHERE counter = 'join hint'
  1935. AND occurrence > 1000
  1936. END
  1937.  
  1938. IF NOT EXISTS ( SELECT 1
  1939. FROM #SkipChecks
  1940. WHERE DatabaseName IS NULL AND CheckID = 49 )
  1941. BEGIN
  1942. INSERT INTO #BlitzResults
  1943. ( CheckID ,
  1944. Priority ,
  1945. FindingsGroup ,
  1946. Finding ,
  1947. URL ,
  1948. Details
  1949. )
  1950. SELECT DISTINCT
  1951. 49 AS CheckID ,
  1952. 200 AS Priority ,
  1953. 'Informational' AS FindingsGroup ,
  1954. 'Linked Server Configured' AS Finding ,
  1955. 'http://BrentOzar.com/go/link' AS URL ,
  1956. +CASE WHEN l.remote_name = 'sa'
  1957. THEN s.data_source
  1958. + ' 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.'
  1959. ELSE s.data_source
  1960. + ' 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.'
  1961. END AS Details
  1962. FROM sys.servers s
  1963. INNER JOIN sys.linked_logins l ON s.server_id = l.server_id
  1964. WHERE s.is_linked = 1
  1965. END
  1966.  
  1967. IF NOT EXISTS ( SELECT 1
  1968. FROM #SkipChecks
  1969. WHERE DatabaseName IS NULL AND CheckID = 50 )
  1970. BEGIN
  1971. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  1972. AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
  1973. BEGIN
  1974. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  1975. SELECT 50 AS CheckID ,
  1976. 100 AS Priority ,
  1977. ''Performance'' AS FindingsGroup ,
  1978. ''Max Memory Set Too High'' AS Finding ,
  1979. ''http://BrentOzar.com/go/max'' AS URL ,
  1980. ''SQL Server max memory is set to ''
  1981. + CAST(c.value_in_use AS VARCHAR(20))
  1982. + '' megabytes, but the server only has ''
  1983. + CAST(( CAST(m.total_physical_memory_kb AS BIGINT) / 1024 ) AS VARCHAR(20))
  1984. + '' megabytes. SQL Server may drain the system dry of memory, and under certain conditions, this can cause Windows to swap to disk.'' AS Details
  1985. FROM sys.dm_os_sys_memory m
  1986. INNER JOIN sys.configurations c ON c.name = ''max server memory (MB)''
  1987. WHERE CAST(m.total_physical_memory_kb AS BIGINT) < ( CAST(c.value_in_use AS BIGINT) * 1024 )'
  1988. EXECUTE(@StringToExecute)
  1989. END;
  1990. END
  1991.  
  1992. IF NOT EXISTS ( SELECT 1
  1993. FROM #SkipChecks
  1994. WHERE DatabaseName IS NULL AND CheckID = 51 )
  1995. BEGIN
  1996. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  1997. AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
  1998. BEGIN
  1999. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2000. SELECT 51 AS CheckID ,
  2001. 1 AS Priority ,
  2002. ''Performance'' AS FindingsGroup ,
  2003. ''Memory Dangerously Low'' AS Finding ,
  2004. ''http://BrentOzar.com/go/max'' AS URL ,
  2005. ''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))
  2006. + '' megabytes are available. As the server runs out of memory, there is danger of swapping to disk, which will kill performance.'' AS Details
  2007. FROM sys.dm_os_sys_memory m
  2008. WHERE CAST(m.available_physical_memory_kb AS BIGINT) < 262144'
  2009. EXECUTE(@StringToExecute)
  2010. END;
  2011. END
  2012.  
  2013. IF NOT EXISTS ( SELECT 1
  2014. FROM #SkipChecks
  2015. WHERE DatabaseName IS NULL AND CheckID = 159 )
  2016. BEGIN
  2017. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  2018. AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
  2019. BEGIN
  2020. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2021. SELECT DISTINCT 159 AS CheckID ,
  2022. 1 AS Priority ,
  2023. ''Performance'' AS FindingsGroup ,
  2024. ''Memory Dangerously Low in NUMA Nodes'' AS Finding ,
  2025. ''http://BrentOzar.com/go/max'' AS URL ,
  2026. ''At least one NUMA node is reporting THREAD_RESOURCES_LOW in sys.dm_os_nodes and can no longer create threads.'' AS Details
  2027. FROM sys.dm_os_nodes m
  2028. WHERE node_state_desc LIKE ''%THREAD_RESOURCES_LOW%'''
  2029. EXECUTE(@StringToExecute)
  2030. END;
  2031. END
  2032.  
  2033. IF NOT EXISTS ( SELECT 1
  2034. FROM #SkipChecks
  2035. WHERE DatabaseName IS NULL AND CheckID = 53 )
  2036. BEGIN
  2037. INSERT INTO #BlitzResults
  2038. ( CheckID ,
  2039. Priority ,
  2040. FindingsGroup ,
  2041. Finding ,
  2042. URL ,
  2043. Details
  2044. )
  2045. SELECT TOP 1
  2046. 53 AS CheckID ,
  2047. 200 AS Priority ,
  2048. 'Informational' AS FindingsGroup ,
  2049. 'Cluster Node' AS Finding ,
  2050. 'http://BrentOzar.com/go/node' AS URL ,
  2051. 'This is a node in a cluster.' AS Details
  2052. FROM sys.dm_os_cluster_nodes
  2053. END
  2054.  
  2055. IF NOT EXISTS ( SELECT 1
  2056. FROM #SkipChecks
  2057. WHERE DatabaseName IS NULL AND CheckID = 55 )
  2058. BEGIN
  2059. INSERT INTO #BlitzResults
  2060. ( CheckID ,
  2061. DatabaseName ,
  2062. Priority ,
  2063. FindingsGroup ,
  2064. Finding ,
  2065. URL ,
  2066. Details
  2067. )
  2068. SELECT 55 AS CheckID ,
  2069. [name] AS DatabaseName ,
  2070. 200 AS Priority ,
  2071. 'Security' AS FindingsGroup ,
  2072. 'Database Owner <> SA' AS Finding ,
  2073. 'http://BrentOzar.com/go/owndb' AS URL ,
  2074. ( 'Database name: ' + [name] + ' '
  2075. + 'Owner name: ' + SUSER_SNAME(owner_sid) ) AS Details
  2076. FROM sys.databases
  2077. WHERE SUSER_SNAME(owner_sid) <> SUSER_SNAME(0x01)
  2078. AND name NOT IN ( SELECT DISTINCT
  2079. DatabaseName
  2080. FROM #SkipChecks
  2081. WHERE CheckID IS NULL);
  2082. END
  2083.  
  2084. IF NOT EXISTS ( SELECT 1
  2085. FROM #SkipChecks
  2086. WHERE DatabaseName IS NULL AND CheckID = 57 )
  2087. BEGIN
  2088. INSERT INTO #BlitzResults
  2089. ( CheckID ,
  2090. Priority ,
  2091. FindingsGroup ,
  2092. Finding ,
  2093. URL ,
  2094. Details
  2095. )
  2096. SELECT 57 AS CheckID ,
  2097. 10 AS Priority ,
  2098. 'Security' AS FindingsGroup ,
  2099. 'SQL Agent Job Runs at Startup' AS Finding ,
  2100. 'http://BrentOzar.com/go/startup' AS URL ,
  2101. ( 'Job [' + j.name
  2102. + '] 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
  2103. FROM msdb.dbo.sysschedules sched
  2104. JOIN msdb.dbo.sysjobschedules jsched ON sched.schedule_id = jsched.schedule_id
  2105. JOIN msdb.dbo.sysjobs j ON jsched.job_id = j.job_id
  2106. WHERE sched.freq_type = 64;
  2107. END
  2108.  
  2109.  
  2110.  
  2111. IF NOT EXISTS ( SELECT 1
  2112. FROM #SkipChecks
  2113. WHERE DatabaseName IS NULL AND CheckID = 97 )
  2114. BEGIN
  2115. INSERT INTO #BlitzResults
  2116. ( CheckID ,
  2117. Priority ,
  2118. FindingsGroup ,
  2119. Finding ,
  2120. URL ,
  2121. Details
  2122. )
  2123. SELECT 97 AS CheckID ,
  2124. 100 AS Priority ,
  2125. 'Performance' AS FindingsGroup ,
  2126. 'Unusual SQL Server Edition' AS Finding ,
  2127. 'http://BrentOzar.com/go/workgroup' AS URL ,
  2128. ( 'This server is using '
  2129. + CAST(SERVERPROPERTY('edition') AS VARCHAR(100))
  2130. + ', which is capped at low amounts of CPU and memory.' ) AS Details
  2131. WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Standard%'
  2132. AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Enterprise%'
  2133. AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Data Center%'
  2134. AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Developer%'
  2135. AND CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%Business Intelligence%'
  2136. END
  2137.  
  2138. IF NOT EXISTS ( SELECT 1
  2139. FROM #SkipChecks
  2140. WHERE DatabaseName IS NULL AND CheckID = 154 )
  2141. BEGIN
  2142. INSERT INTO #BlitzResults
  2143. ( CheckID ,
  2144. Priority ,
  2145. FindingsGroup ,
  2146. Finding ,
  2147. URL ,
  2148. Details
  2149. )
  2150. SELECT 154 AS CheckID ,
  2151. 10 AS Priority ,
  2152. 'Performance' AS FindingsGroup ,
  2153. '32-bit SQL Server Installed' AS Finding ,
  2154. 'http://BrentOzar.com/go/32bit' AS URL ,
  2155. ( '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
  2156. WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) NOT LIKE '%64%'
  2157. END
  2158.  
  2159. IF NOT EXISTS ( SELECT 1
  2160. FROM #SkipChecks
  2161. WHERE DatabaseName IS NULL AND CheckID = 62 )
  2162. BEGIN
  2163. INSERT INTO #BlitzResults
  2164. ( CheckID ,
  2165. DatabaseName ,
  2166. Priority ,
  2167. FindingsGroup ,
  2168. Finding ,
  2169. URL ,
  2170. Details
  2171. )
  2172. SELECT 62 AS CheckID ,
  2173. [name] AS DatabaseName ,
  2174. 200 AS Priority ,
  2175. 'Performance' AS FindingsGroup ,
  2176. 'Old Compatibility Level' AS Finding ,
  2177. 'http://BrentOzar.com/go/compatlevel' AS URL ,
  2178. ( 'Database ' + [name]
  2179. + ' is compatibility level '
  2180. + CAST(compatibility_level AS VARCHAR(20))
  2181. + ', which may cause unwanted results when trying to run queries that have newer T-SQL features.' ) AS Details
  2182. FROM sys.databases
  2183. WHERE name NOT IN ( SELECT DISTINCT
  2184. DatabaseName
  2185. FROM #SkipChecks
  2186. WHERE CheckID IS NULL)
  2187. AND compatibility_level <= 90
  2188. END
  2189.  
  2190. IF NOT EXISTS ( SELECT 1
  2191. FROM #SkipChecks
  2192. WHERE DatabaseName IS NULL AND CheckID = 94 )
  2193. BEGIN
  2194. INSERT INTO #BlitzResults
  2195. ( CheckID ,
  2196. Priority ,
  2197. FindingsGroup ,
  2198. Finding ,
  2199. URL ,
  2200. Details
  2201. )
  2202. SELECT 94 AS CheckID ,
  2203. 200 AS [Priority] ,
  2204. 'Monitoring' AS FindingsGroup ,
  2205. 'Agent Jobs Without Failure Emails' AS Finding ,
  2206. 'http://BrentOzar.com/go/alerts' AS URL ,
  2207. 'The job ' + [name]
  2208. + ' has not been set up to notify an operator if it fails.' AS Details
  2209. FROM msdb.[dbo].[sysjobs] j
  2210. INNER JOIN ( SELECT DISTINCT
  2211. [job_id]
  2212. FROM [msdb].[dbo].[sysjobschedules]
  2213. WHERE next_run_date > 0
  2214. ) s ON j.job_id = s.job_id
  2215. WHERE j.enabled = 1
  2216. AND j.notify_email_operator_id = 0
  2217. AND j.notify_netsend_operator_id = 0
  2218. AND j.notify_page_operator_id = 0
  2219. AND j.category_id <> 100 /* Exclude SSRS category */
  2220. END
  2221.  
  2222.  
  2223. IF EXISTS ( SELECT 1
  2224. FROM sys.configurations
  2225. WHERE name = 'remote admin connections'
  2226. AND value_in_use = 0 )
  2227. AND NOT EXISTS ( SELECT 1
  2228. FROM #SkipChecks
  2229. WHERE DatabaseName IS NULL AND CheckID = 100 )
  2230. BEGIN
  2231. INSERT INTO #BlitzResults
  2232. ( CheckID ,
  2233. Priority ,
  2234. FindingsGroup ,
  2235. Finding ,
  2236. URL ,
  2237. Details
  2238. )
  2239. SELECT 100 AS CheckID ,
  2240. 50 AS Priority ,
  2241. 'Reliability' AS FindingGroup ,
  2242. 'Remote DAC Disabled' AS Finding ,
  2243. 'http://BrentOzar.com/go/dac' AS URL ,
  2244. 'Remote access to the Dedicated Admin Connection (DAC) is not enabled. The DAC can make remote troubleshooting much easier when SQL Server is unresponsive.'
  2245. END
  2246.  
  2247.  
  2248. IF EXISTS ( SELECT *
  2249. FROM sys.dm_os_schedulers
  2250. WHERE is_online = 0 )
  2251. AND NOT EXISTS ( SELECT 1
  2252. FROM #SkipChecks
  2253. WHERE DatabaseName IS NULL AND CheckID = 101 )
  2254. BEGIN
  2255. INSERT INTO #BlitzResults
  2256. ( CheckID ,
  2257. Priority ,
  2258. FindingsGroup ,
  2259. Finding ,
  2260. URL ,
  2261. Details
  2262. )
  2263. SELECT 101 AS CheckID ,
  2264. 50 AS Priority ,
  2265. 'Performance' AS FindingGroup ,
  2266. 'CPU Schedulers Offline' AS Finding ,
  2267. 'http://BrentOzar.com/go/schedulers' AS URL ,
  2268. 'Some CPU cores are not accessible to SQL Server due to affinity masking or licensing problems.'
  2269. END
  2270.  
  2271.  
  2272. IF NOT EXISTS ( SELECT 1
  2273. FROM #SkipChecks
  2274. WHERE DatabaseName IS NULL AND CheckID = 110 )
  2275. AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_os_memory_nodes')
  2276. BEGIN
  2277. SET @StringToExecute = 'IF EXISTS (SELECT *
  2278. FROM sys.dm_os_nodes n
  2279. INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id
  2280. WHERE n.node_state_desc = ''OFFLINE'')
  2281. INSERT INTO #BlitzResults
  2282. ( CheckID ,
  2283. Priority ,
  2284. FindingsGroup ,
  2285. Finding ,
  2286. URL ,
  2287. Details
  2288. )
  2289. SELECT 110 AS CheckID ,
  2290. 50 AS Priority ,
  2291. ''Performance'' AS FindingGroup ,
  2292. ''Memory Nodes Offline'' AS Finding ,
  2293. ''http://BrentOzar.com/go/schedulers'' AS URL ,
  2294. ''Due to affinity masking or licensing problems, some of the memory may not be available.''';
  2295. EXECUTE(@StringToExecute);
  2296. END
  2297.  
  2298.  
  2299. IF EXISTS ( SELECT *
  2300. FROM sys.databases
  2301. WHERE state > 1 )
  2302. AND NOT EXISTS ( SELECT 1
  2303. FROM #SkipChecks
  2304. WHERE DatabaseName IS NULL AND CheckID = 102 )
  2305. BEGIN
  2306. INSERT INTO #BlitzResults
  2307. ( CheckID ,
  2308. DatabaseName ,
  2309. Priority ,
  2310. FindingsGroup ,
  2311. Finding ,
  2312. URL ,
  2313. Details
  2314. )
  2315. SELECT 102 AS CheckID ,
  2316. [name] ,
  2317. 20 AS Priority ,
  2318. 'Reliability' AS FindingGroup ,
  2319. 'Unusual Database State: ' + [state_desc] AS Finding ,
  2320. 'http://BrentOzar.com/go/repair' AS URL ,
  2321. 'This database may not be online.'
  2322. FROM sys.databases
  2323. WHERE state > 1
  2324. END
  2325.  
  2326. IF EXISTS ( SELECT *
  2327. FROM master.sys.extended_procedures )
  2328. AND NOT EXISTS ( SELECT 1
  2329. FROM #SkipChecks
  2330. WHERE DatabaseName IS NULL AND CheckID = 105 )
  2331. BEGIN
  2332. INSERT INTO #BlitzResults
  2333. ( CheckID ,
  2334. DatabaseName ,
  2335. Priority ,
  2336. FindingsGroup ,
  2337. Finding ,
  2338. URL ,
  2339. Details
  2340. )
  2341. SELECT 105 AS CheckID ,
  2342. 'master' ,
  2343. 50 AS Priority ,
  2344. 'Reliability' AS FindingGroup ,
  2345. 'Extended Stored Procedures in Master' AS Finding ,
  2346. 'http://BrentOzar.com/go/clr' AS URL ,
  2347. 'The [' + name
  2348. + '] 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.'
  2349. FROM master.sys.extended_procedures
  2350. END
  2351.  
  2352.  
  2353.  
  2354. IF NOT EXISTS ( SELECT 1
  2355. FROM #SkipChecks
  2356. WHERE DatabaseName IS NULL AND CheckID = 107 )
  2357. BEGIN
  2358. INSERT INTO #BlitzResults
  2359. ( CheckID ,
  2360. Priority ,
  2361. FindingsGroup ,
  2362. Finding ,
  2363. URL ,
  2364. Details
  2365. )
  2366. SELECT 107 AS CheckID ,
  2367. 50 AS Priority ,
  2368. 'Performance' AS FindingGroup ,
  2369. 'Poison Wait Detected: THREADPOOL' AS Finding ,
  2370. 'http://BrentOzar.com/go/poison' AS URL ,
  2371. CAST(SUM([wait_time_ms]) / 10000 / 1000 / 60 / 60 / 24 AS VARCHAR) + CAST(CONVERT(TIME, DATEADD(ms, SUM([wait_time_ms] / 10000 % 1000), DATEADD(ss, SUM([wait_time_ms] / 10000000), 0))) AS VARCHAR) + ' of this wait have been recorded. This wait often indicates killer performance problems.'
  2372. FROM sys.[dm_os_wait_stats]
  2373. WHERE wait_type = 'THREADPOOL'
  2374. GROUP BY wait_type
  2375. HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
  2376. AND SUM([wait_time_ms]) > 60000
  2377. END
  2378.  
  2379. IF NOT EXISTS ( SELECT 1
  2380. FROM #SkipChecks
  2381. WHERE DatabaseName IS NULL AND CheckID = 108 )
  2382. BEGIN
  2383. INSERT INTO #BlitzResults
  2384. ( CheckID ,
  2385. Priority ,
  2386. FindingsGroup ,
  2387. Finding ,
  2388. URL ,
  2389. Details
  2390. )
  2391. SELECT 108 AS CheckID ,
  2392. 50 AS Priority ,
  2393. 'Performance' AS FindingGroup ,
  2394. 'Poison Wait Detected: RESOURCE_SEMAPHORE' AS Finding ,
  2395. 'http://BrentOzar.com/go/poison' AS URL ,
  2396. CAST(SUM([wait_time_ms]) / 10000 / 1000 / 60 / 60 / 24 AS VARCHAR) + CAST(CONVERT(TIME, DATEADD(ms, SUM([wait_time_ms] / 10000 % 1000), DATEADD(ss, SUM([wait_time_ms] / 10000000), 0))) AS VARCHAR) + ' of this wait have been recorded. This wait often indicates killer performance problems.'
  2397. FROM sys.[dm_os_wait_stats]
  2398. WHERE wait_type = 'RESOURCE_SEMAPHORE'
  2399. GROUP BY wait_type
  2400. HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
  2401. AND SUM([wait_time_ms]) > 60000
  2402. END
  2403.  
  2404.  
  2405. IF NOT EXISTS ( SELECT 1
  2406. FROM #SkipChecks
  2407. WHERE DatabaseName IS NULL AND CheckID = 109 )
  2408. BEGIN
  2409. INSERT INTO #BlitzResults
  2410. ( CheckID ,
  2411. Priority ,
  2412. FindingsGroup ,
  2413. Finding ,
  2414. URL ,
  2415. Details
  2416. )
  2417. SELECT 109 AS CheckID ,
  2418. 50 AS Priority ,
  2419. 'Performance' AS FindingGroup ,
  2420. 'Poison Wait Detected: RESOURCE_SEMAPHORE_QUERY_COMPILE' AS Finding ,
  2421. 'http://BrentOzar.com/go/poison' AS URL ,
  2422. CAST(SUM([wait_time_ms]) / 10000 / 1000 / 60 / 60 / 24 AS VARCHAR) + CAST(CONVERT(TIME, DATEADD(ms, SUM([wait_time_ms] / 10000 % 1000), DATEADD(ss, SUM([wait_time_ms] / 10000000), 0))) AS VARCHAR) + ' of this wait have been recorded. This wait often indicates killer performance problems.'
  2423. FROM sys.[dm_os_wait_stats]
  2424. WHERE wait_type = 'RESOURCE_SEMAPHORE_QUERY_COMPILE'
  2425. GROUP BY wait_type
  2426. HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
  2427. AND SUM([wait_time_ms]) > 60000
  2428. END
  2429.  
  2430.  
  2431. IF NOT EXISTS ( SELECT 1
  2432. FROM #SkipChecks
  2433. WHERE DatabaseName IS NULL AND CheckID = 121 )
  2434. BEGIN
  2435. INSERT INTO #BlitzResults
  2436. ( CheckID ,
  2437. Priority ,
  2438. FindingsGroup ,
  2439. Finding ,
  2440. URL ,
  2441. Details
  2442. )
  2443. SELECT 121 AS CheckID ,
  2444. 50 AS Priority ,
  2445. 'Performance' AS FindingGroup ,
  2446. 'Poison Wait Detected: Serializable Locking' AS Finding ,
  2447. 'http://BrentOzar.com/go/serializable' AS URL ,
  2448. CAST(SUM([wait_time_ms]) / 10000 / 1000 / 60 / 60 / 24 AS VARCHAR) + CAST(CONVERT(TIME, DATEADD(ms, SUM([wait_time_ms] / 10000 % 1000), DATEADD(ss, SUM([wait_time_ms] / 10000000), 0))) AS VARCHAR) + ' of LCK_R% waits have been recorded. This wait often indicates killer performance problems.'
  2449. FROM sys.[dm_os_wait_stats]
  2450. WHERE wait_type LIKE '%LCK%R%'
  2451. HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
  2452. AND SUM([wait_time_ms]) > 60000
  2453. END
  2454.  
  2455.  
  2456.  
  2457.  
  2458. IF @ProductVersionMajor >= 11 AND NOT EXISTS ( SELECT 1
  2459. FROM #SkipChecks
  2460. WHERE DatabaseName IS NULL AND CheckID = 162 )
  2461. BEGIN
  2462. INSERT INTO #BlitzResults
  2463. ( CheckID ,
  2464. Priority ,
  2465. FindingsGroup ,
  2466. Finding ,
  2467. URL ,
  2468. Details
  2469. )
  2470. SELECT 162 AS CheckID ,
  2471. 50 AS Priority ,
  2472. 'Performance' AS FindingGroup ,
  2473. 'Poison Wait Detected: CMEMTHREAD & NUMA' AS Finding ,
  2474. 'http://BrentOzar.com/go/poison' AS URL ,
  2475. CAST(SUM([wait_time_ms]) / 10000 / 1000 / 60 / 60 / 24 AS VARCHAR) + CAST(CONVERT(TIME, DATEADD(ms, SUM([wait_time_ms] / 10000 % 1000), DATEADD(ss, SUM([wait_time_ms] / 10000000), 0))) AS VARCHAR) + ' 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.'
  2476. FROM sys.dm_os_nodes n
  2477. INNER JOIN sys.[dm_os_wait_stats] w ON w.wait_type = 'CMEMTHREAD'
  2478. WHERE n.node_id = 0 AND n.online_scheduler_count >= 8
  2479. GROUP BY w.wait_type
  2480. HAVING SUM([wait_time_ms]) > (SELECT 5000 * datediff(HH,create_date,CURRENT_TIMESTAMP) AS hours_since_startup FROM sys.databases WHERE name='tempdb')
  2481. AND SUM([wait_time_ms]) > 60000;
  2482. END
  2483.  
  2484.  
  2485.  
  2486.  
  2487. IF NOT EXISTS ( SELECT 1
  2488. FROM #SkipChecks
  2489. WHERE DatabaseName IS NULL AND CheckID = 111 )
  2490. BEGIN
  2491. INSERT INTO #BlitzResults
  2492. ( CheckID ,
  2493. Priority ,
  2494. FindingsGroup ,
  2495. Finding ,
  2496. DatabaseName ,
  2497. URL ,
  2498. Details
  2499. )
  2500. SELECT 111 AS CheckID ,
  2501. 50 AS Priority ,
  2502. 'Reliability' AS FindingGroup ,
  2503. 'Possibly Broken Log Shipping' AS Finding ,
  2504. d.[name] ,
  2505. 'http://BrentOzar.com/go/shipping' AS URL ,
  2506. 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.'
  2507. FROM [master].sys.databases d
  2508. INNER JOIN [master].sys.database_mirroring dm ON d.database_id = dm.database_id
  2509. AND dm.mirroring_role IS NULL
  2510. WHERE ( d.[state] = 1
  2511. OR (d.[state] = 0 AND d.[is_in_standby] = 1) )
  2512. AND NOT EXISTS(SELECT * FROM msdb.dbo.restorehistory rh
  2513. INNER JOIN msdb.dbo.backupset bs ON rh.backup_set_id = bs.backup_set_id
  2514. WHERE d.[name] COLLATE SQL_Latin1_General_CP1_CI_AS = rh.destination_database_name COLLATE SQL_Latin1_General_CP1_CI_AS
  2515. AND rh.restore_date >= DATEADD(dd, -2, GETDATE()))
  2516.  
  2517. END
  2518.  
  2519.  
  2520. IF NOT EXISTS ( SELECT 1
  2521. FROM #SkipChecks
  2522. WHERE DatabaseName IS NULL AND CheckID = 112 )
  2523. AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'change_tracking_databases')
  2524. BEGIN
  2525. SET @StringToExecute = 'INSERT INTO #BlitzResults
  2526. (CheckID,
  2527. Priority,
  2528. FindingsGroup,
  2529. Finding,
  2530. URL,
  2531. Details)
  2532. SELECT 112 AS CheckID,
  2533. 100 AS Priority,
  2534. ''Performance'' AS FindingsGroup,
  2535. ''Change Tracking Enabled'' AS Finding,
  2536. ''http://BrentOzar.com/go/tracking'' AS URL,
  2537. ( 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';
  2538. EXECUTE(@StringToExecute);
  2539. END
  2540.  
  2541.  
  2542. IF NOT EXISTS ( SELECT 1
  2543. FROM #SkipChecks
  2544. WHERE DatabaseName IS NULL AND CheckID = 116 )
  2545. AND EXISTS (SELECT * FROM msdb.sys.all_columns WHERE name = 'compressed_backup_size')
  2546. BEGIN
  2547. SET @StringToExecute = 'INSERT INTO #BlitzResults
  2548. ( CheckID ,
  2549. Priority ,
  2550. FindingsGroup ,
  2551. Finding ,
  2552. URL ,
  2553. Details
  2554. )
  2555. SELECT 116 AS CheckID ,
  2556. 200 AS Priority ,
  2557. ''Informational'' AS FindingGroup ,
  2558. ''Backup Compression Default Off'' AS Finding ,
  2559. ''http://BrentOzar.com/go/backup'' AS URL ,
  2560. ''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.''
  2561. FROM sys.configurations
  2562. WHERE configuration_id = 1579 AND CAST(value_in_use AS INT) = 0
  2563. AND EXISTS (SELECT * FROM msdb.dbo.backupset WHERE backup_size = compressed_backup_size AND type = ''D'' AND backup_finish_date >= DATEADD(DD, -14, GETDATE()));'
  2564. EXECUTE(@StringToExecute);
  2565. END
  2566.  
  2567. IF NOT EXISTS ( SELECT 1
  2568. FROM #SkipChecks
  2569. WHERE DatabaseName IS NULL AND CheckID = 117 )
  2570. AND EXISTS (SELECT * FROM master.sys.all_objects WHERE name = 'dm_exec_query_resource_semaphores')
  2571. BEGIN
  2572. SET @StringToExecute = 'IF 0 < (SELECT SUM([forced_grant_count]) FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL)
  2573. INSERT INTO #BlitzResults
  2574. (CheckID,
  2575. Priority,
  2576. FindingsGroup,
  2577. Finding,
  2578. URL,
  2579. Details)
  2580. SELECT 117 AS CheckID,
  2581. 100 AS Priority,
  2582. ''Performance'' AS FindingsGroup,
  2583. ''Memory Pressure Affecting Queries'' AS Finding,
  2584. ''http://BrentOzar.com/go/grants'' AS URL,
  2585. 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.''
  2586. FROM sys.dm_exec_query_resource_semaphores WHERE [forced_grant_count] IS NOT NULL;'
  2587. EXECUTE(@StringToExecute);
  2588. END
  2589.  
  2590.  
  2591.  
  2592. IF NOT EXISTS ( SELECT 1
  2593. FROM #SkipChecks
  2594. WHERE DatabaseName IS NULL AND CheckID = 124 )
  2595. BEGIN
  2596. INSERT INTO #BlitzResults
  2597. (CheckID,
  2598. Priority,
  2599. FindingsGroup,
  2600. Finding,
  2601. URL,
  2602. Details)
  2603. SELECT 124, 150, 'Performance', 'Deadlocks Happening Daily', 'http://BrentOzar.com/go/deadlocks',
  2604. CAST(p.cntr_value AS NVARCHAR(100)) + ' deadlocks have been recorded since startup.' AS Details
  2605. FROM sys.dm_os_performance_counters p
  2606. INNER JOIN sys.databases d ON d.name = 'tempdb'
  2607. WHERE RTRIM(p.counter_name) = 'Number of Deadlocks/sec'
  2608. AND RTRIM(p.instance_name) = '_Total'
  2609. AND p.cntr_value > 0
  2610. AND (1.0 * p.cntr_value / NULLIF(datediff(DD,create_date,CURRENT_TIMESTAMP),0)) > 10;
  2611. END
  2612.  
  2613.  
  2614. IF DATEADD(mi, -15, GETDATE()) < (SELECT TOP 1 creation_time FROM sys.dm_exec_query_stats ORDER BY creation_time)
  2615. BEGIN
  2616. INSERT INTO #BlitzResults
  2617. (CheckID,
  2618. Priority,
  2619. FindingsGroup,
  2620. Finding,
  2621. URL,
  2622. Details)
  2623. SELECT TOP 1 125, 10, 'Performance', 'Plan Cache Erased Recently', 'http://BrentOzar.com/askbrent/plan-cache-erased-recently/',
  2624. '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.'
  2625. FROM sys.dm_exec_query_stats WITH (NOLOCK)
  2626. ORDER BY creation_time
  2627. END;
  2628.  
  2629. IF EXISTS (SELECT * FROM sys.configurations WHERE name = 'priority boost' AND (value = 1 OR value_in_use = 1))
  2630. BEGIN
  2631. INSERT INTO #BlitzResults
  2632. (CheckID,
  2633. Priority,
  2634. FindingsGroup,
  2635. Finding,
  2636. URL,
  2637. Details)
  2638. VALUES(126, 5, 'Reliability', 'Priority Boost Enabled', 'http://BrentOzar.com/go/priorityboost/',
  2639. 'Priority Boost sounds awesome, but it can actually cause your SQL Server to crash.')
  2640. END;
  2641.  
  2642. IF NOT EXISTS ( SELECT 1
  2643. FROM #SkipChecks
  2644. WHERE DatabaseName IS NULL AND CheckID = 128 )
  2645. BEGIN
  2646.  
  2647. IF (@ProductVersionMajor = 12 AND @ProductVersionMinor < 2000) OR
  2648. (@ProductVersionMajor = 11 AND @ProductVersionMinor <= 2100) OR
  2649. (@ProductVersionMajor = 10.5 AND @ProductVersionMinor <= 2500) OR
  2650. (@ProductVersionMajor = 10 AND @ProductVersionMinor <= 4000) OR
  2651. (@ProductVersionMajor = 9 AND @ProductVersionMinor <= 5000)
  2652. BEGIN
  2653. INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2654. VALUES(128, 20, 'Reliability', 'Unsupported Build of SQL Server', 'http://BrentOzar.com/go/unsupported',
  2655. 'Version ' + CAST(@ProductVersionMajor AS VARCHAR(100)) + '.' + CAST(@ProductVersionMinor AS VARCHAR(100)) + ' is no longer supported by Microsoft. You need to apply a service pack.');
  2656. END;
  2657.  
  2658. END;
  2659.  
  2660. /* Reliability - Dangerous Build of SQL Server (Corruption) */
  2661. IF NOT EXISTS ( SELECT 1
  2662. FROM #SkipChecks
  2663. WHERE DatabaseName IS NULL AND CheckID = 129 )
  2664. BEGIN
  2665. IF (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3436) OR
  2666. (@ProductVersionMajor = 11 AND @ProductVersionMinor = 5058) OR
  2667. (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2342)
  2668. BEGIN
  2669. INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2670. VALUES(129, 20, 'Reliability', 'Dangerous Build of SQL Server (Corruption)', 'http://sqlperformance.com/2014/06/sql-indexes/hotfix-sql-2012-rebuilds',
  2671. '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.');
  2672. END;
  2673.  
  2674. END;
  2675.  
  2676. /* Reliability - Dangerous Build of SQL Server (Security) */
  2677. IF NOT EXISTS ( SELECT 1
  2678. FROM #SkipChecks
  2679. WHERE DatabaseName IS NULL AND CheckID = 157 )
  2680. BEGIN
  2681. IF (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5500 AND @ProductVersionMinor <= 5512) OR
  2682. (@ProductVersionMajor = 10 AND @ProductVersionMinor >= 5750 AND @ProductVersionMinor <= 5867) OR
  2683. (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4000 AND @ProductVersionMinor <= 4017) OR
  2684. (@ProductVersionMajor = 10.5 AND @ProductVersionMinor >= 4251 AND @ProductVersionMinor <= 4319) OR
  2685. (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3000 AND @ProductVersionMinor <= 3129) OR
  2686. (@ProductVersionMajor = 11 AND @ProductVersionMinor >= 3300 AND @ProductVersionMinor <= 3447) OR
  2687. (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2000 AND @ProductVersionMinor <= 2253) OR
  2688. (@ProductVersionMajor = 12 AND @ProductVersionMinor >= 2300 AND @ProductVersionMinor <= 2370)
  2689. BEGIN
  2690. INSERT INTO #BlitzResults(CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2691. VALUES(157, 20, 'Reliability', 'Dangerous Build of SQL Server (Security)', 'https://technet.microsoft.com/en-us/library/security/MS14-044',
  2692. '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.');
  2693. END;
  2694.  
  2695. END;
  2696.  
  2697.  
  2698. /* Performance - High Memory Use for In-Memory OLTP (Hekaton) */
  2699. IF NOT EXISTS ( SELECT 1
  2700. FROM #SkipChecks
  2701. WHERE DatabaseName IS NULL AND CheckID = 145 )
  2702. AND EXISTS ( SELECT *
  2703. FROM sys.all_objects o
  2704. WHERE o.name = 'dm_db_xtp_table_memory_stats' )
  2705. BEGIN
  2706. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2707. SELECT 145 AS CheckID,
  2708. 10 AS Priority,
  2709. ''Performance'' AS FindingsGroup,
  2710. ''High Memory Use for In-Memory OLTP (Hekaton)'' AS Finding,
  2711. ''http://BrentOzar.com/go/hekaton'' AS URL,
  2712. 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
  2713. FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP''
  2714. WHERE c.name = ''max server memory (MB)''
  2715. GROUP BY c.value_in_use
  2716. HAVING CAST(value_in_use AS DECIMAL(38,2)) * .25 < SUM(mem.pages_kb / 1024.0)
  2717. OR SUM(mem.pages_kb / 1024.0) > 250000';
  2718. EXECUTE(@StringToExecute);
  2719. END
  2720.  
  2721.  
  2722. /* Performance - In-Memory OLTP (Hekaton) In Use */
  2723. IF NOT EXISTS ( SELECT 1
  2724. FROM #SkipChecks
  2725. WHERE DatabaseName IS NULL AND CheckID = 146 )
  2726. AND EXISTS ( SELECT *
  2727. FROM sys.all_objects o
  2728. WHERE o.name = 'dm_db_xtp_table_memory_stats' )
  2729. BEGIN
  2730. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2731. SELECT 146 AS CheckID,
  2732. 200 AS Priority,
  2733. ''Performance'' AS FindingsGroup,
  2734. ''In-Memory OLTP (Hekaton) In Use'' AS Finding,
  2735. ''http://BrentOzar.com/go/hekaton'' AS URL,
  2736. 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
  2737. FROM sys.configurations c INNER JOIN sys.dm_os_memory_clerks mem ON mem.type = ''MEMORYCLERK_XTP''
  2738. WHERE c.name = ''max server memory (MB)''
  2739. GROUP BY c.value_in_use
  2740. HAVING SUM(mem.pages_kb / 1024.0) > 10';
  2741. EXECUTE(@StringToExecute);
  2742. END
  2743.  
  2744. /* In-Memory OLTP (Hekaton) - Transaction Errors */
  2745. IF NOT EXISTS ( SELECT 1
  2746. FROM #SkipChecks
  2747. WHERE DatabaseName IS NULL AND CheckID = 147 )
  2748. AND EXISTS ( SELECT *
  2749. FROM sys.all_objects o
  2750. WHERE o.name = 'dm_xtp_transaction_stats' )
  2751. BEGIN
  2752. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2753. SELECT 147 AS CheckID,
  2754. 100 AS Priority,
  2755. ''In-Memory OLTP (Hekaton)'' AS FindingsGroup,
  2756. ''Transaction Errors'' AS Finding,
  2757. ''http://BrentOzar.com/go/hekaton'' AS URL,
  2758. ''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
  2759. FROM sys.dm_xtp_transaction_stats
  2760. WHERE validation_failures <> 0
  2761. OR dependencies_failed <> 0
  2762. OR write_conflicts <> 0
  2763. OR unique_constraint_violations <> 0;'
  2764. EXECUTE(@StringToExecute);
  2765. END
  2766.  
  2767.  
  2768.  
  2769. /* Reliability - Database Files on Network File Shares */
  2770. IF NOT EXISTS ( SELECT 1
  2771. FROM #SkipChecks
  2772. WHERE DatabaseName IS NULL AND CheckID = 148 )
  2773. BEGIN
  2774. INSERT INTO #BlitzResults
  2775. ( CheckID ,
  2776. DatabaseName ,
  2777. Priority ,
  2778. FindingsGroup ,
  2779. Finding ,
  2780. URL ,
  2781. Details
  2782. )
  2783. SELECT DISTINCT 148 AS CheckID ,
  2784. d.[name] AS DatabaseName ,
  2785. 50 AS Priority ,
  2786. 'Reliability' AS FindingsGroup ,
  2787. 'Database Files on Network File Shares' AS Finding ,
  2788. 'http://BrentOzar.com/go/nas' AS URL ,
  2789. ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details
  2790. FROM sys.databases d
  2791. INNER JOIN sys.master_files mf ON d.database_id = mf.database_id
  2792. WHERE mf.physical_name LIKE '\\%'
  2793. AND d.name NOT IN ( SELECT DISTINCT
  2794. DatabaseName
  2795. FROM #SkipChecks
  2796. WHERE CheckID IS NULL)
  2797. END
  2798.  
  2799. /* Reliability - Database Files Stored in Azure */
  2800. IF NOT EXISTS ( SELECT 1
  2801. FROM #SkipChecks
  2802. WHERE DatabaseName IS NULL AND CheckID = 149 )
  2803. BEGIN
  2804. INSERT INTO #BlitzResults
  2805. ( CheckID ,
  2806. DatabaseName ,
  2807. Priority ,
  2808. FindingsGroup ,
  2809. Finding ,
  2810. URL ,
  2811. Details
  2812. )
  2813. SELECT DISTINCT 149 AS CheckID ,
  2814. d.[name] AS DatabaseName ,
  2815. 50 AS Priority ,
  2816. 'Reliability' AS FindingsGroup ,
  2817. 'Database Files Stored in Azure' AS Finding ,
  2818. 'http://BrentOzar.com/go/azurefiles' AS URL ,
  2819. ( 'Files for this database are on: ' + LEFT(mf.physical_name, 30)) AS Details
  2820. FROM sys.databases d
  2821. INNER JOIN sys.master_files mf ON d.database_id = mf.database_id
  2822. WHERE mf.physical_name LIKE 'http://%'
  2823. AND d.name NOT IN ( SELECT DISTINCT
  2824. DatabaseName
  2825. FROM #SkipChecks
  2826. WHERE CheckID IS NULL)
  2827. END
  2828.  
  2829.  
  2830. /* Reliability - Errors Logged Recently in the Default Trace */
  2831. IF NOT EXISTS ( SELECT 1
  2832. FROM #SkipChecks
  2833. WHERE DatabaseName IS NULL AND CheckID = 150 )
  2834. AND @TracePath IS NOT NULL
  2835. BEGIN
  2836.  
  2837. INSERT INTO #BlitzResults
  2838. ( CheckID ,
  2839. DatabaseName ,
  2840. Priority ,
  2841. FindingsGroup ,
  2842. Finding ,
  2843. URL ,
  2844. Details
  2845. )
  2846. SELECT DISTINCT 150 AS CheckID ,
  2847. t.DatabaseName,
  2848. 50 AS Priority ,
  2849. 'Reliability' AS FindingsGroup ,
  2850. 'Errors Logged Recently in the Default Trace' AS Finding ,
  2851. 'http://BrentOzar.com/go/defaulttrace' AS URL ,
  2852. CAST(t.TextData AS NVARCHAR(4000)) AS Details
  2853. FROM sys.fn_trace_gettable(@TracePath, DEFAULT) t
  2854. WHERE t.EventClass = 22
  2855. AND t.Severity >= 17
  2856. AND t.StartTime > DATEADD(dd, -30, GETDATE())
  2857. END
  2858.  
  2859.  
  2860. /* Performance - Log File Growths Slow */
  2861. IF NOT EXISTS ( SELECT 1
  2862. FROM #SkipChecks
  2863. WHERE DatabaseName IS NULL AND CheckID = 151 )
  2864. AND @TracePath IS NOT NULL
  2865. BEGIN
  2866. INSERT INTO #BlitzResults
  2867. ( CheckID ,
  2868. DatabaseName ,
  2869. Priority ,
  2870. FindingsGroup ,
  2871. Finding ,
  2872. URL ,
  2873. Details
  2874. )
  2875. SELECT DISTINCT 151 AS CheckID ,
  2876. t.DatabaseName,
  2877. 50 AS Priority ,
  2878. 'Performance' AS FindingsGroup ,
  2879. 'Log File Growths Slow' AS Finding ,
  2880. 'http://BrentOzar.com/go/filegrowth' AS URL ,
  2881. CAST(COUNT(*) AS NVARCHAR(100)) + ' growths took more than 15 seconds each. Consider setting log file autogrowth to a smaller increment.' AS Details
  2882. FROM sys.fn_trace_gettable(@TracePath, DEFAULT) t
  2883. WHERE t.EventClass = 93
  2884. AND t.StartTime > DATEADD(dd, -30, GETDATE())
  2885. AND t.Duration > 15000000
  2886. GROUP BY t.DatabaseName
  2887. HAVING COUNT(*) > 1
  2888. END
  2889.  
  2890.  
  2891. /* Performance - Many Plans for One Query */
  2892. IF NOT EXISTS ( SELECT 1
  2893. FROM #SkipChecks
  2894. WHERE DatabaseName IS NULL AND CheckID = 160 )
  2895. AND EXISTS (SELECT * FROM sys.all_columns WHERE name = 'query_hash')
  2896. BEGIN
  2897. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2898. SELECT TOP 1 160 AS CheckID,
  2899. 100 AS Priority,
  2900. ''Performance'' AS FindingsGroup,
  2901. ''Many Plans for One Query'' AS Finding,
  2902. ''http://BrentOzar.com/go/parameterization'' AS URL,
  2903. 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
  2904. FROM sys.dm_exec_query_stats qs
  2905. CROSS APPLY sys.dm_exec_plan_attributes(qs.plan_handle) pa
  2906. WHERE pa.attribute = ''dbid''
  2907. GROUP BY qs.query_hash, pa.value
  2908. HAVING COUNT(DISTINCT plan_handle) > 50
  2909. ORDER BY COUNT(DISTINCT plan_handle) DESC;';
  2910. EXECUTE(@StringToExecute);
  2911. END
  2912.  
  2913.  
  2914. /* Performance - High Number of Cached Plans */
  2915. IF NOT EXISTS ( SELECT 1
  2916. FROM #SkipChecks
  2917. WHERE DatabaseName IS NULL AND CheckID = 161 )
  2918. BEGIN
  2919. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  2920. SELECT TOP 1 161 AS CheckID,
  2921. 100 AS Priority,
  2922. ''Performance'' AS FindingsGroup,
  2923. ''High Number of Cached Plans'' AS Finding,
  2924. ''http://BrentOzar.com/go/planlimits'' AS URL,
  2925. ''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
  2926. FROM sys.dm_os_memory_cache_hash_tables ht
  2927. INNER JOIN sys.dm_os_memory_cache_counters cc ON ht.name = cc.name AND ht.type = cc.type
  2928. where ht.name IN ( ''SQL Plans'' , ''Object Plans'' , ''Bound Trees'' )
  2929. AND cc.entries_count >= (3 * ht.buckets_count)';
  2930. EXECUTE(@StringToExecute);
  2931. END
  2932.  
  2933.  
  2934. /* Performance - Too Much Free Memory */
  2935. IF NOT EXISTS ( SELECT 1
  2936. FROM #SkipChecks
  2937. WHERE DatabaseName IS NULL AND CheckID = 165 )
  2938. BEGIN
  2939. INSERT INTO #BlitzResults
  2940. (CheckID,
  2941. Priority,
  2942. FindingsGroup,
  2943. Finding,
  2944. URL,
  2945. Details)
  2946. SELECT 165, 50, 'Performance', 'Too Much Free Memory', 'http://BrentOzar.com/go/freememory',
  2947. 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
  2948. FROM sys.dm_os_performance_counters cFree
  2949. INNER JOIN sys.dm_os_performance_counters cTotal ON cTotal.object_name LIKE N'%Memory Manager%'
  2950. AND cTotal.counter_name = N'Total Server Memory (KB) '
  2951. WHERE cFree.object_name LIKE N'%Memory Manager%'
  2952. AND cFree.counter_name = N'Free Memory (KB) '
  2953. AND CAST(cTotal.cntr_value AS BIGINT) * .3 <= CAST(cFree.cntr_value AS BIGINT)
  2954.  
  2955. END
  2956.  
  2957.  
  2958. /* Outdated sp_Blitz - sp_Blitz is Over 6 Months Old */
  2959. IF NOT EXISTS ( SELECT 1
  2960. FROM #SkipChecks
  2961. WHERE DatabaseName IS NULL AND CheckID = 155 )
  2962. AND DATEDIFF(MM, @VersionDate, GETDATE()) > 6
  2963. BEGIN
  2964. INSERT INTO #BlitzResults
  2965. ( CheckID ,
  2966. Priority ,
  2967. FindingsGroup ,
  2968. Finding ,
  2969. URL ,
  2970. Details
  2971. )
  2972. SELECT 155 AS CheckID ,
  2973. 0 AS Priority ,
  2974. 'Outdated sp_Blitz' AS FindingsGroup ,
  2975. 'sp_Blitz is Over 6 Months Old' AS Finding ,
  2976. 'http://www.BrentOzar.com/blitz/' AS URL ,
  2977. '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
  2978. END
  2979.  
  2980.  
  2981. /* Populate a list of database defaults. I'm doing this kind of oddly -
  2982. it reads like a lot of work, but this way it compiles & runs on all
  2983. versions of SQL Server.
  2984. */
  2985. INSERT INTO #DatabaseDefaults
  2986. SELECT 'is_supplemental_logging_enabled', 0, 131, 210, 'Supplemental Logging Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  2987. FROM sys.all_columns
  2988. WHERE name = 'is_supplemental_logging_enabled' AND object_id = OBJECT_ID('sys.databases');
  2989. INSERT INTO #DatabaseDefaults
  2990. SELECT 'snapshot_isolation_state', 0, 132, 210, 'Snapshot Isolation Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  2991. FROM sys.all_columns
  2992. WHERE name = 'snapshot_isolation_state' AND object_id = OBJECT_ID('sys.databases');
  2993. INSERT INTO #DatabaseDefaults
  2994. SELECT 'is_read_committed_snapshot_on', 0, 133, 210, 'Read Committed Snapshot Isolation Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  2995. FROM sys.all_columns
  2996. WHERE name = 'is_read_committed_snapshot_on' AND object_id = OBJECT_ID('sys.databases');
  2997. INSERT INTO #DatabaseDefaults
  2998. SELECT 'is_auto_create_stats_incremental_on', 0, 134, 210, 'Auto Create Stats Incremental Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  2999. FROM sys.all_columns
  3000. WHERE name = 'is_auto_create_stats_incremental_on' AND object_id = OBJECT_ID('sys.databases');
  3001. INSERT INTO #DatabaseDefaults
  3002. SELECT 'is_ansi_null_default_on', 0, 135, 210, 'ANSI NULL Default Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3003. FROM sys.all_columns
  3004. WHERE name = 'is_ansi_null_default_on' AND object_id = OBJECT_ID('sys.databases');
  3005. INSERT INTO #DatabaseDefaults
  3006. SELECT 'is_recursive_triggers_on', 0, 136, 210, 'Recursive Triggers Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3007. FROM sys.all_columns
  3008. WHERE name = 'is_recursive_triggers_on' AND object_id = OBJECT_ID('sys.databases');
  3009. INSERT INTO #DatabaseDefaults
  3010. SELECT 'is_trustworthy_on', 0, 137, 210, 'Trustworthy Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3011. FROM sys.all_columns
  3012. WHERE name = 'is_trustworthy_on' AND object_id = OBJECT_ID('sys.databases');
  3013. INSERT INTO #DatabaseDefaults
  3014. SELECT 'is_parameterization_forced', 0, 138, 210, 'Forced Parameterization Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3015. FROM sys.all_columns
  3016. WHERE name = 'is_parameterization_forced' AND object_id = OBJECT_ID('sys.databases');
  3017. INSERT INTO #DatabaseDefaults
  3018. SELECT 'is_query_store_on', 0, 139, 210, 'Query Store Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3019. FROM sys.all_columns
  3020. WHERE name = 'is_query_store_on' AND object_id = OBJECT_ID('sys.databases');
  3021. INSERT INTO #DatabaseDefaults
  3022. SELECT 'is_cdc_enabled', 0, 140, 210, 'Change Data Capture Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3023. FROM sys.all_columns
  3024. WHERE name = 'is_cdc_enabled' AND object_id = OBJECT_ID('sys.databases');
  3025. INSERT INTO #DatabaseDefaults
  3026. SELECT 'containment', 0, 141, 210, 'Containment Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3027. FROM sys.all_columns
  3028. WHERE name = 'containment' AND object_id = OBJECT_ID('sys.databases');
  3029. INSERT INTO #DatabaseDefaults
  3030. SELECT 'target_recovery_time_in_seconds', 0, 142, 210, 'Target Recovery Time Changed', 'http://BrentOzar.com/go/dbdefaults', NULL
  3031. FROM sys.all_columns
  3032. WHERE name = 'target_recovery_time_in_seconds' AND object_id = OBJECT_ID('sys.databases');
  3033. INSERT INTO #DatabaseDefaults
  3034. SELECT 'delayed_durability', 0, 143, 210, 'Delayed Durability Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3035. FROM sys.all_columns
  3036. WHERE name = 'delayed_durability' AND object_id = OBJECT_ID('sys.databases');
  3037. INSERT INTO #DatabaseDefaults
  3038. SELECT 'is_memory_optimized_elevate_to_snapshot_on', 0, 144, 210, 'Memory Optimized Enabled', 'http://BrentOzar.com/go/dbdefaults', NULL
  3039. FROM sys.all_columns
  3040. WHERE name = 'is_memory_optimized_elevate_to_snapshot_on' AND object_id = OBJECT_ID('sys.databases');
  3041.  
  3042. DECLARE DatabaseDefaultsLoop CURSOR FOR
  3043. SELECT name, DefaultValue, CheckID, Priority, Finding, URL, Details
  3044. FROM #DatabaseDefaults
  3045.  
  3046. OPEN DatabaseDefaultsLoop
  3047. FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails
  3048. WHILE @@FETCH_STATUS = 0
  3049. BEGIN
  3050.  
  3051. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details)
  3052. 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.') + '''
  3053. FROM sys.databases d
  3054. WHERE d.database_id > 4 AND (d.[' + @CurrentName + '] <> ' + @CurrentDefaultValue + ' OR d.[' + @CurrentName + '] IS NULL);';
  3055. EXEC (@StringToExecute);
  3056.  
  3057. FETCH NEXT FROM DatabaseDefaultsLoop into @CurrentName, @CurrentDefaultValue, @CurrentCheckID, @CurrentPriority, @CurrentFinding, @CurrentURL, @CurrentDetails
  3058. END
  3059.  
  3060. CLOSE DatabaseDefaultsLoop
  3061. DEALLOCATE DatabaseDefaultsLoop;
  3062.  
  3063.  
  3064. /*This checks to see if Agent is Offline*/
  3065. IF @ProductVersionMajor >= 10 AND @ProductVersionMinor >= 50
  3066. AND NOT EXISTS ( SELECT 1
  3067. FROM #SkipChecks
  3068. WHERE DatabaseName IS NULL AND CheckID = 167 )
  3069. BEGIN
  3070. IF EXISTS ( SELECT 1
  3071. FROM sys.all_objects
  3072. WHERE name = 'dm_server_services' )
  3073. BEGIN
  3074. INSERT INTO [#BlitzResults]
  3075. ( [CheckID] ,
  3076. [Priority] ,
  3077. [FindingsGroup] ,
  3078. [Finding] ,
  3079. [URL] ,
  3080. [Details] )
  3081.  
  3082. SELECT
  3083. 167 AS [CheckID] ,
  3084. 250 AS [Priority] ,
  3085. 'Server Info' AS [FindingsGroup] ,
  3086. 'Agent is Currently Offline' AS [Finding] ,
  3087. '' AS [URL] ,
  3088. ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.'
  3089. ) AS [Details]
  3090. FROM
  3091. [sys].[dm_server_services]
  3092. WHERE [status_desc] <> 'Running'
  3093. AND [servicename] LIKE 'SQL Server Agent%'
  3094.  
  3095. END;
  3096. END;
  3097.  
  3098. /*This checks to see if the Full Text thingy is offline*/
  3099. IF @ProductVersionMajor >= 10 AND @ProductVersionMinor >= 50
  3100. AND NOT EXISTS ( SELECT 1
  3101. FROM #SkipChecks
  3102. WHERE DatabaseName IS NULL AND CheckID = 168 )
  3103. BEGIN
  3104. IF EXISTS ( SELECT 1
  3105. FROM sys.all_objects
  3106. WHERE name = 'dm_server_services' )
  3107. BEGIN
  3108. INSERT INTO [#BlitzResults]
  3109. ( [CheckID] ,
  3110. [Priority] ,
  3111. [FindingsGroup] ,
  3112. [Finding] ,
  3113. [URL] ,
  3114. [Details] )
  3115.  
  3116. SELECT
  3117. 168 AS [CheckID] ,
  3118. 250 AS [Priority] ,
  3119. 'Server Info' AS [FindingsGroup] ,
  3120. 'Full-text Filter Daemon Launcher is Currently Offline' AS [Finding] ,
  3121. '' AS [URL] ,
  3122. ( 'Oops! It looks like the ' + [servicename] + ' service is ' + [status_desc] + '. The startup type is ' + [startup_type_desc] + '.'
  3123. ) AS [Details]
  3124. FROM
  3125. [sys].[dm_server_services]
  3126. WHERE [status_desc] <> 'Running'
  3127. AND [servicename] LIKE 'SQL Full-text Filter Daemon Launcher%'
  3128.  
  3129. END;
  3130. END;
  3131.  
  3132. /*This checks which service account SQL Server is running as.*/
  3133. IF @ProductVersionMajor >= 10 AND @ProductVersionMinor >= 50
  3134. AND NOT EXISTS ( SELECT 1
  3135. FROM #SkipChecks
  3136. WHERE DatabaseName IS NULL AND CheckID = 169 )
  3137.  
  3138. BEGIN
  3139. IF EXISTS ( SELECT 1
  3140. FROM sys.all_objects
  3141. WHERE name = 'dm_server_services' )
  3142. BEGIN
  3143. INSERT INTO [#BlitzResults]
  3144. ( [CheckID] ,
  3145. [Priority] ,
  3146. [FindingsGroup] ,
  3147. [Finding] ,
  3148. [URL] ,
  3149. [Details] )
  3150.  
  3151. SELECT
  3152. 169 AS [CheckID] ,
  3153. 250 AS [Priority] ,
  3154. 'Informational' AS [FindingsGroup] ,
  3155. 'SQL Server is running under an NT Service account' AS [Finding] ,
  3156. 'http://BrentOzar.com/go/setup' AS [URL] ,
  3157. ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.'
  3158. ) AS [Details]
  3159. FROM
  3160. [sys].[dm_server_services]
  3161. WHERE [service_account] LIKE 'NT Service%'
  3162. AND [servicename] LIKE 'SQL Server%'
  3163. AND [servicename] NOT LIKE 'SQL Server Agent%'
  3164.  
  3165. END;
  3166. END;
  3167.  
  3168. /*This checks which service account SQL Agent is running as.*/
  3169. IF @ProductVersionMajor >= 10 AND @ProductVersionMinor >= 50
  3170. AND NOT EXISTS ( SELECT 1
  3171. FROM #SkipChecks
  3172. WHERE DatabaseName IS NULL AND CheckID = 170 )
  3173.  
  3174. BEGIN
  3175. IF EXISTS ( SELECT 1
  3176. FROM sys.all_objects
  3177. WHERE name = 'dm_server_services' )
  3178. BEGIN
  3179. INSERT INTO [#BlitzResults]
  3180. ( [CheckID] ,
  3181. [Priority] ,
  3182. [FindingsGroup] ,
  3183. [Finding] ,
  3184. [URL] ,
  3185. [Details] )
  3186.  
  3187. SELECT
  3188. 170 AS [CheckID] ,
  3189. 250 AS [Priority] ,
  3190. 'Informational' AS [FindingsGroup] ,
  3191. 'SQL Server Agent is running under an NT Service account' AS [Finding] ,
  3192. 'http://BrentOzar.com/go/setup' AS [URL] ,
  3193. ( 'I''m running as ' + [service_account] + '. I wish I had an Active Directory service account instead.'
  3194. ) AS [Details]
  3195. FROM
  3196. [sys].[dm_server_services]
  3197. WHERE [service_account] LIKE 'NT Service%'
  3198. AND [servicename] LIKE 'SQL Server Agent%'
  3199.  
  3200. END;
  3201. END;
  3202.  
  3203. /*This counts memory dumps and gives min and max date of in view*/
  3204. IF @ProductVersionMajor >= 10 AND @ProductVersionMinor >= 50
  3205. AND NOT EXISTS ( SELECT 1
  3206. FROM #SkipChecks
  3207. WHERE DatabaseName IS NULL AND CheckID = 171 )
  3208. BEGIN
  3209. IF EXISTS ( SELECT 1
  3210. FROM sys.all_objects
  3211. WHERE name = 'dm_server_memory_dumps' )
  3212. BEGIN
  3213. IF EXISTS (SELECT * FROM [sys].[dm_server_memory_dumps] WHERE [creation_time] >= DATEADD(year, -1, GETDATE()))
  3214. INSERT INTO [#BlitzResults]
  3215. ( [CheckID] ,
  3216. [Priority] ,
  3217. [FindingsGroup] ,
  3218. [Finding] ,
  3219. [URL] ,
  3220. [Details] )
  3221.  
  3222. SELECT
  3223. 171 AS [CheckID] ,
  3224. 20 AS [Priority] ,
  3225. 'Reliability' AS [FindingsGroup] ,
  3226. 'Memory Dumps Have Occurred' AS [Finding] ,
  3227. 'http://BrentOzar.com/go/dump' AS [URL] ,
  3228. ( 'That ain''t good. I''ve had ' +
  3229. CAST(COUNT(*) AS VARCHAR(100)) + ' memory dumps between ' +
  3230. CAST(CAST(MIN([creation_time]) AS DATETIME) AS VARCHAR(100)) +
  3231. ' and ' +
  3232. CAST(CAST(MAX([creation_time]) AS DATETIME) AS VARCHAR(100)) +
  3233. '!'
  3234. ) AS [Details]
  3235. FROM
  3236. [sys].[dm_server_memory_dumps]
  3237. WHERE [creation_time] >= DATEADD(year, -1, GETDATE());
  3238.  
  3239. END;
  3240. END;
  3241.  
  3242. /*Checks to see if you're on Developer or Evaluation*/
  3243. IF NOT EXISTS ( SELECT 1
  3244. FROM #SkipChecks
  3245. WHERE DatabaseName IS NULL AND CheckID = 173 )
  3246. BEGIN
  3247. INSERT INTO [#BlitzResults]
  3248. ( [CheckID] ,
  3249. [Priority] ,
  3250. [FindingsGroup] ,
  3251. [Finding] ,
  3252. [URL] ,
  3253. [Details] )
  3254.  
  3255. SELECT
  3256. 173 AS [CheckID] ,
  3257. 200 AS [Priority] ,
  3258. 'Licensing' AS [FindingsGroup] ,
  3259. 'Non-Production License' AS [Finding] ,
  3260. 'http://BrentOzar.com/go/licensing' AS [URL] ,
  3261. ( 'We''re not the licensing police, but if this is supposed to be a production server, and you''re running ' +
  3262. CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) +
  3263. ' the good folks at Microsoft might get upset with you. Better start counting those cores.'
  3264. ) AS [Details]
  3265. WHERE CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Developer%'
  3266. OR CAST(SERVERPROPERTY('edition') AS VARCHAR(100)) LIKE '%Evaluation%'
  3267.  
  3268. END
  3269.  
  3270. /*Checks to see if Buffer Pool Extensions are in use*/
  3271. IF @ProductVersionMajor >= 12
  3272. AND NOT EXISTS ( SELECT 1
  3273. FROM #SkipChecks
  3274. WHERE DatabaseName IS NULL AND CheckID = 174 )
  3275. BEGIN
  3276. INSERT INTO [#BlitzResults]
  3277. ( [CheckID] ,
  3278. [Priority] ,
  3279. [FindingsGroup] ,
  3280. [Finding] ,
  3281. [URL] ,
  3282. [Details] )
  3283.  
  3284. SELECT
  3285. 174 AS [CheckID] ,
  3286. 200 AS [Priority] ,
  3287. 'Performance' AS [FindingsGroup] ,
  3288. 'Buffer Pool Extensions Enabled' AS [Finding] ,
  3289. 'http://BrentOzar.com/go/bpe' AS [URL] ,
  3290. ( 'You have Buffer Pool Extensions enabled, and one lives here: ' +
  3291. [path] +
  3292. '. It''s currently ' +
  3293. CASE WHEN [current_size_in_kb] / 1024. / 1024. > 0
  3294. THEN CAST([current_size_in_kb] / 1024. / 1024. AS VARCHAR(100))
  3295. + ' GB'
  3296. ELSE CAST([current_size_in_kb] / 1024. AS VARCHAR(100))
  3297. + ' MB'
  3298. END +
  3299. '. Did you know that BPEs only provide single threaded access 8 bytes at a time?'
  3300. ) AS [Details]
  3301. FROM sys.dm_os_buffer_pool_extension_configuration
  3302. WHERE [state_description] <> 'BUFFER POOL EXTENSION DISABLED'
  3303.  
  3304. END
  3305.  
  3306. /*Check for too many tempdb files*/
  3307. IF NOT EXISTS ( SELECT 1
  3308. FROM #SkipChecks
  3309. WHERE DatabaseName IS NULL AND CheckID = 175 )
  3310. BEGIN
  3311. INSERT INTO #BlitzResults
  3312. ( CheckID ,
  3313. DatabaseName ,
  3314. Priority ,
  3315. FindingsGroup ,
  3316. Finding ,
  3317. URL ,
  3318. Details
  3319. )
  3320. SELECT DISTINCT
  3321. 175 AS CheckID ,
  3322. 'TempDB' AS DatabaseName ,
  3323. 170 AS Priority ,
  3324. 'File Configuration' AS FindingsGroup ,
  3325. 'TempDB Has >16 Data Files' AS Finding ,
  3326. 'http://BrentOzar.com/go/tempdb' AS URL ,
  3327. 'Woah, Nelly! TempDB has ' + CAST(COUNT_BIG(*) AS VARCHAR) + '. Did you forget to terminate a loop somewhere?' AS Details
  3328. FROM sys.[master_files] AS [mf]
  3329. WHERE [mf].[database_id] = 2 AND [mf].[type] = 0
  3330. HAVING COUNT_BIG(*) > 16;
  3331. END
  3332.  
  3333. IF NOT EXISTS ( SELECT 1
  3334. FROM #SkipChecks
  3335. WHERE DatabaseName IS NULL AND CheckID = 176 )
  3336. IF EXISTS ( SELECT 1
  3337. FROM sys.all_objects
  3338. WHERE name = 'dm_xe_sessions' )
  3339. BEGIN
  3340. BEGIN
  3341. INSERT INTO #BlitzResults
  3342. ( CheckID ,
  3343. DatabaseName ,
  3344. Priority ,
  3345. FindingsGroup ,
  3346. Finding ,
  3347. URL ,
  3348. Details
  3349. )
  3350. SELECT DISTINCT
  3351. 176 AS CheckID ,
  3352. '' AS DatabaseName ,
  3353. 200 AS Priority ,
  3354. 'Monitoring' AS FindingsGroup ,
  3355. 'Extended Events Hyperextension' AS Finding ,
  3356. 'http://BrentOzar.com/go/xe' AS URL ,
  3357. 'Hey big spender, you have ' + CAST(COUNT_BIG(*) AS VARCHAR) + ' Extended Events sessions running. You sure you meant to do that?' AS Details
  3358. FROM sys.dm_xe_sessions
  3359. WHERE [name] NOT IN
  3360. ('system_health', 'sp_server_diagnostics session', 'hkenginexesession'
  3361. )
  3362. HAVING COUNT_BIG(*) >= 2;
  3363. END
  3364. END
  3365.  
  3366. /*Harmful startup parameter*/
  3367. IF NOT EXISTS ( SELECT 1
  3368. FROM #SkipChecks
  3369. WHERE DatabaseName IS NULL AND CheckID = 177 )
  3370. BEGIN
  3371. IF EXISTS ( SELECT 1
  3372. FROM sys.all_objects
  3373. WHERE name = 'dm_server_registry' )
  3374.  
  3375. BEGIN
  3376. INSERT INTO #BlitzResults
  3377. ( CheckID ,
  3378. DatabaseName ,
  3379. Priority ,
  3380. FindingsGroup ,
  3381. Finding ,
  3382. URL ,
  3383. Details
  3384. )
  3385. SELECT DISTINCT
  3386. 177 AS CheckID ,
  3387. '' AS DatabaseName ,
  3388. 5 AS Priority ,
  3389. 'Monitoring' AS FindingsGroup ,
  3390. 'Disabled Internal Monitoring Features' AS Finding ,
  3391. 'https://msdn.microsoft.com/en-us/library/ms190737.aspx' AS URL ,
  3392. '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
  3393. FROM
  3394. [sys].[dm_server_registry] AS [dsr]
  3395. WHERE
  3396. [dsr].[registry_key] LIKE N'%MSSQLServer\Parameters'
  3397. AND [dsr].[value_data] = '-x';;
  3398. END
  3399. END
  3400.  
  3401.  
  3402.  
  3403.  
  3404.  
  3405. IF @CheckUserDatabaseObjects = 1
  3406. BEGIN
  3407.  
  3408. /*
  3409. But what if you need to run a query in every individual database?
  3410. Check out CheckID 99 below. Yes, it uses sp_MSforeachdb, and no,
  3411. we're not happy about that. sp_MSforeachdb is known to have a lot
  3412. of issues, like skipping databases sometimes. However, this is the
  3413. only built-in option that we have. If you're writing your own code
  3414. for database maintenance, consider Aaron Bertrand's alternative:
  3415. http://www.mssqltips.com/sqlservertip/2201/making-a-more-reliable-and-flexible-spmsforeachdb/
  3416. We don't include that as part of sp_Blitz, of course, because
  3417. copying and distributing copyrighted code from others without their
  3418. written permission isn't a good idea.
  3419. */
  3420. IF NOT EXISTS ( SELECT 1
  3421. FROM #SkipChecks
  3422. WHERE DatabaseName IS NULL AND CheckID = 99 )
  3423. BEGIN
  3424. 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'', ''http://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.'')';
  3425. END
  3426. /*
  3427. Note that by using sp_MSforeachdb, we're running the query in all
  3428. databases. We're not checking #SkipChecks here for each database to
  3429. see if we should run the check in this database. That means we may
  3430. still run a skipped check if it involves sp_MSforeachdb. We just
  3431. don't output those results in the last step.
  3432. */
  3433.  
  3434.  
  3435. IF NOT EXISTS ( SELECT 1
  3436. FROM #SkipChecks
  3437. WHERE DatabaseName IS NULL AND CheckID = 163 )
  3438. AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'database_query_store_options')
  3439. BEGIN
  3440. EXEC dbo.sp_MSforeachdb 'USE [?];
  3441. INSERT INTO #BlitzResults
  3442. (CheckID,
  3443. DatabaseName,
  3444. Priority,
  3445. FindingsGroup,
  3446. Finding,
  3447. URL,
  3448. Details)
  3449. SELECT TOP 1 163,
  3450. ''?'',
  3451. 10,
  3452. ''Performance'',
  3453. ''Query Store Disabled'',
  3454. ''http://BrentOzar.com/go/querystore'',
  3455. (''The new SQL Server 2016 Query Store feature has not been enabled on this database.'')
  3456. FROM [?].sys.database_query_store_options WHERE desired_state = 0 AND ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'', ''ReportServer'', ''ReportServerTempDB'')';
  3457. END
  3458.  
  3459.  
  3460. IF NOT EXISTS ( SELECT 1
  3461. FROM #SkipChecks
  3462. WHERE DatabaseName IS NULL AND CheckID = 41 )
  3463. BEGIN
  3464. EXEC dbo.sp_MSforeachdb 'use [?];
  3465. INSERT INTO #BlitzResults
  3466. (CheckID,
  3467. DatabaseName,
  3468. Priority,
  3469. FindingsGroup,
  3470. Finding,
  3471. URL,
  3472. Details)
  3473. SELECT 41,
  3474. ''?'',
  3475. 170,
  3476. ''File Configuration'',
  3477. ''Multiple Log Files on One Drive'',
  3478. ''http://BrentOzar.com/go/manylogs'',
  3479. (''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.'')
  3480. FROM [?].sys.database_files WHERE type_desc = ''LOG''
  3481. AND ''?'' <> ''[tempdb]''
  3482. GROUP BY LEFT(physical_name, 1)
  3483. HAVING COUNT(*) > 1';
  3484. END
  3485.  
  3486. IF NOT EXISTS ( SELECT 1
  3487. FROM #SkipChecks
  3488. WHERE DatabaseName IS NULL AND CheckID = 42 )
  3489. BEGIN
  3490. EXEC dbo.sp_MSforeachdb 'use [?];
  3491. INSERT INTO #BlitzResults
  3492. (CheckID,
  3493. DatabaseName,
  3494. Priority,
  3495. FindingsGroup,
  3496. Finding,
  3497. URL,
  3498. Details)
  3499. SELECT DISTINCT 42,
  3500. ''?'',
  3501. 170,
  3502. ''File Configuration'',
  3503. ''Uneven File Growth Settings in One Filegroup'',
  3504. ''http://BrentOzar.com/go/grow'',
  3505. (''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.'')
  3506. FROM [?].sys.database_files
  3507. WHERE type_desc = ''ROWS''
  3508. GROUP BY data_space_id
  3509. HAVING COUNT(DISTINCT growth) > 1 OR COUNT(DISTINCT is_percent_growth) > 1';
  3510. END
  3511.  
  3512.  
  3513. IF NOT EXISTS ( SELECT 1
  3514. FROM #SkipChecks
  3515. WHERE DatabaseName IS NULL AND CheckID = 82 )
  3516. BEGIN
  3517. EXEC sp_MSforeachdb 'use [?];
  3518. INSERT INTO #BlitzResults
  3519. (CheckID,
  3520. DatabaseName,
  3521. Priority,
  3522. FindingsGroup,
  3523. Finding,
  3524. URL, Details)
  3525. SELECT DISTINCT 82 AS CheckID,
  3526. ''?'' as DatabaseName,
  3527. 170 AS Priority,
  3528. ''File Configuration'' AS FindingsGroup,
  3529. ''File growth set to percent'',
  3530. ''http://brentozar.com/go/percentgrowth'' AS URL,
  3531. ''The ['' + DB_NAME() + ''] database file '' + f.physical_name + '' has grown to '' + CAST((f.size * 8 / 1000000) AS NVARCHAR(10)) + '' GB, and is using percent filegrowth settings. This can lead to slow performance during growths if Instant File Initialization is not enabled.''
  3532. FROM [?].sys.database_files f
  3533. WHERE is_percent_growth = 1 and size > 128000 ';
  3534. END
  3535.  
  3536.  
  3537.  
  3538. /* addition by Henrik Staun Poulsen, Stovi Software */
  3539. IF NOT EXISTS ( SELECT 1
  3540. FROM #SkipChecks
  3541. WHERE DatabaseName IS NULL AND CheckID = 158 )
  3542. BEGIN
  3543. EXEC sp_MSforeachdb 'use [?];
  3544. INSERT INTO #BlitzResults
  3545. (CheckID,
  3546. DatabaseName,
  3547. Priority,
  3548. FindingsGroup,
  3549. Finding,
  3550. URL, Details)
  3551. SELECT DISTINCT 158 AS CheckID,
  3552. ''?'' as DatabaseName,
  3553. 170 AS Priority,
  3554. ''File Configuration'' AS FindingsGroup,
  3555. ''File growth set to 1MB'',
  3556. ''http://brentozar.com/go/percentgrowth'' AS URL,
  3557. ''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.''
  3558. FROM [?].sys.database_files f
  3559. WHERE is_percent_growth = 0 and growth=128 and size > 128000 ';
  3560. END
  3561.  
  3562.  
  3563.  
  3564. IF NOT EXISTS ( SELECT 1
  3565. FROM #SkipChecks
  3566. WHERE DatabaseName IS NULL AND CheckID = 33 )
  3567. BEGIN
  3568. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  3569. AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
  3570. BEGIN
  3571. EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults
  3572. (CheckID,
  3573. DatabaseName,
  3574. Priority,
  3575. FindingsGroup,
  3576. Finding,
  3577. URL,
  3578. Details)
  3579. SELECT DISTINCT 33,
  3580. db_name(),
  3581. 200,
  3582. ''Licensing'',
  3583. ''Enterprise Edition Features In Use'',
  3584. ''http://BrentOzar.com/go/ee'',
  3585. (''The ['' + DB_NAME() + ''] database is using '' + feature_name + ''. If this database is restored onto a Standard Edition server, the restore will fail.'')
  3586. FROM [?].sys.dm_db_persisted_sku_features';
  3587. END;
  3588. END
  3589.  
  3590.  
  3591. IF NOT EXISTS ( SELECT 1
  3592. FROM #SkipChecks
  3593. WHERE DatabaseName IS NULL AND CheckID = 19 )
  3594. BEGIN
  3595. /* Method 1: Check sys.databases parameters */
  3596. INSERT INTO #BlitzResults
  3597. ( CheckID ,
  3598. DatabaseName ,
  3599. Priority ,
  3600. FindingsGroup ,
  3601. Finding ,
  3602. URL ,
  3603. Details
  3604. )
  3605.  
  3606. SELECT 19 AS CheckID ,
  3607. [name] AS DatabaseName ,
  3608. 200 AS Priority ,
  3609. 'Informational' AS FindingsGroup ,
  3610. 'Replication In Use' AS Finding ,
  3611. 'http://BrentOzar.com/go/repl' AS URL ,
  3612. ( 'Database [' + [name]
  3613. + '] is a replication publisher, subscriber, or distributor.' ) AS Details
  3614. FROM sys.databases
  3615. WHERE name NOT IN ( SELECT DISTINCT
  3616. DatabaseName
  3617. FROM #SkipChecks
  3618. WHERE CheckID IS NULL)
  3619. AND is_published = 1
  3620. OR is_subscribed = 1
  3621. OR is_merge_published = 1
  3622. OR is_distributor = 1;
  3623.  
  3624. /* Method B: check subscribers for MSreplication_objects tables */
  3625. EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults
  3626. (CheckID,
  3627. DatabaseName,
  3628. Priority,
  3629. FindingsGroup,
  3630. Finding,
  3631. URL,
  3632. Details)
  3633. SELECT DISTINCT 19,
  3634. db_name(),
  3635. 200,
  3636. ''Informational'',
  3637. ''Replication In Use'',
  3638. ''http://BrentOzar.com/go/repl'',
  3639. (''['' + DB_NAME() + ''] has MSreplication_objects tables in it, indicating it is a replication subscriber.'')
  3640. FROM [?].sys.tables
  3641. WHERE name = ''dbo.MSreplication_objects'' AND ''?'' <> ''master''';
  3642.  
  3643. END
  3644.  
  3645.  
  3646.  
  3647. IF NOT EXISTS ( SELECT 1
  3648. FROM #SkipChecks
  3649. WHERE DatabaseName IS NULL AND CheckID = 32 )
  3650. BEGIN
  3651. EXEC dbo.sp_MSforeachdb 'USE [?];
  3652. INSERT INTO #BlitzResults
  3653. (CheckID,
  3654. DatabaseName,
  3655. Priority,
  3656. FindingsGroup,
  3657. Finding,
  3658. URL,
  3659. Details)
  3660. SELECT 32,
  3661. ''?'',
  3662. 150,
  3663. ''Performance'',
  3664. ''Triggers on Tables'',
  3665. ''http://BrentOzar.com/go/trig'',
  3666. (''The ['' + DB_NAME() + ''] database has '' + CAST(SUM(1) AS NVARCHAR(50)) + '' triggers.'')
  3667. FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id
  3668. INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id WHERE t.is_ms_shipped = 0 AND DB_NAME() != ''ReportServer''
  3669. HAVING SUM(1) > 0';
  3670. END
  3671.  
  3672. IF NOT EXISTS ( SELECT 1
  3673. FROM #SkipChecks
  3674. WHERE DatabaseName IS NULL AND CheckID = 38 )
  3675. BEGIN
  3676. EXEC dbo.sp_MSforeachdb 'USE [?];
  3677. INSERT INTO #BlitzResults
  3678. (CheckID,
  3679. DatabaseName,
  3680. Priority,
  3681. FindingsGroup,
  3682. Finding,
  3683. URL,
  3684. Details)
  3685. SELECT DISTINCT 38,
  3686. ''?'',
  3687. 110,
  3688. ''Performance'',
  3689. ''Active Tables Without Clustered Indexes'',
  3690. ''http://BrentOzar.com/go/heaps'',
  3691. (''The ['' + DB_NAME() + ''] database has heaps - tables without a clustered index - that are being actively queried.'')
  3692. FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id
  3693. INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id
  3694. INNER JOIN sys.databases sd ON sd.name = ''?''
  3695. 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
  3696. WHERE i.type_desc = ''HEAP'' AND COALESCE(ius.user_seeks, ius.user_scans, ius.user_lookups, ius.user_updates) IS NOT NULL
  3697. AND sd.name <> ''tempdb'' AND o.is_ms_shipped = 0 AND o.type <> ''S''';
  3698. END
  3699.  
  3700. IF NOT EXISTS ( SELECT 1
  3701. FROM #SkipChecks
  3702. WHERE DatabaseName IS NULL AND CheckID = 164 )
  3703. AND EXISTS(SELECT * FROM sys.all_objects WHERE name = 'fn_validate_plan_guide')
  3704. BEGIN
  3705. EXEC dbo.sp_MSforeachdb 'USE [?];
  3706. INSERT INTO #BlitzResults
  3707. (CheckID,
  3708. DatabaseName,
  3709. Priority,
  3710. FindingsGroup,
  3711. Finding,
  3712. URL,
  3713. Details)
  3714. SELECT DISTINCT 164,
  3715. ''?'',
  3716. 20,
  3717. ''Reliability'',
  3718. ''Plan Guides Failing'',
  3719. ''http://BrentOzar.com/go/misguided'',
  3720. (''The ['' + DB_NAME() + ''] database has plan guides that are no longer valid, so the queries involved may be failing silently.'')
  3721. FROM [?].sys.plan_guides g CROSS APPLY fn_validate_plan_guide(g.plan_guide_id)';
  3722. END
  3723.  
  3724. IF NOT EXISTS ( SELECT 1
  3725. FROM #SkipChecks
  3726. WHERE DatabaseName IS NULL AND CheckID = 39 )
  3727. BEGIN
  3728. EXEC dbo.sp_MSforeachdb 'USE [?];
  3729. INSERT INTO #BlitzResults
  3730. (CheckID,
  3731. DatabaseName,
  3732. Priority,
  3733. FindingsGroup,
  3734. Finding,
  3735. URL,
  3736. Details)
  3737. SELECT DISTINCT 39,
  3738. ''?'',
  3739. 150,
  3740. ''Performance'',
  3741. ''Inactive Tables Without Clustered Indexes'',
  3742. ''http://BrentOzar.com/go/heaps'',
  3743. (''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.'')
  3744. FROM [?].sys.indexes i INNER JOIN [?].sys.objects o ON i.object_id = o.object_id
  3745. INNER JOIN [?].sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id
  3746. INNER JOIN sys.databases sd ON sd.name = ''?''
  3747. 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
  3748. WHERE i.type_desc = ''HEAP'' AND COALESCE(ius.user_seeks, ius.user_scans, ius.user_lookups, ius.user_updates) IS NULL
  3749. AND sd.name <> ''tempdb'' AND o.is_ms_shipped = 0 AND o.type <> ''S''';
  3750. END
  3751.  
  3752. IF NOT EXISTS ( SELECT 1
  3753. FROM #SkipChecks
  3754. WHERE DatabaseName IS NULL AND CheckID = 46 )
  3755. BEGIN
  3756. EXEC dbo.sp_MSforeachdb 'USE [?];
  3757. INSERT INTO #BlitzResults
  3758. (CheckID,
  3759. DatabaseName,
  3760. Priority,
  3761. FindingsGroup,
  3762. Finding,
  3763. URL,
  3764. Details)
  3765. SELECT 46,
  3766. ''?'',
  3767. 150,
  3768. ''Performance'',
  3769. ''Leftover Fake Indexes From Wizards'',
  3770. ''http://BrentOzar.com/go/hypo'',
  3771. (''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.'')
  3772. 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
  3773. WHERE i.is_hypothetical = 1';
  3774. END
  3775.  
  3776. IF NOT EXISTS ( SELECT 1
  3777. FROM #SkipChecks
  3778. WHERE DatabaseName IS NULL AND CheckID = 47 )
  3779. BEGIN
  3780. EXEC dbo.sp_MSforeachdb 'USE [?];
  3781. INSERT INTO #BlitzResults
  3782. (CheckID,
  3783. DatabaseName,
  3784. Priority,
  3785. FindingsGroup,
  3786. Finding,
  3787. URL,
  3788. Details)
  3789. SELECT 47,
  3790. ''?'',
  3791. 100,
  3792. ''Performance'',
  3793. ''Indexes Disabled'',
  3794. ''http://BrentOzar.com/go/ixoff'',
  3795. (''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.'')
  3796. 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
  3797. WHERE i.is_disabled = 1';
  3798. END
  3799.  
  3800.  
  3801. IF NOT EXISTS ( SELECT 1
  3802. FROM #SkipChecks
  3803. WHERE DatabaseName IS NULL AND CheckID = 48 )
  3804. BEGIN
  3805. EXEC dbo.sp_MSforeachdb 'USE [?];
  3806. INSERT INTO #BlitzResults
  3807. (CheckID,
  3808. DatabaseName,
  3809. Priority,
  3810. FindingsGroup,
  3811. Finding,
  3812. URL,
  3813. Details)
  3814. SELECT DISTINCT 48,
  3815. ''?'',
  3816. 150,
  3817. ''Performance'',
  3818. ''Foreign Keys Not Trusted'',
  3819. ''http://BrentOzar.com/go/trust'',
  3820. (''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.'')
  3821. 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
  3822. 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'')';
  3823. END
  3824.  
  3825. IF NOT EXISTS ( SELECT 1
  3826. FROM #SkipChecks
  3827. WHERE DatabaseName IS NULL AND CheckID = 56 )
  3828. BEGIN
  3829. EXEC dbo.sp_MSforeachdb 'USE [?];
  3830. INSERT INTO #BlitzResults
  3831. (CheckID,
  3832. DatabaseName,
  3833. Priority,
  3834. FindingsGroup,
  3835. Finding,
  3836. URL,
  3837. Details)
  3838. SELECT 56,
  3839. ''?'',
  3840. 150,
  3841. ''Performance'',
  3842. ''Check Constraint Not Trusted'',
  3843. ''http://BrentOzar.com/go/trust'',
  3844. (''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.'')
  3845. from [?].sys.check_constraints i INNER JOIN [?].sys.objects o ON i.parent_object_id = o.object_id
  3846. INNER JOIN [?].sys.schemas s ON o.schema_id = s.schema_id
  3847. WHERE i.is_not_trusted = 1 AND i.is_not_for_replication = 0 AND i.is_disabled = 0';
  3848. END
  3849.  
  3850. IF NOT EXISTS ( SELECT 1
  3851. FROM #SkipChecks
  3852. WHERE DatabaseName IS NULL AND CheckID = 95 )
  3853. BEGIN
  3854. IF @@VERSION NOT LIKE '%Microsoft SQL Server 2000%'
  3855. AND @@VERSION NOT LIKE '%Microsoft SQL Server 2005%'
  3856. BEGIN
  3857. EXEC dbo.sp_MSforeachdb 'USE [?];
  3858. INSERT INTO #BlitzResults
  3859. (CheckID,
  3860. DatabaseName,
  3861. Priority,
  3862. FindingsGroup,
  3863. Finding,
  3864. URL,
  3865. Details)
  3866. SELECT TOP 1 95 AS CheckID,
  3867. ''?'' as DatabaseName,
  3868. 110 AS Priority,
  3869. ''Performance'' AS FindingsGroup,
  3870. ''Plan Guides Enabled'' AS Finding,
  3871. ''http://BrentOzar.com/go/guides'' AS URL,
  3872. (''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
  3873. FROM [?].sys.plan_guides WHERE is_disabled = 0'
  3874. END;
  3875. END
  3876.  
  3877. IF NOT EXISTS ( SELECT 1
  3878. FROM #SkipChecks
  3879. WHERE DatabaseName IS NULL AND CheckID = 60 )
  3880. BEGIN
  3881. EXEC sp_MSforeachdb 'USE [?];
  3882. INSERT INTO #BlitzResults
  3883. (CheckID,
  3884. DatabaseName,
  3885. Priority,
  3886. FindingsGroup,
  3887. Finding,
  3888. URL,
  3889. Details)
  3890. SELECT DISTINCT 60 AS CheckID,
  3891. ''?'' as DatabaseName,
  3892. 100 AS Priority,
  3893. ''Performance'' AS FindingsGroup,
  3894. ''Fill Factor Changed'',
  3895. ''http://brentozar.com/go/fillfactor'' AS URL,
  3896. ''The ['' + DB_NAME() + ''] database has objects with fill factor < 80%. This can cause memory and storage performance problems, but may also prevent page splits.''
  3897. FROM [?].sys.indexes
  3898. WHERE fill_factor <> 0 AND fill_factor < 80 AND is_disabled = 0 AND is_hypothetical = 0';
  3899. END
  3900.  
  3901.  
  3902.  
  3903. IF NOT EXISTS ( SELECT 1
  3904. FROM #SkipChecks
  3905. WHERE DatabaseName IS NULL AND CheckID = 78 )
  3906. BEGIN
  3907. EXECUTE master.sys.sp_MSforeachdb 'USE [?];
  3908. INSERT INTO #Recompile
  3909. SELECT DBName = DB_Name(), SPName = SO.name, SM.is_recompiled, ISR.SPECIFIC_SCHEMA
  3910. FROM sys.sql_modules AS SM
  3911. LEFT OUTER JOIN master.sys.databases AS sDB ON SM.object_id = DB_id()
  3912. LEFT OUTER JOIN dbo.sysobjects AS SO ON SM.object_id = SO.id and type = ''P''
  3913. LEFT OUTER JOIN INFORMATION_SCHEMA.ROUTINES AS ISR on ISR.Routine_Name = SO.name AND ISR.SPECIFIC_CATALOG = DB_Name()
  3914. WHERE SM.is_recompiled=1
  3915. '
  3916. INSERT INTO #BlitzResults
  3917. (Priority,
  3918. FindingsGroup,
  3919. Finding,
  3920. DatabaseName,
  3921. URL,
  3922. Details,
  3923. CheckID)
  3924. SELECT [Priority] = '100',
  3925. FindingsGroup = 'Performance',
  3926. Finding = 'Stored Procedure WITH RECOMPILE',
  3927. DatabaseName = DBName,
  3928. URL = 'http://BrentOzar.com/go/recompile',
  3929. 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.',
  3930. CheckID = '78'
  3931. FROM #Recompile AS TR WHERE ProcName NOT LIKE 'sp_AskBrent%' AND ProcName NOT LIKE 'sp_Blitz%'
  3932. DROP TABLE #Recompile;
  3933. END
  3934.  
  3935.  
  3936.  
  3937. IF NOT EXISTS ( SELECT 1
  3938. FROM #SkipChecks
  3939. WHERE DatabaseName IS NULL AND CheckID = 86 )
  3940. BEGIN
  3941. EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 86, DB_NAME(), 20, ''Security'', ''Elevated Permissions on a Database'', ''http://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'')';
  3942. END
  3943.  
  3944.  
  3945. /*Check for non-aligned indexes in partioned databases*/
  3946.  
  3947. IF NOT EXISTS ( SELECT 1
  3948. FROM #SkipChecks
  3949. WHERE DatabaseName IS NULL AND CheckID = 72 )
  3950. BEGIN
  3951. EXEC dbo.sp_MSforeachdb 'USE [?];
  3952. insert into #partdb(dbname, objectname, type_desc)
  3953. SELECT distinct db_name(DB_ID()) as DBName,o.name Object_Name,ds.type_desc
  3954. FROM sys.objects AS o JOIN sys.indexes AS i ON o.object_id = i.object_id
  3955. JOIN sys.data_spaces ds on ds.data_space_id = i.data_space_id
  3956. 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()
  3957. WHERE o.type = ''u''
  3958. -- Clustered and Non-Clustered indexes
  3959. AND i.type IN (1, 2)
  3960. AND o.object_id in
  3961. (
  3962. SELECT a.object_id from
  3963. (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
  3964. GROUP BY ob.object_id, ds.type_desc ) a group by a.object_id having COUNT (*) > 1
  3965. )'
  3966. INSERT INTO #BlitzResults
  3967. ( CheckID ,
  3968. DatabaseName ,
  3969. Priority ,
  3970. FindingsGroup ,
  3971. Finding ,
  3972. URL ,
  3973. Details
  3974. )
  3975. SELECT DISTINCT
  3976. 72 AS CheckID ,
  3977. dbname AS DatabaseName ,
  3978. 100 AS Priority ,
  3979. 'Performance' AS FindingsGroup ,
  3980. 'The partitioned database ' + dbname
  3981. + ' may have non-aligned indexes' AS Finding ,
  3982. 'http://BrentOzar.com/go/aligned' AS URL ,
  3983. 'Having non-aligned indexes on partitioned tables may cause inefficient query plans and CPU pressure' AS Details
  3984. FROM #partdb
  3985. WHERE dbname IS NOT NULL
  3986. AND dbname NOT IN ( SELECT DISTINCT
  3987. DatabaseName
  3988. FROM #SkipChecks
  3989. WHERE CheckID IS NULL)
  3990. DROP TABLE #partdb
  3991. END
  3992.  
  3993.  
  3994. IF NOT EXISTS ( SELECT 1
  3995. FROM #SkipChecks
  3996. WHERE DatabaseName IS NULL AND CheckID = 113 )
  3997. BEGIN
  3998. EXEC dbo.sp_MSforeachdb 'USE [?];
  3999. INSERT INTO #BlitzResults
  4000. (CheckID,
  4001. DatabaseName,
  4002. Priority,
  4003. FindingsGroup,
  4004. Finding,
  4005. URL,
  4006. Details)
  4007. SELECT DISTINCT 113,
  4008. ''?'',
  4009. 50,
  4010. ''Reliability'',
  4011. ''Full Text Indexes Not Updating'',
  4012. ''http://BrentOzar.com/go/fulltext'',
  4013. (''At least one full text index in this database has not been crawled in the last week.'')
  4014. 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())';
  4015. END
  4016.  
  4017. IF NOT EXISTS ( SELECT 1
  4018. FROM #SkipChecks
  4019. WHERE DatabaseName IS NULL AND CheckID = 115 )
  4020. BEGIN
  4021. EXEC dbo.sp_MSforeachdb 'USE [?];
  4022. INSERT INTO #BlitzResults
  4023. (CheckID,
  4024. DatabaseName,
  4025. Priority,
  4026. FindingsGroup,
  4027. Finding,
  4028. URL,
  4029. Details)
  4030. SELECT 115,
  4031. ''?'',
  4032. 110,
  4033. ''Performance'',
  4034. ''Parallelism Rocket Surgery'',
  4035. ''http://BrentOzar.com/go/makeparallel'',
  4036. (''['' + DB_NAME() + ''] has a make_parallel function, indicating that an advanced developer may be manhandling SQL Server into forcing queries to go parallel.'')
  4037. from [?].INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = ''make_parallel'' AND ROUTINE_TYPE = ''FUNCTION''';
  4038. END
  4039.  
  4040.  
  4041. IF NOT EXISTS ( SELECT 1
  4042. FROM #SkipChecks
  4043. WHERE DatabaseName IS NULL AND CheckID = 122 )
  4044. BEGIN
  4045. /* SQL Server 2012 and newer uses temporary stats for AlwaysOn Availability Groups, and those show up as user-created */
  4046. IF EXISTS (SELECT *
  4047. FROM sys.all_columns c
  4048. INNER JOIN sys.all_objects o ON c.object_id = o.object_id
  4049. WHERE c.name = 'is_temporary' AND o.name = 'stats')
  4050.  
  4051. EXEC dbo.sp_MSforeachdb 'USE [?];
  4052. INSERT INTO #BlitzResults
  4053. (CheckID,
  4054. DatabaseName,
  4055. Priority,
  4056. FindingsGroup,
  4057. Finding,
  4058. URL,
  4059. Details)
  4060. SELECT TOP 1 122,
  4061. ''?'',
  4062. 200,
  4063. ''Performance'',
  4064. ''User-Created Statistics In Place'',
  4065. ''http://BrentOzar.com/go/userstats'',
  4066. (''['' + 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.'')
  4067. from [?].sys.stats WHERE user_created = 1 AND is_temporary = 0
  4068. HAVING SUM(1) > 0;';
  4069.  
  4070. ELSE
  4071. EXEC dbo.sp_MSforeachdb 'USE [?];
  4072. INSERT INTO #BlitzResults
  4073. (CheckID,
  4074. DatabaseName,
  4075. Priority,
  4076. FindingsGroup,
  4077. Finding,
  4078. URL,
  4079. Details)
  4080. SELECT 122,
  4081. ''?'',
  4082. 200,
  4083. ''Performance'',
  4084. ''User-Created Statistics In Place'',
  4085. ''http://BrentOzar.com/go/userstats'',
  4086. (''['' + 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.'')
  4087. from [?].sys.stats WHERE user_created = 1
  4088. HAVING SUM(1) > 0;';
  4089.  
  4090.  
  4091. END /* IF NOT EXISTS ( SELECT 1 */
  4092.  
  4093.  
  4094. /*Check for high VLF count: this will omit any database snapshots*/
  4095.  
  4096. IF NOT EXISTS ( SELECT 1
  4097. FROM #SkipChecks
  4098. WHERE DatabaseName IS NULL AND CheckID = 69 )
  4099. BEGIN
  4100. IF @ProductVersionMajor >= 11
  4101.  
  4102. BEGIN
  4103. EXEC sp_MSforeachdb N'USE [?];
  4104. INSERT INTO #LogInfo2012
  4105. EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS'';
  4106. IF @@ROWCOUNT > 999
  4107. BEGIN
  4108. INSERT INTO #BlitzResults
  4109. ( CheckID
  4110. ,DatabaseName
  4111. ,Priority
  4112. ,FindingsGroup
  4113. ,Finding
  4114. ,URL
  4115. ,Details)
  4116. SELECT 69
  4117. ,DB_NAME()
  4118. ,170
  4119. ,''File Configuration''
  4120. ,''High VLF Count''
  4121. ,''http://BrentOzar.com/go/vlf''
  4122. ,''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.''
  4123. FROM #LogInfo2012
  4124. WHERE EXISTS (SELECT name FROM master.sys.databases
  4125. WHERE source_database_id is null) ;
  4126. END
  4127. TRUNCATE TABLE #LogInfo2012;'
  4128. DROP TABLE #LogInfo2012;
  4129. END
  4130. ELSE
  4131. BEGIN
  4132. EXEC sp_MSforeachdb N'USE [?];
  4133. INSERT INTO #LogInfo
  4134. EXEC sp_executesql N''DBCC LogInfo() WITH NO_INFOMSGS'';
  4135. IF @@ROWCOUNT > 999
  4136. BEGIN
  4137. INSERT INTO #BlitzResults
  4138. ( CheckID
  4139. ,DatabaseName
  4140. ,Priority
  4141. ,FindingsGroup
  4142. ,Finding
  4143. ,URL
  4144. ,Details)
  4145. SELECT 69
  4146. ,DB_NAME()
  4147. ,170
  4148. ,''File Configuration''
  4149. ,''High VLF Count''
  4150. ,''http://BrentOzar.com/go/vlf''
  4151. ,''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.''
  4152. FROM #LogInfo
  4153. WHERE EXISTS (SELECT name FROM master.sys.databases
  4154. WHERE source_database_id is null);
  4155. END
  4156. TRUNCATE TABLE #LogInfo;'
  4157. DROP TABLE #LogInfo;
  4158. END
  4159. END
  4160.  
  4161.  
  4162. IF NOT EXISTS ( SELECT 1
  4163. FROM #SkipChecks
  4164. WHERE DatabaseName IS NULL AND CheckID = 80 )
  4165. BEGIN
  4166. EXEC dbo.sp_MSforeachdb 'USE [?]; INSERT INTO #BlitzResults (CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details) SELECT DISTINCT 80, DB_NAME(), 50, ''Reliability'', ''Max File Size Set'', ''http://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';
  4167. END
  4168.  
  4169. END /* IF @CheckUserDatabaseObjects = 1 */
  4170.  
  4171. IF @CheckProcedureCache = 1
  4172. BEGIN
  4173.  
  4174. IF NOT EXISTS ( SELECT 1
  4175. FROM #SkipChecks
  4176. WHERE DatabaseName IS NULL AND CheckID = 35 )
  4177. BEGIN
  4178. INSERT INTO #BlitzResults
  4179. ( CheckID ,
  4180. Priority ,
  4181. FindingsGroup ,
  4182. Finding ,
  4183. URL ,
  4184. Details
  4185. )
  4186. SELECT 35 AS CheckID ,
  4187. 100 AS Priority ,
  4188. 'Performance' AS FindingsGroup ,
  4189. 'Single-Use Plans in Procedure Cache' AS Finding ,
  4190. 'http://BrentOzar.com/go/single' AS URL ,
  4191. ( CAST(COUNT(*) AS VARCHAR(10))
  4192. + ' 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
  4193. FROM sys.dm_exec_cached_plans AS cp
  4194. WHERE cp.usecounts = 1
  4195. AND cp.objtype = 'Adhoc'
  4196. AND EXISTS ( SELECT
  4197. 1
  4198. FROM sys.configurations
  4199. WHERE
  4200. name = 'optimize for ad hoc workloads'
  4201. AND value_in_use = 0 )
  4202. HAVING COUNT(*) > 1;
  4203. END
  4204.  
  4205.  
  4206. /* Set up the cache tables. Different on 2005 since it doesn't support query_hash, query_plan_hash. */
  4207. IF @@VERSION LIKE '%Microsoft SQL Server 2005%'
  4208. BEGIN
  4209. IF @CheckProcedureCacheFilter = 'CPU'
  4210. OR @CheckProcedureCacheFilter IS NULL
  4211. BEGIN
  4212. 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])
  4213. 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]
  4214. FROM sys.dm_exec_query_stats qs
  4215. ORDER BY qs.total_worker_time DESC)
  4216. 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])
  4217. 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]
  4218. FROM queries qs
  4219. 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
  4220. WHERE qsCaught.sql_handle IS NULL;'
  4221. EXECUTE(@StringToExecute)
  4222. END
  4223.  
  4224. IF @CheckProcedureCacheFilter = 'Reads'
  4225. OR @CheckProcedureCacheFilter IS NULL
  4226. BEGIN
  4227. 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])
  4228. 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]
  4229. FROM sys.dm_exec_query_stats qs
  4230. ORDER BY qs.total_logical_reads DESC)
  4231. 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])
  4232. 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]
  4233. FROM queries qs
  4234. 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
  4235. WHERE qsCaught.sql_handle IS NULL;'
  4236. EXECUTE(@StringToExecute)
  4237. END
  4238.  
  4239. IF @CheckProcedureCacheFilter = 'ExecCount'
  4240. OR @CheckProcedureCacheFilter IS NULL
  4241. BEGIN
  4242. 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])
  4243. 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]
  4244. FROM sys.dm_exec_query_stats qs
  4245. ORDER BY qs.execution_count DESC)
  4246. 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])
  4247. 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]
  4248. FROM queries qs
  4249. 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
  4250. WHERE qsCaught.sql_handle IS NULL;'
  4251. EXECUTE(@StringToExecute)
  4252. END
  4253.  
  4254. IF @CheckProcedureCacheFilter = 'Duration'
  4255. OR @CheckProcedureCacheFilter IS NULL
  4256. BEGIN
  4257. 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])
  4258. 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]
  4259. FROM sys.dm_exec_query_stats qs
  4260. ORDER BY qs.total_elapsed_time DESC)
  4261. 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])
  4262. 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]
  4263. FROM queries qs
  4264. 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
  4265. WHERE qsCaught.sql_handle IS NULL;'
  4266. EXECUTE(@StringToExecute)
  4267. END
  4268.  
  4269. END;
  4270. IF @ProductVersionMajor >= 10
  4271. BEGIN
  4272. IF @CheckProcedureCacheFilter = 'CPU'
  4273. OR @CheckProcedureCacheFilter IS NULL
  4274. BEGIN
  4275. 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])
  4276. 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]
  4277. FROM sys.dm_exec_query_stats qs
  4278. ORDER BY qs.total_worker_time DESC)
  4279. 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])
  4280. 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]
  4281. FROM queries qs
  4282. 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
  4283. WHERE qsCaught.sql_handle IS NULL;'
  4284. EXECUTE(@StringToExecute)
  4285. END
  4286.  
  4287. IF @CheckProcedureCacheFilter = 'Reads'
  4288. OR @CheckProcedureCacheFilter IS NULL
  4289. BEGIN
  4290. 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])
  4291. 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]
  4292. FROM sys.dm_exec_query_stats qs
  4293. ORDER BY qs.total_logical_reads DESC)
  4294. 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])
  4295. 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]
  4296. FROM queries qs
  4297. 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
  4298. WHERE qsCaught.sql_handle IS NULL;'
  4299. EXECUTE(@StringToExecute)
  4300. END
  4301.  
  4302. IF @CheckProcedureCacheFilter = 'ExecCount'
  4303. OR @CheckProcedureCacheFilter IS NULL
  4304. BEGIN
  4305. 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])
  4306. 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]
  4307. FROM sys.dm_exec_query_stats qs
  4308. ORDER BY qs.execution_count DESC)
  4309. 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])
  4310. 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]
  4311. FROM queries qs
  4312. 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
  4313. WHERE qsCaught.sql_handle IS NULL;'
  4314. EXECUTE(@StringToExecute)
  4315. END
  4316.  
  4317. IF @CheckProcedureCacheFilter = 'Duration'
  4318. OR @CheckProcedureCacheFilter IS NULL
  4319. BEGIN
  4320. 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])
  4321. 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]
  4322. FROM sys.dm_exec_query_stats qs
  4323. ORDER BY qs.total_elapsed_time DESC)
  4324. 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])
  4325. 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]
  4326. FROM queries qs
  4327. 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
  4328. WHERE qsCaught.sql_handle IS NULL;'
  4329. EXECUTE(@StringToExecute)
  4330. END
  4331.  
  4332. /* Populate the query_plan_filtered field. Only works in 2005SP2+, but we're just doing it in 2008 to be safe. */
  4333. UPDATE #dm_exec_query_stats
  4334. SET query_plan_filtered = qp.query_plan
  4335. FROM #dm_exec_query_stats qs
  4336. CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle,
  4337. qs.statement_start_offset,
  4338. qs.statement_end_offset)
  4339. AS qp
  4340.  
  4341. END;
  4342.  
  4343. /* Populate the additional query_plan, text, and text_filtered fields */
  4344. UPDATE #dm_exec_query_stats
  4345. SET query_plan = qp.query_plan ,
  4346. [text] = st.[text] ,
  4347. text_filtered = SUBSTRING(st.text,
  4348. ( qs.statement_start_offset
  4349. / 2 ) + 1,
  4350. ( ( CASE qs.statement_end_offset
  4351. WHEN -1
  4352. THEN DATALENGTH(st.text)
  4353. ELSE qs.statement_end_offset
  4354. END
  4355. - qs.statement_start_offset )
  4356. / 2 ) + 1)
  4357. FROM #dm_exec_query_stats qs
  4358. CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
  4359. CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle)
  4360. AS qp
  4361.  
  4362. /* Dump instances of our own script. We're not trying to tune ourselves. */
  4363. DELETE #dm_exec_query_stats
  4364. WHERE text LIKE '%sp_Blitz%'
  4365. OR text LIKE '%#BlitzResults%'
  4366.  
  4367. /* Look for implicit conversions */
  4368.  
  4369. IF NOT EXISTS ( SELECT 1
  4370. FROM #SkipChecks
  4371. WHERE DatabaseName IS NULL AND CheckID = 63 )
  4372. BEGIN
  4373. INSERT INTO #BlitzResults
  4374. ( CheckID ,
  4375. Priority ,
  4376. FindingsGroup ,
  4377. Finding ,
  4378. URL ,
  4379. Details ,
  4380. QueryPlan ,
  4381. QueryPlanFiltered
  4382. )
  4383. SELECT 63 AS CheckID ,
  4384. 120 AS Priority ,
  4385. 'Query Plans' AS FindingsGroup ,
  4386. 'Implicit Conversion' AS Finding ,
  4387. 'http://BrentOzar.com/go/implicit' AS URL ,
  4388. ( 'One of the top resource-intensive queries is comparing two fields that are not the same datatype.' ) AS Details ,
  4389. qs.query_plan ,
  4390. qs.query_plan_filtered
  4391. FROM #dm_exec_query_stats qs
  4392. WHERE COALESCE(qs.query_plan_filtered,
  4393. CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%CONVERT_IMPLICIT%'
  4394. AND COALESCE(qs.query_plan_filtered,
  4395. CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%PhysicalOp="Index Scan"%'
  4396. END
  4397.  
  4398. IF NOT EXISTS ( SELECT 1
  4399. FROM #SkipChecks
  4400. WHERE DatabaseName IS NULL AND CheckID = 64 )
  4401. BEGIN
  4402. INSERT INTO #BlitzResults
  4403. ( CheckID ,
  4404. Priority ,
  4405. FindingsGroup ,
  4406. Finding ,
  4407. URL ,
  4408. Details ,
  4409. QueryPlan ,
  4410. QueryPlanFiltered
  4411. )
  4412. SELECT 64 AS CheckID ,
  4413. 120 AS Priority ,
  4414. 'Query Plans' AS FindingsGroup ,
  4415. 'Implicit Conversion Affecting Cardinality' AS Finding ,
  4416. 'http://BrentOzar.com/go/implicit' AS URL ,
  4417. ( 'One of the top resource-intensive queries has an implicit conversion that is affecting cardinality estimation.' ) AS Details ,
  4418. qs.query_plan ,
  4419. qs.query_plan_filtered
  4420. FROM #dm_exec_query_stats qs
  4421. WHERE COALESCE(qs.query_plan_filtered,
  4422. CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<PlanAffectingConvert ConvertIssue="Cardinality Estimate" Expression="CONVERT_IMPLICIT%'
  4423. END
  4424.  
  4425. /* @cms4j, 29.11.2013: Look for RID or Key Lookups */
  4426. IF NOT EXISTS ( SELECT 1
  4427. FROM #SkipChecks
  4428. WHERE DatabaseName IS NULL AND CheckID = 118 )
  4429. BEGIN
  4430. INSERT INTO #BlitzResults
  4431. ( CheckID ,
  4432. Priority ,
  4433. FindingsGroup ,
  4434. Finding ,
  4435. URL ,
  4436. Details ,
  4437. QueryPlan ,
  4438. QueryPlanFiltered
  4439. )
  4440. SELECT 118 AS CheckID ,
  4441. 120 AS Priority ,
  4442. 'Query Plans' AS FindingsGroup ,
  4443. 'RID or Key Lookups' AS Finding ,
  4444. 'http://BrentOzar.com/go/lookup' AS URL ,
  4445. 'One of the top resource-intensive queries contains RID or Key Lookups. Try to avoid them by creating covering indexes.' AS Details ,
  4446. qs.query_plan ,
  4447. qs.query_plan_filtered
  4448. FROM #dm_exec_query_stats qs
  4449. WHERE COALESCE(qs.query_plan_filtered,
  4450. CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%Lookup="1"%'
  4451. END /* @cms4j, 29.11.2013: Look for RID or Key Lookups */
  4452.  
  4453.  
  4454. /* Look for missing indexes */
  4455. IF NOT EXISTS ( SELECT 1
  4456. FROM #SkipChecks
  4457. WHERE DatabaseName IS NULL AND CheckID = 65 )
  4458. BEGIN
  4459. INSERT INTO #BlitzResults
  4460. ( CheckID ,
  4461. Priority ,
  4462. FindingsGroup ,
  4463. Finding ,
  4464. URL ,
  4465. Details ,
  4466. QueryPlan ,
  4467. QueryPlanFiltered
  4468. )
  4469. SELECT 65 AS CheckID ,
  4470. 120 AS Priority ,
  4471. 'Query Plans' AS FindingsGroup ,
  4472. 'Missing Index' AS Finding ,
  4473. 'http://BrentOzar.com/go/missingindex' AS URL ,
  4474. ( 'One of the top resource-intensive queries may be dramatically improved by adding an index.' ) AS Details ,
  4475. qs.query_plan ,
  4476. qs.query_plan_filtered
  4477. FROM #dm_exec_query_stats qs
  4478. WHERE COALESCE(qs.query_plan_filtered,
  4479. CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%MissingIndexGroup%'
  4480. END
  4481.  
  4482. /* Look for cursors */
  4483. IF NOT EXISTS ( SELECT 1
  4484. FROM #SkipChecks
  4485. WHERE DatabaseName IS NULL AND CheckID = 66 )
  4486. BEGIN
  4487. INSERT INTO #BlitzResults
  4488. ( CheckID ,
  4489. Priority ,
  4490. FindingsGroup ,
  4491. Finding ,
  4492. URL ,
  4493. Details ,
  4494. QueryPlan ,
  4495. QueryPlanFiltered
  4496. )
  4497. SELECT 66 AS CheckID ,
  4498. 120 AS Priority ,
  4499. 'Query Plans' AS FindingsGroup ,
  4500. 'Cursor' AS Finding ,
  4501. 'http://BrentOzar.com/go/cursor' AS URL ,
  4502. ( 'One of the top resource-intensive queries is using a cursor.' ) AS Details ,
  4503. qs.query_plan ,
  4504. qs.query_plan_filtered
  4505. FROM #dm_exec_query_stats qs
  4506. WHERE COALESCE(qs.query_plan_filtered,
  4507. CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<StmtCursor%'
  4508. END
  4509.  
  4510. /* Look for scalar user-defined functions */
  4511.  
  4512. IF NOT EXISTS ( SELECT 1
  4513. FROM #SkipChecks
  4514. WHERE DatabaseName IS NULL AND CheckID = 67 )
  4515. BEGIN
  4516. INSERT INTO #BlitzResults
  4517. ( CheckID ,
  4518. Priority ,
  4519. FindingsGroup ,
  4520. Finding ,
  4521. URL ,
  4522. Details ,
  4523. QueryPlan ,
  4524. QueryPlanFiltered
  4525. )
  4526. SELECT 67 AS CheckID ,
  4527. 120 AS Priority ,
  4528. 'Query Plans' AS FindingsGroup ,
  4529. 'Scalar UDFs' AS Finding ,
  4530. 'http://BrentOzar.com/go/functions' AS URL ,
  4531. ( 'One of the top resource-intensive queries is using a user-defined scalar function that may inhibit parallelism.' ) AS Details ,
  4532. qs.query_plan ,
  4533. qs.query_plan_filtered
  4534. FROM #dm_exec_query_stats qs
  4535. WHERE COALESCE(qs.query_plan_filtered,
  4536. CAST(qs.query_plan AS NVARCHAR(MAX))) LIKE '%<UserDefinedFunction%'
  4537. END
  4538.  
  4539. END /* IF @CheckProcedureCache = 1 */
  4540.  
  4541. /*Check for the last good DBCC CHECKDB date */
  4542. IF NOT EXISTS ( SELECT 1
  4543. FROM #SkipChecks
  4544. WHERE DatabaseName IS NULL AND CheckID = 68 )
  4545. BEGIN
  4546. EXEC sp_MSforeachdb N'USE [?];
  4547. INSERT #DBCCs
  4548. (ParentObject,
  4549. Object,
  4550. Field,
  4551. Value)
  4552. EXEC (''DBCC DBInfo() With TableResults, NO_INFOMSGS'');
  4553. UPDATE #DBCCs SET DbName = N''?'' WHERE DbName IS NULL;';
  4554.  
  4555. WITH DB2
  4556. AS ( SELECT DISTINCT
  4557. Field ,
  4558. Value ,
  4559. DbName
  4560. FROM #DBCCs
  4561. WHERE Field = 'dbi_dbccLastKnownGood'
  4562. )
  4563. INSERT INTO #BlitzResults
  4564. ( CheckID ,
  4565. DatabaseName ,
  4566. Priority ,
  4567. FindingsGroup ,
  4568. Finding ,
  4569. URL ,
  4570. Details
  4571. )
  4572. SELECT 68 AS CheckID ,
  4573. DB2.DbName AS DatabaseName ,
  4574. 50 AS PRIORITY ,
  4575. 'Reliability' AS FindingsGroup ,
  4576. 'Last good DBCC CHECKDB over 2 weeks old' AS Finding ,
  4577. 'http://BrentOzar.com/go/checkdb' AS URL ,
  4578. 'Database [' + DB2.DbName + ']'
  4579. + CASE DB2.Value
  4580. WHEN '1900-01-01 00:00:00.000'
  4581. THEN ' never had a successful DBCC CHECKDB.'
  4582. ELSE ' last had a successful DBCC CHECKDB run on '
  4583. + DB2.Value + '.'
  4584. END
  4585. + ' This check should be run regularly to catch any database corruption as soon as possible.'
  4586. + ' Note: you can restore a backup of a busy production database to a test server and run DBCC CHECKDB '
  4587. + ' against that to minimize impact. If you do that, you can ignore this warning.' AS Details
  4588. FROM DB2
  4589. WHERE DB2.DbName <> 'tempdb'
  4590. AND DB2.DbName NOT IN ( SELECT DISTINCT
  4591. DatabaseName
  4592. FROM
  4593. #SkipChecks
  4594. WHERE CheckID IS NULL)
  4595. AND CONVERT(DATETIME, DB2.Value, 121) < DATEADD(DD,
  4596. -14,
  4597. CURRENT_TIMESTAMP)
  4598. END
  4599.  
  4600.  
  4601.  
  4602.  
  4603. /*Verify that the servername is set */
  4604. IF NOT EXISTS ( SELECT 1
  4605. FROM #SkipChecks
  4606. WHERE DatabaseName IS NULL AND CheckID = 70 )
  4607. BEGIN
  4608. IF @@SERVERNAME IS NULL
  4609. BEGIN
  4610. INSERT INTO #BlitzResults
  4611. ( CheckID ,
  4612. Priority ,
  4613. FindingsGroup ,
  4614. Finding ,
  4615. URL ,
  4616. Details
  4617. )
  4618. SELECT 70 AS CheckID ,
  4619. 200 AS Priority ,
  4620. 'Informational' AS FindingsGroup ,
  4621. '@@Servername Not Set' AS Finding ,
  4622. 'http://BrentOzar.com/go/servername' AS URL ,
  4623. '@@Servername variable is null. You can fix it by executing: "sp_addserver ''<LocalServerName>'', local"' AS Details
  4624. END;
  4625.  
  4626. IF /* @@SERVERNAME IS set */
  4627. (@@SERVERNAME IS NOT NULL
  4628. AND
  4629. /* not a named instance */
  4630. CHARINDEX('\',CAST(SERVERPROPERTY('ServerName') AS NVARCHAR)) = 0
  4631. AND
  4632. /* not clustered, when computername may be different than the servername */
  4633. SERVERPROPERTY('IsClustered') = 0
  4634. AND
  4635. /* @@SERVERNAME is different than the computer name */
  4636. @@SERVERNAME <> CAST(ISNULL(SERVERPROPERTY('ComputerNamePhysicalNetBIOS'),@@SERVERNAME) AS NVARCHAR) )
  4637. BEGIN
  4638. INSERT INTO #BlitzResults
  4639. ( CheckID ,
  4640. Priority ,
  4641. FindingsGroup ,
  4642. Finding ,
  4643. URL ,
  4644. Details
  4645. )
  4646. SELECT 70 AS CheckID ,
  4647. 200 AS Priority ,
  4648. 'Configuration' AS FindingsGroup ,
  4649. '@@Servername Not Correct' AS Finding ,
  4650. 'http://BrentOzar.com/go/servername' AS URL ,
  4651. 'The @@Servername is different than the computer name, which may trigger certificate errors.' AS Details
  4652. END;
  4653.  
  4654. END
  4655. /*Check to see if a failsafe operator has been configured*/
  4656. IF NOT EXISTS ( SELECT 1
  4657. FROM #SkipChecks
  4658. WHERE DatabaseName IS NULL AND CheckID = 73 )
  4659. BEGIN
  4660.  
  4661. DECLARE @AlertInfo TABLE
  4662. (
  4663. FailSafeOperator NVARCHAR(255) ,
  4664. NotificationMethod INT ,
  4665. ForwardingServer NVARCHAR(255) ,
  4666. ForwardingSeverity INT ,
  4667. PagerToTemplate NVARCHAR(255) ,
  4668. PagerCCTemplate NVARCHAR(255) ,
  4669. PagerSubjectTemplate NVARCHAR(255) ,
  4670. PagerSendSubjectOnly NVARCHAR(255) ,
  4671. ForwardAlways INT
  4672. )
  4673. INSERT INTO @AlertInfo
  4674. EXEC [master].[dbo].[sp_MSgetalertinfo] @includeaddresses = 0
  4675. INSERT INTO #BlitzResults
  4676. ( CheckID ,
  4677. Priority ,
  4678. FindingsGroup ,
  4679. Finding ,
  4680. URL ,
  4681. Details
  4682. )
  4683. SELECT 73 AS CheckID ,
  4684. 200 AS Priority ,
  4685. 'Monitoring' AS FindingsGroup ,
  4686. 'No failsafe operator configured' AS Finding ,
  4687. 'http://BrentOzar.com/go/failsafe' AS URL ,
  4688. ( '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
  4689. FROM @AlertInfo
  4690. WHERE FailSafeOperator IS NULL;
  4691. END
  4692.  
  4693. /*Identify globally enabled trace flags*/
  4694. IF NOT EXISTS ( SELECT 1
  4695. FROM #SkipChecks
  4696. WHERE DatabaseName IS NULL AND CheckID = 74 )
  4697. BEGIN
  4698. INSERT INTO #TraceStatus
  4699. EXEC ( ' DBCC TRACESTATUS(-1) WITH NO_INFOMSGS'
  4700. )
  4701. INSERT INTO #BlitzResults
  4702. ( CheckID ,
  4703. Priority ,
  4704. FindingsGroup ,
  4705. Finding ,
  4706. URL ,
  4707. Details
  4708. )
  4709. SELECT 74 AS CheckID ,
  4710. 200 AS Priority ,
  4711. 'Informational' AS FindingsGroup ,
  4712. 'TraceFlag On' AS Finding ,
  4713. 'http://www.BrentOzar.com/go/traceflags/' AS URL ,
  4714. 'Trace flag ' +
  4715. CASE WHEN [T].[TraceFlag] = '2330' THEN 'You have Trace Flag 2330 enabled globally. Using this trace Flag disables missing index requests'
  4716. WHEN [T].[TraceFlag] = '1211' THEN 'You have Trace Flag 1211 enabled globally. Using this Trace Flag disables lock escalation when you least expect it. No Bueno!'
  4717. WHEN [T].[TraceFlag] = '1224' THEN 'You have Trace Flag 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.'
  4718. WHEN [T].[TraceFlag] = '652' THEN 'You have Trace Flag 652 enabled globally. Using this Trace Flag disables pre-fetching during index scans. If you hate slow queries, you should turn that off.'
  4719. WHEN [T].[TraceFlag] = '661' THEN 'You have Trace Flag 661 enabled globally. Using this Trace Flag disables ghost record removal. Who you gonna call? No one, turn that thing off.'
  4720. WHEN [T].[TraceFlag] = '1806' THEN 'You have Trace Flag 1806 enabled globally. Using this Trace Flag disables instant file initialization. I question your sanity.'
  4721. WHEN [T].[TraceFlag] = '3505' THEN 'You have Trace Flag 3505 enabled globally. Using this Trace Flag disables Checkpoints. Probably not the wisest idea.'
  4722. WHEN [T].[TraceFlag] = '8649' THEN 'You have Trace Flag 8649 enabled globally. Using this Trace Flag drops cost thresholf for parallelism down to 0. I hope this is a dev server.'
  4723. ELSE [T].[TraceFlag] + ' is enabled globally.' END
  4724. AS Details
  4725. FROM #TraceStatus T
  4726. END
  4727.  
  4728. /*Check for transaction log file larger than data file */
  4729. IF NOT EXISTS ( SELECT 1
  4730. FROM #SkipChecks
  4731. WHERE DatabaseName IS NULL AND CheckID = 75 )
  4732. BEGIN
  4733. INSERT INTO #BlitzResults
  4734. ( CheckID ,
  4735. DatabaseName ,
  4736. Priority ,
  4737. FindingsGroup ,
  4738. Finding ,
  4739. URL ,
  4740. Details
  4741. )
  4742. SELECT 75 AS CheckID ,
  4743. DB_NAME(a.database_id) ,
  4744. 50 AS Priority ,
  4745. 'Reliability' AS FindingsGroup ,
  4746. 'Transaction Log Larger than Data File' AS Finding ,
  4747. 'http://BrentOzar.com/go/biglog' AS URL ,
  4748. 'The database [' + DB_NAME(a.database_id)
  4749. + '] has a ' + CAST((a.size * 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
  4750. FROM sys.master_files a
  4751. WHERE a.type = 1
  4752. AND DB_NAME(a.database_id) NOT IN (
  4753. SELECT DISTINCT
  4754. DatabaseName
  4755. FROM #SkipChecks )
  4756. AND a.size > 125000 /* Size is measured in pages here, so this gets us log files over 1GB. */
  4757. AND a.size > ( SELECT SUM(CAST(b.size AS BIGINT))
  4758. FROM sys.master_files b
  4759. WHERE a.database_id = b.database_id
  4760. AND b.type = 0
  4761. )
  4762. AND a.database_id IN (
  4763. SELECT database_id
  4764. FROM sys.databases
  4765. WHERE source_database_id IS NULL )
  4766. END
  4767.  
  4768. /*Check for collation conflicts between user databases and tempdb */
  4769. IF NOT EXISTS ( SELECT 1
  4770. FROM #SkipChecks
  4771. WHERE DatabaseName IS NULL AND CheckID = 76 )
  4772. BEGIN
  4773. INSERT INTO #BlitzResults
  4774. ( CheckID ,
  4775. DatabaseName ,
  4776. Priority ,
  4777. FindingsGroup ,
  4778. Finding ,
  4779. URL ,
  4780. Details
  4781. )
  4782. SELECT 76 AS CheckID ,
  4783. name AS DatabaseName ,
  4784. 200 AS Priority ,
  4785. 'Informational' AS FindingsGroup ,
  4786. 'Collation is ' + collation_name AS Finding ,
  4787. 'http://BrentOzar.com/go/collate' AS URL ,
  4788. 'Collation differences between user databases and tempdb can cause conflicts especially when comparing string values' AS Details
  4789. FROM sys.databases
  4790. WHERE name NOT IN ( 'master', 'model', 'msdb')
  4791. AND name NOT LIKE 'ReportServer%'
  4792. AND name NOT IN ( SELECT DISTINCT
  4793. DatabaseName
  4794. FROM #SkipChecks
  4795. WHERE CheckID IS NULL)
  4796. AND collation_name <> ( SELECT
  4797. collation_name
  4798. FROM
  4799. sys.databases
  4800. WHERE
  4801. name = 'tempdb'
  4802. )
  4803. END
  4804.  
  4805. IF NOT EXISTS ( SELECT 1
  4806. FROM #SkipChecks
  4807. WHERE DatabaseName IS NULL AND CheckID = 77 )
  4808. BEGIN
  4809. INSERT INTO #BlitzResults
  4810. ( CheckID ,
  4811. DatabaseName ,
  4812. Priority ,
  4813. FindingsGroup ,
  4814. Finding ,
  4815. URL ,
  4816. Details
  4817. )
  4818. SELECT 77 AS CheckID ,
  4819. dSnap.[name] AS DatabaseName ,
  4820. 50 AS Priority ,
  4821. 'Reliability' AS FindingsGroup ,
  4822. 'Database Snapshot Online' AS Finding ,
  4823. 'http://BrentOzar.com/go/snapshot' AS URL ,
  4824. 'Database [' + dSnap.[name]
  4825. + '] is a snapshot of ['
  4826. + dOriginal.[name]
  4827. + ']. Make sure you have enough drive space to maintain the snapshot as the original database grows.' AS Details
  4828. FROM sys.databases dSnap
  4829. INNER JOIN sys.databases dOriginal ON dSnap.source_database_id = dOriginal.database_id
  4830. AND dSnap.name NOT IN (
  4831. SELECT DISTINCT
  4832. DatabaseName
  4833. FROM
  4834. #SkipChecks )
  4835. END
  4836.  
  4837. IF NOT EXISTS ( SELECT 1
  4838. FROM #SkipChecks
  4839. WHERE DatabaseName IS NULL AND CheckID = 79 )
  4840. BEGIN
  4841. INSERT INTO #BlitzResults
  4842. ( CheckID ,
  4843. Priority ,
  4844. FindingsGroup ,
  4845. Finding ,
  4846. URL ,
  4847. Details
  4848. )
  4849. SELECT 79 AS CheckID ,
  4850. 100 AS Priority ,
  4851. 'Performance' AS FindingsGroup ,
  4852. 'Shrink Database Job' AS Finding ,
  4853. 'http://BrentOzar.com/go/autoshrink' AS URL ,
  4854. 'In the [' + j.[name] + '] job, step ['
  4855. + step.[step_name]
  4856. + '] has SHRINKDATABASE or SHRINKFILE, which may be causing database fragmentation.' AS Details
  4857. FROM msdb.dbo.sysjobs j
  4858. INNER JOIN msdb.dbo.sysjobsteps step ON j.job_id = step.job_id
  4859. WHERE step.command LIKE N'%SHRINKDATABASE%'
  4860. OR step.command LIKE N'%SHRINKFILE%'
  4861. END
  4862.  
  4863. IF NOT EXISTS ( SELECT 1
  4864. FROM #SkipChecks
  4865. WHERE DatabaseName IS NULL AND CheckID = 81 )
  4866. BEGIN
  4867. INSERT INTO #BlitzResults
  4868. ( CheckID ,
  4869. Priority ,
  4870. FindingsGroup ,
  4871. Finding ,
  4872. URL ,
  4873. Details
  4874. )
  4875. SELECT 81 AS CheckID ,
  4876. 200 AS Priority ,
  4877. 'Non-Active Server Config' AS FindingsGroup ,
  4878. cr.name AS Finding ,
  4879. 'http://www.BrentOzar.com/blitz/sp_configure/' AS URL ,
  4880. ( 'This sp_configure option isn''t running under its set value. Its set value is '
  4881. + CAST(cr.[Value] AS VARCHAR(100))
  4882. + ' and its running value is '
  4883. + CAST(cr.value_in_use AS VARCHAR(100))
  4884. + '. When someone does a RECONFIGURE or restarts the instance, this setting will start taking effect.' ) AS Details
  4885. FROM sys.configurations cr
  4886. WHERE cr.value <> cr.value_in_use
  4887. AND NOT (cr.name = 'min server memory (MB)' AND cr.value IN (0,16) AND cr.value_in_use IN (0,16));
  4888. END
  4889.  
  4890. IF NOT EXISTS ( SELECT 1
  4891. FROM #SkipChecks
  4892. WHERE DatabaseName IS NULL AND CheckID = 123 )
  4893. BEGIN
  4894. INSERT INTO #BlitzResults
  4895. ( CheckID ,
  4896. Priority ,
  4897. FindingsGroup ,
  4898. Finding ,
  4899. URL ,
  4900. Details
  4901. )
  4902. SELECT TOP 1 123 AS CheckID ,
  4903. 200 AS Priority ,
  4904. 'Informational' AS FindingsGroup ,
  4905. 'Agent Jobs Starting Simultaneously' AS Finding ,
  4906. 'http://BrentOzar.com/go/busyagent/' AS URL ,
  4907. ( 'Multiple SQL Server Agent jobs are configured to start simultaneously. For detailed schedule listings, see the query in the URL.' ) AS Details
  4908. FROM msdb.dbo.sysjobactivity
  4909. WHERE start_execution_date > DATEADD(dd, -14, GETDATE())
  4910. GROUP BY start_execution_date HAVING COUNT(*) > 1;
  4911. END
  4912.  
  4913.  
  4914. IF @CheckServerInfo = 1
  4915. BEGIN
  4916.  
  4917. /*This checks Windows version. It would be better if Microsoft gave everything a separate build number, but whatever.*/
  4918. IF @ProductVersionMajor >= 10 AND @ProductVersionMinor >= 50
  4919. AND NOT EXISTS ( SELECT 1
  4920. FROM #SkipChecks
  4921. WHERE DatabaseName IS NULL AND CheckID = 172 )
  4922. BEGIN
  4923. IF EXISTS ( SELECT 1
  4924. FROM sys.all_objects
  4925. WHERE name = 'dm_os_windows_info' )
  4926.  
  4927. BEGIN
  4928. INSERT INTO [#BlitzResults]
  4929. ( [CheckID] ,
  4930. [Priority] ,
  4931. [FindingsGroup] ,
  4932. [Finding] ,
  4933. [URL] ,
  4934. [Details] )
  4935.  
  4936. SELECT
  4937. 172 AS [CheckID] ,
  4938. 250 AS [Priority] ,
  4939. 'Server Info' AS [FindingsGroup] ,
  4940. 'Windows Version' AS [Finding] ,
  4941. 'http://BrentOzar.com/go/' AS [URL] ,
  4942. ( CASE
  4943. WHEN [owi].[windows_release] = '5' THEN 'You''re running a really old version: Windows 2000, version ' + CAST([owi].[windows_release] AS VARCHAR(5))
  4944. 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))
  4945. 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))
  4946. 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))
  4947. 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))
  4948. WHEN [owi].[windows_release] > '6.3' THEN 'Hot dog! You''re living in the future! You''re running version ' + CAST([owi].[windows_release] AS VARCHAR(5))
  4949. ELSE 'I have no idea which version of Windows you''re on. Sorry.'
  4950. END
  4951. ) AS [Details]
  4952. FROM [sys].[dm_os_windows_info] [owi]
  4953.  
  4954. END;
  4955. END;
  4956.  
  4957. /*
  4958. This check hits the dm_os_process_memory system view
  4959. to see if locked_page_allocations_kb is > 0,
  4960. which could indicate that locked pages in memory is enabled.
  4961. */
  4962. IF @ProductVersionMajor >= 10 AND NOT EXISTS ( SELECT 1
  4963. FROM #SkipChecks
  4964. WHERE DatabaseName IS NULL AND CheckID = 166 )
  4965. BEGIN
  4966. INSERT INTO [#BlitzResults]
  4967. ( [CheckID] ,
  4968. [Priority] ,
  4969. [FindingsGroup] ,
  4970. [Finding] ,
  4971. [URL] ,
  4972. [Details] )
  4973. SELECT
  4974. 166 AS [CheckID] ,
  4975. 250 AS [Priority] ,
  4976. 'Server Info' AS [FindingsGroup] ,
  4977. 'Locked Pages In Memory Enabled' AS [Finding] ,
  4978. 'http://BrentOzar.com/go/lpim' AS [URL] ,
  4979. ( 'You currently have '
  4980. + CASE WHEN [dopm].[locked_page_allocations_kb] / 1024. / 1024. > 0
  4981. THEN CAST([dopm].[locked_page_allocations_kb] / 1024. / 1024. AS VARCHAR(100))
  4982. + ' GB'
  4983. ELSE CAST([dopm].[locked_page_allocations_kb] / 1024. AS VARCHAR(100))
  4984. + ' MB'
  4985. END + ' of pages locked in memory.' ) AS [Details]
  4986. FROM
  4987. [sys].[dm_os_process_memory] AS [dopm]
  4988. WHERE
  4989. [dopm].[locked_page_allocations_kb] > 0;
  4990. END;
  4991.  
  4992.  
  4993. IF NOT EXISTS ( SELECT 1
  4994. FROM #SkipChecks
  4995. WHERE DatabaseName IS NULL AND CheckID = 130 )
  4996. BEGIN
  4997. INSERT INTO #BlitzResults
  4998. ( CheckID ,
  4999. Priority ,
  5000. FindingsGroup ,
  5001. Finding ,
  5002. URL ,
  5003. Details
  5004. )
  5005. SELECT 130 AS CheckID ,
  5006. 250 AS Priority ,
  5007. 'Server Info' AS FindingsGroup ,
  5008. 'Server Name' AS Finding ,
  5009. 'http://BrentOzar.com/go/servername' AS URL ,
  5010. @@SERVERNAME AS Details
  5011. WHERE @@SERVERNAME IS NOT NULL;
  5012. END;
  5013.  
  5014.  
  5015.  
  5016. IF NOT EXISTS ( SELECT 1
  5017. FROM #SkipChecks
  5018. WHERE DatabaseName IS NULL AND CheckID = 83 )
  5019. BEGIN
  5020. IF EXISTS ( SELECT *
  5021. FROM sys.all_objects
  5022. WHERE name = 'dm_server_services' )
  5023. BEGIN
  5024. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  5025. SELECT 83 AS CheckID ,
  5026. 250 AS Priority ,
  5027. ''Server Info'' AS FindingsGroup ,
  5028. ''Services'' AS Finding ,
  5029. '''' AS URL ,
  5030. N''Service: '' + servicename + N'' runs under service account '' + service_account + N''. Last startup time: '' + COALESCE(CAST(CAST(last_startup_time AS DATETIME) AS VARCHAR(50)), ''not shown.'') + ''. Startup type: '' + startup_type_desc + N'', currently '' + status_desc + ''.''
  5031. FROM sys.dm_server_services;'
  5032. EXECUTE(@StringToExecute);
  5033. END
  5034. END
  5035.  
  5036. /* Check 84 - SQL Server 2012 */
  5037. IF NOT EXISTS ( SELECT 1
  5038. FROM #SkipChecks
  5039. WHERE DatabaseName IS NULL AND CheckID = 84 )
  5040. BEGIN
  5041. IF EXISTS ( SELECT *
  5042. FROM sys.all_objects o
  5043. INNER JOIN sys.all_columns c ON o.object_id = c.object_id
  5044. WHERE o.name = 'dm_os_sys_info'
  5045. AND c.name = 'physical_memory_kb' )
  5046. BEGIN
  5047. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  5048. SELECT 84 AS CheckID ,
  5049. 250 AS Priority ,
  5050. ''Server Info'' AS FindingsGroup ,
  5051. ''Hardware'' AS Finding ,
  5052. '''' AS URL ,
  5053. ''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.''
  5054. FROM sys.dm_os_sys_info';
  5055. EXECUTE(@StringToExecute);
  5056. END
  5057.  
  5058. /* Check 84 - SQL Server 2008 */
  5059. IF EXISTS ( SELECT *
  5060. FROM sys.all_objects o
  5061. INNER JOIN sys.all_columns c ON o.object_id = c.object_id
  5062. WHERE o.name = 'dm_os_sys_info'
  5063. AND c.name = 'physical_memory_in_bytes' )
  5064. BEGIN
  5065. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  5066. SELECT 84 AS CheckID ,
  5067. 250 AS Priority ,
  5068. ''Server Info'' AS FindingsGroup ,
  5069. ''Hardware'' AS Finding ,
  5070. '''' AS URL ,
  5071. ''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.''
  5072. FROM sys.dm_os_sys_info';
  5073. EXECUTE(@StringToExecute);
  5074. END
  5075. END
  5076.  
  5077.  
  5078. IF NOT EXISTS ( SELECT 1
  5079. FROM #SkipChecks
  5080. WHERE DatabaseName IS NULL AND CheckID = 85 )
  5081. BEGIN
  5082. INSERT INTO #BlitzResults
  5083. ( CheckID ,
  5084. Priority ,
  5085. FindingsGroup ,
  5086. Finding ,
  5087. URL ,
  5088. Details
  5089. )
  5090. SELECT 85 AS CheckID ,
  5091. 250 AS Priority ,
  5092. 'Server Info' AS FindingsGroup ,
  5093. 'SQL Server Service' AS Finding ,
  5094. '' AS URL ,
  5095. N'Version: '
  5096. + CAST(SERVERPROPERTY('productversion') AS NVARCHAR(100))
  5097. + N'. Patch Level: '
  5098. + CAST(SERVERPROPERTY('productlevel') AS NVARCHAR(100))
  5099. + N'. Edition: '
  5100. + CAST(SERVERPROPERTY('edition') AS VARCHAR(100))
  5101. + N'. AlwaysOn Enabled: '
  5102. + CAST(COALESCE(SERVERPROPERTY('IsHadrEnabled'),
  5103. 0) AS VARCHAR(100))
  5104. + N'. AlwaysOn Mgr Status: '
  5105. + CAST(COALESCE(SERVERPROPERTY('HadrManagerStatus'),
  5106. 0) AS VARCHAR(100))
  5107. END
  5108.  
  5109.  
  5110. IF NOT EXISTS ( SELECT 1
  5111. FROM #SkipChecks
  5112. WHERE DatabaseName IS NULL AND CheckID = 88 )
  5113. BEGIN
  5114. INSERT INTO #BlitzResults
  5115. ( CheckID ,
  5116. Priority ,
  5117. FindingsGroup ,
  5118. Finding ,
  5119. URL ,
  5120. Details
  5121. )
  5122. SELECT 88 AS CheckID ,
  5123. 250 AS Priority ,
  5124. 'Server Info' AS FindingsGroup ,
  5125. 'SQL Server Last Restart' AS Finding ,
  5126. '' AS URL ,
  5127. CAST(create_date AS VARCHAR(100))
  5128. FROM sys.databases
  5129. WHERE database_id = 2
  5130. END
  5131.  
  5132. IF NOT EXISTS ( SELECT 1
  5133. FROM #SkipChecks
  5134. WHERE DatabaseName IS NULL AND CheckID = 92 )
  5135. BEGIN
  5136. INSERT INTO #driveInfo
  5137. ( drive, SIZE )
  5138. EXEC master..xp_fixeddrives
  5139.  
  5140. INSERT INTO #BlitzResults
  5141. ( CheckID ,
  5142. Priority ,
  5143. FindingsGroup ,
  5144. Finding ,
  5145. URL ,
  5146. Details
  5147. )
  5148. SELECT 92 AS CheckID ,
  5149. 250 AS Priority ,
  5150. 'Server Info' AS FindingsGroup ,
  5151. 'Drive ' + i.drive + ' Space' AS Finding ,
  5152. '' AS URL ,
  5153. CAST(i.SIZE AS VARCHAR)
  5154. + 'MB free on ' + i.drive
  5155. + ' drive' AS Details
  5156. FROM #driveInfo AS i
  5157. DROP TABLE #driveInfo
  5158. END
  5159.  
  5160.  
  5161. IF NOT EXISTS ( SELECT 1
  5162. FROM #SkipChecks
  5163. WHERE DatabaseName IS NULL AND CheckID = 103 )
  5164. AND EXISTS ( SELECT *
  5165. FROM sys.all_objects o
  5166. INNER JOIN sys.all_columns c ON o.object_id = c.object_id
  5167. WHERE o.name = 'dm_os_sys_info'
  5168. AND c.name = 'virtual_machine_type_desc' )
  5169. BEGIN
  5170. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  5171. SELECT 103 AS CheckID,
  5172. 250 AS Priority,
  5173. ''Server Info'' AS FindingsGroup,
  5174. ''Virtual Server'' AS Finding,
  5175. ''http://BrentOzar.com/go/virtual'' AS URL,
  5176. ''Type: ('' + virtual_machine_type_desc + '')'' AS Details
  5177. FROM sys.dm_os_sys_info
  5178. WHERE virtual_machine_type <> 0';
  5179. EXECUTE(@StringToExecute);
  5180. END
  5181.  
  5182. IF NOT EXISTS ( SELECT 1
  5183. FROM #SkipChecks
  5184. WHERE DatabaseName IS NULL AND CheckID = 114 )
  5185. AND EXISTS ( SELECT *
  5186. FROM sys.all_objects o
  5187. WHERE o.name = 'dm_os_memory_nodes' )
  5188. AND EXISTS ( SELECT *
  5189. FROM sys.all_objects o
  5190. INNER JOIN sys.all_columns c ON o.object_id = c.object_id
  5191. WHERE o.name = 'dm_os_nodes'
  5192. AND c.name = 'processor_group' )
  5193. BEGIN
  5194. SET @StringToExecute = 'INSERT INTO #BlitzResults (CheckID, Priority, FindingsGroup, Finding, URL, Details)
  5195. SELECT 114 AS CheckID ,
  5196. 250 AS Priority ,
  5197. ''Server Info'' AS FindingsGroup ,
  5198. ''Hardware - NUMA Config'' AS Finding ,
  5199. '''' AS URL ,
  5200. ''Node: '' + CAST(n.node_id AS NVARCHAR(10)) + '' State: '' + node_state_desc
  5201. + '' Online schedulers: '' + CAST(n.online_scheduler_count AS NVARCHAR(10)) + '' Processor Group: '' + CAST(n.processor_group AS NVARCHAR(10))
  5202. + '' 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))
  5203. FROM sys.dm_os_nodes n
  5204. INNER JOIN sys.dm_os_memory_nodes m ON n.memory_node_id = m.memory_node_id
  5205. WHERE n.node_state_desc NOT LIKE ''%DAC%''
  5206. ORDER BY n.node_id'
  5207. EXECUTE(@StringToExecute);
  5208. END
  5209.  
  5210.  
  5211. IF NOT EXISTS ( SELECT 1
  5212. FROM #SkipChecks
  5213. WHERE DatabaseName IS NULL AND CheckID = 106 )
  5214. AND (select convert(int,value_in_use) from sys.configurations where name = 'default trace enabled' ) = 1
  5215. AND DATALENGTH( COALESCE( @base_tracefilename, '' ) ) > DATALENGTH('.TRC')
  5216. BEGIN
  5217.  
  5218. INSERT INTO #BlitzResults
  5219. ( CheckID ,
  5220. Priority ,
  5221. FindingsGroup ,
  5222. Finding ,
  5223. URL ,
  5224. Details
  5225. )
  5226. SELECT
  5227. 106 AS CheckID
  5228. ,250 AS Priority
  5229. ,'Server Info' AS FindingsGroup
  5230. ,'Default Trace Contents' AS Finding
  5231. ,'http://BrentOzar.com/go/trace' AS URL
  5232. ,'The default trace holds '+cast(DATEDIFF(hour,MIN(StartTime),GETDATE())as varchar)+' hours of data'
  5233. +' between '+cast(Min(StartTime) as varchar)+' and '+cast(GETDATE()as varchar)
  5234. +('. The default trace files are located in: '+left( @curr_tracefilename,len(@curr_tracefilename) - @indx)
  5235. ) as Details
  5236. FROM ::fn_trace_gettable( @base_tracefilename, default )
  5237. WHERE EventClass BETWEEN 65500 and 65600
  5238. END /* CheckID 106 */
  5239.  
  5240.  
  5241. IF NOT EXISTS ( SELECT 1
  5242. FROM #SkipChecks
  5243. WHERE DatabaseName IS NULL AND CheckID = 152 )
  5244. BEGIN
  5245. IF EXISTS (SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > .1 * @CPUMSsinceStartup AND waiting_tasks_count > 0
  5246. AND wait_type NOT IN ('REQUEST_FOR_DEADLOCK_SEARCH',
  5247. 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
  5248. 'SQLTRACE_BUFFER_FLUSH',
  5249. 'LAZYWRITER_SLEEP',
  5250. 'XE_TIMER_EVENT',
  5251. 'XE_DISPATCHER_WAIT',
  5252. 'FT_IFTS_SCHEDULER_IDLE_WAIT',
  5253. 'LOGMGR_QUEUE',
  5254. 'CHECKPOINT_QUEUE',
  5255. 'BROKER_TO_FLUSH',
  5256. 'BROKER_TASK_STOP',
  5257. 'BROKER_EVENTHANDLER',
  5258. 'SLEEP_TASK',
  5259. 'WAITFOR',
  5260. 'DBMIRROR_DBM_MUTEX',
  5261. 'DBMIRROR_EVENTS_QUEUE',
  5262. 'DBMIRRORING_CMD',
  5263. 'DISPATCHER_QUEUE_SEMAPHORE',
  5264. 'BROKER_RECEIVE_WAITFOR',
  5265. 'CLR_AUTO_EVENT',
  5266. 'DIRTY_PAGE_POLL',
  5267. 'HADR_FILESTREAM_IOMGR_IOCOMPLETION',
  5268. 'ONDEMAND_TASK_QUEUE',
  5269. 'FT_IFTSHC_MUTEX',
  5270. 'CLR_MANUAL_EVENT',
  5271. 'CLR_SEMAPHORE',
  5272. 'DBMIRROR_WORKER_QUEUE',
  5273. 'DBMIRROR_DBM_EVENT',
  5274. 'SP_SERVER_DIAGNOSTICS_SLEEP',
  5275. 'HADR_CLUSAPI_CALL',
  5276. 'HADR_LOGCAPTURE_WAIT',
  5277. 'HADR_NOTIFICATION_DEQUEUE',
  5278. 'HADR_TIMER_TASK',
  5279. 'HADR_WORK_QUEUE',
  5280. 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
  5281. 'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',
  5282. 'REDO_THREAD_PENDING_WORK',
  5283. 'UCS_SESSION_REGISTRATION',
  5284. 'BROKER_TRANSMITTER'))
  5285. BEGIN
  5286. /* Check for waits that have had more than 10% of the server's wait time */
  5287. WITH os(wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms)
  5288. AS
  5289. (SELECT wait_type, waiting_tasks_count, wait_time_ms, max_wait_time_ms, signal_wait_time_ms
  5290. FROM sys.dm_os_wait_stats
  5291. WHERE wait_type NOT IN ('REQUEST_FOR_DEADLOCK_SEARCH',
  5292. 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
  5293. 'SQLTRACE_BUFFER_FLUSH',
  5294. 'LAZYWRITER_SLEEP',
  5295. 'XE_TIMER_EVENT',
  5296. 'XE_DISPATCHER_WAIT',
  5297. 'FT_IFTS_SCHEDULER_IDLE_WAIT',
  5298. 'LOGMGR_QUEUE',
  5299. 'CHECKPOINT_QUEUE',
  5300. 'BROKER_TO_FLUSH',
  5301. 'BROKER_TASK_STOP',
  5302. 'BROKER_EVENTHANDLER',
  5303. 'SLEEP_TASK',
  5304. 'WAITFOR',
  5305. 'DBMIRROR_DBM_MUTEX',
  5306. 'DBMIRROR_EVENTS_QUEUE',
  5307. 'DBMIRRORING_CMD',
  5308. 'DISPATCHER_QUEUE_SEMAPHORE',
  5309. 'BROKER_RECEIVE_WAITFOR',
  5310. 'CLR_AUTO_EVENT',
  5311. 'DIRTY_PAGE_POLL',
  5312. 'HADR_FILESTREAM_IOMGR_IOCOMPLETION',
  5313. 'ONDEMAND_TASK_QUEUE',
  5314. 'FT_IFTSHC_MUTEX',
  5315. 'CLR_MANUAL_EVENT',
  5316. 'CLR_SEMAPHORE',
  5317. 'DBMIRROR_WORKER_QUEUE',
  5318. 'DBMIRROR_DBM_EVENT',
  5319. 'SP_SERVER_DIAGNOSTICS_SLEEP',
  5320. 'HADR_CLUSAPI_CALL',
  5321. 'HADR_LOGCAPTURE_WAIT',
  5322. 'HADR_NOTIFICATION_DEQUEUE',
  5323. 'HADR_TIMER_TASK',
  5324. 'HADR_WORK_QUEUE',
  5325. 'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP',
  5326. 'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',
  5327. 'REDO_THREAD_PENDING_WORK',
  5328. 'UCS_SESSION_REGISTRATION',
  5329. 'BROKER_TRANSMITTER',
  5330. 'PREEMPTIVE_SP_SERVER_DIAGNOSTICS',
  5331. 'PREEMPTIVE_HADR_LEASE_MECHANISM',
  5332. 'SLEEP_SYSTEMTASK',
  5333. 'QDS_SHUTDOWN_QUEUE',
  5334. 'XE_LIVE_TARGET_TVF')
  5335. AND wait_time_ms > .1 * @CPUMSsinceStartup
  5336. AND waiting_tasks_count > 0)
  5337. INSERT INTO #BlitzResults
  5338. ( CheckID ,
  5339. Priority ,
  5340. FindingsGroup ,
  5341. Finding ,
  5342. URL ,
  5343. Details
  5344. )
  5345. SELECT TOP 9
  5346. 152 AS CheckID
  5347. ,240 AS Priority
  5348. ,'Wait Stats' AS FindingsGroup
  5349. , CAST(ROW_NUMBER() OVER(ORDER BY os.wait_time_ms DESC) AS NVARCHAR(10)) + N' - ' + os.wait_type AS Finding
  5350. ,'http://BrentOzar.com/go/waits' AS URL
  5351. , Details = CAST(CAST(SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) AS NUMERIC(10,1)) AS NVARCHAR(20)) + N' hours of waits, ' +
  5352. CAST(CAST((SUM(60.0 * os.wait_time_ms) OVER (PARTITION BY os.wait_type) ) / @MSSinceStartup AS NUMERIC(10,1)) AS NVARCHAR(20)) + N' minutes average wait time per hour, ' +
  5353. CAST(CAST(
  5354. 100.* SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type)
  5355. / (1. * SUM(os.wait_time_ms) OVER () )
  5356. AS NUMERIC(10,1)) AS NVARCHAR(40)) + N'% of waits, ' +
  5357. CAST(CAST(
  5358. 100. * SUM(os.signal_wait_time_ms) OVER (PARTITION BY os.wait_type)
  5359. / (1. * SUM(os.wait_time_ms) OVER ())
  5360. AS NUMERIC(10,1)) AS NVARCHAR(40)) + N'% signal wait, ' +
  5361. CAST(SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS NVARCHAR(40)) + N' waiting tasks, ' +
  5362. CAST(CASE WHEN SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) > 0
  5363. THEN
  5364. CAST(
  5365. SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type)
  5366. / (1. * SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type))
  5367. AS NUMERIC(10,1))
  5368. ELSE 0 END AS NVARCHAR(40)) + N' ms average wait time.'
  5369. FROM os
  5370. ORDER BY SUM(os.wait_time_ms / 1000.0 / 60 / 60) OVER (PARTITION BY os.wait_type) DESC;
  5371. END /* IF EXISTS (SELECT * FROM sys.dm_os_wait_stats WHERE wait_time_ms > 0 AND waiting_tasks_count > 0) */
  5372.  
  5373. /* If no waits were found, add a note about that */
  5374. IF NOT EXISTS (SELECT * FROM #BlitzResults WHERE CheckID IN (107, 108, 109, 121, 152, 162))
  5375. BEGIN
  5376. INSERT INTO #BlitzResults
  5377. ( CheckID ,
  5378. Priority ,
  5379. FindingsGroup ,
  5380. Finding ,
  5381. URL ,
  5382. Details
  5383. )
  5384. VALUES (153, 240, 'Wait Stats', 'No Significant Waits Detected', 'http://BrentOzar.com/go/waits', 'This server might be just sitting around idle, or someone may have cleared wait stats recently.');
  5385. END
  5386. END /* CheckID 152 */
  5387.  
  5388. END /* IF @CheckServerInfo = 1 */
  5389. END /* IF ( ( SERVERPROPERTY('ServerName') NOT IN ( SELECT ServerName */
  5390.  
  5391.  
  5392. /* Delete priorites they wanted to skip. */
  5393. IF @IgnorePrioritiesAbove IS NOT NULL
  5394. DELETE #BlitzResults
  5395. WHERE [Priority] > @IgnorePrioritiesAbove AND CheckID <> -1;
  5396.  
  5397. IF @IgnorePrioritiesBelow IS NOT NULL
  5398. DELETE #BlitzResults
  5399. WHERE [Priority] < @IgnorePrioritiesBelow AND CheckID <> -1;
  5400.  
  5401. /* Delete checks they wanted to skip. */
  5402. IF @SkipChecksTable IS NOT NULL
  5403. BEGIN
  5404. DELETE FROM #BlitzResults
  5405. WHERE DatabaseName IN ( SELECT DatabaseName
  5406. FROM #SkipChecks
  5407. WHERE CheckID IS NULL
  5408. AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')));
  5409. DELETE FROM #BlitzResults
  5410. WHERE CheckID IN ( SELECT CheckID
  5411. FROM #SkipChecks
  5412. WHERE DatabaseName IS NULL
  5413. AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName')));
  5414. DELETE r FROM #BlitzResults r
  5415. INNER JOIN #SkipChecks c ON r.DatabaseName = c.DatabaseName and r.CheckID = c.CheckID
  5416. AND (ServerName IS NULL OR ServerName = SERVERPROPERTY('ServerName'));
  5417. END
  5418.  
  5419. /* Add summary mode */
  5420. IF @SummaryMode > 0
  5421. BEGIN
  5422. UPDATE #BlitzResults
  5423. SET Finding = br.Finding + ' (' + CAST(brTotals.recs AS NVARCHAR(20)) + ')'
  5424. FROM #BlitzResults br
  5425. 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
  5426. WHERE brTotals.recs > 1;
  5427.  
  5428. DELETE br
  5429. FROM #BlitzResults br
  5430. 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);
  5431.  
  5432. END
  5433.  
  5434. /* Add credits for the nice folks who put so much time into building and maintaining this for free: */
  5435. INSERT INTO #BlitzResults
  5436. ( CheckID ,
  5437. Priority ,
  5438. FindingsGroup ,
  5439. Finding ,
  5440. URL ,
  5441. Details
  5442. )
  5443. VALUES ( -1 ,
  5444. 255 ,
  5445. 'Thanks!' ,
  5446. 'From Brent Ozar Unlimited' ,
  5447. 'http://www.BrentOzar.com/blitz/' ,
  5448. 'Thanks from the Brent Ozar Unlimited team. We hope you found this tool useful, and if you need help relieving your SQL Server pains, email us at Help@BrentOzar.com.'
  5449. );
  5450.  
  5451. INSERT INTO #BlitzResults
  5452. ( CheckID ,
  5453. Priority ,
  5454. FindingsGroup ,
  5455. Finding ,
  5456. URL ,
  5457. Details
  5458.  
  5459. )
  5460. VALUES ( -1 ,
  5461. 0 ,
  5462. 'sp_Blitz (TM) v' + CAST(@Version AS VARCHAR(20)) + ' as of ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)),
  5463. 'From Brent Ozar Unlimited' ,
  5464. 'http://www.BrentOzar.com/blitz/' ,
  5465. 'Thanks from the Brent Ozar Unlimited team. We hope you found this tool useful, and if you need help relieving your SQL Server pains, email us at Help@BrentOzar.com.'
  5466.  
  5467. );
  5468.  
  5469. INSERT INTO #BlitzResults
  5470. ( CheckID ,
  5471. Priority ,
  5472. FindingsGroup ,
  5473. Finding ,
  5474. URL ,
  5475. Details
  5476.  
  5477. )
  5478. SELECT 156 ,
  5479. 254 ,
  5480. 'Rundate' ,
  5481. GETDATE() ,
  5482. 'http://www.BrentOzar.com/blitz/' ,
  5483. 'Captain''s log: stardate something and something...';
  5484.  
  5485. IF @EmailRecipients IS NOT NULL
  5486. BEGIN
  5487. /* Database mail won't work off a local temp table. I'm not happy about this hacky workaround either. */
  5488. IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults;
  5489. SELECT * INTO ##BlitzResults FROM #BlitzResults;
  5490. SET @query_result_separator = char(9);
  5491. SET @StringToExecute = 'SET NOCOUNT ON;SELECT [Priority] , [FindingsGroup] , [Finding] , [DatabaseName] , [URL] , [Details] , CheckID FROM ##BlitzResults ORDER BY Priority , FindingsGroup, Finding, Details; SET NOCOUNT OFF;';
  5492. SET @EmailSubject = 'sp_Blitz (TM) Results for ' + @@SERVERNAME;
  5493. SET @EmailBody = 'sp_Blitz (TM) v' + CAST(@Version AS VARCHAR(20)) + ' as of ' + CAST(CONVERT(DATETIME, @VersionDate, 102) AS VARCHAR(100)) + '. From Brent Ozar Unlimited: http://www.BrentOzar.com/blitz/';
  5494. IF @EmailProfile IS NULL
  5495. EXEC msdb.dbo.sp_send_dbmail
  5496. @recipients = @EmailRecipients,
  5497. @subject = @EmailSubject,
  5498. @body = @EmailBody,
  5499. @query_attachment_filename = 'sp_Blitz-Results.csv',
  5500. @attach_query_result_as_file = 1,
  5501. @query_result_header = 1,
  5502. @query_result_width = 32767,
  5503. @append_query_error = 1,
  5504. @query_result_no_padding = 1,
  5505. @query_result_separator = @query_result_separator,
  5506. @query = @StringToExecute;
  5507. ELSE
  5508. EXEC msdb.dbo.sp_send_dbmail
  5509. @profile_name = @EmailProfile,
  5510. @recipients = @EmailRecipients,
  5511. @subject = @EmailSubject,
  5512. @body = @EmailBody,
  5513. @query_attachment_filename = 'sp_Blitz-Results.csv',
  5514. @attach_query_result_as_file = 1,
  5515. @query_result_header = 1,
  5516. @query_result_width = 32767,
  5517. @append_query_error = 1,
  5518. @query_result_no_padding = 1,
  5519. @query_result_separator = @query_result_separator,
  5520. @query = @StringToExecute;
  5521. IF (OBJECT_ID('tempdb..##BlitzResults', 'U') IS NOT NULL) DROP TABLE ##BlitzResults;
  5522. END
  5523.  
  5524.  
  5525. /* @OutputTableName lets us export the results to a permanent table */
  5526. IF @OutputDatabaseName IS NOT NULL
  5527. AND @OutputSchemaName IS NOT NULL
  5528. AND @OutputTableName IS NOT NULL
  5529. AND EXISTS ( SELECT *
  5530. FROM sys.databases
  5531. WHERE QUOTENAME([name]) = @OutputDatabaseName)
  5532. BEGIN
  5533. SET @StringToExecute = 'USE '
  5534. + @OutputDatabaseName
  5535. + '; IF EXISTS(SELECT * FROM '
  5536. + @OutputDatabaseName
  5537. + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
  5538. + @OutputSchemaName
  5539. + ''') AND NOT EXISTS (SELECT * FROM '
  5540. + @OutputDatabaseName
  5541. + '.INFORMATION_SCHEMA.TABLES WHERE QUOTENAME(TABLE_SCHEMA) = '''
  5542. + @OutputSchemaName + ''' AND QUOTENAME(TABLE_NAME) = '''
  5543. + @OutputTableName + ''') CREATE TABLE '
  5544. + @OutputSchemaName + '.'
  5545. + @OutputTableName
  5546. + ' (ID INT IDENTITY(1,1) NOT NULL,
  5547. ServerName NVARCHAR(128),
  5548. CheckDate DATETIME,
  5549. BlitzVersion INT,
  5550. Priority TINYINT ,
  5551. FindingsGroup VARCHAR(50) ,
  5552. Finding VARCHAR(200) ,
  5553. DatabaseName NVARCHAR(128),
  5554. URL VARCHAR(200) ,
  5555. Details NVARCHAR(4000) ,
  5556. QueryPlan [XML] NULL ,
  5557. QueryPlanFiltered [NVARCHAR](MAX) NULL,
  5558. CheckID INT ,
  5559. CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
  5560. EXEC(@StringToExecute);
  5561. SET @StringToExecute = N' IF EXISTS(SELECT * FROM '
  5562. + @OutputDatabaseName
  5563. + '.INFORMATION_SCHEMA.SCHEMATA WHERE QUOTENAME(SCHEMA_NAME) = '''
  5564. + @OutputSchemaName + ''') INSERT '
  5565. + @OutputDatabaseName + '.'
  5566. + @OutputSchemaName + '.'
  5567. + @OutputTableName
  5568. + ' (ServerName, CheckDate, BlitzVersion, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
  5569. + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
  5570. + ''', GETDATE(), ' + CAST(@Version AS NVARCHAR(128))
  5571. + ', CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details';
  5572. EXEC(@StringToExecute);
  5573. END
  5574. ELSE IF (SUBSTRING(@OutputTableName, 2, 2) = '##')
  5575. BEGIN
  5576. SET @StringToExecute = N' IF (OBJECT_ID(''tempdb..'
  5577. + @OutputTableName
  5578. + ''') IS NOT NULL) DROP TABLE ' + @OutputTableName + ';'
  5579. + 'CREATE TABLE '
  5580. + @OutputTableName
  5581. + ' (ID INT IDENTITY(1,1) NOT NULL,
  5582. ServerName NVARCHAR(128),
  5583. CheckDate DATETIME,
  5584. BlitzVersion INT,
  5585. Priority TINYINT ,
  5586. FindingsGroup VARCHAR(50) ,
  5587. Finding VARCHAR(200) ,
  5588. DatabaseName NVARCHAR(128),
  5589. URL VARCHAR(200) ,
  5590. Details NVARCHAR(4000) ,
  5591. QueryPlan [XML] NULL ,
  5592. QueryPlanFiltered [NVARCHAR](MAX) NULL,
  5593. CheckID INT ,
  5594. CONSTRAINT [PK_' + CAST(NEWID() AS CHAR(36)) + '] PRIMARY KEY CLUSTERED (ID ASC));'
  5595. + ' INSERT '
  5596. + @OutputTableName
  5597. + ' (ServerName, CheckDate, BlitzVersion, CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered) SELECT '''
  5598. + CAST(SERVERPROPERTY('ServerName') AS NVARCHAR(128))
  5599. + ''', GETDATE(), ' + CAST(@Version AS NVARCHAR(128))
  5600. + ', CheckID, DatabaseName, Priority, FindingsGroup, Finding, URL, Details, QueryPlan, QueryPlanFiltered FROM #BlitzResults ORDER BY Priority , FindingsGroup , Finding , Details';
  5601. EXEC(@StringToExecute);
  5602. END
  5603. ELSE IF (SUBSTRING(@OutputTableName, 2, 1) = '#')
  5604. BEGIN
  5605. RAISERROR('Due to the nature of Dymamic SQL, only global (i.e. double pound (##)) temp tables are supported for @OutputTableName', 16, 0)
  5606. END
  5607.  
  5608.  
  5609. DECLARE @separator AS VARCHAR(1);
  5610. IF @OutputType = 'RSV'
  5611. SET @separator = CHAR(31);
  5612. ELSE
  5613. SET @separator = ',';
  5614.  
  5615. IF @OutputType = 'COUNT'
  5616. BEGIN
  5617. SELECT COUNT(*) AS Warnings
  5618. FROM #BlitzResults
  5619. END
  5620. ELSE
  5621. IF @OutputType IN ( 'CSV', 'RSV' )
  5622. BEGIN
  5623.  
  5624. SELECT Result = CAST([Priority] AS NVARCHAR(100))
  5625. + @separator + CAST(CheckID AS NVARCHAR(100))
  5626. + @separator + COALESCE([FindingsGroup],
  5627. '(N/A)') + @separator
  5628. + COALESCE([Finding], '(N/A)') + @separator
  5629. + COALESCE(DatabaseName, '(N/A)') + @separator
  5630. + COALESCE([URL], '(N/A)') + @separator
  5631. + COALESCE([Details], '(N/A)')
  5632. FROM #BlitzResults
  5633. ORDER BY Priority ,
  5634. FindingsGroup ,
  5635. Finding ,
  5636. Details;
  5637. END
  5638. ELSE IF @OutputXMLasNVARCHAR = 1 AND @OutputType <> 'NONE'
  5639. BEGIN
  5640. SELECT [Priority] ,
  5641. [FindingsGroup] ,
  5642. [Finding] ,
  5643. [DatabaseName] ,
  5644. [URL] ,
  5645. [Details] ,
  5646. CAST([QueryPlan] AS NVARCHAR(MAX)) AS QueryPlan,
  5647. [QueryPlanFiltered] ,
  5648. CheckID
  5649. FROM #BlitzResults
  5650. ORDER BY Priority ,
  5651. FindingsGroup ,
  5652. Finding ,
  5653. Details;
  5654. END
  5655. ELSE IF @OutputType <> 'NONE'
  5656. BEGIN
  5657. SELECT [Priority] ,
  5658. [FindingsGroup] ,
  5659. [Finding] ,
  5660. [DatabaseName] ,
  5661. [URL] ,
  5662. [Details] ,
  5663. [QueryPlan] ,
  5664. [QueryPlanFiltered] ,
  5665. CheckID
  5666. FROM #BlitzResults
  5667. ORDER BY Priority ,
  5668. FindingsGroup ,
  5669. Finding ,
  5670. Details;
  5671. END
  5672.  
  5673. DROP TABLE #BlitzResults;
  5674.  
  5675. IF @OutputProcedureCache = 1
  5676. AND @CheckProcedureCache = 1
  5677. SELECT TOP 20
  5678. total_worker_time / execution_count AS AvgCPU ,
  5679. total_worker_time AS TotalCPU ,
  5680. CAST(ROUND(100.00 * total_worker_time
  5681. / ( SELECT SUM(total_worker_time)
  5682. FROM sys.dm_exec_query_stats
  5683. ), 2) AS MONEY) AS PercentCPU ,
  5684. total_elapsed_time / execution_count AS AvgDuration ,
  5685. total_elapsed_time AS TotalDuration ,
  5686. CAST(ROUND(100.00 * total_elapsed_time
  5687. / ( SELECT SUM(total_elapsed_time)
  5688. FROM sys.dm_exec_query_stats
  5689. ), 2) AS MONEY) AS PercentDuration ,
  5690. total_logical_reads / execution_count AS AvgReads ,
  5691. total_logical_reads AS TotalReads ,
  5692. CAST(ROUND(100.00 * total_logical_reads
  5693. / ( SELECT SUM(total_logical_reads)
  5694. FROM sys.dm_exec_query_stats
  5695. ), 2) AS MONEY) AS PercentReads ,
  5696. execution_count ,
  5697. CAST(ROUND(100.00 * execution_count
  5698. / ( SELECT SUM(execution_count)
  5699. FROM sys.dm_exec_query_stats
  5700. ), 2) AS MONEY) AS PercentExecutions ,
  5701. CASE WHEN DATEDIFF(mi, creation_time,
  5702. qs.last_execution_time) = 0 THEN 0
  5703. ELSE CAST(( 1.00 * execution_count / DATEDIFF(mi,
  5704. creation_time,
  5705. qs.last_execution_time) ) AS MONEY)
  5706. END AS executions_per_minute ,
  5707. qs.creation_time AS plan_creation_time ,
  5708. qs.last_execution_time ,
  5709. text ,
  5710. text_filtered ,
  5711. query_plan ,
  5712. query_plan_filtered ,
  5713. sql_handle ,
  5714. query_hash ,
  5715. plan_handle ,
  5716. query_plan_hash
  5717. FROM #dm_exec_query_stats qs
  5718. ORDER BY CASE UPPER(@CheckProcedureCacheFilter)
  5719. WHEN 'CPU' THEN total_worker_time
  5720. WHEN 'READS' THEN total_logical_reads
  5721. WHEN 'EXECCOUNT' THEN execution_count
  5722. WHEN 'DURATION' THEN total_elapsed_time
  5723. ELSE total_worker_time
  5724. END DESC
  5725.  
  5726. END /* ELSE -- IF @OutputType = 'SCHEMA' */
  5727.  
  5728. SET NOCOUNT OFF;
  5729. GO
  5730.  
  5731. /*
  5732. --Sample execution call with the most common parameters:
  5733. EXEC [master].[dbo].[sp_Blitz]
  5734. @CheckUserDatabaseObjects = 1 ,
  5735. @CheckProcedureCache = 0 ,
  5736. @OutputType = 'TABLE' ,
  5737. @OutputProcedureCache = 0 ,
  5738. @CheckProcedureCacheFilter = NULL,
  5739. @CheckServerInfo = 1
  5740. */
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement