Troubleshoot performance problems

How to diagnose performance problems in SQL Server

If your SQL Server is slow, you absolutely must gather evidence, so that you do not assume problems — and solutions — that have nothing to do with the actual issue.

If you were a doctor, would you prescribe a treatment without making a diagnosis first?

Hypotheses

Where can performance problems come from?

  1. An undersized server;
  2. Bad configuration of SQL Server or of the databases;
  3. Badly written queries that run too long;
  4. Excessive round trips between the client and the server;
  5. Blocking caused by too much locking;
  6. Trigger execution, or transactions that run too long;
  7. Missing indexes;
  8. Waits, on query parallelism for instance;
  9. The client code, rather than SQL Server itself;
  10. Execution plan, compilation and parameter sniffing problems.

How to test the hypotheses

Undersized server

SQL Server runs perfectly well on modest hardware, provided the database and the queries are optimized.

A few leads:

Is there a disk problem?

To find out whether disk performance is to blame, you can:

Bad configuration of SQL Server or of the databases

To find out whether the instance configuration is involved:

  • Parallelism? The one configurable thing that can genuinely change the picture.
    • Experiment with it.
    • Look at the waits of type CXPACKET. If you have a lot of them, check whether hyperthreading is enabled.
    • The sys.dm_os_latch_stats DMV contains information about the specific latch waits that have occurred in the instance, and if one of the top latch waits is ACCESS_METHODS_DATASET_PARENT, in conjunction with CXPACKET, LATCH_*, and SOS_SCHEDULER_YIELD wait types as the top waits, the level of parallelism on the system is the cause of bottlenecking during query execution, and reducing the ‘max degree of parallelism’ sp_configure option may be required to resolve the problems.

Parallelism

ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 1;

Database configuration

  • auto close — you can feel this one. Always FALSE.
  • auto shrink — always FALSE. This option should not even exist.
  • auto create statistics — always true.
  • auto update statistics — always true.
  • In short: leave the database options at their defaults.
  • compatibility level — as high as possible, to benefit from the relational engine improvements.
  • Manage the version of the cardinality estimator (legacy cardinality estimation) as needed, based on the estimation errors you observe in your queries.

Badly written queries that run too long

Excessive round trips between the client and the server

Harder to spot. The pattern to look for is an excess of small repetitive queries doing one round trip at a time between the client and the server, instead of set-based queries. This is behaviour to change in the client code.

  • With an Extended Events session and no query cost filter, look for repeated calls of similar queries — for example runs of inserts, or similar single-row SELECT statements with only the parameters changing.
  • Query the dm_exec_query_stats view looking for cheap queries executed very often — sort on the execution_count column.
  • Use the Query Store to identify the most frequently executed queries.

Blocking caused by too much locking

Blocking is a wait on locks held by other sessions.

Is there any blocking?

Trigger execution, transactions that run too long

Missing indexes

  • Check the missing index report.
  • Check that your indexes are actually used, with the index usage report.
  • Use the Database Engine Tuning Advisor on a query or on a workload.
  • If your queries are more analytical in nature, try ColumnStore indexes.

Execution plan, compilation and parameter sniffing problems

When the problem shows up, flush the plan cache with DBCC FREEPROCCACHE. Does that fix it? You can run this command instead of restarting a SQL Server.

Check the statistics with this diagnostic query.

Cardinality estimation errors

A classic sign: the query degrades over time. Schedule more frequent statistics updates.

  • Check the statistics with this script, and look at the tables that have seen a lot of modifications.
  • Look at the actual execution plan.
  • Use Plan Explorer.
  • Trigger a recalculation with UPDATE STATISTICS.
  • Enable the legacy cardinality estimator, either at database level or per query with OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION')). See Cardinality Estimator.
  • Check whether the query uses table variables, whose cardinality is badly estimated in older compatibility levels.