SQL Server MAXDOP Recommendation Script
MAXDOP (max degree of parallelism) caps how many processors SQL Server uses for a single parallel query plan. The default value of 0 lets SQL Server use all available processors, up to 64, which Microsoft says is not the recommended value for most cases. This script reads the CPU and NUMA layout from system DMVs, applies Microsoft's SQL Server 2016 and later guidance, and compares the result with the current setting.
Purpose and Overview
Microsoft's MAXDOP guidance changed with SQL Server 2016. Through SQL Server 2014 the rule for multi-NUMA servers capped MAXDOP at 8; from 2016 onward the cap depends on the number of logical processors per NUMA node, and soft-NUMA (created automatically when a node has more than eight physical cores) counts as a NUMA node. A script that hard-codes the older rule gives the wrong answer on a modern server.
MAXDOP 0 on a large server lets one parallel query take every processor SQL Server can see, which starves other queries of CPU time. Setting it to 1 suppresses parallel plan generation entirely and slows queries that benefit from parallel execution.
For SQL Server 2016 and later, the Microsoft guidance is:
- Single NUMA node, 8 or fewer logical processors: keep MAXDOP at or under the number of logical processors
- Single NUMA node, more than 8 logical processors: keep MAXDOP at 8
- Multiple NUMA nodes, 16 or fewer logical processors per node: keep MAXDOP at or under the number of logical processors per NUMA node
- Multiple NUMA nodes, more than 16 logical processors per node: keep MAXDOP at half the number of logical processors per NUMA node, with a maximum of 16
The guidance counts logical processors and has no separate hyperthreading rule. This script reads the layout from sys.dm_os_sys_info and sys.dm_os_schedulers, calculates the upper bound, and compares it to the current sp_configure value.
Code Breakdown
Script 1: MAXDOP Recommendation Calculator
1DECLARE @LogicalCPUs INT,
2 @NUMANodes INT,
3 @LogicalPerNUMA INT,
4 @MaxDOPRecommended INT,
5 @MaxDOPCurrent INT;
6
7SELECT @LogicalCPUs = cpu_count
8FROM sys.dm_os_sys_info;
9
10SELECT @NUMANodes = COUNT(DISTINCT parent_node_id)
11FROM sys.dm_os_schedulers
12WHERE status = 'VISIBLE ONLINE'
13 AND is_online = 1; -- user schedulers only; the DAC scheduler has its own status
14
15SET @LogicalPerNUMA = @LogicalCPUs / NULLIF(@NUMANodes, 0);
16
17IF @LogicalCPUs IS NULL OR ISNULL(@NUMANodes, 0) = 0
18BEGIN
19 RAISERROR('Could not read sys.dm_os_sys_info or sys.dm_os_schedulers. This script needs VIEW SERVER STATE (VIEW SERVER PERFORMANCE STATE on SQL Server 2022 and later).', 16, 1);
20 RETURN;
21END
22
23SELECT @MaxDOPCurrent = CAST(value_in_use AS INT)
24FROM sys.configurations
25WHERE name = N'max degree of parallelism';
26
27-- Microsoft MAXDOP guidance for SQL Server 2016 and later
28IF @NUMANodes = 1
29BEGIN
30 -- Single NUMA node: logical processors, up to 8
31 SET @MaxDOPRecommended = CASE WHEN @LogicalCPUs < 8
32 THEN @LogicalCPUs
33 ELSE 8 END;
34END
35ELSE IF @LogicalPerNUMA <= 16
36BEGIN
37 -- Multiple NUMA nodes, 16 or fewer logical processors per node
38 SET @MaxDOPRecommended = @LogicalPerNUMA;
39END
40ELSE
41BEGIN
42 -- Multiple NUMA nodes, more than 16 logical processors per node:
43 -- half the logical processors per node, maximum 16
44 SET @MaxDOPRecommended = CASE WHEN @LogicalPerNUMA / 2 < 16
45 THEN @LogicalPerNUMA / 2
46 ELSE 16 END;
47END
48
49SELECT
50 @@SERVERNAME AS server_name,
51 @LogicalCPUs AS logical_cpus_total,
52 @NUMANodes AS numa_nodes,
53 @LogicalPerNUMA AS logical_cpus_per_numa,
54 @MaxDOPRecommended AS maxdop_upper_bound,
55 @MaxDOPCurrent AS maxdop_current,
56 CASE
57 WHEN @MaxDOPCurrent = 0
58 THEN 'WARNING - unlimited parallelism (MAXDOP 0)'
59 WHEN @MaxDOPCurrent = @MaxDOPRecommended
60 THEN 'OK - matches recommendation'
61 WHEN @MaxDOPCurrent < @MaxDOPRecommended
62 THEN 'OK - at or under the recommended upper bound'
63 ELSE 'REVIEW - current setting is above the recommendation'
64 END AS status;
sys.dm_os_sys_info.cpu_count is the number of logical CPUs on the system. The script divides it by the NUMA node count to get logical processors per node, which is the quantity Microsoft's table uses. It does not use hyperthread_ratio: that column is the ratio of logical or physical cores exposed by one physical processor package, not a hyperthreading multiplier, so dividing by it does not give a physical core count.
NUMA node count comes from sys.dm_os_schedulers, where parent_node_id is the NUMA node a scheduler belongs to. Filtering on status = 'VISIBLE ONLINE' keeps the schedulers that process user requests and drops the hidden schedulers and the dedicated administrator connection (DAC) scheduler, whose status is VISIBLE ONLINE (DAC). Because the guidance refers to soft-NUMA nodes where SQL Server has created them, the node count here reflects what SQL Server reports, not just the hardware.
The three branches follow Microsoft's 2016+ table. Because the guidance says to keep MAXDOP "at or under" the figure for most rows, the output column is an upper bound and the status column treats any non-zero value at or below it as acceptable. If the instance has CPU affinity configured, cpu_count still reports every logical CPU on the machine, so adjust the result by hand.
On SQL Server 2008 through 2014 the multi-node rows differ: with 8 or fewer logical processors per node, keep MAXDOP at or under that number, and with more than 8, keep it at 8. This script does not implement that older table.
Script 2: Apply the Recommended MAXDOP Setting
1-- Review the calculated upper bound from Script 1 before running this
2EXEC sys.sp_configure N'show advanced options', 1;
3RECONFIGURE WITH OVERRIDE;
4GO
5
6EXEC sys.sp_configure N'max degree of parallelism', 8; -- replace 8 with your chosen value
7RECONFIGURE WITH OVERRIDE;
8GO
9
10-- Verify the change
11SELECT
12 name,
13 value,
14 value_in_use,
15 description
16FROM sys.configurations
17WHERE name = N'max degree of parallelism';
max degree of parallelism is an advanced option, so show advanced options must be set to 1 before sp_configure can change it. The change takes effect immediately after RECONFIGURE, with no restart. Replace 8 with a value at or below the maxdop_upper_bound from Script 1 before executing. Microsoft's own example sets show advanced options back to 0 afterward, which you can do once the change is verified.
Key Benefits and Use Cases
- Calculates the MAXDOP upper bound automatically without manual CPU counting
- Surfaces the gap between the current setting and the Microsoft guidance
- Useful for new instance configuration, post-migration audits, and periodic health checks
- Handles single-NUMA and multi-NUMA layouts under the SQL Server 2016 and later rules
- Flags MAXDOP 0 so an unlimited setting is easy to find across a fleet
Performance Considerations
- MAXDOP is a starting point, not a final answer: the Microsoft figure is an upper bound. Heavily OLTP-loaded servers sometimes do better with a lower value after testing, and analytical or reporting workloads may do better near the bound.
- Cost threshold for parallelism: MAXDOP works alongside the
cost threshold for parallelismsetting. Microsoft describes the default of 5 as "a starting point, not a recommendation" and notes that raising it can keep smaller OLTP queries on serial plans. Raise it in small increments and observe a full business cycle. - Overrides: Microsoft documents MAXDOP overrides at the query level (the
MAXDOPquery hint), the database level (ALTER DATABASE SCOPED CONFIGURATION, SQL Server 2016 and later), and the Resource Governor workload group level. A lower database-scoped MAXDOP suits a high-concurrency OLTP database that shares an instance with a reporting database. - Availability Groups: run the calculation on each replica, because replicas on different hardware can report different CPU and NUMA layouts.
Practical Tips
To check MAXDOP across multiple instances from a Central Management Server:
1-- Run against all registered servers via CMS multi-server query
2SELECT
3 @@SERVERNAME AS server_name,
4 name,
5 value_in_use AS maxdop_current
6FROM sys.configurations
7WHERE name = N'max degree of parallelism';
To also raise the cost threshold for parallelism alongside MAXDOP:
1-- Example value only; raise in small increments and observe a full business cycle
2EXEC sys.sp_configure N'show advanced options', 1;
3RECONFIGURE;
4GO
5EXEC sys.sp_configure N'cost threshold for parallelism', 20;
6RECONFIGURE;
7GO
After changing MAXDOP, monitor sys.dm_exec_query_stats for query CPU time changes over the following week. Look for queries where total_worker_time / execution_count increased significantly. Those may need query-level hints or plan guides if the new MAXDOP is too restrictive for them.
Conclusion
MAXDOP is one of the first settings to review on any new or inherited SQL Server instance. This script removes the manual arithmetic by reading the logical processor and NUMA layout from system DMVs and applying Microsoft's SQL Server 2016 and later guidance. Use the output as an upper bound, then tune further based on observed workload behavior.
References
- BrentOzarULTD/SQL-Server-First-Responder-Kit on GitHub — sp_Blitz checks MAXDOP configuration as part of its standard health check output
- ktaranov/sqlserver-kit on GitHub — Community SQL Server script collection including server configuration checks
- Microsoft Docs: Configure the max degree of parallelism — Official guidance, including the NUMA-based recommendation tables for 2016+ and for 2008 to 2014
- Microsoft Docs: Configure the cost threshold for parallelism — Default value, advanced option note, and tuning guidance
- Microsoft Docs: sys.dm_os_sys_info (Transact-SQL) — cpu_count and hyperthread_ratio column reference
- Microsoft Docs: sys.dm_os_schedulers (Transact-SQL) — Scheduler status values and NUMA node metadata
- Microsoft Docs: ALTER DATABASE SCOPED CONFIGURATION — Per-database MAXDOP override available in SQL Server 2016+
Posts in this series
- SQL Server Scripts and Commands: DBA Reference All Versions
- SQL Server DBCC CHECKIDENT: Bulk Identity Column Analysis
- SQL Server Error Log Search Script: xp_readerrorlog
- SQL Server Search Stored Procedure and View Text
- SQL Server Agent Job Failure History Report
- SQL Server Kill Sessions: Filtered SPID Management
- SQL Server MAXDOP Recommendation Script
- SQL Server Max Server Memory Calculator Script
- SQL Server Object Dependencies Report: Find All References
- SQL Server DBCC CHECKIDENT: Check and Reseed Identity
- List All Tables Across All Databases in SQL Server
- Configure Database Mail and Send a Test Message
- Audit Server Configuration Drift with sys.configurations
- Check AG Replica Health with sys.dm_hadr_availability_replica_states