Skip to content

MattSQL/DarlingData

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Repository files navigation

Darling Data Logo

Darling Data: SQL Server Troubleshooting Scripts

licence badge

Navigatory

Who are these scripts for?

You need to troubleshoot performance problems with SQL Server, and you need to do it now.

You don't have time to track down a bunch of DMVs, figure out Extended Events, wrestle with terrible SSMS interfaces, or learn XML.

These scripts aren't a replacement for a mature monitoring tool, but they do a good job of capturing important issues and reporting on existing diagnostic data

Support

Right now, all support and Q&A is handled on GitHub. Please be patient; it's just me over here answering questions, fixing bugs, and adding new features.

As far as compatibility goes, they're only guaranted to work on Microsoft-supported SQL Server versions.

Older versions are either missing too much information, or simply aren't compatible (Hello, Extended Events. Hello, Query Store) with the intent of the script.

If you have questions about performance tuning, or SQL Server in general, you'll wanna hit a Q&A site:

Back to top

Pressure Detector

Is your client/server relationship on the rocks? Are queries timing out, dragging along, or causing CPU fans to spin out of control?

All you need to do is hit F5 to get information about:

  • Wait stats since startup
  • Database file size, stall, and activity
  • tempdb configuration details
  • Memory consumers
  • Low memory indicators
  • Memory configuration and allocation
  • Current query memory grants, along with other execution details
  • CPU configuration and retained utilization details
  • Thread count and current usage
  • Any current THREADPOOL waits (best observed with the DAC)
  • Currently executing queries, along with other execution details

For a video walkthrough of the script and the results, head over here.

Current valid parameter details:

parameter_namedata_typedescriptionvalid_inputsdefaults
@what_to_checkvarcharareas to check for pressure"all", "cpu", and "memory"all
@skip_queriesbitif you want to skip looking at running queries0 or 10
@skip_plan_xmlbitif you want to skip getting plan XML0 or 10
@minimum_disk_latency_mssmallintlow bound for reporting disk latencya reasonable number of milliseconds for disk latency100
@cpu_utilization_thresholdsmallintlow bound for reporting high cpu utlizationa reasonable cpu utlization percentage50
@skip_waitsbitskips waits when you do not need them on every run0 or 10
@skip_perfmonbitskips perfmon counters when you do not need them on every runa valid tinyint: 0-2550
@sample_secondstinyinttake a sample of your server's metrics0 or 10
@log_to_tablebitenable logging to permanent tables0 or 10
@log_database_namesysnamedatabase to store logging tablesvalid database nameNULL
@log_schema_namesysnameschema to store logging tablesvalid schema nameNULL
@log_table_name_prefixsysnameprefix for all logging tablesvalid table name prefix'PressureDetector'
@log_retention_daysintegerNumber of days to keep logs, 0 = keep indefinitelyinteger30
@helpbithow you got here0 or 10
@debugbitprints dynamic sql, displays parameter and variable values, and table contents0 or 10
@versionvarcharOUTPUT; for supportnonenone; OUTPUT
@version_datedatetimeOUTPUT; for supportnonenone; OUTPUT

Back to top

Human Events

Extended Events are hard. You don't know which ones to use, when to use them, or how to get useful information out of them.

This procedure is designed to make them easier for you, by creating event sessions to help you troubleshoot common scenarios:

  • Blocking: blocked process report
  • Query performance: query execution metrics an actual execution plans
  • Compiles: catch query compilations
  • Recompiles: catch query recompilations
  • Wait Stats: server wait stats, broken down by query and database

The default behavior is to run a session for a set period of time to capture information, but you can also set sessions up to data to permanent tables.

For execution examples, see here: Examples

If you set up sessions to capture long term data, you'll need an agent job set up to poll them. You can find an example of that here: Examples

Misuse of this procedure can harm performance. Be very careful about introducing observer overhead, especially when gathering query plans. Be even more careful when setting up permanent sessions!

More resources:

Current valid parameter details:

parameternamedescriptionvalid_inputsdefaults
@event_typesysnameused to pick which session you want to run"blocking", "query", "waits", "recompiles", "compiles" and certain variations on those words"query"
@query_duration_msinteger(>=) used to set a minimum query duration to collect data foran integer500 (ms)
@query_sort_ordernvarcharwhen you use the "query" event, lets you choose which metrics to sort results by"cpu", "reads", "writes", "duration", "memory", "spills", and you can add "avg" to sort by averages, e.g. "avg cpu""cpu"
@skip_plansbitwhen you use the "query" event, lets you skip collecting actual execution plans1 or 00
@blocking_duration_msinteger(>=) used to set a minimum blocking duration to collect data foran integer500 (ms)
@wait_typenvarchar(inclusive) filter to only specific wait typesa single wait type, or a CSV list of wait types"all", which uses a list of "interesting" waits
@wait_duration_msinteger(>=) used to set a minimum time per wait to collect data foran integer10 (ms)
@client_app_namesysname(inclusive) filter to only specific app namesa stringy thingintentionally left blank
@client_hostnamesysname(inclusive) filter to only specific host namesa stringy thingintentionally left blank
@database_namesysname(inclusive) filter to only specific databasesa stringy thingintentionally left blank
@session_idnvarchar(inclusive) filter to only a specific session id, or a sample of session idsan integer, or "sample" to sample a workloadintentionally left blank
@sample_divisorintegerthe divisor for session ids when sampling a workload, e.g. SPID % 5an integer5
@usernamesysname(inclusive) filter to only a specific usera stringy thingintentionally left blank
@object_namesysname(inclusive) to only filter to a specific object namea stringy thingintentionally left blank
@object_schemasysname(inclusive) the schema of the object you want to filter to; only needed with blocking eventsa stringy thingdbo
@requested_memory_mbinteger(>=) the memory grant a query must ask for to have data collectedan integer0
@seconds_sampletinyintthe duration in seconds to run the event session foran integer10
@gimme_dangerbitused to override default minimums for query, wait, and blocking durations.1 or 00
@keep_alivebitcreates a permanent session, either to watch live or log to a table from1 or 00
@custom_namenvarcharif you want to custom name a permanent sessiona stringy thingintentionally left blank
@output_database_namesysnamethe database you want to log data toa valid database nameintentionally left blank
@output_schema_namesysnamethe schema you want to log data toa valid schemadbo
@delete_retention_daysintegerhow many days of logged data you want to keepa POSITIVE integer3 (days)
@cleanupbitdeletes all sessions, tables, and views. requires output database and schema.1 or 00
@max_memory_kbbigintset a max ring buffer size to log data toan integer102400
@versionvarcharto make sure you have the most recent bitsnone, outputnone, output
@version_datedatetimeto make sure you have the most recent bitsnone, outputnone, output
@debugbituse to print out dynamic SQL1 or 00
@helpbitwell you're here so you figured this one out1 or 00

Back to top

Human Events Block Viewer

This was originally a companion script to analyze the blocked process report Extended Event created by sp_HumanEvents, but has since turned into its own monster.

It will work on any Extended Event that captures the blocked process report. If you need to set that up, run these two pieces of code.

Enable the blocked process report:

EXEC sys.sp_configure N'show advanced options', 1; RECONFIGURE; GO EXEC sys.sp_configure N'blocked process threshold', 5; --Seconds RECONFIGURE; GO 

Set up the Extended Event:

CREATE EVENT SESSION blocked_process_report ON SERVER ADD EVENT sqlserver.blocked_process_report ADD TARGET package0.event_file ( SET filename = N'bpr' ) WITH ( MAX_MEMORY = 4096KB, EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY = 5 SECONDS, MAX_EVENT_SIZE = 0KB, MEMORY_PARTITION_MODE = NONE, TRACK_CAUSALITY = OFF, STARTUP_STATE = ON ); ALTER EVENT SESSION blocked_process_report ON SERVER STATE = START; 

