Cumulative Wait Stats Lie: Measuring Waits Over an Interval

Wait statistics are cumulative since the instance last started, so on a server with months of uptime they describe history rather than the problem in front of you. Take two snapshots, subtract one from the other, and read the interval instead. Do not clear the counters to get a clean read.This is part of the SQL Server Wait Types Library, every wait type explained.Related pillar: Performance & Troubleshooting


The wait library on this site covers what each wait type means, and the library page covers how to read the output of sys.dm_os_wait_stats. Both assume you are looking at a number worth reading. On an instance that has been up for months, often you are not.

Every counter in that DMV accumulates from the moment SQL Server started. A server up for 300 days is showing you 300 days of accumulated waiting. An incident that lasted 40 minutes this morning contributes a rounding error to that total, and a problem that was fixed in June is still sitting near the top of the list, still collecting attention it no longer deserves.

That is not a flaw in the DMV. It is a counter, and counters count. The mistake is reading a cumulative counter as though it described the present.


Check the Uptime Before You Read Anything

The first number to look at is not a wait type. It is how long the counters have been accumulating, because that decides whether the list means anything at all:

SELECT sqlserver_start_time,
       DATEDIFF(hour, sqlserver_start_time, GETDATE()) AS hours_up
FROM   sys.dm_os_sys_info;
SSMS results grid showing the sys.dm_os_sys_info uptime query returning sqlserver_start_time 2026-08-18 23:17:19.983 and hours_up 324

A few hours of uptime after a restart is a genuinely useful window, and the totals are close enough to “recently” to act on. Weeks or months of uptime means the top of the list is an average across every workload the server has run since it started, including the ones nobody is running any more.


Do Not Clear the Counters

The obvious shortcut is to reset them and look again:

-- Instance-wide. Think before running this on a shared server.
DBCC SQLPERF ('sys.dm_os_wait_stats', CLEAR);

It works, and it costs more than it looks. The reset is instance-wide, so anything else reading those counters loses its baseline at the same moment: a monitoring product, a collector job, a colleague mid-investigation. You also destroy the history rather than setting it aside, so the comparison you might want tomorrow is gone. It is a reasonable thing to do on a lab instance you own outright and a poor habit on production.

The snapshot approach gets you the same interval view and takes nothing away from anyone else.


Two Snapshots and a Subtraction

The method is simple enough to describe in one sentence: record the counters, wait, record them again, and report the difference. Generate-CollectorJob-WaitStats.sql in the repo creates a SQL Agent job that writes a snapshot into a collector table on a schedule, and Get-WaitStatsDelta.sql compares the two most recent snapshots and returns what actually accumulated between them.

The delta is the number worth arguing about. It answers “what is this server waiting on now“, which is the question you were asking when you opened the DMV.


Reading the Delta

Three columns carry most of the meaning, and one of them is easy to misread.

delta_wait_ms
Milliseconds of waiting accumulated in the window. On its own it is hard to judge, because a busy server waits a lot and that is normal.
pct_of_total_wait
That wait as a share of all wait in the window. This is the ranking column, and it is worth being precise about what it is not: it is not a percentage of the interval. Wait is accumulated per task, so a server running fifty parallel workers can accumulate far more wait than wall-clock time in the window. A single wait type showing a very large millisecond figure with delta_tasks = 1 is one long wait, not a busy server.
delta_tasks
How many waits made up that total. Divide one by the other and you get the average wait, which separates “thousands of short waits” from “one task blocked for ten minutes”. Those are different problems with different fixes.

The Restart Trap

If the instance restarts between two snapshots, the counters reset to zero and the later snapshot is smaller than the earlier one. Subtracting produces a negative number, or worse, a plausible small one. Any delta script worth running checks sqlserver_start_time on both snapshots and refuses to calculate when it changed, which is the behaviour you want: a refusal you can see beats a number you cannot trust.

The same applies if somebody cleared the counters manually in between, which is a second reason not to make a habit of clearing them.


Common Questions

How long should the interval be?
Long enough to contain the behaviour you are chasing and short enough to exclude everything else. Fifteen minutes to an hour suits most investigations. A five-minute window during a live incident is more useful than a daily average; a daily average is more useful for capacity questions than a five-minute window.
Can I just run the DMV twice by hand instead of a collector job?
Yes, and for a one-off investigation that is the sensible thing to do: select the counters into a temp table, wait, select again, join on wait type and subtract. The collector job earns its place when you want the window that has already passed, which is most of the time, because incidents are usually reported after they end.
Why is my top wait something I have never heard of?
Many wait types are internal housekeeping that a healthy instance accumulates constantly and nobody should chase. That is what the wait library is for: look the type up before acting on it, because “top of the list” and “worth investigating” are not the same claim.
Does the delta approach work across a failover?
No, and for the same reason as a restart. The replica you are now reading has its own counters that started when it came up, so the earlier snapshot belongs to a different instance. Treat a failover as a reset.
Should I still look at the cumulative numbers at all?
They are useful for one thing: a long-run profile of what the server spends its life waiting on. Just do not use them to diagnose something that started on Tuesday.

Related Scripts

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *