diff --git a/SAM - AlwaysON/Automatic Seeding Check.sql b/SAM - AlwaysON/Automatic Seeding Check.sql new file mode 100644 index 0000000..aee9c27 --- /dev/null +++ b/SAM - AlwaysON/Automatic Seeding Check.sql @@ -0,0 +1,12 @@ +/** +There are two dynamic management views (DMVs) for monitoring seeding: sys.dm_hadr_automatic_seeding and sys.dm_hadr_physical_seeding_stats. + +sys.dm_hadr_automatic_seeding contains the general status of automatic seeding, and retains the history for each time it is executed (whether successful or not). +The column current_state has either a value of COMPLETED or FAILED. If the value is FAILED, use the value in failure_state_desc to help in diagnosing the problem. +You may need to combine that with what it in the SQL Server Log to see what went wrong. This DMV is populated on the primary replica and all secondary replicas. + +sys.dm_hadr_physical_seeding_stats shows the status of the automatic seeding operation as it is executing. +As with sys.dm_hadr_automatic_seeding, this returns values for both the primary and secondary replicas, but this history is not stored. +#The values are for the current execution only, and is not retained. +Columns of interest include start_time_utc, end_time_utc, estimate_time_complete_utc, total_disk_io_wait_time_ms, total_network_wait_time_ms, and if the seeding operation fails, failure_message. +**/ diff --git a/SAM - CONNECTIONS/CONNECTIONS - Find details of active connections.sql b/SAM - CONNECTIONS/CONNECTIONS - Find details of active connections.sql new file mode 100644 index 0000000..c2133b9 --- /dev/null +++ b/SAM - CONNECTIONS/CONNECTIONS - Find details of active connections.sql @@ -0,0 +1,10 @@ +SELECT + c.session_id, c.net_transport, c.encrypt_option, + c.auth_scheme, s.host_name, s.program_name, + s.client_interface_name, s.login_name, s.nt_domain, + s.nt_user_name, s.original_login_name, c.connect_time, + s.login_time +FROM sys.dm_exec_connections AS c +JOIN sys.dm_exec_sessions AS s + ON c.session_id = s.session_id +WHERE c.session_id = @@SPID; diff --git a/SAM - COlumnstore INdex/create table columstore index and populate example.sql b/SAM - COlumnstore INdex/create table columstore index and populate example.sql new file mode 100644 index 0000000..19ebe49 --- /dev/null +++ b/SAM - COlumnstore INdex/create table columstore index and populate example.sql @@ -0,0 +1,29 @@ +create table t_colstor( +accountkey int not null, +accountdescription nvarchar(50), +accounttype nvarchar(50), +unitsold int) + +create unique clustered index t_colstor_ci on t_colstor(accountkey) + +create nonclustered columnstore index t_colstor_NCCI on t_colstor (accountdescription, unitsold) + +declare @outerloop int=0 +declare @i int=0 +while (@outerloop < 1000000) +begin + select @i=0 + + begin tran + while (@i<2000) + begin + insert t_colstor values (@i + @outerloop, 'test1', 'test2', @i*4) + set @i +=1; + end + commit + + set @outerloop = @outerloop + 2000 + set @i =0 +end +go + diff --git a/SAM - Deadlock/DEADLOCK - Read deadlocks from error logs.sql b/SAM - Deadlock/DEADLOCK - Read deadlocks from error logs.sql new file mode 100644 index 0000000..5497389 --- /dev/null +++ b/SAM - Deadlock/DEADLOCK - Read deadlocks from error logs.sql @@ -0,0 +1,41 @@ +/* +https://www.sqlservercentral.com/articles/catching-deadlock-information-in-sql-logs + +*/ + + +declare @RawLogs table (id int IDENTITY (1, 1), logdate datetime, processinfo nvarchar(50), logtext nvarchar(max)) + +insert into @RawLogs +exec sp_readerrorlog + +declare @results table (id int IDENTITY (1,1), logdate datetime, processinfo nvarchar(50), logtext nvarchar(max)) +declare @ids table (id int, processinfo nvarchar(50)) + +insert into @ids +select id, processinfo +from @RawLogs +where logteXt = 'deadlock-list' +order by id + +declare @Startid int, @endid int, @processinfo nvarchar(50) +select top 1 @Startid = id from @ids order by id +while(@@rowcount<>0) +begin +select @processinfo = processinfo from @ids where id = @Startid +select top 1 @endid = id from @ids where id > @Startid and processinfo = @processinfo order by id +insert into @results (logdate, processinfo, logtext) +select logdate, processinfo, logtext +from @RawLogs +where + id >=@Startid and + processinfo = @processinfo and + id < @endid +order by id +delete @ids where id = @Startid +select top 1 @Startid = id from @ids order by id +end + +select logdate, processinfo, logtext +from @results +order by id diff --git a/SAM - Disk/AZURE - Check Disk Configuration.sql b/SAM - Disk/AZURE - Check Disk Configuration.sql new file mode 100644 index 0000000..3e8eb60 --- /dev/null +++ b/SAM - Disk/AZURE - Check Disk Configuration.sql @@ -0,0 +1,39 @@ +/* +https://medium.com/azure-sqldb-managed-instance/file-layout-in-general-purpose-azure-sql-managed-instance-cf21fff9c76c + +*/ + +CREATE SCHEMA mi; +GO +CREATE OR ALTER VIEW mi.master_files +AS +WITH mi_master_files AS +( SELECT *, size_gb = CAST(size * 8. / 1024 / 1024 AS decimal(12,4)) +FROM sys.master_files where physical_name LIKE 'https:%') +SELECT *, azure_disk_size_gb = IIF( +database_id <> 2, +CASE WHEN size_gb <= 128 THEN 128 +WHEN size_gb > 128 AND size_gb <= 256 THEN 256 +WHEN size_gb > 256 AND size_gb <= 512 THEN 512 +WHEN size_gb > 512 AND size_gb <= 1024 THEN 1024 +WHEN size_gb > 1024 AND size_gb <= 2048 THEN 2048 +WHEN size_gb > 2048 AND size_gb <= 4096 THEN 4096 +ELSE 8192 +END, NULL) +FROM mi_master_files; +GO + +--Now we can see the size allocated for the underlying Azure Premium Disks for every database file: +SELECT db = db_name(database_id), name, size_gb, azure_disk_size_gb +FROM mi.master_files; + +--Sum of the azure disk sizes should not exceed 35 TB — otherwise you will reach the azure storage limit errors. You can check total allocated azure storage space using the following query: +SELECT storage_size_tb = SUM(azure_disk_size_gb) /1024. +FROM mi.master_files + +--Using this information, you can find out how many additional files you can add on a managed instance (assuming that new file will be smaller than 128GB): +SELECT remaining_number_of_128gb_files = +(35 - ROUND(SUM(azure_disk_size_gb) /1024,0)) * 8 +FROM mi.master_files + +--This is important check because if this count became zero, you will not be able to add more files of database on the instance. diff --git a/SAM - Disk/DISK - CrystalDiskMark Reference.sql b/SAM - Disk/DISK - CrystalDiskMark Reference.sql index c8210bb..95c19ec 100644 --- a/SAM - Disk/DISK - CrystalDiskMark Reference.sql +++ b/SAM - Disk/DISK - CrystalDiskMark Reference.sql @@ -1,12 +1,15 @@ /*** -Seq – long, sequential operations. For SQL Server, this is somewhat akin to doing backups or doing table scans of perfectly defragmented data, like a data warehouse. +https://glennsqlperformance.com/2020/12/13/some-quick-comparative-crystaldiskmark-results-in-2020/ +https://www.brentozar.com/archive/2019/11/how-to-use-crystaldiskmark-7-to-test-your-sql-servers-storage/ -512K – random large operations one at a time. This doesn’t really match up to how SQL Server works. +Seq – long, sequential operations. For SQL Server, this is somewhat akin to doing backups or doing table scans of perfectly defragmented data, like a data warehouse. -4K – random tiny operations one at a time. This is somewhat akin to a lightly loaded OLTP server. +512K – random large operations one at a time. This doesn’t really match up to how SQL Server works. -4K QD32 – random tiny operations, but many done at a time. This is somewhat akin to an active OLTP server. +4K – random tiny operations one at a time. This is somewhat akin to a lightly loaded OLTP server. +4K QD32 – random tiny operations, but many done at a time. This is somewhat akin to an active OLTP server. -***/ \ No newline at end of file + +***/ diff --git a/SAM - Disk/SAM - Free space in database files.sql b/SAM - Disk/SAM - Free space in database files.sql new file mode 100644 index 0000000..45a6e8a --- /dev/null +++ b/SAM - Disk/SAM - Free space in database files.sql @@ -0,0 +1,26 @@ +CREATE TABLE #FileSize +(dbName NVARCHAR(128), + FileName NVARCHAR(128), + type_desc NVARCHAR(128), + CurrentSizeMB DECIMAL(10,2), + FreeSpaceMB DECIMAL(10,2) +); + +INSERT INTO #FileSize(dbName, FileName, type_desc, CurrentSizeMB, FreeSpaceMB) +exec sp_msforeachdb +'use [?]; + SELECT DB_NAME() AS DbName, + name AS FileName, + type_desc, + size/128.0 AS CurrentSizeMB, + size/128.0 - CAST(FILEPROPERTY(name, ''SpaceUsed'') AS INT)/128.0 AS FreeSpaceMB +FROM sys.database_files +WHERE type IN (0,1);'; + +SELECT * +FROM #FileSize +WHERE dbName NOT IN ('distribution', 'master', 'model', 'msdb') +order by FreeSpaceMB asc +--AND FreeSpaceMB > ?; + +DROP TABLE #FileSize; diff --git a/SAM - Errors/SAM - Count number of files in error log location.sql b/SAM - Errors/SAM - Count number of files in error log location.sql new file mode 100644 index 0000000..b041c49 --- /dev/null +++ b/SAM - Errors/SAM - Count number of files in error log location.sql @@ -0,0 +1,28 @@ +SET NOCOUNT ON; + +-- first get the path to the error log directory +DECLARE @errorLogPath as NVARCHAR(4000); +SELECT @errorLogPath = cast(SERVERPROPERTY('ErrorLogFileName') AS NVARCHAR(4000)); + +SET @errorLogPath = REPLACE(@errorLogPath, '\ERRORLOG', ''); + + +-- now get a list of files -- this may take a while +CREATE TABLE #DirectoryTree ( + subdirectory nvarchar(512), + depth int, + isfile bit); + +INSERT INTO #DirectoryTree (subdirectory,depth,isfile) +EXEC master.sys.xp_dirtree @errorLogPath,1,1; + + +-- get a count of files in the error log directory +DECLARE @fileCount AS INTEGER = 0; +SELECT @fileCount = COUNT(*) FROM #DirectoryTree +WHERE isfile = 1; + + +PRINT 'Error log directory ' + @errorLogPath + ' contains ' + cast(@fileCount as varchar(100)) + ' files.'; + +DROP TABLE #DirectoryTree; diff --git a/SAM - Extended Events/XE- Extended Events in Azure SQL.sql b/SAM - Extended Events/XE- Extended Events in Azure SQL.sql new file mode 100644 index 0000000..cdb3703 --- /dev/null +++ b/SAM - Extended Events/XE- Extended Events in Azure SQL.sql @@ -0,0 +1,152 @@ +/****** +https://docs.microsoft.com/en-us/azure/azure-sql/database/xevent-code-ring-buffer + +*******/ + +GO +---- Transact-SQL. +---- Step set 1. + +SET NOCOUNT ON; +GO + + +IF EXISTS + (SELECT * FROM sys.objects + WHERE type = 'U' and name = 'tabEmployee') +BEGIN + DROP TABLE tabEmployee; +END +GO + + +CREATE TABLE tabEmployee +( + EmployeeGuid uniqueIdentifier not null default newid() primary key, + EmployeeId int not null identity(1,1), + EmployeeKudosCount int not null default 0, + EmployeeDescr nvarchar(256) null +); +GO + + +INSERT INTO tabEmployee ( EmployeeDescr ) + VALUES ( 'Jane Doe' ); +GO + +---- Step set 2. + + +IF EXISTS + (SELECT * from sys.database_event_sessions + WHERE name = 'eventsession_gm_azuresqldb51') +BEGIN + DROP EVENT SESSION eventsession_gm_azuresqldb51 + ON DATABASE; +END +GO + + +CREATE + EVENT SESSION eventsession_gm_azuresqldb51 + ON DATABASE + ADD EVENT + sqlserver.sql_statement_starting + ( + ACTION (sqlserver.sql_text) + WHERE statement LIKE '%UPDATE tabEmployee%' + ) + ADD TARGET + package0.ring_buffer + (SET + max_memory = 500 -- Units of KB. + ); +GO + +---- Step set 3. + + +ALTER EVENT SESSION eventsession_gm_azuresqldb51 + ON DATABASE + STATE = START; +GO + +---- Step set 4. + + +SELECT 'BEFORE_Updates', EmployeeKudosCount, * FROM tabEmployee; + +UPDATE tabEmployee + SET EmployeeKudosCount = EmployeeKudosCount + 102; + +UPDATE tabEmployee + SET EmployeeKudosCount = EmployeeKudosCount + 1015; + +SELECT 'AFTER__Updates', EmployeeKudosCount, * FROM tabEmployee; +GO + +---- Step set 5. + + +SELECT + se.name AS [session-name], + ev.event_name, + ac.action_name, + st.target_name, + se.session_source, + st.target_data, + CAST(st.target_data AS XML) AS [target_data_XML] +FROM + sys.dm_xe_database_session_event_actions AS ac + + INNER JOIN sys.dm_xe_database_session_events AS ev ON ev.event_name = ac.event_name + AND CAST(ev.event_session_address AS BINARY(8)) = CAST(ac.event_session_address AS BINARY(8)) + + INNER JOIN sys.dm_xe_database_session_object_columns AS oc + ON CAST(oc.event_session_address AS BINARY(8)) = CAST(ac.event_session_address AS BINARY(8)) + + INNER JOIN sys.dm_xe_database_session_targets AS st + ON CAST(st.event_session_address AS BINARY(8)) = CAST(ac.event_session_address AS BINARY(8)) + + INNER JOIN sys.dm_xe_database_sessions AS se + ON CAST(ac.event_session_address AS BINARY(8)) = CAST(se.address AS BINARY(8)) +WHERE + oc.column_name = 'occurrence_number' + AND + se.name = 'eventsession_gm_azuresqldb51' + AND + ac.action_name = 'sql_text' +ORDER BY + se.name, + ev.event_name, + ac.action_name, + st.target_name, + se.session_source +; +GO + +---- Step set 6. + + +ALTER EVENT SESSION eventsession_gm_azuresqldb51 + ON DATABASE + STATE = STOP; +GO + +---- Step set 7. + + +ALTER EVENT SESSION eventsession_gm_azuresqldb51 + ON DATABASE + DROP TARGET package0.ring_buffer; +GO + +---- Step set 8. + + +DROP EVENT SESSION eventsession_gm_azuresqldb51 + ON DATABASE; +GO + +DROP TABLE tabEmployee; +GO diff --git a/SAM - INDEX/INDEX - Drop Constraints.sql b/SAM - INDEX/INDEX - Drop Constraints.sql new file mode 100644 index 0000000..711b22b --- /dev/null +++ b/SAM - INDEX/INDEX - Drop Constraints.sql @@ -0,0 +1,38 @@ +/* +https://docs.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-objects-transact-sql?view=sql-server-ver15 +C = CHECK constraint + +D = DEFAULT (constraint or stand-alone) + +F = FOREIGN KEY constraint + +PK = PRIMARY KEY constraint + +R = Rule (old-style, stand-alone) + +TA = Assembly (CLR-integration) trigger + +TR = SQL trigger + +UQ = UNIQUE constraint + +EC = Edge constraint +*/ + +DECLARE @sql NVARCHAR(MAX); +SET @sql = N''; + +SELECT @sql = @sql + N' + ALTER TABLE ' + QUOTENAME(s.name) + N'.' + + QUOTENAME(t.name) + N' DROP CONSTRAINT ' + + QUOTENAME(c.name) + ';' +FROM sys.objects AS c +INNER JOIN sys.tables AS t +ON c.parent_object_id = t.[object_id] +INNER JOIN sys.schemas AS s +ON t.[schema_id] = s.[schema_id] +WHERE c.[type] IN ('UQ') +ORDER BY c.[type]; + +--PRINT @sql; +EXEC sys.sp_executesql @sql; diff --git a/SAM - INDEX/INDEX - Drop Non CLustered Indexes.sql b/SAM - INDEX/INDEX - Drop Non CLustered Indexes.sql new file mode 100644 index 0000000..a8141a4 --- /dev/null +++ b/SAM - INDEX/INDEX - Drop Non CLustered Indexes.sql @@ -0,0 +1,15 @@ +DECLARE @sql NVARCHAR(MAX) = N''; + +SELECT @sql += N'DROP INDEX' + + QUOTENAME(SCHEMA_NAME(o.[schema_id])) + + '.' + QUOTENAME(o.name) + + '.' + QUOTENAME(i.name) + ';' + FROM sys.indexes AS i + INNER JOIN sys.tables AS o + ON i.[object_id] = o.[object_id] +WHERE i.is_primary_key = 0 +AND i.index_id <> 0 +AND o.is_ms_shipped = 0; + +--PRINT @sql; + EXEC sp_executesql @sql; diff --git a/SAM - INDEX/INDEX - Drop all INdexes and Constraints.sql b/SAM - INDEX/INDEX - Drop all INdexes and Constraints.sql new file mode 100644 index 0000000..8384a7e --- /dev/null +++ b/SAM - INDEX/INDEX - Drop all INdexes and Constraints.sql @@ -0,0 +1,35 @@ +/* +o.type filters: +S = System table +IT = Internal table +*/ + +/* Drop constraints */ +DECLARE @sql NVARCHAR(MAX); +SET @sql = N''; + +SELECT @sql = @sql + N' + ALTER TABLE ' + QUOTENAME(s.name) + N'.' + + QUOTENAME(t.name) + N' DROP CONSTRAINT ' + + QUOTENAME(c.name) + ';' +FROM sys.objects AS c +INNER JOIN sys.tables AS t +ON c.parent_object_id = t.[object_id] +INNER JOIN sys.schemas AS s +ON t.[schema_id] = s.[schema_id] +WHERE c.[type] IN ('D','C','F','PK','UQ') +AND c.type not in ('S','IT') +ORDER BY c.[type]; +--PRINT @sql; +EXEC sys.sp_executesql @sql; + +/* Drop indexes */ +declare @qry nvarchar(max); +SET @qry = N''; +select @qry =@qry+N' +drop index '+QUOTENAME(s.name)+'.'+QUOTENAME(o.name)+'.'+QUOTENAME(i.name)+';' + from sys.indexes i join sys.objects o on i.object_id=o.object_id + join sys.schemas s on o.schema_id=s.schema_id + where o.type not in ('S','IT') and is_primary_key<>1 and index_id>0; +--print @qry; +exec sp_executesql @qry diff --git a/SAM - Jobs/JOBS - Disable jobs on Failover in AG using catagories.sql b/SAM - Jobs/JOBS - Disable jobs on Failover in AG using catagories.sql new file mode 100644 index 0000000..7e4107a --- /dev/null +++ b/SAM - Jobs/JOBS - Disable jobs on Failover in AG using catagories.sql @@ -0,0 +1,108 @@ +USE [msdb] +GO + +/****** Object: Job [DBA:SSRS_CHANGE_JOBS_STATUS] Script Date: 20/11/2024 17:01:40 ******/ +BEGIN TRANSACTION +DECLARE @ReturnCode INT +SELECT @ReturnCode = 0 +/****** Object: JobCategory [[Uncategorized (Local)]] Script Date: 20/11/2024 17:01:41 ******/ +IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) +BEGIN +EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback + +END + +DECLARE @jobId BINARY(16) +EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'DBA:SSRS_CHANGE_JOBS_STATUS', + @enabled=1, + @notify_level_eventlog=0, + @notify_level_email=2, + @notify_level_netsend=0, + @notify_level_page=0, + @delete_level=0, + @description=N'Enable COMPASS jobs on primary node only', + @category_name=N'[Uncategorized (Local)]', + @owner_login_name=N'sa', + @notify_email_operator_name=N'prfSQL_ERRORLOG_ALERTS', @job_id = @jobId OUTPUT +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +/****** Object: Step [monitor jobs status] Script Date: 20/11/2024 17:01:42 ******/ +EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'monitor jobs status', + @step_id=1, + @cmdexec_success_code=0, + @on_success_action=1, + @on_success_step_id=0, + @on_fail_action=2, + @on_fail_step_id=0, + @retry_attempts=0, + @retry_interval=0, + @os_run_priority=0, @subsystem=N'TSQL', + @command=N'DECLARE @is_primary smallint +DECLARE @sql varchar(max) +DECLARE @job_name VARCHAR(100) + + +------------------------------------------------------------------------------------------ + +SELECT @is_primary = (SELECT sys.fn_hadr_is_primary_replica (''ReportServer'')) + +------------------------------------------------------------------------------------------ + +-- Cursor for all SSRS jobs +DECLARE csr_Jobs_Names CURSOR +FOR +SELECT [sJOB].name +FROM msdb.dbo.sysjobs AS [sJOB] +LEFT JOIN [msdb].[dbo].[syscategories] AS [sCAT] +ON [sJOB].[category_id] = [sCAT].[category_id] +WHERE [sCAT].name = ''Report Server'' +AND [sJOB].enabled <> @is_primary +ORDER BY name + + +OPEN csr_Jobs_Names +FETCH NEXT FROM csr_Jobs_Names INTO @job_name + +WHILE (@@FETCH_STATUS <> -1) -- Do for each job +BEGIN + SELECT @sql = ''EXEC msdb.dbo.sp_update_job @job_name='' + '''''''' + @job_name + '''''''' + '',@enabled = '' + cast(@is_primary as varchar) + EXEC (@sql) + --PRINT @sql + + FETCH NEXT FROM csr_Jobs_Names INTO @job_name +END + +CLOSE csr_Jobs_Names +DEALLOCATE csr_Jobs_Names +SET NOCOUNT OFF + +------------------------------------------------------------------------------------------', + @database_name=N'master', + @flags=0 +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'monitor jobs status', + @enabled=1, + @freq_type=4, + @freq_interval=1, + @freq_subday_type=4, + @freq_subday_interval=1, + @freq_relative_interval=0, + @freq_recurrence_factor=0, + @active_start_date=20210514, + @active_end_date=99991231, + @active_start_time=0, + @active_end_time=235959--, + --@schedule_uid=N'df9212bd-7105-4c83-88c4-e552d55b9288' +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' +IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback +COMMIT TRANSACTION +GOTO EndSave +QuitWithRollback: + IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION +EndSave: +GO + + diff --git a/SAM - PLAN CACHE/PLAN CACHE - sp_cachobjects.sql b/SAM - PLAN CACHE/PLAN CACHE - sp_cachobjects.sql new file mode 100644 index 0000000..0abc27d --- /dev/null +++ b/SAM - PLAN CACHE/PLAN CACHE - sp_cachobjects.sql @@ -0,0 +1,57 @@ +/* + +https://www.sqlserverinternals.com/blog/2018/4/19/kalen-delaney-geek-city-spcacheobjects + +*/ + + + +-- Create a view to show most of the same information +-- as SQL Server 2000's syscacheobjects +USE master +GO +DROP VIEW IF EXISTS sp_cacheobjects; +GO +-- You may want to add other filters in the WHERE clause to remove +-- other system operations on your own SQL Server +CREATE VIEW sp_cacheobjects (bucketid, cacheobjtype, objtype, + usecounts, pagesused, objid, dbid, + dbidexec, uid, refcounts, setopts, langid, + dateformat, status, lasttime, maxexectime, + avgexectime, lastreads, lastwrites, sqlbytes, + sql, plan_handle) +AS + SELECT pvt.bucketid, + CONVERT(nvarchar(18), pvt.cacheobjtype) as cacheobjtype, + pvt.objtype, pvt.usecounts, + pvt.size_in_bytes / 8192 as size_in_bytes, + CONVERT(int, pvt.objectid)as object_id, + CONVERT(smallint, pvt.dbid) as dbid, + CONVERT(smallint, pvt.dbid_execute) as execute_dbid, + CONVERT(smallint, pvt.user_id) as user_id, + pvt.refcounts, + CONVERT(int, pvt.set_options) as setopts, + CONVERT(smallint, pvt.language_id) as langid, + CONVERT(smallint, pvt.date_format) as date_format, + CONVERT(int, pvt.status) as status, + CONVERT(bigint, 0), CONVERT(bigint, 0), + CONVERT(bigint, 0), CONVERT(bigint, 0), + CONVERT(bigint, 0), + CONVERT(int, LEN(CONVERT(nvarchar(max), fgs.text)) * 2), + CONVERT(nvarchar(3900), fgs.text), plan_handle + FROM (SELECT ecp.*, epa.attribute, epa.value + FROM sys.dm_exec_cached_plans ecp + OUTER APPLY + sys.dm_exec_plan_attributes(ecp.plan_handle) epa) as ecpa + PIVOT (MAX(ecpa.value) + for ecpa.attribute IN + ("set_options", "objectid", "dbid", + "dbid_execute", "user_id", "language_id", + "date_format", "status")) as pvt + OUTER APPLY sys.dm_exec_sql_text(pvt.plan_handle) fgs + WHERE cacheobjtype like 'Compiled%' + AND pvt.dbid between 5 and 32766 + AND text NOT LIKE '%msparam%' + AND text not like '%xtp%' + AND text not like '%filetable%' + AND text not like '%fulltext%'; diff --git a/SAM - PLAN CACHE/PLANCACHE - 8 Ways to Clear Plan Cache.sql b/SAM - PLAN CACHE/PLANCACHE - 8 Ways to Clear Plan Cache.sql new file mode 100644 index 0000000..e4d4fb9 --- /dev/null +++ b/SAM - PLAN CACHE/PLANCACHE - 8 Ways to Clear Plan Cache.sql @@ -0,0 +1,101 @@ +/* +https://www.sqlskills.com/blogs/glenn/eight-different-ways-to-clear-the-sql-server-plan-cache/ +*/ + + +-- Eight different ways to clear the plan cache +-- Glenn Berry +-- SQLskills.com + + + +-- Example 1 *********************** +-- Remove all elements from the plan cache for the entire instance +DBCC FREEPROCCACHE; + + +-- Example 2 *********************** +-- Flush the plan cache for the entire instance and suppress the regular completion message +-- "DBCC execution completed. If DBCC printed error messages, contact your system administrator." +DBCC FREEPROCCACHE WITH NO_INFOMSGS; + + +-- Example 3 *********************** +-- Flush the ad hoc and prepared plan cache for the entire instance +DBCC FREESYSTEMCACHE ('SQL Plans'); + + +-- Example 4 *********************** +-- Flush the ad hoc and prepared plan cache for one resource pool + +-- Get Resource Pool information +SELECT name AS [Resource Pool Name], cache_memory_kb/1024.0 AS [cache_memory (MB)], + used_memory_kb/1024.0 AS [used_memory (MB)] +FROM sys.dm_resource_governor_resource_pools; + +-- Flush the ad hoc and prepared plan cache for one resource pool +DBCC FREESYSTEMCACHE ('SQL Plans', 'LimitedIOPool'); + + +-- Example 5 ********************** +-- Flush the entire plan cache for one resource pool + +-- Get Resource Pool information +SELECT name AS [Resource Pool Name], cache_memory_kb/1024.0 AS [cache_memory (MB)], + used_memory_kb/1024.0 AS [used_memory (MB)] +FROM sys.dm_resource_governor_resource_pools; + + +-- Flush the plan cache for one resource pool +DBCC FREEPROCCACHE ('LimitedIOPool'); +GO + + +-- Example 6 ********************** +-- Remove all elements from the plan cache for one database (does not work in SQL Azure) + +-- Get DBID from one database name first +DECLARE @intDBID INT; +SET @intDBID = (SELECT [dbid] + FROM master.dbo.sysdatabases + WHERE name = N'AdventureWorks2014'); + +-- Flush the plan cache for one database only +DBCC FLUSHPROCINDB (@intDBID); + + + +-- Example 7 ********************** +-- Clear plan cache for the current database + +USE AdventureWorks2014; +GO +-- Clear plan cache for the current database +-- New in SQL Server 2016 and SQL Azure +ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; + + + +-- Example 8 ********************** +-- Remove one query plan from the cache + +USE AdventureWorks2014; +GO + +-- Run a stored procedure or query +EXEC dbo.uspGetEmployeeManagers 9; + +-- Find the plan handle for that query +-- OPTION (RECOMPILE) keeps this query from going into the plan cache +SELECT cp.plan_handle, cp.objtype, cp.usecounts, +DB_NAME(st.dbid) AS [DatabaseName] +[text][/text] + +FROM sys.dm_exec_cached_plans AS cp CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS st +WHERE OBJECT_NAME (st.objectid) +[text][/text] + +LIKE N'%uspGetEmployeeManagers%' OPTION (RECOMPILE); + +-- Remove the specific query plan from the cache using the plan handle from the above query +DBCC FREEPROCCACHE (0x050011007A2CC30E204991F30200000001000000000000000000000000000000000000000000000000000000); diff --git a/SAM - PLAN CACHE/PLANCACHE - CLear Single Use Plans.sql b/SAM - PLAN CACHE/PLANCACHE - CLear Single Use Plans.sql new file mode 100644 index 0000000..72a4168 --- /dev/null +++ b/SAM - PLAN CACHE/PLANCACHE - CLear Single Use Plans.sql @@ -0,0 +1,78 @@ +/* https://dba.stackexchange.com/questions/241127/dbcc-freesystemcache-sql-plans-deletes-all-ad-hoc-and-prepared-plans-not-jus +*/ + +DECLARE @plan_handle varbinary(64) + +DECLARE db_cursor CURSOR FOR +SELECT plan_handle +FROM sys.dm_exec_cached_plans +WHERE objtype = 'Adhoc' + +OPEN db_cursor +FETCH NEXT FROM db_cursor INTO @plan_handle + +WHILE @@FETCH_STATUS = 0 +BEGIN + DBCC FREEPROCCACHE (@plan_handle); + FETCH NEXT FROM db_cursor INTO @plan_handle +END + +CLOSE db_cursor +DEALLOCATE db_cursor + + + +/* Erik Darling version */ + + +ALTER PROCEDURE [dbo].[clear_single_plans] +AS +BEGIN +SET NOCOUNT, XACT_ABORT ON; + +IF OBJECT_ID('tempdb..#plan_handles') +IS NOT NULL +BEGIN + DROP TABLE #plan_handles +END + +CREATE TABLE #plan_handles(plan_handle VARCHAR(1000)); + +DECLARE @plan_handle_command VARCHAR(1000) = ''; + +INSERT #plan_handles ( plan_handle ) +SELECT DISTINCT 'DBCC FREEPROCCACHE (' + + CONVERT(VARCHAR(128), decp.plan_handle, 1) + + ');' +FROM sys.dm_exec_cached_plans AS decp +JOIN sys.dm_exec_query_stats AS deqs + ON decp.plan_handle = deqs.plan_handle +WHERE decp.usecounts = 1 +AND decp.objtype = 'Adhoc' +AND deqs.last_execution_time < DATEADD(HOUR, -1, GETDATE()); + +DECLARE plan_cursor CURSOR +FORWARD_ONLY LOCAL +FOR +SELECT plan_handle +FROM #plan_handles; + +OPEN plan_cursor; +FETCH NEXT FROM plan_cursor + INTO @plan_handle_command +WHILE @@FETCH_STATUS = 0 +BEGIN + + PRINT @plan_handle_command; + EXEC(@plan_handle_command); + +FETCH NEXT FROM plan_cursor + INTO @plan_handle_command; +END; + +CLOSE plan_cursor; +DEALLOCATE plan_cursor; +END; + + + diff --git a/SAM - PLAN CACHE/PLANCACHE - List Plans in Cache with Parameterized Plans.sql b/SAM - PLAN CACHE/PLANCACHE - List Plans in Cache with Parameterized Plans.sql new file mode 100644 index 0000000..0ef49de --- /dev/null +++ b/SAM - PLAN CACHE/PLANCACHE - List Plans in Cache with Parameterized Plans.sql @@ -0,0 +1,22 @@ +/* +https://sqlserverfast.com/blog/hugo/2014/01/parameterization-and-filtered-indexes-part-1/ + +*/ + + + WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan') + +SELECT usecounts, cacheobjtype, objtype, text , db_name(d.database_id), cp.plan_handle,qs.plan_handle, qs.query_plan_hash, qs.creation_time,qs.query_hash, er.plan_handle,q.query_plan,cp.size_in_bytes +,stmt.value('(@ParameterizedPlanHandle)', + 'varchar(64)') AS ParameterizedPlanHandle +FROM sys.dm_exec_cached_plans cp +CROSS APPLY sys.dm_exec_sql_text(plan_handle) st +cross apply sys.dm_exec_query_plan(plan_handle) q +cross apply q.query_plan.nodes('/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS batch(stmt) +left join sys.dm_exec_query_stats qs on cp.plan_handle=qs.plan_handle +left join sys.dm_exec_requests er on cp.plan_handle=er.plan_handle +join sys.databases d on st.dbid = d.database_id +--WHERE usecounts = 1 +where db_name(d.database_id) = 'TEST' +ORDER BY usecounts DESC; +GO diff --git a/SAM - SSIS/Check oldest record is SSISDB.sql b/SAM - SSIS/Check oldest record is SSISDB.sql new file mode 100644 index 0000000..4b4e296 --- /dev/null +++ b/SAM - SSIS/Check oldest record is SSISDB.sql @@ -0,0 +1,6 @@ +/*Find the last date in SSISDB*/ + +use SSISDB +go + +select min(end_time) from internal.operations diff --git a/SAM - Security/SECURITY - Find membership of AD groups -xp_logininfo.sql b/SAM - Security/SECURITY - Find membership of AD groups -xp_logininfo.sql new file mode 100644 index 0000000..af7e153 --- /dev/null +++ b/SAM - Security/SECURITY - Find membership of AD groups -xp_logininfo.sql @@ -0,0 +1,5 @@ +--List all members of AD group +xp_logininfo [BILLING\OXF_Reporting Server], 'members' + +--list AD groups a user is part of +xp_logininfo [uk\deaconi], 'all' diff --git a/SAM - Security/SECURITY - Script out COMPLETE user permissions to TSQL.sql b/SAM - Security/SECURITY - Script out COMPLETE user permissions to TSQL.sql index 99e6dfd..6a07838 100644 --- a/SAM - Security/SECURITY - Script out COMPLETE user permissions to TSQL.sql +++ b/SAM - Security/SECURITY - Script out COMPLETE user permissions to TSQL.sql @@ -1,6 +1,34 @@ /** ** This script is to script out the TSQL required to apply the complete permissions of a given user. ** Simply assign the login name you would like to script permissions for to the @login_name variable and execute + + +--27/08/25 + +Output is incorrect. Format should be as so + +--TSQL +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--Logins to be created +USE master; IF (SUSER_ID('ABanas') IS NULL) BEGIN CREATE LOGIN [ABanas] WITH PASSWORD = 'Mz7$kW3&nB6^eY1*' END; + +--Server level permissions +USE master; GRANT CONNECT SQL TO [ABanas]; + +--Server role permissions + +--Database level permissions +USE Enterprise; CREATE USER [ABanas] FOR LOGIN [ABanas]; + +--Database role permissions +USE Enterprise; EXEC sp_addrolemember @rolename = 'udrAppAccount', @membername = 'ABanas'; + +--Object level permissions + + +**Need to add corrections to the script + + **/ DECLARE @login_name nvarchar(100)='closebrothersgp\zSvcALMUAT' diff --git a/SAM - Security/SECURITY - xp_logininfo.sql b/SAM - Security/SECURITY - xp_logininfo.sql deleted file mode 100644 index 9b60640..0000000 --- a/SAM - Security/SECURITY - xp_logininfo.sql +++ /dev/null @@ -1,2 +0,0 @@ -xp_logininfo [BILLING\OXF_Reporting Server], 'members' -xp_logininfo [uk\deaconi], 'all' \ No newline at end of file diff --git a/SAM - Waits/Azure SQL DB Diagnostic Queries.sql b/SAM - Waits/Azure SQL DB Diagnostic Queries.sql new file mode 100644 index 0000000..1dc07fc --- /dev/null +++ b/SAM - Waits/Azure SQL DB Diagnostic Queries.sql @@ -0,0 +1,1263 @@ +-- Azure SQL Database Diagnostic Information Queries +-- Glenn Berry +-- Last Modified: October 1, 2020 +-- https://glennsqlperformance.com/ +-- https://sqlserverperformance.wordpress.com/ +-- YouTube: https://bit.ly/2PkoAM1 +-- Twitter: GlennAlanBerry + +-- Diagnostic Queries are available here +-- https://glennsqlperformance.com/resources/ + + +-- If you like PowerShell, there is a very useful community solution for running these queries in an automated fashion +-- https://dbatools.io/ + +-- Invoke-DbaDiagnosticQuery +-- https://dbatools.io/functions/invoke-dbadiagnosticquery/ + + +--****************************************************************************** +--* Copyright (C) 2020 Glenn Berry +--* All rights reserved. +--* +--* +--* You may alter this code for your own *non-commercial* purposes. You may +--* republish altered code as long as you include this copyright and give due credit. +--* +--* +--* THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF +--* ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED +--* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +--* PARTICULAR PURPOSE. +--* +--****************************************************************************** + +-- Make sure you are connected a user database, rather than the master system database + + +-- Server level queries ******************************* + +-- SQL and OS Version information for current instance (Query 1) (Version Info) +SELECT @@SERVERNAME AS [Server Name], @@VERSION AS [SQL Server and OS Version Info]; +------ + +-- Azure SQL Database does not expose as much information as on-premises SQL Server does + + + +-- Get logical instance-level configuration values for instance (Query 2) (Configuration Values) +SELECT name, value, value_in_use, minimum, maximum, [description], is_dynamic, is_advanced +FROM sys.configurations WITH (NOLOCK) +ORDER BY name OPTION (RECOMPILE); +------ + +-- All of these settings are read-only in Azure SQL Database, so they are informational only + + + +-- SQL Server NUMA Node information (Query 3) (SQL Server NUMA Info) +SELECT node_id, node_state_desc, memory_node_id, processor_group, cpu_count, online_scheduler_count, + idle_scheduler_count, active_worker_count, avg_load_balance, resource_monitor_state +FROM sys.dm_os_nodes WITH (NOLOCK) +WHERE node_state_desc <> N'ONLINE DAC' OPTION (RECOMPILE); +------ + +-- Gives you some useful information about the composition and relative load on your NUMA nodes +-- You want to see an equal number of schedulers on each NUMA node + + + +-- Calculates average stalls per read, per write, and per total input/output for each database file (Query 4) (IO Stalls by File) +SELECT DB_NAME(fs.database_id) AS [Database Name], CAST(fs.io_stall_read_ms/(1.0 + fs.num_of_reads) AS NUMERIC(16,1)) AS [avg_read_stall_ms], +CAST(fs.io_stall_write_ms/(1.0 + fs.num_of_writes) AS NUMERIC(16,1)) AS [avg_write_stall_ms], +CAST((fs.io_stall_read_ms + fs.io_stall_write_ms)/(1.0 + fs.num_of_reads + fs.num_of_writes) AS NUMERIC(16,1)) AS [avg_io_stall_ms], +fs.io_stall_read_ms, fs.num_of_reads, +fs.io_stall_write_ms, fs.num_of_writes, fs.io_stall_read_ms + fs.io_stall_write_ms AS [io_stalls], fs.num_of_reads + fs.num_of_writes AS [total_io], +io_stall_queued_read_ms AS [Resource Governor Total Read IO Latency (ms)], io_stall_queued_write_ms AS [Resource Governor Total Write IO Latency (ms)] +FROM sys.dm_io_virtual_file_stats(null,null) AS fs +ORDER BY avg_io_stall_ms DESC OPTION (RECOMPILE); +------ + +-- Helps determine which database files on the entire instance have the most I/O bottlenecks +-- This can help you decide whether certain LUNs are overloaded and whether you might +-- want to move some files to a different location or perhaps improve your I/O performance +-- These latency numbers include all file activity against each SQL Server +-- database file since SQL Server was last started + + + +-- Get I/O utilization by database (Query 5) (IO Usage By Database) +WITH Aggregate_IO_Statistics +AS (SELECT DB_NAME(database_id) AS [Database Name], + CAST(SUM(num_of_bytes_read + num_of_bytes_written) / 1048576 AS DECIMAL(12, 2)) AS [ioTotalMB], + CAST(SUM(num_of_bytes_read ) / 1048576 AS DECIMAL(12, 2)) AS [ioReadMB], + CAST(SUM(num_of_bytes_written) / 1048576 AS DECIMAL(12, 2)) AS [ioWriteMB] + FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS [DM_IO_STATS] + GROUP BY database_id) +SELECT ROW_NUMBER() OVER (ORDER BY ioTotalMB DESC) AS [I/O Rank], + [Database Name], ioTotalMB AS [Total I/O (MB)], + CAST(ioTotalMB / SUM(ioTotalMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [Total I/O %], + ioReadMB AS [Read I/O (MB)], + CAST(ioReadMB / SUM(ioReadMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [Read I/O %], + ioWriteMB AS [Write I/O (MB)], + CAST(ioWriteMB / SUM(ioWriteMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [Write I/O %] +FROM Aggregate_IO_Statistics +ORDER BY [I/O Rank] OPTION (RECOMPILE); +------ + +-- Helps determine which database is using the most I/O resources on the instance + + +-- Get total buffer usage by database for current instance (Query 6) (Total Buffer Usage by Database) +-- This make take some time to run on a busy instance +WITH AggregateBufferPoolUsage +AS +(SELECT DB_NAME(database_id) AS [Database Name], COUNT(page_id) AS [Page Count], +CAST(COUNT(*) * 8/1024.0 AS DECIMAL (10,2)) AS [CachedSize], +AVG(read_microsec) AS [Avg Read Time (microseconds)] +FROM sys.dm_os_buffer_descriptors WITH (NOLOCK) +GROUP BY DB_NAME(database_id)) +SELECT ROW_NUMBER() OVER(ORDER BY CachedSize DESC) AS [Buffer Pool Rank], [Database Name], + CAST(CachedSize / SUM(CachedSize) OVER() * 100.0 AS DECIMAL(5,2)) AS [Buffer Pool Percent], + [Page Count], CachedSize AS [Cached Size (MB)], [Avg Read Time (microseconds)] +FROM AggregateBufferPoolUsage +ORDER BY [Buffer Pool Rank] OPTION (RECOMPILE); +------ + +-- Tells you how much memory (in the buffer pool) +-- is being used by each database on the instance + + + +-- Get a count of SQL connections by IP address (Query 7) (Connection Counts by IP Address) +SELECT ec.client_net_address, es.[program_name], es.[host_name], es.login_name, +COUNT(ec.session_id) AS [connection count] +FROM sys.dm_exec_sessions AS es WITH (NOLOCK) +INNER JOIN sys.dm_exec_connections AS ec WITH (NOLOCK) +ON es.session_id = ec.session_id +GROUP BY ec.client_net_address, es.[program_name], es.[host_name], es.login_name +ORDER BY ec.client_net_address, es.[program_name] OPTION (RECOMPILE); +------ + +-- This helps you figure where your database load is coming from +-- and verifies connectivity from other machines + +-- Solving Connectivity errors to SQL Server +-- https://bit.ly/2EgzoD0 + + + +-- Get Average Task Counts (run multiple times) (Query 8) (Avg Task Counts) +SELECT AVG(current_tasks_count) AS [Avg Task Count], +AVG(work_queue_count) AS [Avg Work Queue Count], +AVG(runnable_tasks_count) AS [Avg Runnable Task Count], +AVG(pending_disk_io_count) AS [Avg Pending DiskIO Count] +FROM sys.dm_os_schedulers WITH (NOLOCK) +WHERE scheduler_id < 255 OPTION (RECOMPILE); +------ + +-- Sustained values above 10 suggest further investigation in that area (depending on your Service Tier) +-- Avg Task Counts will be higher with lower service tiers +-- High Avg Task Counts are often caused by blocking/deadlocking or other resource contention + +-- Sustained values above 1 suggest further investigation in that area +-- High Avg Runnable Task Counts are a good sign of CPU pressure +-- High Avg Pending DiskIO Counts are a sign of disk pressure + + + +-- Detect blocking (run multiple times) (Query 9) (Detect Blocking) +SELECT t1.resource_type AS [lock type], DB_NAME(resource_database_id) AS [database], +t1.resource_associated_entity_id AS [blk object],t1.request_mode AS [lock req], -- lock requested +t1.request_session_id AS [waiter sid], t2.wait_duration_ms AS [wait time], -- spid of waiter +(SELECT [text] FROM sys.dm_exec_requests AS r WITH (NOLOCK) -- get sql for waiter +CROSS APPLY sys.dm_exec_sql_text(r.[sql_handle]) +WHERE r.session_id = t1.request_session_id) AS [waiter_batch], +(SELECT SUBSTRING(qt.[text],r.statement_start_offset/2, + (CASE WHEN r.statement_end_offset = -1 + THEN LEN(CONVERT(nvarchar(max), qt.[text])) * 2 + ELSE r.statement_end_offset END - r.statement_start_offset)/2) +FROM sys.dm_exec_requests AS r WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(r.[sql_handle]) AS qt +WHERE r.session_id = t1.request_session_id) AS [waiter_stmt], -- statement blocked +t2.blocking_session_id AS [blocker sid], -- spid of blocker +(SELECT [text] FROM sys.sysprocesses AS p -- get sql for blocker +CROSS APPLY sys.dm_exec_sql_text(p.[sql_handle]) +WHERE p.spid = t2.blocking_session_id) AS [blocker_batch] +FROM sys.dm_tran_locks AS t1 WITH (NOLOCK) +INNER JOIN sys.dm_os_waiting_tasks AS t2 WITH (NOLOCK) +ON t1.lock_owner_address = t2.resource_address OPTION (RECOMPILE); +------ + +-- Helps troubleshoot blocking and deadlocking issues +-- The results will change from second to second on a busy system +-- You should run this query multiple times when you see signs of blocking + + + +-- Page Life Expectancy (PLE) value for each NUMA node in current instance (Query 10) (PLE by NUMA Node) +SELECT @@SERVERNAME AS [Server Name], RTRIM([object_name]) AS [Object Name], + instance_name, cntr_value AS [Page Life Expectancy] +FROM sys.dm_os_performance_counters WITH (NOLOCK) +WHERE [object_name] LIKE N'%Buffer Node%' -- Handles named instances +AND counter_name = N'Page life expectancy' OPTION (RECOMPILE); +------ + +-- PLE is a good measurement of internal memory pressure +-- Higher PLE is better. Watch the trend over time, not the absolute value +-- This will only return one row for non-NUMA systems + +-- Page Life Expectancy isn’t what you think… +-- https://bit.ly/2EgynLa + + +-- Memory Grants Pending value for current instance (Query 11) (Memory Grants Pending) +SELECT @@SERVERNAME AS [Server Name], RTRIM([object_name]) AS [Object Name], cntr_value AS [Memory Grants Pending] +FROM sys.dm_os_performance_counters WITH (NOLOCK) +WHERE [object_name] LIKE N'%Memory Manager%' -- Handles named instances +AND counter_name = N'Memory Grants Pending' OPTION (RECOMPILE); +------ + +-- Run multiple times, and run periodically if you suspect you are under memory pressure +-- Memory Grants Pending above zero for a sustained period is a very strong indicator of internal memory pressure + + +-- Memory Clerk Usage for instance (Query 12) (Memory Clerk Usage) +-- Look for high value for CACHESTORE_SQLCP (Ad-hoc query plans) +SELECT TOP(10) mc.[type] AS [Memory Clerk Type], + CAST((SUM(mc.pages_kb)/1024.0) AS DECIMAL (15,2)) AS [Memory Usage (MB)] +FROM sys.dm_os_memory_clerks AS mc WITH (NOLOCK) +GROUP BY mc.[type] +ORDER BY SUM(mc.pages_kb) DESC OPTION (RECOMPILE); +------ + +-- MEMORYCLERK_SQLBUFFERPOOL was new for SQL Server 2012. It should be your highest consumer of memory + +-- CACHESTORE_SQLCP SQL Plans +-- These are cached SQL statements or batches that aren't in stored procedures, functions and triggers +-- Watch out for high values for CACHESTORE_SQLCP +-- Enabling 'optimize for ad hoc workloads' at the instance level can help reduce this + + +-- CACHESTORE_OBJCP Object Plans +-- These are compiled plans for stored procedures, functions and triggers + + + +-- Find single-use, ad-hoc and prepared queries that are bloating the plan cache (Query 13) (Ad hoc Queries) +SELECT TOP(50) DB_NAME(t.[dbid]) AS [Database Name], t.[text] AS [Query Text], +cp.objtype AS [Object Type], cp.cacheobjtype AS [Cache Object Type], +cp.size_in_bytes/1024 AS [Plan Size in KB] +FROM sys.dm_exec_cached_plans AS cp WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +WHERE cp.cacheobjtype = N'Compiled Plan' +AND cp.objtype IN (N'Adhoc', N'Prepared') +AND cp.usecounts = 1 +ORDER BY cp.size_in_bytes DESC, DB_NAME(t.[dbid]) OPTION (RECOMPILE); +------ + +-- Gives you the text, type and size of single-use ad-hoc and prepared queries that waste space in the plan cache +-- Enabling forced parameterization for the database can help, but test first! + +-- Plan cache, adhoc workloads and clearing the single-use plan cache bloat +-- https://bit.ly/2EfYOkl + + + + + + +-- Database specific queries ***************************************************************** + + +-- Azure SQL Database size (Query 14) (Azure SQL DB Size) +SELECT CAST(SUM(CAST(FILEPROPERTY(name, 'SpaceUsed') AS bigint) * 8192.) / 1024 / 1024 AS DECIMAL(15,2)) AS [Database Size In MB], + CAST(SUM(CAST(FILEPROPERTY(name, 'SpaceUsed') AS bigint) * 8192.) / 1024 / 1024 / 1024 AS DECIMAL(15,2)) AS [Database Size In GB] +FROM sys.database_files WITH (NOLOCK) +WHERE [type_desc] = N'ROWS' OPTION (RECOMPILE); +------ + +-- This gives you the actual space usage within the data file only, to match what the Azure portal shows for the database size + +-- Determining Database Size in Azure SQL Database V12 +-- https://bit.ly/2JjrqNh + + + +-- Individual File Sizes and space available for current database (Query 15) (File Sizes and Space) +SELECT f.name AS [File Name] , f.physical_name AS [Physical Name], +CAST((f.size/128.0) AS DECIMAL(15,2)) AS [Total Size in MB], +CAST(f.size/128.0 - CAST(FILEPROPERTY(f.name, 'SpaceUsed') AS int)/128.0 AS DECIMAL(15,2)) +AS [Available Space In MB], f.[file_id], fg.name AS [Filegroup Name], +f.is_percent_growth, f.growth, fg.is_default, fg.is_read_only, +fg.is_autogrow_all_files +FROM sys.database_files AS f WITH (NOLOCK) +LEFT OUTER JOIN sys.filegroups AS fg WITH (NOLOCK) +ON f.data_space_id = fg.data_space_id +ORDER BY f.[file_id] OPTION (RECOMPILE); +------ + +-- Look at how large and how full the files are and where they are located + +-- is_autogrow_all_files was new for SQL Server 2016. Equivalent to TF 1117 for user databases + +-- SQL Server 2016: Changes in default behavior for autogrow and allocations for tempdb and user databases +-- http://bit.ly/2evRZSR + + + +-- Log space usage for current database (Query 16) (Log Space Usage) +SELECT DB_NAME(lsu.database_id) AS [Database Name], db.recovery_model_desc AS [Recovery Model], + CAST(lsu.total_log_size_in_bytes/1048576.0 AS DECIMAL(10, 2)) AS [Total Log Space (MB)], + CAST(lsu.used_log_space_in_bytes/1048576.0 AS DECIMAL(10, 2)) AS [Used Log Space (MB)], + CAST(lsu.used_log_space_in_percent AS DECIMAL(10, 2)) AS [Used Log Space %], + CAST(lsu.log_space_in_bytes_since_last_backup/1048576.0 AS DECIMAL(10, 2)) AS [Used Log Space Since Last Backup (MB)], + db.log_reuse_wait_desc +FROM sys.dm_db_log_space_usage AS lsu WITH (NOLOCK) +INNER JOIN sys.databases AS db WITH (NOLOCK) +ON lsu.database_id = db.database_id +OPTION (RECOMPILE); +------ + +-- Look at log file size and usage, along with the log reuse wait description for the current database + + +-- Get VLF Count for current database (Query 17) (VLF Counts) +SELECT [name] AS [Database Name], [VLF Count] +FROM sys.databases AS db WITH (NOLOCK) +CROSS APPLY (SELECT file_id, COUNT(*) AS [VLF Count] + FROM sys.dm_db_log_info(db.database_id) + GROUP BY file_id) AS li +WHERE [name] <> N'master' +ORDER BY [VLF Count] DESC OPTION (RECOMPILE); +------ + +-- High VLF counts can affect write performance to the log file +-- and they can make full database restores and crash recovery take much longer +-- Try to keep your VLF counts under 200 in most cases (depending on log file size) + +-- Important change to VLF creation algorithm in SQL Server 2014 +-- https://bit.ly/2Hsjbg4 + + + +-- Status of last VLF for current database (Query 18) (Last VLF Status) +SELECT TOP(1) DB_NAME(li.database_id) AS [Database Name], li.[file_id], + li.vlf_size_mb, li.vlf_sequence_number, li.vlf_active, li.vlf_status +FROM sys.dm_db_log_info(DB_ID()) AS li +ORDER BY vlf_sequence_number DESC OPTION (RECOMPILE); +------ + +-- Determine whether you will be able to shrink the transaction log file + +-- vlf_status Values +-- 0 is inactive +-- 1 is initialized but unused +-- 2 is active + + +-- Important database properties for current database (Query 19) (Database Properties) +SELECT db.[name] AS [Database Name], db.recovery_model_desc AS [Recovery Model], +db.state_desc, db.containment_desc, db.log_reuse_wait_desc AS [Log Reuse Wait Description], +db.[compatibility_level] AS [DB Compatibility Level], +db.is_mixed_page_allocation_on, db.page_verify_option_desc AS [Page Verify Option], +db.is_auto_create_stats_on, db.is_auto_update_stats_on, db.is_auto_update_stats_async_on, db.is_parameterization_forced, +db.snapshot_isolation_state_desc, db.is_read_committed_snapshot_on, db.is_auto_close_on, db.is_auto_shrink_on, +db.target_recovery_time_in_seconds, db.is_cdc_enabled, db.is_memory_optimized_elevate_to_snapshot_on, +db.delayed_durability_desc, db.is_query_store_on, db.is_temporal_history_retention_enabled, +db.is_accelerated_database_recovery_on +FROM sys.databases AS db WITH (NOLOCK) +WHERE db.[name] <> N'master' +ORDER BY db.[name] OPTION (RECOMPILE); +------ + +-- sys.databases (Transact-SQL) +-- https://bit.ly/2G5wqaX + +-- Things to look at: +-- What recovery model are you using? +-- What is the log reuse wait description? +-- What compatibility level is the database on? +-- What is the Page Verify Option? (should be CHECKSUM) +-- Is Auto Update Statistics Asynchronously enabled? +-- Is Delayed Durability enabled? + + + + +-- Get database scoped configuration values for current database (Query 20) (Database-scoped Configurations) +SELECT configuration_id, [name], [value] AS [value_for_primary] +FROM sys.database_scoped_configurations WITH (NOLOCK) OPTION (RECOMPILE); +------ + +-- This lets you see the value of these new properties for the current database + +-- Clear plan cache for current database +-- ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; + +-- ALTER DATABASE SCOPED CONFIGURATION (Transact-SQL) +-- https://bit.ly/2sOH7nb + + +-- I/O Statistics by file for the current database (Query 21) (IO Stats By File) +SELECT DB_NAME(DB_ID()) AS [Database Name], df.name AS [Logical Name], vfs.[file_id], df.type_desc, +df.physical_name AS [Physical Name], CAST(vfs.size_on_disk_bytes/1048576.0 AS DECIMAL(10, 2)) AS [Size on Disk (MB)], +vfs.num_of_reads, vfs.num_of_writes, vfs.io_stall_read_ms, vfs.io_stall_write_ms, +CAST(100. * vfs.io_stall_read_ms/(vfs.io_stall_read_ms + vfs.io_stall_write_ms) AS DECIMAL(10,1)) AS [IO Stall Reads Pct], +CAST(100. * vfs.io_stall_write_ms/(vfs.io_stall_write_ms + vfs.io_stall_read_ms) AS DECIMAL(10,1)) AS [IO Stall Writes Pct], +(vfs.num_of_reads + vfs.num_of_writes) AS [Writes + Reads], +CAST(vfs.num_of_bytes_read/1048576.0 AS DECIMAL(10, 2)) AS [MB Read], +CAST(vfs.num_of_bytes_written/1048576.0 AS DECIMAL(10, 2)) AS [MB Written], +CAST(100. * vfs.num_of_reads/(vfs.num_of_reads + vfs.num_of_writes) AS DECIMAL(10,1)) AS [# Reads Pct], +CAST(100. * vfs.num_of_writes/(vfs.num_of_reads + vfs.num_of_writes) AS DECIMAL(10,1)) AS [# Write Pct], +CAST(100. * vfs.num_of_bytes_read/(vfs.num_of_bytes_read + vfs.num_of_bytes_written) AS DECIMAL(10,1)) AS [Read Bytes Pct], +CAST(100. * vfs.num_of_bytes_written/(vfs.num_of_bytes_read + vfs.num_of_bytes_written) AS DECIMAL(10,1)) AS [Written Bytes Pct] +FROM sys.dm_io_virtual_file_stats(DB_ID(), NULL) AS vfs +INNER JOIN sys.database_files AS df WITH (NOLOCK) +ON vfs.[file_id]= df.[file_id] OPTION (RECOMPILE); +------ + +-- This helps you characterize your workload better from an I/O perspective for this database +-- It helps you determine whether you has an OLTP or DW/DSS type of workload + + +-- Get recent resource usage (Query 22) (Recent Resource Usage) +SELECT end_time, dtu_limit, cpu_limit, avg_cpu_percent, avg_memory_usage_percent, + avg_data_io_percent, avg_log_write_percent, xtp_storage_percent, + max_worker_percent, max_session_percent, avg_login_rate_percent, + avg_instance_cpu_percent, avg_instance_memory_percent +FROM sys.dm_db_resource_stats WITH (NOLOCK) +ORDER BY end_time DESC OPTION (RECOMPILE); +------ + +-- Returns a row of usage metrics every 15 seconds, going back 64 minutes +-- The end_time column is UTC time + +-- sys.dm_db_resource_stats (Azure SQL Database) +-- https://bit.ly/2HaSpKn + + + +-- Get recent resource usage (Query 23) (Avg/Max Resource Usage) +SELECT CAST(AVG(avg_cpu_percent) AS DECIMAL(10,2)) AS [Average CPU Utilization In Percent], + CAST(MAX(avg_cpu_percent) AS DECIMAL(10,2)) AS [Maximum CPU Utilization In Percent], + CAST(AVG(avg_data_io_percent) AS DECIMAL(10,2)) AS [Average Data IO In Percent], + CAST(MAX(avg_data_io_percent) AS DECIMAL(10,2)) AS [Maximum Data IO In Percent], + CAST(AVG(avg_log_write_percent) AS DECIMAL(10,2)) AS [Average Log Write Utilization In Percent], + CAST(MAX(avg_log_write_percent) AS DECIMAL(10,2)) AS [Maximum Log Write Utilization In Percent], + CAST(AVG(avg_memory_usage_percent) AS DECIMAL(10,2)) AS [Average Memory Usage In Percent], + CAST(MAX(avg_memory_usage_percent) AS DECIMAL(10,2)) AS [Maximum Memory Usage In Percent] +FROM sys.dm_db_resource_stats WITH (NOLOCK) OPTION (RECOMPILE); +------ + + +-- Isolate top waits for this database since last restart or failover (Query 24) (Top DB Waits) +WITH [Waits] +AS (SELECT wait_type, wait_time_ms/ 1000.0 AS [WaitS], + (wait_time_ms - signal_wait_time_ms) / 1000.0 AS [ResourceS], + signal_wait_time_ms / 1000.0 AS [SignalS], + waiting_tasks_count AS [WaitCount], + 100.0 * wait_time_ms / SUM (wait_time_ms) OVER() AS [Percentage], + ROW_NUMBER() OVER(ORDER BY wait_time_ms DESC) AS [RowNum] + FROM sys.dm_db_wait_stats WITH (NOLOCK) + WHERE [wait_type] NOT IN ( + N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', + N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', + N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE', + N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE', + N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', + N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', + N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', + N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', + N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE', + N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE', + N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS', + N'PREEMPTIVE_ODBCOPS', + N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS', + N'PREEMPTIVE_OS_PIPEOPS', N'PREEMPTIVE_OS_AUTHENTICATIONOPS', + N'PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST', + N'PREEMPTIVE_OS_FILEOPS', N'PREEMPTIVE_OS_DEVICEOPS', N'PREEMPTIVE_OS_QUERYREGISTRY', + N'PREEMPTIVE_OS_WRITEFILE', + N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER', + N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT', + N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE', + N'PREEMPTIVE_XHTTP', + N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', + N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', + N'QDS_ASYNC_QUEUE', + N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH', + N'RESOURCE_GOVERNOR_IDLE', + N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP', + N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', + N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', + N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP', + N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES', + N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT', + N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'WAIT_XTP_RECOVERY', + N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN', + N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT') + AND waiting_tasks_count > 0) +SELECT + MAX (W1.wait_type) AS [WaitType], + CAST (MAX (W1.Percentage) AS DECIMAL (5,2)) AS [Wait Percentage], + CAST ((MAX (W1.WaitS) / MAX (W1.WaitCount)) AS DECIMAL (16,4)) AS [AvgWait_Sec], + CAST ((MAX (W1.ResourceS) / MAX (W1.WaitCount)) AS DECIMAL (16,4)) AS [AvgRes_Sec], + CAST ((MAX (W1.SignalS) / MAX (W1.WaitCount)) AS DECIMAL (16,4)) AS [AvgSig_Sec], + CAST (MAX (W1.WaitS) AS DECIMAL (16,2)) AS [Total_Wait_Sec], + CAST (MAX (W1.ResourceS) AS DECIMAL (16,2)) AS [Resource_Sec], + CAST (MAX (W1.SignalS) AS DECIMAL (16,2)) AS [Signal_Sec], + MAX (W1.WaitCount) AS [Wait Count] +FROM Waits AS W1 +INNER JOIN Waits AS W2 +ON W2.RowNum <= W1.RowNum +GROUP BY W1.RowNum +HAVING SUM (W2.Percentage) - MAX (W1.Percentage) < 99 -- percentage threshold +OPTION (RECOMPILE); +------ + +-- Cumulative wait stats are not as useful on an idle instance that is not under load or performance pressure + +-- SQL Server Wait Types Library +-- https://bit.ly/2ePzYO2 + +-- The SQL Server Wait Type Repository +-- https://bit.ly/1afzfjC + +-- Wait statistics, or please tell me where it hurts +-- https://bit.ly/2wsQHQE + +-- SQL Server 2005 Performance Tuning using the Waits and Queues +-- https://bit.ly/1o2NFoF + +-- sys.dm_db_wait_stats (Azure SQL Database) +-- https://bit.ly/2HoJOoT + + + +-- Get most frequently executed queries for this database (Query 25) (Query Execution Counts) +SELECT TOP(50) LEFT(t.[text], 50) AS [Short Query Text], qs.execution_count AS [Execution Count], +qs.total_logical_reads AS [Total Logical Reads], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +qs.total_worker_time AS [Total Worker Time], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +qs.total_elapsed_time AS [Total Elapsed Time], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +qs.creation_time AS [Creation Time] +--,t.[text] AS [Complete Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp +WHERE t.dbid = DB_ID() +ORDER BY qs.execution_count DESC OPTION (RECOMPILE); +------ + + +-- Get top total worker time queries for this database (Query 26) (Top Worker Time Queries) +SELECT TOP(50) DB_NAME(t.[dbid]) AS [Database Name], +REPLACE(REPLACE(LEFT(t.[text], 50), CHAR(10),''), CHAR(13),'') AS [Short Query Text], +qs.total_worker_time AS [Total Worker Time], qs.min_worker_time AS [Min Worker Time], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +qs.max_worker_time AS [Max Worker Time], +qs.min_elapsed_time AS [Min Elapsed Time], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +qs.max_elapsed_time AS [Max Elapsed Time], +qs.min_logical_reads AS [Min Logical Reads], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +qs.max_logical_reads AS [Max Logical Reads], +qs.execution_count AS [Execution Count], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +qs.creation_time AS [Creation Time] +--,t.[text] AS [Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp +WHERE t.dbid = DB_ID() +ORDER BY qs.total_worker_time DESC OPTION (RECOMPILE); +------ + +-- Helps you find the most expensive queries from a CPU perspective for this database +-- Can also help track down parameter sniffing issues + + +-- Get top total logical reads queries for this database (Query 27) (Top Logical Reads Queries) +SELECT TOP(50) DB_NAME(t.[dbid]) AS [Database Name], +REPLACE(REPLACE(LEFT(t.[text], 50), CHAR(10),''), CHAR(13),'') AS [Short Query Text], +qs.total_logical_reads AS [Total Logical Reads], +qs.min_logical_reads AS [Min Logical Reads], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +qs.max_logical_reads AS [Max Logical Reads], +qs.min_worker_time AS [Min Worker Time], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +qs.max_worker_time AS [Max Worker Time], +qs.min_elapsed_time AS [Min Elapsed Time], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +qs.max_elapsed_time AS [Max Elapsed Time], +qs.execution_count AS [Execution Count], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +qs.creation_time AS [Creation Time] +--,t.[text] AS [Complete Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp +WHERE t.dbid = DB_ID() +ORDER BY qs.total_logical_reads DESC OPTION (RECOMPILE); +------ + + +-- Helps you find the most expensive queries from a memory perspective for this database +-- Can also help track down parameter sniffing issues + + + +-- Get top average elapsed time queries for this database (Query 28) (Top Avg Elapsed Time Queries) +SELECT TOP(50) DB_NAME(t.[dbid]) AS [Database Name], +REPLACE(REPLACE(LEFT(t.[text], 255), CHAR(10),''), CHAR(13),'') AS [Short Query Text], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +qs.min_elapsed_time, qs.max_elapsed_time, qs.last_elapsed_time, +qs.execution_count AS [Execution Count], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +qs.total_physical_reads/qs.execution_count AS [Avg Physical Reads], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +qs.creation_time AS [Creation Time] +, qp.query_plan AS [Query Plan] -- comment out this column if copying results to Excel +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp +WHERE t.dbid = DB_ID() +ORDER BY qs.total_elapsed_time/qs.execution_count DESC OPTION (RECOMPILE); +------ + +-- Helps you find the highest average elapsed time queries for this database +-- Can also help track down parameter sniffing issues + + + +-- Top Cached SPs By Execution Count (Query 29) (SP Execution Counts) +SELECT TOP(100) p.name AS [SP Name], qs.execution_count AS [Execution Count], +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY qs.execution_count DESC OPTION (RECOMPILE); +------ + +-- Tells you which cached stored procedures are called the most often +-- This helps you characterize and baseline your workload + + +-- Top Cached SPs By Avg Elapsed Time (Query 30) (SP Avg Elapsed Time) +SELECT TOP(25) p.name AS [SP Name], qs.min_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +qs.max_elapsed_time, qs.last_elapsed_time, qs.total_elapsed_time, qs.execution_count, +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_worker_time/qs.execution_count AS [AvgWorkerTime], +qs.total_worker_time AS [TotalWorkerTime], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY avg_elapsed_time DESC OPTION (RECOMPILE); +------ + +-- This helps you find high average elapsed time cached stored procedures that +-- may be easy to optimize with standard query tuning techniques + + + +-- Top Cached SPs By Total Worker time. Worker time relates to CPU cost (Query 31) (SP Worker Time) +SELECT TOP(25) p.name AS [SP Name], qs.total_worker_time AS [TotalWorkerTime], +qs.total_worker_time/qs.execution_count AS [AvgWorkerTime], qs.execution_count, +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY qs.total_worker_time DESC OPTION (RECOMPILE); +------ + +-- This helps you find the most expensive cached stored procedures from a CPU perspective +-- You should look at this if you see signs of CPU pressure + + +-- Top Cached SPs By Total Logical Reads. Logical reads relate to memory pressure (Query 32) (SP Logical Reads) +SELECT TOP(25) p.name AS [SP Name], qs.total_logical_reads AS [TotalLogicalReads], +qs.total_logical_reads/qs.execution_count AS [AvgLogicalReads],qs.execution_count, +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY qs.total_logical_reads DESC OPTION (RECOMPILE); +------ + +-- This helps you find the most expensive cached stored procedures from a memory perspective +-- You should look at this if you see signs of memory pressure + + +-- Top Cached SPs By Total Physical Reads. Physical reads relate to disk read I/O pressure (Query 33) (SP Physical Reads) +SELECT TOP(25) p.name AS [SP Name],qs.total_physical_reads AS [TotalPhysicalReads], +qs.total_physical_reads/qs.execution_count AS [AvgPhysicalReads], qs.execution_count, +qs.total_logical_reads,qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND qs.total_physical_reads > 0 +ORDER BY qs.total_physical_reads DESC, qs.total_logical_reads DESC OPTION (RECOMPILE); +------ + +-- This helps you find the most expensive cached stored procedures from a read I/O perspective +-- You should look at this if you see signs of I/O pressure or of memory pressure + + + +-- Top Cached SPs By Total Logical Writes (Query 34) (SP Logical Writes) +-- Logical writes relate to both memory and disk I/O pressure +SELECT TOP(25) p.name AS [SP Name], qs.total_logical_writes AS [TotalLogicalWrites], +qs.total_logical_writes/qs.execution_count AS [AvgLogicalWrites], qs.execution_count, +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND qs.total_logical_writes > 0 +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY qs.total_logical_writes DESC OPTION (RECOMPILE); +------ + +-- This helps you find the most expensive cached stored procedures from a write I/O perspective +-- You should look at this if you see signs of I/O pressure or of memory pressure + + + +-- Lists the top statements by average input/output usage for the current database (Query 35) (Top IO Statements) +SELECT TOP(50) OBJECT_NAME(qt.objectid, dbid) AS [SP Name], +(qs.total_logical_reads + qs.total_logical_writes) /qs.execution_count AS [Avg IO], qs.execution_count AS [Execution Count], +SUBSTRING(qt.[text],qs.statement_start_offset/2, + (CASE + WHEN qs.statement_end_offset = -1 + THEN LEN(CONVERT(nvarchar(max), qt.[text])) * 2 + ELSE qs.statement_end_offset + END - qs.statement_start_offset)/2) AS [Query Text] +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt +WHERE qt.[dbid] = DB_ID() +ORDER BY [Avg IO] DESC OPTION (RECOMPILE); +------ + +-- Helps you find the most expensive statements for I/O by SP + + + +-- Possible Bad NC Indexes (writes > reads) (Query 36) (Bad NC Indexes) +SELECT SCHEMA_NAME(o.[schema_id]) AS [Schema Name], +OBJECT_NAME(s.[object_id]) AS [Table Name], +i.name AS [Index Name], i.index_id, +i.is_disabled, i.is_hypothetical, i.has_filter, i.fill_factor, +s.user_updates AS [Total Writes], s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], +s.user_updates - (s.user_seeks + s.user_scans + s.user_lookups) AS [Difference] +FROM sys.dm_db_index_usage_stats AS s WITH (NOLOCK) +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON s.[object_id] = i.[object_id] +AND i.index_id = s.index_id +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON i.[object_id] = o.[object_id] +WHERE OBJECTPROPERTY(s.[object_id],'IsUserTable') = 1 +AND s.database_id = DB_ID() +AND s.user_updates > (s.user_seeks + s.user_scans + s.user_lookups) +AND i.index_id > 1 AND i.[type_desc] = N'NONCLUSTERED' +AND i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND i.is_unique = 0 +ORDER BY [Difference] DESC, [Total Writes] DESC, [Total Reads] ASC OPTION (RECOMPILE); +------ + +-- Look for indexes with high numbers of writes and zero or very low numbers of reads +-- Consider your complete workload, and how long your instance has been running +-- Investigate further before dropping an index! + + +-- Missing Indexes for current database by Index Advantage (Query 37) (Missing Indexes) +SELECT DISTINCT CONVERT(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) AS [index_advantage], +migs.last_user_seek, mid.[statement] AS [Database.Schema.Table], +mid.equality_columns, mid.inequality_columns, mid.included_columns, +migs.unique_compiles, migs.user_seeks, migs.avg_total_user_cost, migs.avg_user_impact, +OBJECT_NAME(mid.[object_id]) AS [Table Name], p.rows AS [Table Rows] +FROM sys.dm_db_missing_index_group_stats AS migs WITH (NOLOCK) +INNER JOIN sys.dm_db_missing_index_groups AS mig WITH (NOLOCK) +ON migs.group_handle = mig.index_group_handle +INNER JOIN sys.dm_db_missing_index_details AS mid WITH (NOLOCK) +ON mig.index_handle = mid.index_handle +INNER JOIN sys.partitions AS p WITH (NOLOCK) +ON p.[object_id] = mid.[object_id] +WHERE mid.database_id = DB_ID() +ORDER BY index_advantage DESC OPTION (RECOMPILE); +------ + +-- Look at index advantage, last user seek time, number of user seeks to help determine source and importance +-- SQL Server is overly eager to add included columns, so beware +-- Do not just blindly add indexes that show up from this query!!! + + +-- Find missing index warnings for cached plans in the current database (Query 38) (Missing Index Warnings) +-- Note: This query could take some time on a busy instance +SELECT TOP(25) OBJECT_NAME(objectid) AS [ObjectName], + cp.objtype, cp.usecounts, cp.size_in_bytes, query_plan +FROM sys.dm_exec_cached_plans AS cp WITH (NOLOCK) +CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp +WHERE CAST(query_plan AS NVARCHAR(MAX)) LIKE N'%MissingIndex%' +AND dbid = DB_ID() +ORDER BY cp.usecounts DESC OPTION (RECOMPILE); +------ + +-- Helps you connect missing indexes to specific stored procedures or queries +-- This can help you decide whether to add them or not + + +-- Breaks down buffers used by current database by object (table, index) in the buffer cache (Query 39) (Buffer Usage) +-- Note: This query could take some time on a busy instance +SELECT SCHEMA_NAME(o.Schema_ID) AS [Schema Name], +OBJECT_NAME(p.[object_id]) AS [Object Name], p.index_id, +CAST(COUNT(*)/128.0 AS DECIMAL(10, 2)) AS [Buffer size(MB)], +COUNT(*) AS [BufferCount], p.[Rows] AS [Row Count], +p.data_compression_desc AS [Compression Type] +FROM sys.allocation_units AS a WITH (NOLOCK) +INNER JOIN sys.dm_os_buffer_descriptors AS b WITH (NOLOCK) +ON a.allocation_unit_id = b.allocation_unit_id +INNER JOIN sys.partitions AS p WITH (NOLOCK) +ON a.container_id = p.hobt_id +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON p.object_id = o.object_id +WHERE b.database_id = CONVERT(int, DB_ID()) +AND p.[object_id] > 100 +AND OBJECT_NAME(p.[object_id]) NOT LIKE N'plan_%' +AND OBJECT_NAME(p.[object_id]) NOT LIKE N'sys%' +AND OBJECT_NAME(p.[object_id]) NOT LIKE N'xml_index_nodes%' +GROUP BY o.Schema_ID, p.[object_id], p.index_id, p.data_compression_desc, p.[Rows] +ORDER BY [BufferCount] DESC OPTION (RECOMPILE); +------ + +-- Tells you what tables and indexes are using the most memory in the buffer cache +-- It can help identify possible candidates for data compression + + +-- Get Table names, row counts, and compression status for clustered index or heap (Query 40) (Table Sizes) +SELECT SCHEMA_NAME(o.Schema_ID) AS [Schema Name], OBJECT_NAME(p.object_id) AS [ObjectName], +SUM(p.Rows) AS [RowCount], data_compression_desc AS [CompressionType] +FROM sys.partitions AS p WITH (NOLOCK) +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON p.object_id = o.object_id +WHERE index_id < 2 --ignore the partitions from the non-clustered index if any +AND OBJECT_NAME(p.object_id) NOT LIKE N'sys%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'spt_%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'queue_%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'filestream_tombstone%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'fulltext%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'ifts_comp_fragment%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'filetable_updates%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'xml_index_nodes%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'sqlagent_job%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'plan_persist%' +GROUP BY SCHEMA_NAME(o.Schema_ID), p.object_id, data_compression_desc +ORDER BY SUM(p.Rows) DESC OPTION (RECOMPILE); +------ + +-- Gives you an idea of table sizes, and possible data compression opportunities + + + +-- Get some key table properties (Query 41) (Table Properties) +SELECT OBJECT_NAME(t.[object_id]) AS [ObjectName], p.[rows] AS [Table Rows], p.index_id, + p.data_compression_desc AS [Index Data Compression], + t.create_date, t.lock_on_bulk_load, t.lock_escalation_desc, + t.is_memory_optimized, t.durability_desc, + t.temporal_type_desc +FROM sys.tables AS t WITH (NOLOCK) +INNER JOIN sys.partitions AS p WITH (NOLOCK) +ON t.[object_id] = p.[object_id] +WHERE OBJECT_NAME(t.[object_id]) NOT LIKE N'sys%' +ORDER BY OBJECT_NAME(t.[object_id]), p.index_id OPTION (RECOMPILE); +------ + +-- Gives you some good information about your tables +-- is_memory_optimized and durability_desc were new in SQL Server 2014 +-- temporal_type_desc, is_remote_data_archive_enabled, is_external are new in SQL Server 2016 + +-- sys.tables (Transact-SQL) +-- https://bit.ly/2Gk7998 + + + +-- When were Statistics last updated on all indexes? (Query 42) (Statistics Update) +SELECT SCHEMA_NAME(o.Schema_ID) + N'.' + o.[NAME] AS [Object Name], o.[type_desc] AS [Object Type], + i.[name] AS [Index Name], STATS_DATE(i.[object_id], i.index_id) AS [Statistics Date], + s.auto_created, s.no_recompute, s.user_created, s.is_incremental, s.is_temporary, + st.row_count, st.used_page_count +FROM sys.objects AS o WITH (NOLOCK) +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON o.[object_id] = i.[object_id] +INNER JOIN sys.stats AS s WITH (NOLOCK) +ON i.[object_id] = s.[object_id] +AND i.index_id = s.stats_id +INNER JOIN sys.dm_db_partition_stats AS st WITH (NOLOCK) +ON o.[object_id] = st.[object_id] +AND i.[index_id] = st.[index_id] +WHERE o.[type] IN ('U', 'V') +AND st.row_count > 0 +ORDER BY STATS_DATE(i.[object_id], i.index_id) DESC OPTION (RECOMPILE); +------ + +-- Helps discover possible problems with out-of-date statistics +-- Also gives you an idea which indexes are the most active + +-- sys.stats (Transact-SQL) +-- https://bit.ly/2GyAxrn + + + +-- Look at most frequently modified indexes and statistics (Query 43) (Volatile Indexes) +SELECT o.[name] AS [Object Name], o.[object_id], o.[type_desc], s.[name] AS [Statistics Name], + s.stats_id, s.no_recompute, s.auto_created, s.is_incremental, s.is_temporary, + sp.modification_counter, sp.[rows], sp.rows_sampled, sp.last_updated +FROM sys.objects AS o WITH (NOLOCK) +INNER JOIN sys.stats AS s WITH (NOLOCK) +ON s.object_id = o.object_id +CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS sp +WHERE o.[type_desc] NOT IN (N'SYSTEM_TABLE', N'INTERNAL_TABLE') +AND sp.modification_counter > 0 +ORDER BY sp.modification_counter DESC, o.name OPTION (RECOMPILE); +------ + +-- This helps you understand your workload and make better decisions about +-- things like data compression and adding new indexes to a table + + + +-- Get fragmentation info for all indexes above a certain size in the current database (Query 44) (Index Fragmentation) +-- Note: This query could take some time on a very large database +SELECT DB_NAME(ps.database_id) AS [Database Name], SCHEMA_NAME(o.[schema_id]) AS [Schema Name], +OBJECT_NAME(ps.OBJECT_ID) AS [Object Name], i.[name] AS [Index Name], ps.index_id, +ps.index_type_desc, ps.avg_fragmentation_in_percent, +ps.fragment_count, ps.page_count, i.fill_factor, i.has_filter, +i.filter_definition, i.[allow_page_locks] +FROM sys.dm_db_index_physical_stats(DB_ID(),NULL, NULL, NULL , N'LIMITED') AS ps +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON ps.[object_id] = i.[object_id] +AND ps.index_id = i.index_id +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON i.[object_id] = o.[object_id] +WHERE ps.database_id = DB_ID() +AND ps.page_count > 2500 +ORDER BY ps.avg_fragmentation_in_percent DESC OPTION (RECOMPILE); +------ + +-- Helps determine whether you have framentation in your relational indexes +-- and how effective your index maintenance strategy is + + +--- Index Read/Write stats (all tables in current DB) ordered by Reads (Query 45) (Overall Index Usage - Reads) +SELECT OBJECT_NAME(i.[object_id]) AS [ObjectName], i.[name] AS [IndexName], i.index_id, + s.user_seeks, s.user_scans, s.user_lookups, + s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], + s.user_updates AS [Writes], + i.[type_desc] AS [Index Type], i.fill_factor AS [Fill Factor], i.has_filter, i.filter_definition, + s.last_user_scan, s.last_user_lookup, s.last_user_seek +FROM sys.indexes AS i WITH (NOLOCK) +LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s WITH (NOLOCK) +ON i.[object_id] = s.[object_id] +AND i.index_id = s.index_id +AND s.database_id = DB_ID() +WHERE OBJECTPROPERTY(i.[object_id],'IsUserTable') = 1 +ORDER BY s.user_seeks + s.user_scans + s.user_lookups DESC OPTION (RECOMPILE); -- Order by reads +------ + +-- Show which indexes in the current database are most active for Reads + + +--- Index Read/Write stats (all tables in current DB) ordered by Writes (Query 46) (Overall Index Usage - Writes) +SELECT OBJECT_NAME(i.[object_id]) AS [ObjectName], i.[name] AS [IndexName], i.index_id, + s.user_updates AS [Writes], s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], + i.[type_desc] AS [Index Type], i.fill_factor AS [Fill Factor], i.has_filter, i.filter_definition, + s.last_system_update, s.last_user_update +FROM sys.indexes AS i WITH (NOLOCK) +LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s WITH (NOLOCK) +ON i.[object_id] = s.[object_id] +AND i.index_id = s.index_id +AND s.database_id = DB_ID() +WHERE OBJECTPROPERTY(i.[object_id],'IsUserTable') = 1 +ORDER BY s.user_updates DESC OPTION (RECOMPILE); -- Order by writes +------ + +-- Show which indexes in the current database are most active for Writes + + +-- Get in-memory OLTP index usage (Query 47) (XTP Index Usage) +SELECT OBJECT_NAME(i.[object_id]) AS [Object Name], i.index_id, i.[name] AS [Index Name], + i.[type_desc], xis.scans_started, xis.scans_retries, + xis.rows_touched, xis.rows_returned +FROM sys.dm_db_xtp_index_stats AS xis WITH (NOLOCK) +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON i.[object_id] = xis.[object_id] +AND i.index_id = xis.index_id +ORDER BY OBJECT_NAME(i.[object_id]) OPTION (RECOMPILE); +------ + +-- This gives you some index usage statistics for in-memory OLTP +-- Returns no data if you are not using in-memory OLTP + +-- Guidelines for Using Indexes on Memory-Optimized Tables +-- https://bit.ly/2GCP8lF + + + +-- Look at Columnstore index physical statistics (Query 48) (Columnstore Index Physical Stat) +SELECT OBJECT_NAME(ps.object_id) AS [TableName], + i.[name] AS [IndexName], ps.index_id, ps.partition_number, + ps.delta_store_hobt_id, ps.state_desc, ps.total_rows, ps.size_in_bytes, + ps.trim_reason_desc, ps.generation, ps.transition_to_compressed_state_desc, + ps.has_vertipaq_optimization, ps.deleted_rows, + 100 * (ISNULL(ps.deleted_rows, 0))/ps.total_rows AS [Fragmentation] +FROM sys.dm_db_column_store_row_group_physical_stats AS ps WITH (NOLOCK) +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON ps.object_id = i.object_id +AND ps.index_id = i.index_id +ORDER BY ps.object_id, ps.partition_number, ps.row_group_id OPTION (RECOMPILE); +------ + +-- sys.dm_db_column_store_row_group_physical_stats (Transact-SQL) +-- https://bit.ly/2q276XQ + + + +-- Get lock waits for current database (Query 49) (Lock Waits) +SELECT o.name AS [table_name], i.name AS [index_name], ios.index_id, ios.partition_number, + SUM(ios.row_lock_wait_count) AS [total_row_lock_waits], + SUM(ios.row_lock_wait_in_ms) AS [total_row_lock_wait_in_ms], + SUM(ios.page_lock_wait_count) AS [total_page_lock_waits], + SUM(ios.page_lock_wait_in_ms) AS [total_page_lock_wait_in_ms], + SUM(ios.page_lock_wait_in_ms)+ SUM(row_lock_wait_in_ms) AS [total_lock_wait_in_ms] +FROM sys.dm_db_index_operational_stats(DB_ID(), NULL, NULL, NULL) AS ios +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON ios.[object_id] = o.[object_id] +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON ios.[object_id] = i.[object_id] +AND ios.index_id = i.index_id +WHERE o.[object_id] > 100 +GROUP BY o.name, i.name, ios.index_id, ios.partition_number +HAVING SUM(ios.page_lock_wait_in_ms)+ SUM(row_lock_wait_in_ms) > 0 +ORDER BY total_lock_wait_in_ms DESC OPTION (RECOMPILE); +------ + +-- This query is helpful for troubleshooting blocking and deadlocking issues + + + +-- Look at UDF execution statistics (Query 50) (UDF Statistics) +SELECT OBJECT_NAME(object_id) AS [Function Name], total_worker_time, + execution_count, total_elapsed_time, + total_elapsed_time/execution_count AS [avg_elapsed_time], + last_elapsed_time, last_execution_time, cached_time +FROM sys.dm_exec_function_stats WITH (NOLOCK) +WHERE database_id = DB_ID() +ORDER BY total_worker_time DESC OPTION (RECOMPILE); +------ + + +-- Helps you investigate scalar UDF performance issues + +-- sys.dm_exec_function_stats (Transact-SQL) +-- https://bit.ly/2q1Q6BM + + + + +-- Get QueryStore Options for this database (Query 51) (QueryStore Options) +SELECT actual_state_desc, desired_state_desc, + current_storage_size_mb, [max_storage_size_mb], + query_capture_mode_desc, size_based_cleanup_mode_desc, + wait_stats_capture_mode_desc, [flush_interval_seconds] +FROM sys.database_query_store_options WITH (NOLOCK) OPTION (RECOMPILE); +------ + +-- Added in SQL Server 2016 +-- Requires that QueryStore is enabled for this database + +-- Tuning Workload Performance with Query Store +-- https://bit.ly/1kHSl7w + + +-- Get highest aggregate duration queries over last hour (Query 52) (High Aggregate Duration Queries) +WITH AggregatedDurationLastHour +AS +(SELECT q.query_id, SUM(count_executions * avg_duration) AS total_duration, + COUNT (distinct p.plan_id) AS number_of_plans + FROM sys.query_store_query_text AS qt WITH (NOLOCK) + INNER JOIN sys.query_store_query AS q WITH (NOLOCK) + ON qt.query_text_id = q.query_text_id + INNER JOIN sys.query_store_plan AS p WITH (NOLOCK) + ON q.query_id = p.query_id + INNER JOIN sys.query_store_runtime_stats AS rs WITH (NOLOCK) + ON rs.plan_id = p.plan_id + INNER JOIN sys.query_store_runtime_stats_interval AS rsi WITH (NOLOCK) + ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id + WHERE rsi.start_time >= DATEADD(hour, -1, GETUTCDATE()) + AND rs.execution_type_desc = N'Regular' + GROUP BY q.query_id), +OrderedDuration AS +(SELECT query_id, total_duration, number_of_plans, + ROW_NUMBER () OVER (ORDER BY total_duration DESC, query_id) AS RN + FROM AggregatedDurationLastHour) +SELECT OBJECT_NAME(q.object_id) AS [Containing Object], qt.query_sql_text, +od.total_duration AS [Total Duration (microsecs)], +od.number_of_plans AS [Plan Count], +p.is_forced_plan, p.is_parallel_plan, p.is_trivial_plan, +q.query_parameterization_type_desc, p.[compatibility_level], +p.last_compile_start_time, q.last_execution_time, +CONVERT(xml, p.query_plan) AS query_plan_xml +FROM OrderedDuration AS od +INNER JOIN sys.query_store_query AS q WITH (NOLOCK) +ON q.query_id = od.query_id +INNER JOIN sys.query_store_query_text AS qt WITH (NOLOCK) +ON q.query_text_id = qt.query_text_id +INNER JOIN sys.query_store_plan AS p WITH (NOLOCK) +ON q.query_id = p.query_id +WHERE od.RN <= 50 +ORDER BY total_duration DESC OPTION (RECOMPILE); +------ + +-- New for SQL Server 2016 +-- Requires that QueryStore is enabled for this database + + +-- Get input buffer information for the current database (Query 53) (Input Buffer) +SELECT es.session_id, DB_NAME(es.database_id) AS [Database Name], + es.login_time, es.cpu_time, es.logical_reads, + es.[status], ib.event_info AS [Input Buffer] +FROM sys.dm_exec_sessions AS es WITH (NOLOCK) +CROSS APPLY sys.dm_exec_input_buffer(es.session_id, NULL) AS ib +WHERE es.database_id = DB_ID() +AND es.session_id > 50 +AND es.session_id <> @@SPID OPTION (RECOMPILE); +------ + +-- Gives you input buffer information from all non-system sessions for the current database +-- Replaces DBCC INPUTBUFFER + +-- New DMF for retrieving input buffer in SQL Server +-- https://bit.ly/2uHKMbz + + + +-- Get any resumable index rebuild operation information (Query 54) (Resumable Index Rebuild) +SELECT OBJECT_NAME(iro.object_id) AS [Object Name], iro.index_id, iro.name AS [Index Name], + iro.sql_text, iro.last_max_dop_used, iro.partition_number, iro.state_desc, iro.start_time, iro.percent_complete +FROM sys.index_resumable_operations AS iro WITH (NOLOCK) +OPTION (RECOMPILE); +------ + +-- index_resumable_operations (Transact-SQL) +-- https://bit.ly/2pYSWqq + + +-- Get database automatic tuning options (Query 55) (Automatic Tuning Options) +SELECT [name], desired_state_desc, actual_state_desc, reason_desc +FROM sys.database_automatic_tuning_options WITH (NOLOCK) +OPTION (RECOMPILE); +------ + +-- sys.database_automatic_tuning_options (Transact-SQL) +-- https://bit.ly/2FHhLkL + + + +-- Get geo-replication link status for all secondary databases (Query 56) (Geo-Replication Link Status) +SELECT link_guid, partner_server, partner_database, last_replication, + replication_lag_sec, replication_state_desc, role_desc, secondary_allow_connections_desc +FROM sys.dm_geo_replication_link_status WITH (NOLOCK) OPTION (RECOMPILE); +------ + +-- sys.dm_geo_replication_link_status (Azure SQL Database) +-- https://bit.ly/2GwIqC2 + + + +-- Retrieve some Azure SQL Database properties (Query 57) (Azure SQL DB Properties) +SELECT DATABASEPROPERTYEX (DB_NAME(DB_ID()), 'Edition') AS [Database Edition], + DATABASEPROPERTYEX (DB_NAME(DB_ID()), 'ServiceObjective') AS [Service Objective], + DATABASEPROPERTYEX (DB_NAME(DB_ID()), 'MaxSizeInBytes') AS [Max Size In Bytes], + DATABASEPROPERTYEX (DB_NAME(DB_ID()), 'IsXTPSupported') AS [Is XTP Supported] + OPTION (RECOMPILE); +------ + +-- DATABASEPROPERTYEX (Transact-SQL) +-- https://bit.ly/2ItexPg + + +-- These six Pluralsight Courses go into more detail about how to run these queries and interpret the results + +-- Azure SQL Database: Diagnosing Performance Issues with DMVs +-- https://bit.ly/2meDRCN + +-- SQL Server 2017: Diagnosing Performance Issues with DMVs +-- https://bit.ly/2FqCeti + +-- SQL Server 2017: Diagnosing Configuration Issues with DMVs +-- https://bit.ly/2MSUDUL + +-- SQL Server 2014 DMV Diagnostic Queries – Part 1 +-- https://bit.ly/2plxCer + +-- SQL Server 2014 DMV Diagnostic Queries – Part 2 +-- https://bit.ly/2IuJpzI + +-- SQL Server 2014 DMV Diagnostic Queries – Part 3 +-- https://bit.ly/2FIlCPb + + + +-- Microsoft Visual Studio Dev Essentials +-- https://bit.ly/2qjNRxi + +-- Microsoft Azure Learn +-- https://bit.ly/2O0Hacc diff --git a/SAM - Waits/SQL Managed Instance Diagnostic Information Queries.sql b/SAM - Waits/SQL Managed Instance Diagnostic Information Queries.sql new file mode 100644 index 0000000..de4c4f3 --- /dev/null +++ b/SAM - Waits/SQL Managed Instance Diagnostic Information Queries.sql @@ -0,0 +1,1687 @@ + +-- SQL Managed Instance Diagnostic Information Queries +-- Glenn Berry +-- Last Modified: December 4, 2019 +-- https://www.sqlskills.com/blogs/glenn/ +-- http://sqlserverperformance.wordpress.com/ +-- Twitter: GlennAlanBerry + +-- Please listen to my Pluralsight courses +-- https://www.pluralsight.com/author/glenn-berry + +-- If you want to find all of our SQLskills SQL101 blog posts, check out https://bit.ly/2qLwfXW + + +-- Please make sure you are using the correct version of these diagnostic queries for your version of SQL Server + + +-- If you like PowerShell, there is a very useful community solution for running these queries in an automated fashion +-- https://dbatools.io/ + +-- Invoke-DbaDiagnosticQuery +-- https://dbatools.io/functions/invoke-dbadiagnosticquery/ + + +--****************************************************************************** +--* Copyright (C) 2019 Glenn Berry, SQLskills.com +--* All rights reserved. +--* +--* For more scripts and sample code, check out +--* https://www.sqlskills.com/blogs/glenn +--* +--* You may alter this code for your own *non-commercial* purposes. You may +--* republish altered code as long as you include this copyright and give due credit. +--* +--* +--* THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF +--* ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED +--* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +--* PARTICULAR PURPOSE. +--* +--****************************************************************************** + +-- Check the major product version to see if it is SQL Managed Instance +IF NOT EXISTS (SELECT * WHERE CONVERT(varchar(128), SERVERPROPERTY('EngineEdition')) = 8) + BEGIN + DECLARE @ProductVersion varchar(128) = CONVERT(varchar(128), SERVERPROPERTY('EngineEdition')); + RAISERROR ('Script does not match the EngineEdition [%s] of this instance. Many of these queries may not work on this version.' , 18 , 16 , @ProductVersion); + END + ELSE + PRINT N'You are using SQL Managed Instance for this diagnostic information script'; + + +-- Instance level queries ******************************* + +-- SQL and OS Version information for current instance (Query 1) (Version Info) +SELECT @@SERVERNAME AS [Server Name], @@VERSION AS [SQL Server and OS Version Info]; +------ + +-- The version number from @@VERSION will not match what is in the SQL Server error log + +-- How to determine the version, edition and update level of SQL Server and its components +-- https://bit.ly/2oAjKgW + +-- Download SQL Server Management Studio (SSMS) +-- https://bit.ly/1OcupT9 + +-- Download and install Azure Data Studio +-- https://bit.ly/2vgke1A + + + +-- Get selected server properties (Query 2) (Server Properties) +SELECT SERVERPROPERTY('ServerName') AS [ServerName], +SERVERPROPERTY('EngineEdition') AS [Engine Edition], -- Engine Edition 8 is Managed Instance +SERVERPROPERTY('Edition') AS [Edition], +SERVERPROPERTY('ProductLevel') AS [ProductLevel], -- What servicing branch (RTM/SP/CU) +SERVERPROPERTY('ProductVersion') AS [ProductVersion], +SERVERPROPERTY('ProductMajorVersion') AS [ProductMajorVersion], +SERVERPROPERTY('ProductMinorVersion') AS [ProductMinorVersion], +SERVERPROPERTY('ProductBuild') AS [ProductBuild], +SERVERPROPERTY('Collation') AS [Collation], +SERVERPROPERTY('IsFullTextInstalled') AS [IsFullTextInstalled], +SERVERPROPERTY('IsIntegratedSecurityOnly') AS [IsIntegratedSecurityOnly], +SERVERPROPERTY('FilestreamConfiguredLevel') AS [FilestreamConfiguredLevel], +SERVERPROPERTY('IsHadrEnabled') AS [IsHadrEnabled], +SERVERPROPERTY('HadrManagerStatus') AS [HadrManagerStatus], +SERVERPROPERTY('InstanceDefaultDataPath') AS [InstanceDefaultDataPath], +SERVERPROPERTY('InstanceDefaultLogPath') AS [InstanceDefaultLogPath], +SERVERPROPERTY('BuildClrVersion') AS [Build CLR Version], +SERVERPROPERTY('IsXTPSupported') AS [IsXTPSupported], +SERVERPROPERTY('IsPolybaseInstalled') AS [IsPolybaseInstalled], +SERVERPROPERTY('IsAdvancedAnalyticsInstalled') AS [IsRServicesInstalled]; +------ + +-- This gives you a lot of useful information about your "instance" of SQL Managed Instance + + +-- SERVERPROPERTY (Transact-SQL) +-- https://bit.ly/2eeaXeI + + + +-- Get instance-level configuration values for instance (Query 3) (Configuration Values) +SELECT name, value, value_in_use, minimum, maximum, [description], is_dynamic, is_advanced +FROM sys.configurations WITH (NOLOCK) +ORDER BY name OPTION (RECOMPILE); +------ + +-- Focus on these settings: +-- automatic soft-NUMA disabled (should be 0 in most cases) +-- backup checksum default (should be 1) +-- backup compression default (should be 1 in most cases) +-- clr enabled (only enable if it is needed) +-- cost threshold for parallelism (depends on your workload) +-- lightweight pooling (should be zero) +-- max degree of parallelism (depends on your workload and hardware) +-- max server memory (MB) (set to an appropriate value, not the default) +-- optimize for ad hoc workloads (should be 1) +-- priority boost (should be zero) +-- remote admin connections (should be 1) + +-- sys.configurations (Transact-SQL) +-- https://bit.ly/2HsyDZI + + + + +-- SQL Server Process Address space info (Query 4) (Process Memory) +-- (shows whether locked pages is enabled, among other things) +SELECT physical_memory_in_use_kb/1024 AS [SQL Server Memory Usage (MB)], + locked_page_allocations_kb/1024 AS [SQL Server Locked Pages Allocation (MB)], + large_page_allocations_kb/1024 AS [SQL Server Large Pages Allocation (MB)], + page_fault_count, memory_utilization_percentage, available_commit_limit_kb, + process_physical_memory_low, process_virtual_memory_low +FROM sys.dm_os_process_memory WITH (NOLOCK) OPTION (RECOMPILE); +------ + +-- You want to see 0 for process_physical_memory_low +-- You want to see 0 for process_virtual_memory_low +-- This indicates that you are not under internal memory pressure +-- If locked_page_allocations_kb > 0, then LPIM is enabled + +-- How to enable the "locked pages" feature in SQL Server 2012 +-- https://bit.ly/2F5UjOA + +-- Memory Management Architecture Guide +-- https://bit.ly/2JKkadC + + + +-- Get SQL Server Agent jobs and Category information (Query 5) (SQL Server Agent Jobs) +SELECT sj.name AS [Job Name], sj.[description] AS [Job Description], SUSER_SNAME(sj.owner_sid) AS [Job Owner], +sj.date_created AS [Date Created], sj.[enabled] AS [Job Enabled], +sj.notify_email_operator_id, sj.notify_level_email, sc.name AS [CategoryName], +s.[enabled] AS [Sched Enabled], js.next_run_date, js.next_run_time +FROM msdb.dbo.sysjobs AS sj WITH (NOLOCK) +INNER JOIN msdb.dbo.syscategories AS sc WITH (NOLOCK) +ON sj.category_id = sc.category_id +LEFT OUTER JOIN msdb.dbo.sysjobschedules AS js WITH (NOLOCK) +ON sj.job_id = js.job_id +LEFT OUTER JOIN msdb.dbo.sysschedules AS s WITH (NOLOCK) +ON js.schedule_id = s.schedule_id +ORDER BY sj.name OPTION (RECOMPILE); +------ + +-- Gives you some basic information about your SQL Server Agent jobs, who owns them and how they are configured +-- Look for Agent jobs that are not owned by sa +-- Look for jobs that have a notify_email_operator_id set to 0 (meaning no operator) +-- Look for jobs that have a notify_level_email set to 0 (meaning no e-mail is ever sent) +-- +-- MSDN sysjobs documentation +-- https://bit.ly/2paDEOP + + + +-- SQL Server NUMA Node information (Query 6) (SQL Server NUMA Info) +SELECT node_id, node_state_desc, memory_node_id, processor_group, cpu_count, online_scheduler_count, + idle_scheduler_count, active_worker_count, avg_load_balance, resource_monitor_state +FROM sys.dm_os_nodes WITH (NOLOCK) +WHERE node_state_desc <> N'ONLINE DAC' OPTION (RECOMPILE); +------ + +-- Gives you some useful information about the composition and relative load on your NUMA nodes +-- You want to see an equal number of schedulers on each NUMA node + + +-- sys.dm_os_nodes (Transact-SQL) +-- https://bit.ly/2pn5Mw8 + +-- Balancing Your Available SQL Server Core Licenses Evenly Across NUMA Nodes +-- https://bit.ly/2vfC4Rq + + + +-- Server Resource Statistics (Query 7) (Server Resource Stats) +SELECT TOP(250) resource_type, resource_name, sku, hardware_generation, +virtual_core_count, avg_cpu_percent, io_requests, +CONVERT(DECIMAL(18,2), io_bytes_read/1048576.0) AS [MB Read], +CONVERT(DECIMAL(18,2), io_bytes_read * 1./(io_bytes_read + io_bytes_written) * 100.) AS [Read Percentage], +CONVERT(DECIMAL(18,2), io_bytes_written/1048576.0) AS [MB Written], +CONVERT(DECIMAL(18,2), io_bytes_written * 1./(io_bytes_read + io_bytes_written) * 100.) AS [Write Percentage], +CONVERT(DECIMAL(18,2), (io_bytes_read + io_bytes_written)/1048576.0) AS [MB Total IO], +start_time, end_time +FROM master.sys.server_resource_stats WITH (NOLOCK) +WHERE io_bytes_read > 0 AND io_bytes_written > 0 +ORDER BY end_time DESC OPTION (RECOMPILE); +------ + +-- Shows recent resource usage, in 15-second slices + +-- SQL Managed Instance Pricing +-- https://bit.ly/2XlEE2u + + + + +-- Hardware information from SQL Managed Instance (Query 8) (Hardware Info) +SELECT cpu_count AS [Logical CPU Count], scheduler_count, + numa_node_count, max_workers_count AS [Max Workers Count], + affinity_type_desc AS [Affinity Type], + sqlserver_start_time AS [SQL Server Start Time], + DATEDIFF(hour, sqlserver_start_time, GETDATE()) AS [SQL Server Up Time (hrs)], + virtual_machine_type_desc AS [Virtual Machine Type], + softnuma_configuration_desc AS [Soft NUMA Configuration], + sql_memory_model_desc +FROM sys.dm_os_sys_info WITH (NOLOCK) OPTION (RECOMPILE); +------ + +-- Gives you some good basic hardware information about your database server +-- Note: virtual_machine_type_desc of HYPERVISOR does not automatically mean you are running SQL Server inside of a VM +-- It merely indicates that you have a hypervisor running on your host + +-- sys.dm_os_sys_info (Transact-SQL) +-- https://bit.ly/2pczOYs + +-- Soft NUMA configuration was a new column for SQL Server 2016 +-- OFF = Soft-NUMA feature is OFF +-- ON = SQL Server automatically determines the NUMA node sizes for Soft-NUMA +-- MANUAL = Manually configured soft-NUMA + +-- Configure SQL Server to Use Soft-NUMA (SQL Server) +-- https://bit.ly/2HTpKJt + +-- sql_memory_model_desc values (Added in SQL Server 2016 SP1) +-- CONVENTIONAL +-- LOCK_PAGES +-- LARGE_PAGES + + +-- Get System Manufacturer and model number from SQL Server Error log (Query 9) (System Manufacturer) +EXEC sys.xp_readerrorlog 0, 1, N'Manufacturer'; +------ + +-- This can help you determine the capabilities and capacities of your database server +-- Can also be used to confirm if you are running in a VM +-- This query might take a few seconds if you have not recycled your error log recently +-- This query will return no results if your error log has been recycled since the instance was started + + + + +-- Get information on location, time and size of any memory dumps from SQL Server (Query 10) (Memory Dump Info) +SELECT [filename], creation_time, size_in_bytes/1048576.0 AS [Size (MB)] +FROM sys.dm_server_memory_dumps WITH (NOLOCK) +ORDER BY creation_time DESC OPTION (RECOMPILE); +------ + +-- This will not return any rows if you have +-- not had any memory dumps (which is a good thing) + +-- sys.dm_server_memory_dumps (Transact-SQL) +-- https://bit.ly/2elwWll + + + +-- Look at Suspect Pages table (Query 11) (Suspect Pages) +SELECT DB_NAME(database_id) AS [Database Name], [file_id], page_id, + event_type, error_count, last_update_date +FROM msdb.dbo.suspect_pages WITH (NOLOCK) +ORDER BY database_id OPTION (RECOMPILE); +------ + +-- event_type value descriptions +-- 1 = 823 error caused by an operating system CRC error +-- or 824 error other than a bad checksum or a torn page (for example, a bad page ID) +-- 2 = Bad checksum +-- 3 = Torn page +-- 4 = Restored (The page was restored after it was marked bad) +-- 5 = Repaired (DBCC repaired the page) +-- 7 = Deallocated by DBCC + +-- Ideally, this query returns no results. The table is limited to 1000 rows. +-- If you do get results here, you should do further investigation to determine the root cause + +-- Manage the suspect_pages Table +-- https://bit.ly/2Fvr1c9 + + + +-- File names and paths for all user and system databases on instance (Query 12) (Database Filenames and Paths) +SELECT DB_NAME([database_id]) AS [Database Name], + [file_id], [name], physical_name, [type_desc], state_desc, + is_percent_growth, growth, + CONVERT(bigint, growth/128.0) AS [Growth in MB], + CONVERT(bigint, size/128.0) AS [Total Size in MB], max_size +FROM sys.master_files WITH (NOLOCK) +ORDER BY DB_NAME([database_id]), [file_id] OPTION (RECOMPILE); +------ + +-- Things to look at: +-- Are data files and log files on different drives? +-- Is everything on the C: drive? +-- Is tempdb on dedicated drives? +-- Is there only one tempdb data file? +-- Are all of the tempdb data files the same size? +-- Are there multiple data files for user databases? +-- Is percent growth enabled for any files (which is bad)? + + + + +-- Volume info for all LUNS that have database files on the current instance (Query 13) (Volume Info) +SELECT DISTINCT vs.volume_mount_point, vs.file_system_type, vs.logical_volume_name, +CONVERT(DECIMAL(18,2), vs.total_bytes/1073741824.0) AS [Total Size (GB)], +CONVERT(DECIMAL(18,2), vs.available_bytes/1073741824.0) AS [Available Size (GB)], +CONVERT(DECIMAL(18,2), vs.available_bytes * 1. / vs.total_bytes * 100.) AS [Space Free %], +vs.supports_compression, vs.is_compressed, +vs.supports_sparse_files, vs.supports_alternate_streams +FROM sys.master_files AS f WITH (NOLOCK) +CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.[file_id]) AS vs +ORDER BY vs.volume_mount_point OPTION (RECOMPILE); +------ + +-- Shows you the total and free space on the LUNs where you have database files +-- Being low on free space can negatively affect performance + +-- sys.dm_os_volume_stats (Transact-SQL) +-- https://bit.ly/2oBPNNr + + + +-- Drive level latency information (Query 14) (Drive Level Latency) +-- Based on code from Jimmy May +SELECT tab.[Drive], tab.volume_mount_point AS [Volume Mount Point], + CASE + WHEN num_of_reads = 0 THEN 0 + ELSE (io_stall_read_ms/num_of_reads) + END AS [Read Latency], + CASE + WHEN num_of_writes = 0 THEN 0 + ELSE (io_stall_write_ms/num_of_writes) + END AS [Write Latency], + CASE + WHEN (num_of_reads = 0 AND num_of_writes = 0) THEN 0 + ELSE (io_stall/(num_of_reads + num_of_writes)) + END AS [Overall Latency], + CASE + WHEN num_of_reads = 0 THEN 0 + ELSE (num_of_bytes_read/num_of_reads) + END AS [Avg Bytes/Read], + CASE + WHEN num_of_writes = 0 THEN 0 + ELSE (num_of_bytes_written/num_of_writes) + END AS [Avg Bytes/Write], + CASE + WHEN (num_of_reads = 0 AND num_of_writes = 0) THEN 0 + ELSE ((num_of_bytes_read + num_of_bytes_written)/(num_of_reads + num_of_writes)) + END AS [Avg Bytes/Transfer] +FROM (SELECT LEFT(UPPER(mf.physical_name), 2) AS Drive, SUM(num_of_reads) AS num_of_reads, + SUM(io_stall_read_ms) AS io_stall_read_ms, SUM(num_of_writes) AS num_of_writes, + SUM(io_stall_write_ms) AS io_stall_write_ms, SUM(num_of_bytes_read) AS num_of_bytes_read, + SUM(num_of_bytes_written) AS num_of_bytes_written, SUM(io_stall) AS io_stall, vs.volume_mount_point + FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs + INNER JOIN sys.master_files AS mf WITH (NOLOCK) + ON vfs.database_id = mf.database_id AND vfs.file_id = mf.file_id + CROSS APPLY sys.dm_os_volume_stats(mf.database_id, mf.[file_id]) AS vs + GROUP BY LEFT(UPPER(mf.physical_name), 2), vs.volume_mount_point) AS tab +ORDER BY [Overall Latency] OPTION (RECOMPILE); +------ + +-- Shows you the drive-level latency for reads and writes, in milliseconds +-- Latency above 30-40ms is usually a problem +-- These latency numbers include all file activity against all SQL Server +-- database files on each drive since SQL Server was last started + + +-- Calculates average latency per read, per write, and per total input/output for each database file (Query 15) (IO Latency by File) +SELECT DB_NAME(fs.database_id) AS [Database Name], CAST(fs.io_stall_read_ms/(1.0 + fs.num_of_reads) AS NUMERIC(10,1)) AS [avg_read_latency_ms], +CAST(fs.io_stall_write_ms/(1.0 + fs.num_of_writes) AS NUMERIC(10,1)) AS [avg_write_latency_ms], +CAST((fs.io_stall_read_ms + fs.io_stall_write_ms)/(1.0 + fs.num_of_reads + fs.num_of_writes) AS NUMERIC(10,1)) AS [avg_io_latency_ms], +CONVERT(DECIMAL(18,2), mf.size/128.0) AS [File Size (MB)], mf.physical_name, mf.type_desc, fs.io_stall_read_ms, fs.num_of_reads, +fs.io_stall_write_ms, fs.num_of_writes, fs.io_stall_read_ms + fs.io_stall_write_ms AS [io_stalls], fs.num_of_reads + fs.num_of_writes AS [total_io], +io_stall_queued_read_ms AS [Resource Governor Total Read IO Latency (ms)], io_stall_queued_write_ms AS [Resource Governor Total Write IO Latency (ms)] +FROM sys.dm_io_virtual_file_stats(null,null) AS fs +INNER JOIN sys.master_files AS mf WITH (NOLOCK) +ON fs.database_id = mf.database_id +AND fs.[file_id] = mf.[file_id] +ORDER BY avg_io_latency_ms DESC OPTION (RECOMPILE); +------ + +-- Helps determine which database files on the entire instance have the most I/O bottlenecks +-- This can help you decide whether certain LUNs are overloaded and whether you might +-- want to move some files to a different location or perhaps improve your I/O performance +-- These latency numbers include all file activity against each SQL Server +-- database file since SQL Server was last started + + +-- Look for I/O requests taking longer than 15 seconds in the six most recent SQL Server Error Logs (Query 16) (IO Warnings) +CREATE TABLE #IOWarningResults(LogDate datetime, ProcessInfo sysname, LogText nvarchar(1000)); + + INSERT INTO #IOWarningResults + EXEC xp_readerrorlog 0, 1, N'taking longer than 15 seconds'; + + INSERT INTO #IOWarningResults + EXEC xp_readerrorlog 1, 1, N'taking longer than 15 seconds'; + + INSERT INTO #IOWarningResults + EXEC xp_readerrorlog 2, 1, N'taking longer than 15 seconds'; + + INSERT INTO #IOWarningResults + EXEC xp_readerrorlog 3, 1, N'taking longer than 15 seconds'; + + INSERT INTO #IOWarningResults + EXEC xp_readerrorlog 4, 1, N'taking longer than 15 seconds'; + + INSERT INTO #IOWarningResults + EXEC xp_readerrorlog 5, 1, N'taking longer than 15 seconds'; + +SELECT LogDate, ProcessInfo, LogText +FROM #IOWarningResults +ORDER BY LogDate DESC; + +DROP TABLE #IOWarningResults; +------ + +-- Finding 15 second I/O warnings in the SQL Server Error Log is useful evidence of +-- poor I/O performance (which might have many different causes) +-- Look to see if you see any patterns in the results (same files, same drives, same time of day, etc.) + +-- Diagnostics in SQL Server help detect stalled and stuck I/O operations +-- https://bit.ly/2qtaw73 + + + +-- Recovery model, log reuse wait description (Query 17) (Database Properties) +-- and compatibility level for all databases on instance +SELECT db.[name] AS [Database Name], SUSER_SNAME(db.owner_sid) AS [Database Owner], db.recovery_model_desc AS [Recovery Model], +db.state_desc, db.containment_desc, db.log_reuse_wait_desc AS [Log Reuse Wait Description], +db.[compatibility_level] AS [DB Compatibility Level], +db.is_mixed_page_allocation_on, db.page_verify_option_desc AS [Page Verify Option], +db.is_auto_create_stats_on, db.is_auto_update_stats_on, db.is_auto_update_stats_async_on, db.is_parameterization_forced, +db.snapshot_isolation_state_desc, db.is_read_committed_snapshot_on, db.is_auto_close_on, db.is_auto_shrink_on, +db.target_recovery_time_in_seconds, db.is_cdc_enabled, +db.delayed_durability_desc, db.is_auto_create_stats_incremental_on, +db.is_query_store_on, db.is_sync_with_backup, db.is_temporal_history_retention_enabled, +db.is_supplemental_logging_enabled, +db.is_encrypted, de.encryption_state, de.percent_complete, de.key_algorithm, de.key_length, db.resource_pool_id, +db.is_tempdb_spill_to_remote_store, db.is_result_set_caching_on, db.is_accelerated_database_recovery_on +FROM sys.databases AS db WITH (NOLOCK) +LEFT OUTER JOIN sys.dm_database_encryption_keys AS de WITH (NOLOCK) +ON db.database_id = de.database_id +ORDER BY db.[name] OPTION (RECOMPILE); +------ + +-- Things to look at: +-- How many databases are on the instance? +-- What recovery models are they using? +-- What is the log reuse wait description? +-- What compatibility level are the databases on? +-- Is Auto Update Statistics Asynchronously enabled? +-- Is Delayed Durability enabled +-- Make sure auto_shrink is not enabled! + +-- is_mixed_page_allocation_on is a new property for SQL Server 2016. Equivalent to TF 1118 for a user database +-- SQL Server 2016: Changes in default behavior for autogrow and allocations for tempdb and user databases +-- https://bit.ly/2evRZSR + +-- A non-zero value for target_recovery_time_in_seconds means that indirect checkpoint is enabled +-- If the setting has a zero value it indicates that automatic checkpoint is enabled + + +-- https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-transact-sql-information + + +-- Missing Indexes for all databases by Index Advantage (Query 18) (Missing Indexes All Databases) +SELECT CONVERT(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) AS [index_advantage], +FORMAT(migs.last_user_seek, 'yyyy-MM-dd HH:mm:ss') AS [last_user_seek], +mid.[statement] AS [Database.Schema.Table], +COUNT(1) OVER(PARTITION BY mid.[statement]) AS [missing_indexes_for_table], +COUNT(1) OVER(PARTITION BY mid.[statement], equality_columns) AS [similar_missing_indexes_for_table], +mid.equality_columns, mid.inequality_columns, mid.included_columns, +migs.unique_compiles, migs.user_seeks, +CONVERT(decimal(18,2), migs.avg_total_user_cost) AS [avg_total_user_cost], migs.avg_user_impact +FROM sys.dm_db_missing_index_group_stats AS migs WITH (NOLOCK) +INNER JOIN sys.dm_db_missing_index_groups AS mig WITH (NOLOCK) +ON migs.group_handle = mig.index_group_handle +INNER JOIN sys.dm_db_missing_index_details AS mid WITH (NOLOCK) +ON mig.index_handle = mid.index_handle +ORDER BY index_advantage DESC OPTION (RECOMPILE); +------ + +-- Getting missing index information for all of the databases on the instance is very useful +-- Look at last user seek time, number of user seeks to help determine source and importance +-- Also look at avg_user_impact and avg_total_user_cost to help determine importance +-- SQL Server is overly eager to add included columns, so beware +-- Do not just blindly add indexes that show up from this query!!! + +-- SQL Server Index Design Guide +-- https://bit.ly/2qtZr4N + + + +-- Get VLF Counts for all databases on the instance (Query 19) (VLF Counts) +SELECT [name] AS [Database Name], [VLF Count] +FROM sys.databases AS db WITH (NOLOCK) +CROSS APPLY (SELECT file_id, COUNT(*) AS [VLF Count] + FROM sys.dm_db_log_info(db.database_id) + GROUP BY file_id) AS li +ORDER BY [VLF Count] DESC OPTION (RECOMPILE); +------ + +-- High VLF counts can affect write performance to the log file +-- and they can make full database restores and crash recovery take much longer +-- Try to keep your VLF counts under 200 in most cases (depending on log file size) + +-- Important change to VLF creation algorithm in SQL Server 2014 +-- https://bit.ly/2Hsjbg4 + +-- SQL Server Transaction Log Architecture and Management Guide +-- https://bit.ly/2JjmQRZ + + +-- Get CPU utilization by database (Query 20) (CPU Usage by Database) +WITH DB_CPU_Stats +AS +(SELECT pa.DatabaseID, DB_Name(pa.DatabaseID) AS [Database Name], + SUM(qs.total_worker_time/1000) AS [CPU_Time_Ms] + FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) + CROSS APPLY (SELECT CONVERT(int, value) AS [DatabaseID] + FROM sys.dm_exec_plan_attributes(qs.plan_handle) + WHERE attribute = N'dbid') AS pa + GROUP BY DatabaseID) +SELECT ROW_NUMBER() OVER(ORDER BY [CPU_Time_Ms] DESC) AS [CPU Rank], + [Database Name], DatabaseID, [CPU_Time_Ms] AS [CPU Time (ms)], + CAST([CPU_Time_Ms] * 1.0 / SUM([CPU_Time_Ms]) OVER() * 100.0 AS DECIMAL(5, 2)) AS [CPU Percent] +FROM DB_CPU_Stats +WHERE DatabaseID <> 32767 -- ResourceDB +ORDER BY [CPU Rank] OPTION (RECOMPILE); +------ + +-- Helps determine which database is using the most CPU resources on the instance +-- There are two copies on the master database. The low DatabaseID is the physical master, +-- and the high DatabaseID is the replicated master +-- Note: This only reflects CPU usage from the currently cached query plans + + +-- Get I/O utilization by database (Query 21) (IO Usage By Database) +WITH Aggregate_IO_Statistics +AS (SELECT DB_NAME(database_id) AS [Database Name], + CAST(SUM(num_of_bytes_read + num_of_bytes_written) / 1048576 AS DECIMAL(12, 2)) AS [ioTotalMB], + CAST(SUM(num_of_bytes_read ) / 1048576 AS DECIMAL(12, 2)) AS [ioReadMB], + CAST(SUM(num_of_bytes_written) / 1048576 AS DECIMAL(12, 2)) AS [ioWriteMB], database_id + FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS [DM_IO_STATS] + GROUP BY database_id) +SELECT ROW_NUMBER() OVER (ORDER BY ioTotalMB DESC) AS [I/O Rank], + [Database Name], database_id, ioTotalMB AS [Total I/O (MB)], + CAST(ioTotalMB / SUM(ioTotalMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [Total I/O %], + ioReadMB AS [Read I/O (MB)], + CAST(ioReadMB / SUM(ioReadMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [Read I/O %], + ioWriteMB AS [Write I/O (MB)], + CAST(ioWriteMB / SUM(ioWriteMB) OVER () * 100.0 AS DECIMAL(5, 2)) AS [Write I/O %] +FROM Aggregate_IO_Statistics +ORDER BY [I/O Rank] OPTION (RECOMPILE); +------ + +-- Helps determine which database is using the most I/O resources on the instance +-- These numbers are cumulative since the last service restart +-- They include all I/O activity, not just the nominal I/O workload + + +-- Get total buffer usage by database for current instance (Query 22) (Total Buffer Usage by Database) +-- This make take some time to run on a busy instance +WITH AggregateBufferPoolUsage +AS +(SELECT DB_NAME(database_id) AS [Database Name], database_id, +CAST(COUNT(*) * 8/1024.0 AS DECIMAL (10,2)) AS [CachedSize] +FROM sys.dm_os_buffer_descriptors WITH (NOLOCK) +WHERE database_id <> 32767 -- ResourceDB +GROUP BY DB_NAME(database_id), database_id) + SELECT ROW_NUMBER() OVER(ORDER BY CachedSize DESC) AS [Buffer Pool Rank], + [Database Name], database_id, CachedSize AS [Cached Size (MB)], + CAST(CachedSize / SUM(CachedSize) OVER() * 100.0 AS DECIMAL(5,2)) AS [Buffer Pool Percent] +FROM AggregateBufferPoolUsage +ORDER BY [Buffer Pool Rank] OPTION (RECOMPILE); +------ + +-- Tells you how much memory (in the buffer pool) +-- is being used by each database on the instance + + +-- Get tempdb version store space usage by database (Query 23) (Version Store Space Usage) +SELECT DB_NAME(database_id) AS [Database Name], + reserved_page_count AS [Version Store Reserved Page Count], + reserved_space_kb/1024 AS [Version Store Reserved Space (MB)] +FROM sys.dm_tran_version_store_space_usage WITH (NOLOCK) +ORDER BY reserved_space_kb/1024 DESC OPTION (RECOMPILE); +------ + +-- sys.dm_tran_version_store_space_usage (Transact-SQL) +-- https://bit.ly/2vh3Bmk + + + + +-- Clear Wait Stats with this command +-- DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR); + +-- Isolate top waits for server instance since last restart or wait statistics clear (Query 24) (Top Waits) +WITH [Waits] +AS (SELECT wait_type, wait_time_ms/ 1000.0 AS [WaitS], + (wait_time_ms - signal_wait_time_ms) / 1000.0 AS [ResourceS], + signal_wait_time_ms / 1000.0 AS [SignalS], + waiting_tasks_count AS [WaitCount], + 100.0 * wait_time_ms / SUM (wait_time_ms) OVER() AS [Percentage], + ROW_NUMBER() OVER(ORDER BY wait_time_ms DESC) AS [RowNum] + FROM sys.dm_os_wait_stats WITH (NOLOCK) + WHERE [wait_type] NOT IN ( + N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', + N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', + N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE', N'CXCONSUMER', + N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE', + N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', + N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', + N'HADR_CLUSAPI_CALL', N'HADR_FABRIC_CALLBACK', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', + N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', + N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE', + N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE', + N'PARALLEL_REDO_DRAIN_WORKER', N'PARALLEL_REDO_LOG_CACHE', N'PARALLEL_REDO_TRAN_LIST', + N'PARALLEL_REDO_WORKER_SYNC', N'PARALLEL_REDO_WORKER_WAIT_WORK', + N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS', + N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS', + N'PREEMPTIVE_OS_PIPEOPS', N'PREEMPTIVE_OS_AUTHENTICATIONOPS', + N'PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST', + N'PREEMPTIVE_OS_FILEOPS', N'PREEMPTIVE_OS_DEVICEOPS', N'PREEMPTIVE_OS_QUERYREGISTRY', + N'PREEMPTIVE_OS_WRITEFILE', + N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER', + N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT', + N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE', + N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', + N'PWAIT_EXTENSIBILITY_CLEANUP_TASK', + N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', N'QDS_ASYNC_QUEUE', + N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH', + N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP', + N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', + N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', + N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SOS_WORK_DISPATCHER', + N'SP_SERVER_DIAGNOSTICS_SLEEP', + N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES', + N'STARTUP_DEPENDENCY_MANAGER', + N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT', + N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'WAIT_XTP_RECOVERY', + N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN', + N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT') + AND waiting_tasks_count > 0) +SELECT + MAX (W1.wait_type) AS [WaitType], + CAST (MAX (W1.Percentage) AS DECIMAL (5,2)) AS [Wait Percentage], + CAST ((MAX (W1.WaitS) / MAX (W1.WaitCount)) AS DECIMAL (16,4)) AS [AvgWait_Sec], + CAST ((MAX (W1.ResourceS) / MAX (W1.WaitCount)) AS DECIMAL (16,4)) AS [AvgRes_Sec], + CAST ((MAX (W1.SignalS) / MAX (W1.WaitCount)) AS DECIMAL (16,4)) AS [AvgSig_Sec], + CAST (MAX (W1.WaitS) AS DECIMAL (16,2)) AS [Wait_Sec], + CAST (MAX (W1.ResourceS) AS DECIMAL (16,2)) AS [Resource_Sec], + CAST (MAX (W1.SignalS) AS DECIMAL (16,2)) AS [Signal_Sec], + MAX (W1.WaitCount) AS [Wait Count], + CAST (N'https://www.sqlskills.com/help/waits/' + W1.wait_type AS XML) AS [Help/Info URL] +FROM Waits AS W1 +INNER JOIN Waits AS W2 +ON W2.RowNum <= W1.RowNum +GROUP BY W1.RowNum, W1.wait_type +HAVING SUM (W2.Percentage) - MAX (W1.Percentage) < 99 -- percentage threshold +OPTION (RECOMPILE); +------ + +-- Cumulative wait stats are not as useful on an idle instance that is not under load or performance pressure + +-- SQL Server Wait Types Library (Paul Randal) +-- https://bit.ly/2ePzYO2 + +-- The SQL Server Wait Type Repository +-- https://bit.ly/1afzfjC + +-- Wait statistics, or please tell me where it hurts +-- https://bit.ly/2wsQHQE + +-- SQL Server 2005 Performance Tuning using the Waits and Queues +-- https://bit.ly/1o2NFoF + +-- sys.dm_os_wait_stats (Transact-SQL) +-- https://bit.ly/2Hjq9Yl + + + +-- Get a count of SQL connections by IP address (Query 25) (Connection Counts by IP Address) +SELECT ec.client_net_address, es.[program_name], es.[host_name], es.login_name, +COUNT(ec.session_id) AS [connection count] +FROM sys.dm_exec_sessions AS es WITH (NOLOCK) +INNER JOIN sys.dm_exec_connections AS ec WITH (NOLOCK) +ON es.session_id = ec.session_id +GROUP BY ec.client_net_address, es.[program_name], es.[host_name], es.login_name +ORDER BY ec.client_net_address, es.[program_name] OPTION (RECOMPILE); +------ + +-- This helps you figure where your database load is coming from +-- and verifies connectivity from other machines + +-- Solving Connectivity errors to SQL Server +-- https://bit.ly/2EgzoD0 + + + +-- Get Average Task Counts (run multiple times) (Query 26) (Avg Task Counts) +SELECT AVG(current_tasks_count) AS [Avg Task Count], +AVG(work_queue_count) AS [Avg Work Queue Count], +AVG(runnable_tasks_count) AS [Avg Runnable Task Count], +AVG(pending_disk_io_count) AS [Avg Pending DiskIO Count] +FROM sys.dm_os_schedulers WITH (NOLOCK) +WHERE scheduler_id < 255 OPTION (RECOMPILE); +------ + +-- Sustained values above 10 suggest further investigation in that area +-- High Avg Task Counts are often caused by blocking/deadlocking or other resource contention + +-- Sustained values above 1 suggest further investigation in that area +-- High Avg Runnable Task Counts are a good sign of CPU pressure +-- High Avg Pending DiskIO Counts are a sign of disk pressure + +-- How to Do Some Very Basic SQL Server Monitoring +-- https://bit.ly/2q3Btgt + + + +-- Detect blocking (run multiple times) (Query 27) (Detect Blocking) +SELECT t1.resource_type AS [lock type], DB_NAME(resource_database_id) AS [database], +t1.resource_associated_entity_id AS [blk object],t1.request_mode AS [lock req], -- lock requested +t1.request_session_id AS [waiter sid], t2.wait_duration_ms AS [wait time], -- spid of waiter +(SELECT [text] FROM sys.dm_exec_requests AS r WITH (NOLOCK) -- get sql for waiter +CROSS APPLY sys.dm_exec_sql_text(r.[sql_handle]) +WHERE r.session_id = t1.request_session_id) AS [waiter_batch], +(SELECT SUBSTRING(qt.[text],r.statement_start_offset/2, + (CASE WHEN r.statement_end_offset = -1 + THEN LEN(CONVERT(nvarchar(max), qt.[text])) * 2 + ELSE r.statement_end_offset END - r.statement_start_offset)/2) +FROM sys.dm_exec_requests AS r WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(r.[sql_handle]) AS qt +WHERE r.session_id = t1.request_session_id) AS [waiter_stmt], -- statement blocked +t2.blocking_session_id AS [blocker sid], -- spid of blocker +(SELECT [text] FROM sys.sysprocesses AS p -- get sql for blocker +CROSS APPLY sys.dm_exec_sql_text(p.[sql_handle]) +WHERE p.spid = t2.blocking_session_id) AS [blocker_batch] +FROM sys.dm_tran_locks AS t1 WITH (NOLOCK) +INNER JOIN sys.dm_os_waiting_tasks AS t2 WITH (NOLOCK) +ON t1.lock_owner_address = t2.resource_address OPTION (RECOMPILE); +------ + +-- Helps troubleshoot blocking and deadlocking issues +-- The results will change from second to second on a busy system +-- You should run this query multiple times when you see signs of blocking + + + +-- Get CPU Utilization History for last 256 minutes (in one minute intervals) (Query 28) (CPU Utilization History) +DECLARE @ts_now bigint = (SELECT cpu_ticks/(cpu_ticks/ms_ticks) FROM sys.dm_os_sys_info WITH (NOLOCK)); + +SELECT TOP(256) SQLProcessUtilization AS [SQL Server Process CPU Utilization], + SystemIdle AS [System Idle Process], + 100 - SystemIdle - SQLProcessUtilization AS [Other Process CPU Utilization], + DATEADD(ms, -1 * (@ts_now - [timestamp]), GETDATE()) AS [Event Time] +FROM (SELECT record.value('(./Record/@id)[1]', 'int') AS record_id, + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') + AS [SystemIdle], + record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') + AS [SQLProcessUtilization], [timestamp] + FROM (SELECT [timestamp], CONVERT(xml, record) AS [record] + FROM sys.dm_os_ring_buffers WITH (NOLOCK) + WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' + AND record LIKE N'%%') AS x) AS y +ORDER BY record_id DESC OPTION (RECOMPILE); +------ + +-- Look at the trend over the entire period +-- Also look at high sustained 'Other Process' CPU Utilization values +-- Note: This query sometimes gives inaccurate results (negative values) +-- on high core count (> 64 cores) systems + + +-- Get top total worker time queries for entire instance (Query 29) (Top Worker Time Queries) +SELECT TOP(50) DB_NAME(t.[dbid]) AS [Database Name], +REPLACE(REPLACE(LEFT(t.[text], 255), CHAR(10),''), CHAR(13),'') AS [Short Query Text], +qs.total_worker_time AS [Total Worker Time], qs.min_worker_time AS [Min Worker Time], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +qs.max_worker_time AS [Max Worker Time], +qs.min_elapsed_time AS [Min Elapsed Time], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +qs.max_elapsed_time AS [Max Elapsed Time], +qs.min_logical_reads AS [Min Logical Reads], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +qs.max_logical_reads AS [Max Logical Reads], +qs.execution_count AS [Execution Count], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +qs.creation_time AS [Creation Time] +--,t.[text] AS [Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp +ORDER BY qs.total_worker_time DESC OPTION (RECOMPILE); +------ + + +-- Helps you find the most expensive queries from a CPU perspective across the entire instance +-- Can also help track down parameter sniffing issues + + + +-- Page Life Expectancy (PLE) value for each NUMA node in current instance (Query 30) (PLE by NUMA Node) +SELECT @@SERVERNAME AS [Server Name], RTRIM([object_name]) AS [Object Name], + instance_name, cntr_value AS [Page Life Expectancy] +FROM sys.dm_os_performance_counters WITH (NOLOCK) +WHERE [object_name] LIKE N'%Buffer Node%' -- Handles named instances +AND counter_name = N'Page life expectancy' OPTION (RECOMPILE); +------ + +-- PLE is a good measurement of internal memory pressure +-- Higher PLE is better. Watch the trend over time, not the absolute value +-- This will only return one row for non-NUMA systems + +-- Page Life Expectancy isn’t what you think… +-- https://bit.ly/2EgynLa + + +-- Memory Grants Pending value for current instance (Query 31) (Memory Grants Pending) +SELECT @@SERVERNAME AS [Server Name], RTRIM([object_name]) AS [Object Name], cntr_value AS [Memory Grants Pending] +FROM sys.dm_os_performance_counters WITH (NOLOCK) +WHERE [object_name] LIKE N'%Memory Manager%' -- Handles named instances +AND counter_name = N'Memory Grants Pending' OPTION (RECOMPILE); +------ + +-- Run multiple times, and run periodically if you suspect you are under memory pressure +-- Memory Grants Pending above zero for a sustained period is a very strong indicator of internal memory pressure + + +-- Memory Clerk Usage for instance (Query 32) (Memory Clerk Usage) +-- Look for high value for CACHESTORE_SQLCP (Ad-hoc query plans) +SELECT TOP(10) mc.[type] AS [Memory Clerk Type], + CAST((SUM(mc.pages_kb)/1024.0) AS DECIMAL (15,2)) AS [Memory Usage (MB)] +FROM sys.dm_os_memory_clerks AS mc WITH (NOLOCK) +GROUP BY mc.[type] +ORDER BY SUM(mc.pages_kb) DESC OPTION (RECOMPILE); +------ + +-- MEMORYCLERK_SQLBUFFERPOOL was new for SQL Server 2012. It should be your highest consumer of memory + +-- CACHESTORE_SQLCP SQL Plans +-- These are cached SQL statements or batches that aren't in stored procedures, functions and triggers +-- Watch out for high values for CACHESTORE_SQLCP +-- Enabling 'optimize for ad hoc workloads' at the instance level can help reduce this +-- Running DBCC FREESYSTEMCACHE ('SQL Plans') periodically may be required to better control this + +-- CACHESTORE_OBJCP Object Plans +-- These are compiled plans for stored procedures, functions and triggers + +-- sys.dm_os_memory_clerks (Transact-SQL) +-- https://bit.ly/2H31xDR + + + +-- Find single-use, ad-hoc and prepared queries that are bloating the plan cache (Query 33) (Ad hoc Queries) +SELECT TOP(50) DB_NAME(t.[dbid]) AS [Database Name], t.[text] AS [Query Text], +cp.objtype AS [Object Type], cp.cacheobjtype AS [Cache Object Type], +cp.size_in_bytes/1024 AS [Plan Size in KB] +FROM sys.dm_exec_cached_plans AS cp WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +WHERE cp.cacheobjtype = N'Compiled Plan' +AND cp.objtype IN (N'Adhoc', N'Prepared') +AND cp.usecounts = 1 +ORDER BY cp.size_in_bytes DESC, DB_NAME(t.[dbid]) OPTION (RECOMPILE); +------ + +-- Gives you the text, type and size of single-use ad-hoc and prepared queries that waste space in the plan cache +-- Enabling 'optimize for ad hoc workloads' for the instance can help (SQL Server 2008 and above only) +-- Running DBCC FREESYSTEMCACHE ('SQL Plans') periodically may be required to better control this +-- Enabling forced parameterization for the database can help, but test first! + +-- Plan cache, adhoc workloads and clearing the single-use plan cache bloat +-- https://bit.ly/2EfYOkl + + +-- Get top total logical reads queries for entire instance (Query 34) (Top Logical Reads Queries) +SELECT TOP(50) DB_NAME(t.[dbid]) AS [Database Name], +REPLACE(REPLACE(LEFT(t.[text], 255), CHAR(10),''), CHAR(13),'') AS [Short Query Text], +qs.total_logical_reads AS [Total Logical Reads], +qs.min_logical_reads AS [Min Logical Reads], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +qs.max_logical_reads AS [Max Logical Reads], +qs.min_worker_time AS [Min Worker Time], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +qs.max_worker_time AS [Max Worker Time], +qs.min_elapsed_time AS [Min Elapsed Time], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +qs.max_elapsed_time AS [Max Elapsed Time], +qs.execution_count AS [Execution Count], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +qs.creation_time AS [Creation Time] +--,t.[text] AS [Complete Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp +ORDER BY qs.total_logical_reads DESC OPTION (RECOMPILE); +------ + + +-- Helps you find the most expensive queries from a memory perspective across the entire instance +-- Can also help track down parameter sniffing issues + + +-- Get top average elapsed time queries for entire instance (Query 35) (Top Avg Elapsed Time Queries) +SELECT TOP(50) DB_NAME(t.[dbid]) AS [Database Name], +REPLACE(REPLACE(LEFT(t.[text], 255), CHAR(10),''), CHAR(13),'') AS [Short Query Text], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +qs.min_elapsed_time, qs.max_elapsed_time, qs.last_elapsed_time, +qs.execution_count AS [Execution Count], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +qs.total_physical_reads/qs.execution_count AS [Avg Physical Reads], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +qs.creation_time AS [Creation Time] +--,t.[text] AS [Complete Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp +ORDER BY qs.total_elapsed_time/qs.execution_count DESC OPTION (RECOMPILE); +------ + +-- Helps you find the highest average elapsed time queries across the entire instance +-- Can also help track down parameter sniffing issues + + +-- Look at UDF execution statistics (Query 36) (UDF Stats by DB) +SELECT TOP (25) DB_NAME(database_id) AS [Database Name], + OBJECT_NAME(object_id, database_id) AS [Function Name], + total_worker_time, execution_count, total_elapsed_time, + total_elapsed_time/execution_count AS [avg_elapsed_time], + last_elapsed_time, last_execution_time, cached_time, [type_desc] +FROM sys.dm_exec_function_stats WITH (NOLOCK) +ORDER BY total_worker_time DESC OPTION (RECOMPILE); +------ + +-- sys.dm_exec_function_stats (Transact-SQL) +-- https://bit.ly/2q1Q6BM + +-- Showplan Enhancements for UDFs +-- https://bit.ly/2LVqiQ1 + + +-- Database specific queries ***************************************************************** + +-- **** Please switch to a user database that you are interested in! ***** +--USE YourDatabaseName; -- make sure to change to an actual database on your instance, not the master system database +--GO + +-- Individual File Sizes and space available for current database (Query 37) (File Sizes and Space) +SELECT f.name AS [File Name] , f.physical_name AS [Physical Name], +CAST((f.size/128.0) AS DECIMAL(15,2)) AS [Total Size in MB], +CAST(f.size/128.0 - CAST(FILEPROPERTY(f.name, 'SpaceUsed') AS int)/128.0 AS DECIMAL(15,2)) +AS [Available Space In MB], f.[file_id], fg.name AS [Filegroup Name], +f.is_percent_growth, f.growth, fg.is_default, fg.is_read_only, +fg.is_autogrow_all_files +FROM sys.database_files AS f WITH (NOLOCK) +LEFT OUTER JOIN sys.filegroups AS fg WITH (NOLOCK) +ON f.data_space_id = fg.data_space_id +ORDER BY f.[file_id] OPTION (RECOMPILE); +------ + +-- Look at how large and how full the files are and where they are located +-- Make sure the transaction log is not full!! + +-- is_autogrow_all_files was new for SQL Server 2016. Equivalent to TF 1117 for user databases + +-- SQL Server 2016: Changes in default behavior for autogrow and allocations for tempdb and user databases +-- https://bit.ly/2evRZSR + + +-- Log space usage for current database (Query 38) (Log Space Usage) +SELECT DB_NAME(lsu.database_id) AS [Database Name], db.recovery_model_desc AS [Recovery Model], + CAST(lsu.total_log_size_in_bytes/1048576.0 AS DECIMAL(10, 2)) AS [Total Log Space (MB)], + CAST(lsu.used_log_space_in_bytes/1048576.0 AS DECIMAL(10, 2)) AS [Used Log Space (MB)], + CAST(lsu.used_log_space_in_percent AS DECIMAL(10, 2)) AS [Used Log Space %], + CAST(lsu.log_space_in_bytes_since_last_backup/1048576.0 AS DECIMAL(10, 2)) AS [Used Log Space Since Last Backup (MB)], + db.log_reuse_wait_desc +FROM sys.dm_db_log_space_usage AS lsu WITH (NOLOCK) +INNER JOIN sys.databases AS db WITH (NOLOCK) +ON lsu.database_id = db.database_id +OPTION (RECOMPILE); +------ + +-- Look at log file size and usage, along with the log reuse wait description for the current database + +-- sys.dm_db_log_space_usage (Transact-SQL) +-- https://bit.ly/2H4MQw9 + + +-- Status of last VLF for current database (Query 39) (Last VLF Status) +SELECT TOP(1) DB_NAME(li.database_id) AS [Database Name], li.[file_id], + li.vlf_size_mb, li.vlf_sequence_number, li.vlf_active, li.vlf_status +FROM sys.dm_db_log_info(DB_ID()) AS li +ORDER BY vlf_sequence_number DESC OPTION (RECOMPILE); +------ + +-- Determine whether you will be able to shrink the transaction log file + +-- vlf_status Values +-- 0 is inactive +-- 1 is initialized but unused +-- 2 is active + +-- sys.dm_db_log_info (Transact-SQL) +-- https://bit.ly/2EQUU1v + + + +-- Get database scoped configuration values for current database (Query 40) (Database-scoped Configurations) +SELECT configuration_id, name, [value] AS [value_for_primary] +FROM sys.database_scoped_configurations WITH (NOLOCK) OPTION (RECOMPILE); +------ + +-- This lets you see the value of these new properties for the current database + +-- Clear plan cache for current database +-- ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; + +-- ALTER DATABASE SCOPED CONFIGURATION (Transact-SQL) +-- https://bit.ly/2sOH7nb + + +-- I/O Statistics by file for the current database (Query 41) (IO Stats By File) +SELECT DB_NAME(DB_ID()) AS [Database Name], df.name AS [Logical Name], vfs.[file_id], df.type_desc, +df.physical_name AS [Physical Name], CAST(vfs.size_on_disk_bytes/1048576.0 AS DECIMAL(10, 2)) AS [Size on Disk (MB)], +vfs.num_of_reads, vfs.num_of_writes, vfs.io_stall_read_ms, vfs.io_stall_write_ms, +CAST(100. * vfs.io_stall_read_ms/(vfs.io_stall_read_ms + vfs.io_stall_write_ms) AS DECIMAL(10,1)) AS [IO Stall Reads Pct], +CAST(100. * vfs.io_stall_write_ms/(vfs.io_stall_write_ms + vfs.io_stall_read_ms) AS DECIMAL(10,1)) AS [IO Stall Writes Pct], +(vfs.num_of_reads + vfs.num_of_writes) AS [Writes + Reads], +CAST(vfs.num_of_bytes_read/1048576.0 AS DECIMAL(10, 2)) AS [MB Read], +CAST(vfs.num_of_bytes_written/1048576.0 AS DECIMAL(10, 2)) AS [MB Written], +CAST(100. * vfs.num_of_reads/(vfs.num_of_reads + vfs.num_of_writes) AS DECIMAL(10,1)) AS [# Reads Pct], +CAST(100. * vfs.num_of_writes/(vfs.num_of_reads + vfs.num_of_writes) AS DECIMAL(10,1)) AS [# Write Pct], +CAST(100. * vfs.num_of_bytes_read/(vfs.num_of_bytes_read + vfs.num_of_bytes_written) AS DECIMAL(10,1)) AS [Read Bytes Pct], +CAST(100. * vfs.num_of_bytes_written/(vfs.num_of_bytes_read + vfs.num_of_bytes_written) AS DECIMAL(10,1)) AS [Written Bytes Pct] +FROM sys.dm_io_virtual_file_stats(DB_ID(), NULL) AS vfs +INNER JOIN sys.database_files AS df WITH (NOLOCK) +ON vfs.[file_id]= df.[file_id] OPTION (RECOMPILE); +------ + +-- This helps you characterize your workload better from an I/O perspective for this database +-- It helps you determine whether you has an OLTP or DW/DSS type of workload + + + +-- Get most frequently executed queries for this database (Query 42) (Query Execution Counts) +SELECT TOP(50) LEFT(t.[text], 50) AS [Short Query Text], qs.execution_count AS [Execution Count], +qs.total_logical_reads AS [Total Logical Reads], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +qs.total_worker_time AS [Total Worker Time], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +qs.total_elapsed_time AS [Total Elapsed Time], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +qs.creation_time AS [Creation Time] +--,t.[text] AS [Complete Query Text], qp.query_plan AS [Query Plan] -- uncomment out these columns if not copying results to Excel +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS t +CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp +WHERE t.dbid = DB_ID() +ORDER BY qs.execution_count DESC OPTION (RECOMPILE); +------ + + +-- Queries 48 through 53 are the "Bad Man List" for stored procedures + +-- Top Cached SPs By Execution Count (Query 43) (SP Execution Counts) +SELECT TOP(100) p.name AS [SP Name], qs.execution_count AS [Execution Count], +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_elapsed_time/qs.execution_count AS [Avg Elapsed Time], +qs.total_worker_time/qs.execution_count AS [Avg Worker Time], +qs.total_logical_reads/qs.execution_count AS [Avg Logical Reads], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY qs.execution_count DESC OPTION (RECOMPILE); +------ + +-- Tells you which cached stored procedures are called the most often +-- This helps you characterize and baseline your workload + + +-- Top Cached SPs By Avg Elapsed Time (Query 44) (SP Avg Elapsed Time) +SELECT TOP(25) p.name AS [SP Name], qs.min_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +qs.max_elapsed_time, qs.last_elapsed_time, qs.total_elapsed_time, qs.execution_count, +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_worker_time/qs.execution_count AS [AvgWorkerTime], +qs.total_worker_time AS [TotalWorkerTime], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY avg_elapsed_time DESC OPTION (RECOMPILE); +------ + +-- This helps you find high average elapsed time cached stored procedures that +-- may be easy to optimize with standard query tuning techniques + + + +-- Top Cached SPs By Total Worker time. Worker time relates to CPU cost (Query 45) (SP Worker Time) +SELECT TOP(25) p.name AS [SP Name], qs.total_worker_time AS [TotalWorkerTime], +qs.total_worker_time/qs.execution_count AS [AvgWorkerTime], qs.execution_count, +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +--,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY qs.total_worker_time DESC OPTION (RECOMPILE); +------ + +-- This helps you find the most expensive cached stored procedures from a CPU perspective +-- You should look at this if you see signs of CPU pressure + + +-- Top Cached SPs By Total Logical Reads. Logical reads relate to memory pressure (Query 46) (SP Logical Reads) +SELECT TOP(25) p.name AS [SP Name], qs.total_logical_reads AS [TotalLogicalReads], +qs.total_logical_reads/qs.execution_count AS [AvgLogicalReads],qs.execution_count, +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY qs.total_logical_reads DESC OPTION (RECOMPILE); +------ + +-- This helps you find the most expensive cached stored procedures from a memory perspective +-- You should look at this if you see signs of memory pressure + + +-- Top Cached SPs By Total Physical Reads. Physical reads relate to disk read I/O pressure (Query 47) (SP Physical Reads) +SELECT TOP(25) p.name AS [SP Name],qs.total_physical_reads AS [TotalPhysicalReads], +qs.total_physical_reads/qs.execution_count AS [AvgPhysicalReads], qs.execution_count, +qs.total_logical_reads,qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND qs.total_physical_reads > 0 +ORDER BY qs.total_physical_reads DESC, qs.total_logical_reads DESC OPTION (RECOMPILE); +------ + +-- This helps you find the most expensive cached stored procedures from a read I/O perspective +-- You should look at this if you see signs of I/O pressure or of memory pressure + + + +-- Top Cached SPs By Total Logical Writes (Query 48) (SP Logical Writes) +-- Logical writes relate to both memory and disk I/O pressure +SELECT TOP(25) p.name AS [SP Name], qs.total_logical_writes AS [TotalLogicalWrites], +qs.total_logical_writes/qs.execution_count AS [AvgLogicalWrites], qs.execution_count, +ISNULL(qs.execution_count/DATEDIFF(Minute, qs.cached_time, GETDATE()), 0) AS [Calls/Minute], +qs.total_elapsed_time, qs.total_elapsed_time/qs.execution_count AS [avg_elapsed_time], +CASE WHEN CONVERT(nvarchar(max), qp.query_plan) LIKE N'%%' THEN 1 ELSE 0 END AS [Has Missing Index], +FORMAT(qs.last_execution_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Last Execution Time], +FORMAT(qs.cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +-- ,qp.query_plan AS [Query Plan] -- Uncomment if you want the Query Plan +FROM sys.procedures AS p WITH (NOLOCK) +INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK) +ON p.[object_id] = qs.[object_id] +CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp +WHERE qs.database_id = DB_ID() +AND qs.total_logical_writes > 0 +AND DATEDIFF(Minute, qs.cached_time, GETDATE()) > 0 +ORDER BY qs.total_logical_writes DESC OPTION (RECOMPILE); +------ + +-- This helps you find the most expensive cached stored procedures from a write I/O perspective +-- You should look at this if you see signs of I/O pressure or of memory pressure + + +-- Lists the top statements by average input/output usage for the current database (Query 49) (Top IO Statements) +SELECT TOP(50) OBJECT_NAME(qt.objectid, dbid) AS [SP Name], +(qs.total_logical_reads + qs.total_logical_writes) /qs.execution_count AS [Avg IO], qs.execution_count AS [Execution Count], +SUBSTRING(qt.[text],qs.statement_start_offset/2, + (CASE + WHEN qs.statement_end_offset = -1 + THEN LEN(CONVERT(nvarchar(max), qt.[text])) * 2 + ELSE qs.statement_end_offset + END - qs.statement_start_offset)/2) AS [Query Text] +FROM sys.dm_exec_query_stats AS qs WITH (NOLOCK) +CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt +WHERE qt.[dbid] = DB_ID() +ORDER BY [Avg IO] DESC OPTION (RECOMPILE); +------ + +-- Helps you find the most expensive statements for I/O by SP + + + +-- Possible Bad NC Indexes (writes > reads) (Query 50) (Bad NC Indexes) +SELECT SCHEMA_NAME(o.[schema_id]) AS [Schema Name], +OBJECT_NAME(s.[object_id]) AS [Table Name], +i.name AS [Index Name], i.index_id, +i.is_disabled, i.is_hypothetical, i.has_filter, i.fill_factor, +s.user_updates AS [Total Writes], s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], +s.user_updates - (s.user_seeks + s.user_scans + s.user_lookups) AS [Difference] +FROM sys.dm_db_index_usage_stats AS s WITH (NOLOCK) +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON s.[object_id] = i.[object_id] +AND i.index_id = s.index_id +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON i.[object_id] = o.[object_id] +WHERE OBJECTPROPERTY(s.[object_id],'IsUserTable') = 1 +AND s.database_id = DB_ID() +AND s.user_updates > (s.user_seeks + s.user_scans + s.user_lookups) +AND i.index_id > 1 AND i.[type_desc] = N'NONCLUSTERED' +AND i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND i.is_unique = 0 +ORDER BY [Difference] DESC, [Total Writes] DESC, [Total Reads] ASC OPTION (RECOMPILE); +------ + +-- Look for indexes with high numbers of writes and zero or very low numbers of reads +-- Consider your complete workload, and how long your instance has been running +-- Investigate further before dropping an index! + + +-- Missing Indexes for current database by Index Advantage (Query 51) (Missing Indexes) +SELECT DISTINCT CONVERT(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) AS [index_advantage], +migs.last_user_seek, mid.[statement] AS [Database.Schema.Table], +mid.equality_columns, mid.inequality_columns, mid.included_columns, +migs.unique_compiles, migs.user_seeks, migs.avg_total_user_cost, migs.avg_user_impact, +OBJECT_NAME(mid.[object_id]) AS [Table Name], p.rows AS [Table Rows] +FROM sys.dm_db_missing_index_group_stats AS migs WITH (NOLOCK) +INNER JOIN sys.dm_db_missing_index_groups AS mig WITH (NOLOCK) +ON migs.group_handle = mig.index_group_handle +INNER JOIN sys.dm_db_missing_index_details AS mid WITH (NOLOCK) +ON mig.index_handle = mid.index_handle +INNER JOIN sys.partitions AS p WITH (NOLOCK) +ON p.[object_id] = mid.[object_id] +WHERE mid.database_id = DB_ID() +AND p.index_id < 2 +ORDER BY index_advantage DESC OPTION (RECOMPILE); +------ + +-- Look at index advantage, last user seek time, number of user seeks to help determine source and importance +-- SQL Server is overly eager to add included columns, so beware +-- Do not just blindly add indexes that show up from this query!!! + + +-- Find missing index warnings for cached plans in the current database (Query 52) (Missing Index Warnings) +-- Note: This query could take some time on a busy instance +SELECT TOP(25) OBJECT_NAME(objectid) AS [ObjectName], + cp.objtype, cp.usecounts, cp.size_in_bytes, query_plan +FROM sys.dm_exec_cached_plans AS cp WITH (NOLOCK) +CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp +WHERE CAST(query_plan AS NVARCHAR(MAX)) LIKE N'%MissingIndex%' +AND dbid = DB_ID() +ORDER BY cp.usecounts DESC OPTION (RECOMPILE); +------ + +-- Helps you connect missing indexes to specific stored procedures or queries +-- This can help you decide whether to add them or not + + +-- Breaks down buffers used by current database by object (table, index) in the buffer cache (Query 53) (Buffer Usage) +-- Note: This query could take some time on a busy instance +SELECT SCHEMA_NAME(o.Schema_ID) AS [Schema Name], +OBJECT_NAME(p.[object_id]) AS [Object Name], p.index_id, +CAST(COUNT(*)/128.0 AS DECIMAL(10, 2)) AS [Buffer size(MB)], +COUNT(*) AS [BufferCount], p.[Rows] AS [Row Count], +p.data_compression_desc AS [Compression Type] +FROM sys.allocation_units AS a WITH (NOLOCK) +INNER JOIN sys.dm_os_buffer_descriptors AS b WITH (NOLOCK) +ON a.allocation_unit_id = b.allocation_unit_id +INNER JOIN sys.partitions AS p WITH (NOLOCK) +ON a.container_id = p.hobt_id +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON p.object_id = o.object_id +WHERE b.database_id = CONVERT(int, DB_ID()) +AND p.[object_id] > 100 +AND OBJECT_NAME(p.[object_id]) NOT LIKE N'plan_%' +AND OBJECT_NAME(p.[object_id]) NOT LIKE N'sys%' +AND OBJECT_NAME(p.[object_id]) NOT LIKE N'xml_index_nodes%' +GROUP BY o.Schema_ID, p.[object_id], p.index_id, p.data_compression_desc, p.[Rows] +ORDER BY [BufferCount] DESC OPTION (RECOMPILE); +------ + +-- Tells you what tables and indexes are using the most memory in the buffer cache +-- It can help identify possible candidates for data compression + + +-- Get Table names, row counts, and compression status for clustered index or heap (Query 54) (Table Sizes) +SELECT SCHEMA_NAME(o.Schema_ID) AS [Schema Name], OBJECT_NAME(p.object_id) AS [ObjectName], +SUM(p.Rows) AS [RowCount], data_compression_desc AS [CompressionType] +FROM sys.partitions AS p WITH (NOLOCK) +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON p.object_id = o.object_id +WHERE index_id < 2 --ignore the partitions from the non-clustered index if any +AND OBJECT_NAME(p.object_id) NOT LIKE N'sys%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'spt_%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'queue_%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'filestream_tombstone%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'fulltext%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'ifts_comp_fragment%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'filetable_updates%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'xml_index_nodes%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'sqlagent_job%' +AND OBJECT_NAME(p.object_id) NOT LIKE N'plan_persist%' +GROUP BY SCHEMA_NAME(o.Schema_ID), p.object_id, data_compression_desc +ORDER BY SUM(p.Rows) DESC OPTION (RECOMPILE); +------ + +-- Gives you an idea of table sizes, and possible data compression opportunities + + + +-- Get some key table properties (Query 55) (Table Properties) +SELECT OBJECT_NAME(t.[object_id]) AS [ObjectName], p.[rows] AS [Table Rows], p.index_id, + p.data_compression_desc AS [Index Data Compression], + t.create_date, t.lock_on_bulk_load, t.is_replicated, t.has_replication_filter, + t.is_tracked_by_cdc, t.lock_escalation_desc, t.is_filetable, + t.is_memory_optimized, t.durability_desc, + t.temporal_type_desc, t.is_remote_data_archive_enabled, t.is_external -- new for SQL Server 2016 +FROM sys.tables AS t WITH (NOLOCK) +INNER JOIN sys.partitions AS p WITH (NOLOCK) +ON t.[object_id] = p.[object_id] +WHERE OBJECT_NAME(t.[object_id]) NOT LIKE N'sys%' +ORDER BY OBJECT_NAME(t.[object_id]), p.index_id OPTION (RECOMPILE); +------ + +-- Gives you some good information about your tables +-- is_memory_optimized and durability_desc were new in SQL Server 2014 +-- temporal_type_desc, is_remote_data_archive_enabled, is_external were new in SQL Server 2016 + +-- sys.tables (Transact-SQL) +-- https://bit.ly/2Gk7998 + + + +-- When were Statistics last updated on all indexes? (Query 56) (Statistics Update) +SELECT SCHEMA_NAME(o.Schema_ID) + N'.' + o.[NAME] AS [Object Name], o.[type_desc] AS [Object Type], + i.[name] AS [Index Name], STATS_DATE(i.[object_id], i.index_id) AS [Statistics Date], + s.auto_created, s.no_recompute, s.user_created, s.is_incremental, s.is_temporary, + st.row_count, st.used_page_count +FROM sys.objects AS o WITH (NOLOCK) +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON o.[object_id] = i.[object_id] +INNER JOIN sys.stats AS s WITH (NOLOCK) +ON i.[object_id] = s.[object_id] +AND i.index_id = s.stats_id +INNER JOIN sys.dm_db_partition_stats AS st WITH (NOLOCK) +ON o.[object_id] = st.[object_id] +AND i.[index_id] = st.[index_id] +WHERE o.[type] IN ('U', 'V') +AND st.row_count > 0 +ORDER BY STATS_DATE(i.[object_id], i.index_id) DESC OPTION (RECOMPILE); +------ + +-- Helps discover possible problems with out-of-date statistics +-- Also gives you an idea which indexes are the most active + +-- sys.stats (Transact-SQL) +-- https://bit.ly/2GyAxrn + +-- UPDATEs to Statistics (Erin Stellato) +-- https://bit.ly/2vhrYQy + + + + +-- Look at most frequently modified indexes and statistics (Query 57) (Volatile Indexes) +SELECT o.[name] AS [Object Name], o.[object_id], o.[type_desc], s.[name] AS [Statistics Name], + s.stats_id, s.no_recompute, s.auto_created, s.is_incremental, s.is_temporary, + sp.modification_counter, sp.[rows], sp.rows_sampled, sp.last_updated +FROM sys.objects AS o WITH (NOLOCK) +INNER JOIN sys.stats AS s WITH (NOLOCK) +ON s.object_id = o.object_id +CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS sp +WHERE o.[type_desc] NOT IN (N'SYSTEM_TABLE', N'INTERNAL_TABLE') +AND sp.modification_counter > 0 +ORDER BY sp.modification_counter DESC, o.name OPTION (RECOMPILE); +------ + +-- This helps you understand your workload and make better decisions about +-- things like data compression and adding new indexes to a table + + + +-- Get fragmentation info for all indexes above a certain size in the current database (Query 58) (Index Fragmentation) +-- Note: This query could take some time on a very large database +SELECT DB_NAME(ps.database_id) AS [Database Name], SCHEMA_NAME(o.[schema_id]) AS [Schema Name], +OBJECT_NAME(ps.OBJECT_ID) AS [Object Name], i.[name] AS [Index Name], ps.index_id, +ps.index_type_desc, ps.avg_fragmentation_in_percent, +ps.fragment_count, ps.page_count, i.fill_factor, i.has_filter, +i.filter_definition, i.[allow_page_locks] +FROM sys.dm_db_index_physical_stats(DB_ID(),NULL, NULL, NULL , N'LIMITED') AS ps +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON ps.[object_id] = i.[object_id] +AND ps.index_id = i.index_id +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON i.[object_id] = o.[object_id] +WHERE ps.database_id = DB_ID() +AND ps.page_count > 2500 +ORDER BY ps.avg_fragmentation_in_percent DESC OPTION (RECOMPILE); +------ + +-- Helps determine whether you have framentation in your relational indexes +-- and how effective your index maintenance strategy is + + +--- Index Read/Write stats (all tables in current DB) ordered by Reads (Query 59) (Overall Index Usage - Reads) +SELECT OBJECT_NAME(i.[object_id]) AS [ObjectName], i.[name] AS [IndexName], i.index_id, + s.user_seeks, s.user_scans, s.user_lookups, + s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], + s.user_updates AS [Writes], + i.[type_desc] AS [Index Type], i.fill_factor AS [Fill Factor], i.has_filter, i.filter_definition, + s.last_user_scan, s.last_user_lookup, s.last_user_seek +FROM sys.indexes AS i WITH (NOLOCK) +LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s WITH (NOLOCK) +ON i.[object_id] = s.[object_id] +AND i.index_id = s.index_id +AND s.database_id = DB_ID() +WHERE OBJECTPROPERTY(i.[object_id],'IsUserTable') = 1 +ORDER BY s.user_seeks + s.user_scans + s.user_lookups DESC OPTION (RECOMPILE); -- Order by reads +------ + +-- Show which indexes in the current database are most active for Reads + + +--- Index Read/Write stats (all tables in current DB) ordered by Writes (Query 60) (Overall Index Usage - Writes) +SELECT OBJECT_NAME(i.[object_id]) AS [ObjectName], i.[name] AS [IndexName], i.index_id, + s.user_updates AS [Writes], s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], + i.[type_desc] AS [Index Type], i.fill_factor AS [Fill Factor], i.has_filter, i.filter_definition, + s.last_system_update, s.last_user_update +FROM sys.indexes AS i WITH (NOLOCK) +LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s WITH (NOLOCK) +ON i.[object_id] = s.[object_id] +AND i.index_id = s.index_id +AND s.database_id = DB_ID() +WHERE OBJECTPROPERTY(i.[object_id],'IsUserTable') = 1 +ORDER BY s.user_updates DESC OPTION (RECOMPILE); -- Order by writes +------ + +-- Show which indexes in the current database are most active for Writes + + + +-- Look at Columnstore index physical statistics (Query 61) (Columnstore Index Physical Stat) +SELECT OBJECT_NAME(ps.object_id) AS [TableName], + i.[name] AS [IndexName], ps.index_id, ps.partition_number, + ps.delta_store_hobt_id, ps.state_desc, ps.total_rows, ps.size_in_bytes, + ps.trim_reason_desc, ps.generation, ps.transition_to_compressed_state_desc, + ps.has_vertipaq_optimization, ps.deleted_rows, + 100 * (ISNULL(ps.deleted_rows, 0))/ps.total_rows AS [Fragmentation] +FROM sys.dm_db_column_store_row_group_physical_stats AS ps WITH (NOLOCK) +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON ps.object_id = i.object_id +AND ps.index_id = i.index_id +ORDER BY ps.object_id, ps.partition_number, ps.row_group_id OPTION (RECOMPILE); +------ + +-- sys.dm_db_column_store_row_group_physical_stats (Transact-SQL) +-- https://bit.ly/2q276XQ + + + +-- Get lock waits for current database (Query 62) (Lock Waits) +SELECT o.name AS [table_name], i.name AS [index_name], ios.index_id, ios.partition_number, + SUM(ios.row_lock_wait_count) AS [total_row_lock_waits], + SUM(ios.row_lock_wait_in_ms) AS [total_row_lock_wait_in_ms], + SUM(ios.page_lock_wait_count) AS [total_page_lock_waits], + SUM(ios.page_lock_wait_in_ms) AS [total_page_lock_wait_in_ms], + SUM(ios.page_lock_wait_in_ms)+ SUM(row_lock_wait_in_ms) AS [total_lock_wait_in_ms] +FROM sys.dm_db_index_operational_stats(DB_ID(), NULL, NULL, NULL) AS ios +INNER JOIN sys.objects AS o WITH (NOLOCK) +ON ios.[object_id] = o.[object_id] +INNER JOIN sys.indexes AS i WITH (NOLOCK) +ON ios.[object_id] = i.[object_id] +AND ios.index_id = i.index_id +WHERE o.[object_id] > 100 +GROUP BY o.name, i.name, ios.index_id, ios.partition_number +HAVING SUM(ios.page_lock_wait_in_ms)+ SUM(row_lock_wait_in_ms) > 0 +ORDER BY total_lock_wait_in_ms DESC OPTION (RECOMPILE); +------ + +-- This query is helpful for troubleshooting blocking and deadlocking issues + + + +-- Look at UDF execution statistics (Query 63) (UDF Statistics) +SELECT OBJECT_NAME(object_id) AS [Function Name], execution_count, + total_worker_time, total_logical_reads, total_physical_reads, total_elapsed_time, + total_elapsed_time/execution_count AS [avg_elapsed_time], + FORMAT(cached_time, 'yyyy-MM-dd HH:mm:ss', 'en-US') AS [Plan Cached Time] +FROM sys.dm_exec_function_stats WITH (NOLOCK) +WHERE database_id = DB_ID() +ORDER BY total_worker_time DESC OPTION (RECOMPILE); +------ + +-- New for SQL Server 2016 +-- Helps you investigate scalar UDF performance issues +-- Does not return information for table valued functions + +-- sys.dm_exec_function_stats (Transact-SQL) +-- https://bit.ly/2q1Q6BM + + +-- Determine which scalar UDFs are in-lineable (Query 64) (Inlineable UDFs) +SELECT OBJECT_NAME(m.object_id) AS [Function Name], m.is_inlineable, m.inline_type, m.definition +FROM sys.sql_modules AS m WITH (NOLOCK) +LEFT OUTER JOIN sys.dm_exec_function_stats AS efs WITH (NOLOCK) +ON m.object_id = efs.object_id +WHERE efs.type_desc = N'SQL_SCALAR_FUNCTION' +OPTION (RECOMPILE); +------ + +-- Scalar UDF Inlining +-- https://bit.ly/2JU971M + +-- sys.sql_modules (Transact-SQL) +-- https://bit.ly/2Qt216S + + +-- Get QueryStore Options for this database (Query 65) (QueryStore Options) +SELECT actual_state_desc, desired_state_desc, [interval_length_minutes], + current_storage_size_mb, [max_storage_size_mb], + query_capture_mode_desc, size_based_cleanup_mode_desc +FROM sys.database_query_store_options WITH (NOLOCK) OPTION (RECOMPILE); +------ + +-- New for SQL Server 2016 +-- Requires that Query Store is enabled for this database + +-- Make sure that the actual_state_desc is the same as desired_state_desc +-- Make sure that the current_storage_size_mb is less than the max_storage_size_mb + +-- Tuning Workload Performance with Query Store +-- https://bit.ly/1kHSl7w + + + +-- Get input buffer information for the current database (Query 66) (Input Buffer) +SELECT es.session_id, DB_NAME(es.database_id) AS [Database Name], + es.login_time, es.cpu_time, es.logical_reads, es.memory_usage, + es.[status], ib.event_info AS [Input Buffer] +FROM sys.dm_exec_sessions AS es WITH (NOLOCK) +CROSS APPLY sys.dm_exec_input_buffer(es.session_id, NULL) AS ib +WHERE es.database_id = DB_ID() +AND es.session_id > 50 +AND es.session_id <> @@SPID OPTION (RECOMPILE); +------ + +-- Gives you input buffer information from all non-system sessions for the current database +-- Replaces DBCC INPUTBUFFER + +-- New DMF for retrieving input buffer in SQL Server +-- https://bit.ly/2uHKMbz + +-- sys.dm_exec_input_buffer (Transact-SQL) +-- https://bit.ly/2J5Hf9q + + + +-- Get any resumable index rebuild operation information (Query 67) (Resumable Index Rebuild) +SELECT OBJECT_NAME(iro.object_id) AS [Object Name], iro.index_id, iro.name AS [Index Name], + iro.sql_text, iro.last_max_dop_used, iro.partition_number, iro.state_desc, iro.start_time, iro.percent_complete +FROM sys.index_resumable_operations AS iro WITH (NOLOCK) +OPTION (RECOMPILE); +------ + +-- index_resumable_operations (Transact-SQL) +-- https://bit.ly/2pYSWqq + + +-- Get database automatic tuning options (Query 68) (Automatic Tuning Options) +SELECT [name], desired_state_desc, actual_state_desc, reason_desc +FROM sys.database_automatic_tuning_options WITH (NOLOCK) +OPTION (RECOMPILE); +------ + +-- sys.database_automatic_tuning_options (Transact-SQL) +-- https://bit.ly/2FHhLkL + + + +-- Look at recent Full backups for the current database (Query 69) (Recent Full Backups) +SELECT TOP (30) bs.machine_name, bs.server_name, bs.database_name AS [Database Name], bs.recovery_model, +CONVERT (BIGINT, bs.backup_size / 1048576 ) AS [Uncompressed Backup Size (MB)], +CONVERT (BIGINT, bs.compressed_backup_size / 1048576 ) AS [Compressed Backup Size (MB)], +CONVERT (NUMERIC (20,2), (CONVERT (FLOAT, bs.backup_size) / +CONVERT (FLOAT, bs.compressed_backup_size))) AS [Compression Ratio], bs.has_backup_checksums, bs.is_copy_only, bs.encryptor_type, +DATEDIFF (SECOND, bs.backup_start_date, bs.backup_finish_date) AS [Backup Elapsed Time (sec)], +bs.backup_finish_date AS [Backup Finish Date], bmf.physical_device_name AS [Backup Location], bmf.physical_block_size +FROM msdb.dbo.backupset AS bs WITH (NOLOCK) +INNER JOIN msdb.dbo.backupmediafamily AS bmf WITH (NOLOCK) +ON bs.media_set_id = bmf.media_set_id +WHERE bs.database_name = DB_NAME(DB_ID()) +AND bs.[type] = 'D' +ORDER BY bs.backup_finish_date DESC OPTION (RECOMPILE); +------ + +-- Automatic database backups by the MI Service will not appear in this list + +-- Are your backup sizes and times changing over time? +-- Are you using backup compression? +-- Are you using backup checksums? +-- Are you doing copy_only backups? +-- Are you doing encrypted backups? +-- Have you done any backup tuning with striped backups, or changing the parameters of the backup command? + +-- In SQL Server 2016, native SQL Server backup compression actually works +-- much better with databases that are using TDE than in previous versions +-- https://bit.ly/28Rpb2x + + +-- These six Pluralsight Courses go into more detail about how to run these queries and interpret the results + +-- Azure SQL Database: Diagnosing Performance Issues with DMVs +-- https://bit.ly/2meDRCN + +-- SQL Server 2017: Diagnosing Performance Issues with DMVs +-- https://bit.ly/2FqCeti + +-- SQL Server 2017: Diagnosing Configuration Issues with DMVs +-- https://bit.ly/2MSUDUL + +-- SQL Server 2014 DMV Diagnostic Queries – Part 1 +-- https://bit.ly/2plxCer + +-- SQL Server 2014 DMV Diagnostic Queries – Part 2 +-- https://bit.ly/2IuJpzI + +-- SQL Server 2014 DMV Diagnostic Queries – Part 3 +-- https://bit.ly/2FIlCPb + + + +-- Microsoft Visual Studio Dev Essentials +-- https://bit.ly/2qjNRxi + +-- Microsoft Azure Learn +-- https://bit.ly/2O0Hacc + + diff --git a/SAM - Waits/WAITS - Collect Stats Reset Method.sql b/SAM - Waits/WAITS - Collect Stats Reset Method.sql new file mode 100644 index 0000000..80689ec --- /dev/null +++ b/SAM - Waits/WAITS - Collect Stats Reset Method.sql @@ -0,0 +1,29 @@ +/* + +https://learning.oreilly.com/library/view/pro-sql-server/9781484249161/html/340881_2_En_4_Chapter.xhtml + +Book: Pro SQL Server 2019 Wait Statistics + +*/ + + +USE [Baseline] +GO +-- Insert Wait Stats into Baseline table +INSERT INTO WaitStats +SELECT + GETDATE() AS 'DateTime', + DATEPART(DAY,GETDATE()) AS 'Day', + DATEPART(MONTH,GETDATE()) AS 'Month', + DATEPART(YEAR,GETDATE()) AS 'Year', + DATEPART(HOUR, GETDATE()) AS 'Hour', + DATEPART(MINUTE, GETDATE()) AS 'Minute', + DATENAME(DW, GETDATE()) AS 'DayOfWeek', + wait_type AS 'WaitType', + wait_time_ms AS 'WaitTime', + waiting_tasks_count AS 'WaitingTasks', + signal_wait_time_ms AS 'SignalWaitTime' +FROM sys.dm_os_wait_stats; +-- Clear sys.dm_os_wait_stats +DBCC SQLPERF ('sys.dm_os_wait_stats',CLEAR) +GO diff --git a/SAM - Waits/WAITS - Collect Waits Over Time.sql b/SAM - Waits/WAITS - Collect Waits Over Time.sql new file mode 100644 index 0000000..c4c45de --- /dev/null +++ b/SAM - Waits/WAITS - Collect Waits Over Time.sql @@ -0,0 +1,195 @@ +/* +https://michaeljswart.com/2021/01/collect-wait-stats-regularly-in-order-to-report-on-them-over-time/ + +*/ + + +IF NOT EXISTS ( + SELECT * + FROM sys.tables + WHERE object_id = object_id('dbo.WaitStats') +) +BEGIN + CREATE TABLE dbo.WaitStats + ( + WaitType NVARCHAR(60), + WaitingTasksCount BIGINT, + WaitTimeMs BIGINT, + MaxWaitTimeMs BIGINT, + SignalWaitTimeMs BIGINT, + CollectionDate DATETIME, + CONSTRAINT PK_WaitStats + PRIMARY KEY CLUSTERED (WaitType, CollectionDate) + ); +END +GO +CREATE OR ALTER VIEW dbo.ImportantWaits +AS + SELECT * + FROM dbo.WaitStats + WHERE WaitType NOT IN ( + N'BROKER_EVENTHANDLER', + N'BROKER_RECEIVE_WAITFOR', + N'BROKER_TASK_STOP', + N'BROKER_TO_FLUSH', + N'BROKER_TRANSMITTER', + N'CHECKPOINT_QUEUE', + N'CHKPT', + N'CLR_AUTO_EVENT', + N'CLR_MANUAL_EVENT', + N'CLR_SEMAPHORE', + N'CXCONSUMER', + N'DBMIRROR_DBM_EVENT', + N'DBMIRROR_EVENTS_QUEUE', + N'DBMIRROR_WORKER_QUEUE', + N'DBMIRRORING_CMD', + N'DIRTY_PAGE_POLL', + N'DISPATCHER_QUEUE_SEMAPHORE', + N'EXECSYNC', + N'FSAGENT', + N'FT_IFTS_SCHEDULER_IDLE_WAIT', + N'FT_IFTSHC_MUTEX', + N'HADR_CLUSAPI_CALL', + N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', + N'HADR_LOGCAPTURE_WAIT', + N'HADR_NOTIFICATION_DEQUEUE', + N'HADR_TIMER_TASK', + N'HADR_WORK_QUEUE', + N'KSOURCE_WAKEUP', + N'LAZYWRITER_SLEEP', + N'LOGMGR_QUEUE', + N'MEMORY_ALLOCATION_EXT', + N'ONDEMAND_TASK_QUEUE', + N'PARALLEL_REDO_DRAIN_WORKER', + N'PARALLEL_REDO_LOG_CACHE', + N'PARALLEL_REDO_TRAN_LIST', + N'PARALLEL_REDO_WORKER_SYNC', + N'PARALLEL_REDO_WORKER_WAIT_WORK', + N'PREEMPTIVE_OS_FLUSHFILEBUFFERS', + N'PREEMPTIVE_XE_GETTARGETSTATE', + N'PWAIT_ALL_COMPONENTS_INITIALIZED', + N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', + N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', + N'QDS_ASYNC_QUEUE', + N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', + N'QDS_SHUTDOWN_QUEUE', + N'REDO_THREAD_PENDING_WORK', + N'REQUEST_FOR_DEADLOCK_SEARCH', + N'RESOURCE_QUEUE', + N'SERVER_IDLE_CHECK', + N'SLEEP_BPOOL_FLUSH', + N'SLEEP_DBSTARTUP', + N'SLEEP_DCOMSTARTUP', + N'SLEEP_MASTERDBREADY', + N'SLEEP_MASTERMDREADY', + N'SLEEP_MASTERUPGRADED', + N'SLEEP_MSDBSTARTUP', + N'SLEEP_SYSTEMTASK', + N'SLEEP_TASK', + N'SLEEP_TEMPDBSTARTUP', + N'SNI_HTTP_ACCEPT', + N'SOS_WORK_DISPATCHER', + N'SP_SERVER_DIAGNOSTICS_SLEEP', + N'SQLTRACE_BUFFER_FLUSH', + N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', + N'SQLTRACE_WAIT_ENTRIES', + N'VDI_CLIENT_OTHER', + N'WAIT_FOR_RESULTS', + N'WAITFOR', + N'WAITFOR_TASKSHUTDOWN', + N'WAIT_XTP_RECOVERY', + N'WAIT_XTP_HOST_WAIT', + N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', + N'WAIT_XTP_CKPT_CLOSE', + N'XE_DISPATCHER_JOIN', + N'XE_DISPATCHER_WAIT', + N'XE_TIMER_EVENT'); +GO +CREATE OR ALTER PROCEDURE dbo.s_CollectWaitStats ( + @WaitThresholdInMs BIGINT +) +AS + INSERT dbo.WaitStats( + WaitType, + WaitingTasksCount, + WaitTimeMs, + MaxWaitTimeMs, + SignalWaitTimeMs, + CollectionDate + ) + SELECT + wait_type, + waiting_tasks_count, + wait_time_ms, + max_wait_time_ms, + signal_wait_time_ms, + GETUTCDATE() + FROM sys.dm_os_wait_stats + WHERE wait_time_ms > @WaitThresholdInMs; + + -- Clean up + DELETE dbo.WaitStats + WHERE CollectionDate < DATEADD( DAY, -7, GETUTCDATE() ); +GO +CREATE OR ALTER PROCEDURE dbo.s_WaitStatsHistogram + @HistogramBucketSizeInMinutes INT = 60 +AS + +WITH WaitStatsHistogram AS +( + SELECT W.WaitType, + C.GroupedCollectionDate AS CollectionDate, + MAX(WaitTimeMs) AS WaitTimeMs + FROM ImportantWaits W + CROSS APPLY ( SELECT DATEADD( + MINUTE, + (DATEDIFF(MINUTE, 0, W.CollectionDate ) / @HistogramBucketSizeInMinutes) * @HistogramBucketSizeInMinutes, + 0) ) AS C(GroupedCollectionDate) + GROUP BY W.WaitType, C.GroupedCollectionDate +) +SELECT WaitType, + CollectionDate, + WaitTimeMs - + LAG(WaitTimeMs, 1, NULL) OVER ( + PARTITION BY (WaitType) + ORDER BY (CollectionDate)) as WaitTimeMs +FROM WaitStatsHistogram; +GO +DECLARE @DBName sysname = DB_NAME(); +DECLARE @JobName sysname = 'CollectWaitStats'; +DECLARE @JobDesc sysname + = ' Source: ' + 'https://github.com/mjswart/CollectWaitStats. ;' + + ' Created: '+ CAST(GETDATE() AS VARCHAR(20)) + ';' + + ' By: '+ SUSER_NAME() + ';'; + +IF NOT EXISTS +( + SELECT * +FROM msdb.dbo.sysjobs +WHERE name = @JobName +) +BEGIN + EXEC msdb.dbo.sp_add_job + @job_name = @JobName, + @description = @JobDesc; + + EXEC msdb.dbo.sp_add_jobstep + @job_name = @JobName, + @step_name = N'Collect Wait Stats', + @command = N'exec s_CollectWaitStats @WaitThresholdInMs = 1000;', + @database_name = @DBName; + + EXEC msdb.dbo.sp_add_jobserver + @job_name = @JobName, + @server_name = N'(local)'; + + EXEC msdb.dbo.sp_add_jobschedule + @job_name = @JobName, + @name=N'Minutely', + @enabled=1, + @freq_type=4, + @freq_interval=1, + @freq_subday_type=4, + @freq_subday_interval=1; +END +GO diff --git a/SAM - Waits/Waits - Collect waits for 24 hours - Paul Randal.sql b/SAM - Waits/Waits - Collect waits for 24 hours - Paul Randal.sql new file mode 100644 index 0000000..96daeec --- /dev/null +++ b/SAM - Waits/Waits - Collect waits for 24 hours - Paul Randal.sql @@ -0,0 +1,147 @@ +/*============================================================================ + + https://www.sqlskills.com/blogs/paul/send-wait-stats-get-advice-30-days-free-pluralsight-return/ + + File: WaitStats2014.sql + + Summary: 24-hour snapshot of wait stats + + SQL Server Versions: 2005 onwards +------------------------------------------------------------------------------ + Written by Paul S. Randal, SQLskills.com + + (c) 2014, SQLskills.com. All rights reserved. + + For more scripts and sample code, check out + http://www.SQLskills.com + + You may alter this code for your own *non-commercial* purposes. You may + republish altered code as long as you include this copyright and give due + credit, but you must obtain prior permission before blogging this code. + + THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF + ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED + TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A + PARTICULAR PURPOSE. +============================================================================*/ + +IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] + WHERE [name] = N'##SQLskillsStats1') + DROP TABLE [##SQLskillsStats1]; + +IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] + WHERE [name] = N'##SQLskillsStats2') + DROP TABLE [##SQLskillsStats2]; +GO + +SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], + [max_wait_time_ms], [signal_wait_time_ms] +INTO ##SQLskillsStats1 +FROM sys.dm_os_wait_stats; +GO + +WAITFOR DELAY '23:59:59'; +GO + +SELECT [wait_type], [waiting_tasks_count], [wait_time_ms], + [max_wait_time_ms], [signal_wait_time_ms] +INTO ##SQLskillsStats2 +FROM sys.dm_os_wait_stats; +GO + +WITH [DiffWaits] AS +(SELECT +-- Waits that weren't in the first snapshot + [ts2].[wait_type], + [ts2].[wait_time_ms], + [ts2].[signal_wait_time_ms], + [ts2].[waiting_tasks_count] + FROM [##SQLskillsStats2] AS [ts2] + LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1] + ON [ts2].[wait_type] = [ts1].[wait_type] + WHERE [ts1].[wait_type] IS NULL + AND [ts2].[wait_time_ms] > 0 +UNION +SELECT +-- Diff of waits in both snapshots + [ts2].[wait_type], + [ts2].[wait_time_ms] - [ts1].[wait_time_ms] AS [wait_time_ms], + [ts2].[signal_wait_time_ms] - [ts1].[signal_wait_time_ms] AS [signal_wait_time_ms], + [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] AS [waiting_tasks_count] + FROM [##SQLskillsStats2] AS [ts2] + LEFT OUTER JOIN [##SQLskillsStats1] AS [ts1] + ON [ts2].[wait_type] = [ts1].[wait_type] + WHERE [ts1].[wait_type] IS NOT NULL + AND [ts2].[waiting_tasks_count] - [ts1].[waiting_tasks_count] > 0 + AND [ts2].[wait_time_ms] - [ts1].[wait_time_ms] > 0), +[Waits] AS + (SELECT + [wait_type], + [wait_time_ms] / 1000.0 AS [WaitS], + ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS], + [signal_wait_time_ms] / 1000.0 AS [SignalS], + [waiting_tasks_count] AS [WaitCount], + 100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage], + ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum] + FROM [DiffWaits] + WHERE [wait_type] NOT IN ( + N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', + N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH', + N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', + N'CHKPT', N'CLR_AUTO_EVENT', + N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE', + N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', + N'DBMIRROR_WORKER_QUEUE', N'DBMIRRORING_CMD', + N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', + N'EXECSYNC', N'FSAGENT', + N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', + N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', + N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE', + N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', + N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', + N'LOGMGR_QUEUE', N'ONDEMAND_TASK_QUEUE', + N'PWAIT_ALL_COMPONENTS_INITIALIZED', + N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', + N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', + N'REQUEST_FOR_DEADLOCK_SEARCH', N'RESOURCE_QUEUE', + N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', + N'SLEEP_DBSTARTUP', N'SLEEP_DCOMSTARTUP', + N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', + N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', + N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', + N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', + N'SP_SERVER_DIAGNOSTICS_SLEEP', N'SQLTRACE_BUFFER_FLUSH', + N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', + N'SQLTRACE_WAIT_ENTRIES', N'WAIT_FOR_RESULTS', + N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', + N'WAIT_XTP_HOST_WAIT', N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', + N'WAIT_XTP_CKPT_CLOSE', N'XE_DISPATCHER_JOIN', + N'XE_DISPATCHER_WAIT', N'XE_TIMER_EVENT') + ) +SELECT + [W1].[wait_type] AS [WaitType], + CAST ([W1].[WaitS] AS DECIMAL (16, 2)) AS [Wait_S], + CAST ([W1].[ResourceS] AS DECIMAL (16, 2)) AS [Resource_S], + CAST ([W1].[SignalS] AS DECIMAL (16, 2)) AS [Signal_S], + [W1].[WaitCount] AS [WaitCount], + CAST ([W1].[Percentage] AS DECIMAL (5, 2)) AS [Percentage], + CAST (([W1].[WaitS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgWait_S], + CAST (([W1].[ResourceS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgRes_S], + CAST (([W1].[SignalS] / [W1].[WaitCount]) AS DECIMAL (16, 4)) AS [AvgSig_S] +FROM [Waits] AS [W1] +INNER JOIN [Waits] AS [W2] + ON [W2].[RowNum] <= [W1].[RowNum] +GROUP BY [W1].[RowNum], [W1].[wait_type], [W1].[WaitS], + [W1].[ResourceS], [W1].[SignalS], [W1].[WaitCount], [W1].[Percentage] +HAVING SUM ([W2].[Percentage]) - [W1].[Percentage] < 95; -- percentage threshold +GO + +-- Cleanup +IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] + WHERE [name] = N'##SQLskillsStats1') + DROP TABLE [##SQLskillsStats1]; + +IF EXISTS (SELECT * FROM [tempdb].[sys].[objects] + WHERE [name] = N'##SQLskillsStats2') + DROP TABLE [##SQLskillsStats2]; +GO diff --git a/SAM - Waits/wait-stats-30s-azure.sql b/SAM - Waits/wait-stats-30s-azure.sql new file mode 100644 index 0000000..917befc --- /dev/null +++ b/SAM - Waits/wait-stats-30s-azure.sql @@ -0,0 +1,213 @@ +/* +SQL Server Wait Information from sys.dm_os_wait_stats +Copyright (C) 2013, Brent Ozar Unlimited. +See http://BrentOzar.com/go/eula for the End User Licensing Agreement. + + +October 2020 - Ignorable wait stats amended, Samuel Yanzu +*/ + +/********************************* +What are the highest overall waits since startup? +*********************************/ +SELECT TOP 25 + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) as sum_wait_time_ms, + CAST( + 100.* SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) + / (1. * SUM(os.wait_time_ms) OVER () ) + AS NUMERIC(10,1)) as pct_wait_time, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks, + CASE WHEN SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) > 0 + THEN + CAST( + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) + / (1. * SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type)) + AS NUMERIC(10,1)) + ELSE 0 END AS avg_wait_time_ms, + CURRENT_TIMESTAMP as sample_time +FROM sys.dm_os_wait_stats os + WHERE [wait_type] NOT IN ( + N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', + N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', + N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE', + N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE', + N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', + N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', + N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', + N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', + N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE', + N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE', + N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS', + N'PREEMPTIVE_ODBCOPS', + N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS', + N'PREEMPTIVE_OS_PIPEOPS', N'PREEMPTIVE_OS_AUTHENTICATIONOPS', + N'PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST', + N'PREEMPTIVE_OS_FILEOPS', N'PREEMPTIVE_OS_DEVICEOPS', N'PREEMPTIVE_OS_QUERYREGISTRY', + N'PREEMPTIVE_OS_WRITEFILE', + N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER', + N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT', + N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE', + N'PREEMPTIVE_XHTTP', + N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', + N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', + N'QDS_ASYNC_QUEUE', + N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH', + N'RESOURCE_GOVERNOR_IDLE', + N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP', + N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', + N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', + N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP', + N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES', + N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT', + N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'WAIT_XTP_RECOVERY', + N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN', + N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT') +ORDER BY sum_wait_time_ms DESC; +GO + +/********************************* +What are the higest waits *right now*? +*********************************/ + +/* Note: this is dependent on the #ignorable_waits table created earlier. */ +if OBJECT_ID('tempdb..#wait_batches') is not null + drop table #wait_batches; +if OBJECT_ID('tempdb..#wait_data') is not null + drop table #wait_data; +GO + +CREATE TABLE #wait_batches ( + batch_id int identity primary key, + sample_time datetime not null +); +CREATE TABLE #wait_data + ( batch_id INT NOT NULL , + wait_type NVARCHAR(256) NOT NULL , + wait_time_ms BIGINT NOT NULL , + waiting_tasks BIGINT NOT NULL + ); +CREATE CLUSTERED INDEX cx_wait_data on #wait_data(batch_id); +GO + +/* +This temporary procedure records wait data to a temp table. +*/ +if OBJECT_ID('tempdb..#get_wait_data') IS NOT NULL + DROP procedure #get_wait_data; +GO +CREATE PROCEDURE #get_wait_data + @intervals tinyint = 2, + @delay char(12)='00:00:30.000' /* 30 seconds*/ +AS +DECLARE @batch_id int, + @current_interval tinyint, + @msg nvarchar(max); + +SET NOCOUNT ON; +SET @current_interval=1; +print 'in stored proc' +WHILE @current_interval <= @intervals +BEGIN + INSERT #wait_batches(sample_time) + SELECT CURRENT_TIMESTAMP; + + SELECT @batch_id=SCOPE_IDENTITY(); + + INSERT #wait_data (batch_id, wait_type, wait_time_ms, waiting_tasks) + SELECT + @batch_id, + os.wait_type, + SUM(os.wait_time_ms) OVER (PARTITION BY os.wait_type) as sum_wait_time_ms, + SUM(os.waiting_tasks_count) OVER (PARTITION BY os.wait_type) AS sum_waiting_tasks + FROM sys.dm_os_wait_stats os + WHERE [wait_type] NOT IN ( + N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP', + N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE', + N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE', + N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE', N'DBMIRROR_WORKER_QUEUE', + N'DBMIRRORING_CMD', N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE', + N'EXECSYNC', N'FSAGENT', N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX', + N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', N'HADR_LOGCAPTURE_WAIT', + N'HADR_NOTIFICATION_DEQUEUE', N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE', + N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP', N'LOGMGR_QUEUE', + N'MEMORY_ALLOCATION_EXT', N'ONDEMAND_TASK_QUEUE', + N'PREEMPTIVE_HADR_LEASE_MECHANISM', N'PREEMPTIVE_SP_SERVER_DIAGNOSTICS', + N'PREEMPTIVE_ODBCOPS', + N'PREEMPTIVE_OS_LIBRARYOPS', N'PREEMPTIVE_OS_COMOPS', N'PREEMPTIVE_OS_CRYPTOPS', + N'PREEMPTIVE_OS_PIPEOPS', N'PREEMPTIVE_OS_AUTHENTICATIONOPS', + N'PREEMPTIVE_OS_GENERICOPS', N'PREEMPTIVE_OS_VERIFYTRUST', + N'PREEMPTIVE_OS_FILEOPS', N'PREEMPTIVE_OS_DEVICEOPS', N'PREEMPTIVE_OS_QUERYREGISTRY', + N'PREEMPTIVE_OS_WRITEFILE', + N'PREEMPTIVE_XE_CALLBACKEXECUTE', N'PREEMPTIVE_XE_DISPATCHER', + N'PREEMPTIVE_XE_GETTARGETSTATE', N'PREEMPTIVE_XE_SESSIONCOMMIT', + N'PREEMPTIVE_XE_TARGETINIT', N'PREEMPTIVE_XE_TARGETFINALIZE', + N'PREEMPTIVE_XHTTP', + N'PWAIT_ALL_COMPONENTS_INITIALIZED', N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', + N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', + N'QDS_ASYNC_QUEUE', + N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP', N'REQUEST_FOR_DEADLOCK_SEARCH', + N'RESOURCE_GOVERNOR_IDLE', + N'RESOURCE_QUEUE', N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH', N'SLEEP_DBSTARTUP', + N'SLEEP_DCOMSTARTUP', N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY', + N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP', N'SLEEP_SYSTEMTASK', N'SLEEP_TASK', + N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT', N'SP_SERVER_DIAGNOSTICS_SLEEP', + N'SQLTRACE_BUFFER_FLUSH', N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', N'SQLTRACE_WAIT_ENTRIES', + N'WAIT_FOR_RESULTS', N'WAITFOR', N'WAITFOR_TASKSHUTDOWN', N'WAIT_XTP_HOST_WAIT', + N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', N'WAIT_XTP_CKPT_CLOSE', N'WAIT_XTP_RECOVERY', + N'XE_BUFFERMGR_ALLPROCESSED_EVENT', N'XE_DISPATCHER_JOIN', + N'XE_DISPATCHER_WAIT', N'XE_LIVE_TARGET_TVF', N'XE_TIMER_EVENT') + ORDER BY sum_wait_time_ms DESC; + + set @msg= CONVERT(char(23),CURRENT_TIMESTAMP,121)+ N': Completed sample ' + + cast(@current_interval as nvarchar(4)) + + N' of ' + cast(@intervals as nvarchar(4)) + + '.' + RAISERROR (@msg,0,1) WITH NOWAIT; + + --select * from #wait_data; + + if @current_interval < @intervals + WAITFOR DELAY @delay; + + SET @current_interval=@current_interval+1; +END +GO + +/* +Let's take two samples 30 seconds apart +*/ +exec #get_wait_data @intervals=2, @delay='00:00:30.000'; +GO + +/* +What were we waiting on? +This query compares the most recent two samples. +*/ +with max_batch as ( + select top 1 batch_id, sample_time + from #wait_batches + order by batch_id desc +) +SELECT + b.sample_time as [Second Sample Time], + datediff(ss,wb1.sample_time, b.sample_time) as [Sample Duration in Seconds], + wd1.wait_type, + cast((wd2.wait_time_ms-wd1.wait_time_ms)/1000. as numeric(10,1)) as [Wait Time (Seconds)], + (wd2.waiting_tasks-wd1.waiting_tasks) AS [Number of Waits], + CASE WHEN (wd2.waiting_tasks-wd1.waiting_tasks) > 0 + THEN + cast((wd2.wait_time_ms-wd1.wait_time_ms)/ + (1.0*(wd2.waiting_tasks-wd1.waiting_tasks)) as numeric(10,1)) + ELSE 0 END AS [Avg ms Per Wait] +FROM max_batch b +JOIN #wait_data wd2 on + wd2.batch_id=b.batch_id +JOIN #wait_data wd1 on + wd1.wait_type=wd2.wait_type AND + wd2.batch_id - 1 = wd1.batch_id +join #wait_batches wb1 on + wd1.batch_id=wb1.batch_id +WHERE (wd2.waiting_tasks-wd1.waiting_tasks) > 0 +ORDER BY [Wait Time (Seconds)] DESC; +GO