Once it has data collected, you can analyze it using this command:

EXECUTE dbo.sp_HumanEventsBlockViewer @session_name = N'blocked_process_report'; 

Current valid parameter details:

parameter_namedata_typedescriptionvalid_inputsdefaults
@session_namesysnamename of the extended event session to pull fromextended event session name capturing sqlserver.blocked_process_reportkeeper_HumanEvents_blocking
@target_typesysnametarget of the extended event sessionevent_file or ring_bufferNULL
@start_datedatetime2filter by datea reasonable dateNULL; will shortcut to last 7 days
@end_datedatetime2filter by datea reasonable dateNULL
@database_namesysnamefilter by database namea database that exists on this serverNULL
@object_namesysnamefilter by table namea schema-prefixed table nameNULL
@target_databasesysnamedatabase containing the table with BPR dataa valid database nameNULL
@target_schemasysnameschema of the tablea valid schema nameNULL
@target_tablesysnametable namea valid table nameNULL
@target_columnsysnamecolumn containing XML dataa valid column nameNULL
@timestamp_columnsysnamecolumn containing timestamp (optional)a valid column nameNULL
@log_to_tablebitenable logging to permanent tables0 or 10
@log_database_namesysnamedatabase to store logging tablesa valid database nameNULL
@log_schema_namesysnameschema to store logging tablesa valid schema nameNULL
@log_table_name_prefixsysnameprefix for all logging tablesa valid table name prefix'HumanEventsBlockViewer'
@log_retention_daysintegerNumber of days to keep logs, 0 = keep indefinitelya valid integer30
@helpbithow you got here0 or 10
@debugbitdumps raw temp table contents0 or 10
@versionvarcharOUTPUT; for supportnone; OUTPUTnone; OUTPUT
@version_datedatetimeOUTPUT; for supportnone; OUTPUTnone; OUTPUT

Back to top

Quickie Store

This procedure will dig into Query Store data for a specific database, or all databases with Query Store enabled.

It's designed to run as quickly as possible, but there are some circumstances that prevent me from realizing my ultimate dream.

The big upside of using this stored procedure over the GUI is that you can search for specific items in Query Store, by:

  • query_id
  • plan_id
  • query hash
  • sql handle
  • module name
  • query text
  • query type (ad hoc or from a module)

You can also choose to filter out specific queries by those, too.

And you can do all that without worrying about incorrect data from the GUI, which doesn't handle UTC conversion correctly when filtering data.

By default, it will return the top 10 queries by average CPU. You can configure all sorts of things to look at queries by other metrics, or just specific queries.

Use the @expert_mode parameter to return additional details.

More examples can be found here: Examples

More resources:

Current valid parameter details:

