Defaults and why they hurt
| Setting | Old default | Problem |
|---|---|---|
max degree of parallelism | 0 (all cores) on older versions | One query can use every core |
cost threshold for parallelism | 5 | Many cheap queries go parallel unnecessarily |
SQL Server 2019+ setup suggests a MAXDOP based on cores, but cost threshold stays 5.
Recommended starting points
| Server layout | MAXDOP starting point |
|---|---|
| Single NUMA node, ≤ 8 logical processors | ≤ number of logical processors |
| Single NUMA node, > 8 logical processors | 8 |
| Multiple NUMA nodes, ≤ 16 logical processors per node | ≤ logical processors per node |
| Multiple NUMA nodes, > 16 per node | Half of per-node processors, max 16 |
Check current Microsoft guidance for your version. For cost threshold, a common starting point is 30–50, then adjust by evidence.
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'max degree of parallelism', 8; RECONFIGURE;
EXEC sp_configure 'cost threshold for parallelism', 50; RECONFIGURE;
Evidence before and after
-- Parallelism-related waits since restart (or snapshot and diff)
SELECT wait_type, waiting_tasks_count, wait_time_ms
FROM sys.dm_os_wait_stats
WHERE wait_type IN ('CXPACKET','CXCONSUMER','CXSYNC_PORT','CXSYNC_CONSUMER','THREADPOOL')
ORDER BY wait_time_ms DESC;
-- Plan cost distribution of cached plans that went parallel
SELECT TOP 50 qs.execution_count, qs.total_worker_time / qs.execution_count AS avg_cpu,
qp.query_plan.value('declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";
(//p:StmtSimple/@StatementSubTreeCost)[1]', 'float') AS est_cost
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qp.query_plan.exist('declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";
//p:RelOp[@Parallel="1"]') = 1
ORDER BY qs.execution_count DESC;
Many frequently executed parallel plans with low estimated cost suggest raising the cost threshold.
Finer control
| Level | How |
|---|---|
| Database | ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 4; |
| Workload | Resource Governor workload groups with MAX_DOP (Enterprise) |
| Query | OPTION (MAXDOP 1) for specific OLTP queries; prefer fixing the plan |
Watch for side effects
THREADPOOLwaits mean worker threads ran out; high parallelism plus many sessions can cause this.- Reports and maintenance (index rebuilds, CHECKDB) may need their own MAXDOP settings.
Tip
Change one setting at a time and compare a week of wait stats and Query Store top-CPU queries. Parallelism tuning is workload-specific; numbers from blog posts are starting points only.
Checklist
- NUMA layout and core counts checked
- MAXDOP set per guidance
- Cost threshold raised from 5 and validated
- Wait stats compared before/after
- Database/workload/query overrides used only where needed