Recently while discussing the Task Execution Model and Thread Scheduling, I was asked the following question, “When discussing worker threads, how can we determine whether we should increase CPU capacity or focus on query tuning? This is when our worker threads are under pressure and the instance is becoming exhausted?“
In my brain, I thought, that is a great question, and it’s exactly the right way to think about worker thread pressure vs. real CPU starvation, especially when worker threads are getting tight. Let’s write a post.
Worker Threads vs CPU Utilization
When managing SQL Server performance, it’s important to understand the relationship, and the difference, between worker threads and CPU utilization. They’re separate objects, and they don’t always move together. You can be running hot on CPU and still have worker threads available, or you can run out of worker threads even when CPU usage looks fine.
Think of it like a busy restaurant.
Worker threads are your waitstaff and kitchen crew, the people who can actually do the work. They take orders, cook meals, and deliver plates. If you run out of staff, customers start lining up, no matter how good your kitchen equipment is.
CPU is the energy and speed of that staff, how fast they can move, cook, and serve while they’re on the clock. Strong CPU means your cooks slice faster and your servers hustle between tables.
Now imagine the dining room is full, all tables are seated, and every server is already working a section. Orders are piling up at the host stand, but there’s no one left to take them. That’s a thread pool wait situation. The restaurant isn’t slow because the staff is exhausted, the problem is that every worker is already busy.
On the flip side, you might have plenty of servers standing around, but the kitchen equipment is struggling. One oven, already maxed out, is trying to handle every order. That’s high CPU utilization. You have workers available, but the core machinery can’t keep up, so meals still come out slow.
Here is the key point:
Adding more capacity doesn’t help if the dining room has no servers left. Putting in a bigger oven won’t seat more guests if you’re short-staffed. And hiring more servers doesn’t help if the kitchen is already overheated and maxed out.
Performance depends on having enough workers to handle demand and enough CPU to keep them moving efficiently. Simply adding CPU is like buying stadium-quality grills when what you really needed was more staff on the floor, great power, wrong fix.
Below are steps you can use in SQL Server to determine whether you should scale CPU or focus on query or workload tuning.
Separate CPU pressure from worker-thread pressure
They often appear together, but they are not the same problem.
| Symptom | What it might indicate |
| High CPU % | CPU-bound workload or inefficient queries |
| High Number of RUNNABLE tasks | CPU scheduler pressure |
| High Number of THREADPOOL waits | Worker thread exhaustion |
| Low CPU, but THREADPOOL waits | Blocking, parallelism abuse, or thread hoarding |
The key is where tasks are stuck:
- RUNNABLE QUEUE – Waiting for CPU
- SUSPENDED QUEUE – Waiting for a resource other than the CPU (lock, I/O, latch)
- THREADPOOL WAIT – waiting for a worker thread
Using sys.dm_os_schedulers to determine CPU Pressure
The sys.dm_os_schedulers dynamic management view tells you whether SQL Server wants more CPU.
SELECT scheduler_id, runnable_tasks_count, current_tasks_count,
active_workers_count, load_factor
FROM sys.dm_os_schedulers
WHERE status = 'VISIBLE ONLINE';| Observation | Interpretation | Action |
| Runnable_tasks_count > 0 consistently across schedulers | CPU pressure | Tune queries or add CPU |
| Runnable_tasks_count = 0 but queries still slow | Not CPU-bound | Look elsewhere |
| High load_factor on many schedulers | Sustained CPU contention | Tune first, then scale |
Consistently non-zero runnable tasks is the clearest indicator that CPU is the bottleneck.
Correlate CPU% with Wait Types
| Wait type | Meaning |
| SOS_SCHEDULER_YIELD | CPU scheduling pressure |
| THREADPOOL | No worker threads available |
| High signal wait time | CPU starvation |
SELECT wait_type, waiting_tasks_count, wait_time_ms, signal_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE wait_type IN ('SOS_SCHEDULER_YIELD', 'THREADPOOL');- High SOS_SCHEDULER_YIELD + high runnable tasks = CPU-bound workload
- High THREADPOOL waits + low CPU = Blocking, parallelism abuse, or thread hoarding
Performance Monitor Counters
Confirm CPU pressure with these Operating System and SQL Server performance monitor counters:
/* Perf counters via DMV: CPU saturation + CPU queuing + SQL throughput */
SELECT pc.object_name, pc.counter_name, pc.cntr_value, pc.cntr_type, GETDATE() AS sample_time
FROM sys.dm_os_performance_counters AS pc
WHERE
-- OS CPU saturation
(pc.object_name LIKE '%Processor%'
AND pc.counter_name = '% Processor Time')
OR
-- OS CPU queuing
(pc.object_name LIKE '%System%'
AND pc.counter_name = 'Processor Queue Length')
OR
-- SQL throughput
(pc.object_name LIKE '%SQL Statistics%'
AND pc.counter_name = 'Batch Requests/sec')
ORDER BY
pc.object_name, pc.counter_name;- Processor: % Processor Time — Sustained over 80% indicates CPU saturation
- System: Processor Queue Length — Consistently over number of cores will signal CPU queuing
- SQL Server: SQL Statistics – Batch Requests/sec — Correlate throughput with CPU usage
Checking Thread Pool Utilization
To determine how close you are to the thread pool ceiling, compare current worker count to the configured maximum:
-- Current worker threads in use
SELECT COUNT(*) AS current_workers FROM sys.dm_os_workers;
-- Maximum configured worker threads
SELECT max_workers_count FROM sys.dm_os_sys_info;If the current worker count approaches max_workers_count and THREADPOOL waits are occurring, you’ve hit the thread capacity. SQL Server dynamically creates threads up to this maximum; if no idle worker thread is available when the limit is reached, the client’s command must wait.
THREADPOOL wait occurs when there are no available threads in the server’s thread pool, which can lead to login failures or queries not being able to run. Short-duration THREADPOOL waits during sudden demand spikes are normal as the thread pool grows. When there are no available threads at all, that constitutes worker thread starvation, which is not normal.
Decide: CPU upgrade or query tuning?
Scale Up CPU
Only when CPU is persistently 80–90%+ utilized, runnable task queues are building, signal wait times are growing, and top queries are already optimized; adding cores or faster processors is likely needed.
Tune Workload
When worker threads are exhausted (THREADPOOL waits) but CPU isn’t maxed, the bottleneck is concurrency or query inefficiency. A worker is never released until its task completes, even if that task is just waiting on a lock, so blocking, parallelism abuse, or thread hoarding are the real targets.
Find Root Cause
Always diagnose why threads are stuck before choosing a remedy. Adding CPU to a system choked by a single blocking query or excessive parallelism wastes budget and masks the real problem.

Be the first to comment on "CPU Pressure or Time to Tune a Query?"