parameter_namedata_typedescriptionvalid_inputsdefaults
@database_namesysnamethe name of the database you want to look at query store ina database name with query store enabledNULL; current database name if NULL
@sort_ordervarcharthe runtime metric you want to prioritize results bycpu, logical reads, physical reads, writes, duration, memory, tempdb, executions, recent, plan count by hashes, cpu waits, lock waits, locks waits, latch waits, latches waits, buffer latch waits, buffer latches waits, buffer io waits, log waits, log io waits, network waits, network io waits, parallel waits, parallelism waits, memory waits, total waits, rowscpu
@topbigintthe number of queries you want to pull backa positive integer between 1 and 9,223,372,036,854,775,80710
@start_datedatetimeoffsetthe begin date of your search, will be converted to UTC internallyJanuary 1, 1753, through December 31, 9999the last seven days
@end_datedatetimeoffsetthe end date of your search, will be converted to UTC internallyJanuary 1, 1753, through December 31, 9999NULL
@timezonesysnameuser specified time zone to override dates displayed in resultsSELECT tzi.* FROM sys.time_zone_info AS tzi;NULL
@execution_countbigintthe minimum number of executions a query must havea positive integer between 1 and 9,223,372,036,854,775,807NULL
@duration_msbigintthe minimum duration a query must have to show up in resultsa positive integer between 1 and 9,223,372,036,854,775,807NULL
@execution_type_descnvarcharthe type of execution you want to filter by (regular, aborted, exception)regular, aborted, exceptionNULL
@procedure_schemasysnamethe schema of the procedure you're searching fora valid schema in your databaseNULL; dbo if NULL and procedure name is not NULL
@procedure_namesysnamethe name of the programmable object you're searching fora valid programmable object in your database, can use wildcardsNULL
@include_plan_idsnvarchara list of plan ids to search fora string; comma separated for multiple idsNULL
@include_query_idsnvarchara list of query ids to search fora string; comma separated for multiple idsNULL
@include_query_hashesnvarchara list of query hashes to search fora string; comma separated for multiple hashesNULL
@include_plan_hashesnvarchara list of query plan hashes to search fora string; comma separated for multiple hashesNULL
@include_sql_handlesnvarchara list of sql handles to search fora string; comma separated for multiple handlesNULL
@ignore_plan_idsnvarchara list of plan ids to ignorea string; comma separated for multiple idsNULL
@ignore_query_idsnvarchara list of query ids to ignorea string; comma separated for multiple idsNULL
@ignore_query_hashesnvarchara list of query hashes to ignorea string; comma separated for multiple hashesNULL
@ignore_plan_hashesnvarchara list of query plan hashes to ignorea string; comma separated for multiple hashesNULL
@ignore_sql_handlesnvarchara list of sql handles to ignorea string; comma separated for multiple handlesNULL
@query_text_searchnvarcharquery text to search fora string; leading and trailing wildcards will be added if missingNULL
@query_text_search_notnvarcharquery text to excludea string; leading and trailing wildcards will be added if missingNULL
@escape_bracketsbitSet this bit to 1 to search for query text containing square brackets (common in .NET Entity Framework and other ORM queries)0 or 10
@escape_characterncharSets the ESCAPE character for special character searches, defaults to the SQL standard backslash () charactersome escape character, SQL standard is backslash ()\
@only_queries_with_hintsbitonly return queries with query hints0 or 10
@only_queries_with_feedbackbitonly return queries with query feedback0 or 10
@only_queries_with_variantsbitonly return queries with query variants0 or 10
@only_queries_with_forced_plansbitonly return queries with forced plans0 or 10
@only_queries_with_forced_plan_failuresbitonly return queries with forced plan failures0 or 10
@wait_filtervarcharwait category to search for; category details are belowcpu, lock, latch, buffer latch, buffer io, log io, network io, parallelism, memoryNULL
@query_typevarcharfilter for only ad hoc queries or only from queries from modulesad hoc, adhoc, proc, procedure, whatever.NULL
@expert_modebitreturns additional columns and results0 or 10
@hide_help_tablebithides the "bottom table" that shows help and support information0 or 10
@format_outputbitreturns numbers formatted with commas0 or 11
@get_all_databasesbitlooks for query store enabled user databases and returns combined results from all of them0 or 10
@include_databasesnvarchar(4000)comma-separated list of databases to include (only when @get_all_databases = 1)a string; comma separated database namesNULL
@exclude_databasesnvarchar(4000)comma-separated list of databases to exclude (only when @get_all_databases = 1)a string; comma separated database namesNULL
@workdaysbituse this to filter out weekends and after-hours queries0 or 10
@work_starttimeuse this to set a specific start of your work daysa time like 8am, 9am or something9am
@work_endtimeuse this to set a specific end of your work daysa time like 5pm, 6pm or something5pm
@regression_baseline_start_datedatetimeoffsetthe begin date of the baseline that you are checking for regressions against (if any), will be converted to UTC internallyJanuary 1, 1753, through December 31, 9999NULL
@regression_baseline_end_datedatetimeoffsetthe end date of the baseline that you are checking for regressions against (if any), will be converted to UTC internallyJanuary 1, 1753, through December 31, 9999NULL; One week after @regression_baseline_start_date if that is specified
@regression_comparatorvarcharwhat difference to use ('relative' or 'absolute') when comparing @sort_order's metric for the normal time period with any regression time period.relative, absoluteNULL; absolute if @regression_baseline_start_date is specified
@regression_directionvarcharwhen comparing against any regression baseline, what do you want the results sorted by ('magnitude', 'improved', or 'regressed')?regressed, worse, improved, better, magnitude, absolute, whateverNULL; regressed if @regression_baseline_start_date is specified
@include_query_hash_totalsbitwill add an additional column to final output with total resource usage by query hash0 or 10
@helpbithow you got here0 or 10
@debugbitprints dynamic sql, statement length, parameter and variable values, and raw temp table contents0 or 10
@troubleshoot_performancebitset statistics xml on for queries against views0 or 10
@versionvarcharOUTPUT; for supportnone; OUTPUTnone; OUTPUT
@version_datedatetimeOUTPUT; for supportnone; OUTPUTnone; OUTPUT

Back to top

Health Parser

The system health extended event has been around for a while, hiding in the shadows, and collecting all sorts of crazy information about your SQL Server.

The problem is, hardly anyone ever looks at it, and when they do, they realize how awful the Extended Events GUI is. Or that if they want to dig deeper into anything, they're going to have to parse XML.

This stored procedure takes all that pain away.

Note that it focuses on performance data, and does not output errors or security details, or any of the other non-performance related data.

Typical result set will show you

  • Queries with significant waits
  • Waits by count
  • Waits by duration
  • Potential I/O issues
  • CPU task details
  • Memory conditions
  • Overall system health
  • A limited version of the blocked process report
  • XML deadlock report
  • Query plans for queries involved in blocking and deadlocks (when available)

More resources:

Current valid parameter details:

parameter_namedata_typedescriptionvalid_inputsdefaults
@what_to_checkvarcharareas of system health to checkall, waits, disk, cpu, memory, system, lockingall
@start_datedatetimeoffsetearliest date to show data for, will be internally converted to UTCa reasonable dateseven days back
@end_datedatetimeoffsetlatest date to show data for, will be internally converted to UTCa reasonable datecurrent date
@warnings_onlybitonly show rows where a warning was reportedNULL, 0, 10
@database_namesysnamedatabase name to show blocking events forthe name of a databaseNULL
@wait_duration_msbigintminimum wait durationthe minimum duration of a wait for queries with interesting waits0
@wait_round_interval_minutesbigintinterval to round minutes to for wait statsinterval to round minutes to for top wait stats by count and duration60
@skip_locksbitskip the blocking and deadlocking section0 or 10
@pending_task_thresholdintegerminimum number of pending tasks to displaya valid integer10
@log_to_tablebitenable logging to permanent tables0 or 10
@log_database_namesysnamedatabase to store logging tablesvalid database nameNULL
@log_schema_namesysnameschema to store logging tablesvalid schema nameNULL
@log_table_name_prefixsysnameprefix for all logging tablesvalid table name prefix'HealthParser'
@log_retention_daysintegerNumber of days to keep logs, 0 = keep indefinitelyinteger30
@debugbitprints dynamic sql, selects from temp tables0 or 10
@helpbithow you got here0 or 10
@versionvarcharOUTPUT; for supportnonenone; OUTPUT
@version_datedatetimeOUTPUT; for supportnonenone; OUTPUT

Back to top

Log Hunter

The SQL Server error log can have a lot of good information in it about what's goin on, whether it's right or wrong.

The problem is that it's hard to know what to look for, and what else was going on once you filter it.

It's another notoriously bad Microsoft GUI, just like Query Store and Extended Events.

I created sp_LogHunter to search through your error logs for the important stuff, with some configurability for you, and return everything ordered by log entry time.

It helps you give you a fuller, better picture of any bad stuff happening.

More resources:

Current valid parameter details:

parameter_namedata_typedescriptionvalid_inputsdefaults
@days_backintegerhow many days back you want to search the logsan integer; will be converted to a negative number automatically-7
@start_datedatetimeif you want to search a specific time framea datetime valueNULL
@end_datedatetimeif you want to search a specific time framea datetime valueNULL
@custom_messagenvarcharif you want to search for a custom stringsomething specific you want to search for. no wildcards or substitions.NULL
@custom_message_onlybitonly search for the custom stringNULL, 0, 10
@first_log_onlybitonly search through the first error logNULL, 0, 10
@language_idintegerto use something other than EnglishSELECT DISTINCT m.language_id FROM sys.messages AS m ORDER BY m.language_id;1033
@helpbithow you got hereNULL, 0, 10
@debugbitdumps raw temp table contentsNULL, 0, 10
@versionvarcharOUTPUT; for supportOUTPUT; for supportnone; OUTPUT
@version_datedatetimeOUTPUT; for supportOUTPUT; for supportnone; OUTPUT

Back to top

Perf Check

sp_PerfCheck is a comprehensive SQL Server performance diagnostic tool that quickly identifies configuration issues, capacity problems, and performance bottlenecks at both the server and database level.

The procedure performs extensive checks in multiple categories:

  • Server configuration and resource usage
  • Memory configuration and pressure indicators
  • CPU scheduling and pressure
  • Storage performance and I/O bottlenecks
  • TempDB configuration
  • Database configuration settings
  • Database file auto-growth settings
  • Query Store health and configuration

Results are returned in a prioritized list, with the most critical issues displayed first. The procedure works on SQL Server 2016+ and Azure SQL DB.

Current valid parameter details:

ParameterData TypeDefaultDescription
@database_namesysnameNULLSpecific database to check; NULL runs against all accessible user databases
@debugbit0Print diagnostic messages and intermediate query results
@versionvarchar(30)NULL OUTPUTReturns version number
@version_datedatetimeNULL OUTPUTReturns version date

Back to top

Index Cleanup

This stored procedure helps identify unused and duplicate indexes in your SQL Server databases that could be candidates for removal. It analyzes index usage statistics and can generate scripts for removing unnecessary indexes.

IMPORTANT: This is currently a BETA VERSION. It needs extensive testing in real environments with real indexes to address several issues:

  • Data collection accuracy
  • Deduping logic
  • Result correctness
  • Edge cases

Misuse of this procedure can potentially harm your database. If you run this, only use the output to validate result correctness. Do not run any of the output scripts without thorough review and testing, as doing so may be harmful to your database performance.

The procedure requires SQL Server 2012 (11.0) or later due to the use of FORMAT and CONCAT functions.

Current valid parameter details:

Parameter NameData TypeDefault ValueDescription
@database_namesysnameNULLThe name of the database you wish to analyze
@schema_namesysnameNULLThe schema name to filter indexes by
@table_namesysnameNULLThe table name to filter indexes by
@min_readsbigint0Minimum number of reads for an index to be considered used
@min_writesbigint0Minimum number of writes for an index to be considered used
@min_size_gbdecimal(10,2)0Minimum size in GB for an index to be analyzed
@min_rowsbigint0Minimum number of rows for a table to be analyzed
@get_all_databasesbit0When set to 1, analyzes all eligible databases on the server
@include_databasesnvarchar(max)NULLComma-separated list of databases to include (used with @get_all_databases = 1)
@exclude_databasesnvarchar(max)NULLComma-separated list of databases to exclude (used with @get_all_databases = 1)
@helpbit0Displays help information
@debugbit0Prints debug information during execution
@versionvarchar(20)NULLOUTPUT parameter that returns the version number of the procedure
@version_datedatetimeNULLOUTPUT parameter that returns the date this version was released

Back to top

About

Open source SQL Server nonsense

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TSQL100.0%