CtrlK
BlogDocsLog inGet started
Tessl Logo

alonso-skills/mssql-server

Writes, optimizes, and debugs T-SQL queries. Explains SQL Server internals, troubleshoots performance issues, and guides database administration tasks including backup/restore, high availability, security, and index design. Use when the user asks about T-SQL syntax, SQL Server administration, query performance, stored procedures, indexes, locking, transactions, backup/restore, high availability, security, or any MSSQL-related topic — even without saying 'SQL Server' explicitly. Also trigger on terms like SSMS, tempdb, bcp, sqlcmd, MSSQL, sp_executesql, NOLOCK, columnstore, Hekaton, RCSI, param sniffing, or execution plan.

100

Quality

100%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

49-configuration-tuning.mdreferences/

SQL Server Configuration & Tuning Reference

A complete reference for sp_configure, server memory, MAXDOP, parallelism thresholds, Resource Governor, NUMA topology, and trace flags — with opinionated guidance on safe defaults and when to deviate.


Table of Contents

  1. When to Use This Reference
  2. sp_configure Overview
  3. sp_configure Cheat Sheet
  4. Max Server Memory Calculation
  5. MAXDOP Formula
  6. Cost Threshold for Parallelism
  7. Lightweight Pooling / Fiber Mode
  8. Locked Pages in Memory (LPIM)
  9. Optimize for Ad Hoc Workloads
  10. Backup Compression Default
  11. Database Mail and CLR
  12. Remote Connections and Protocols
  13. Resource Governor
  14. Database-Scoped Configurations
  15. NUMA Topology and Affinity
  16. Trace Flags Reference
  17. Monitoring Configuration State
  18. Azure SQL Considerations
  19. Common Patterns
  20. Gotchas
  21. See Also
  22. Sources

When to Use This Reference

Load this file when:

  • Sizing a new SQL Server instance (memory, MAXDOP, parallelism)
  • Auditing or hardening an existing server configuration
  • Troubleshooting parallel query runaway, memory pressure, or plan cache bloat
  • Setting up Resource Governor workload isolation
  • Researching a specific sp_configure option
  • Enabling or disabling a feature via trace flag or database-scoped config

sp_configure Overview

sp_configure manages server-level settings. Two categories exist:

CategoryDescriptionShow Advanced?
BasicSafe defaults, low-risk changesNo
AdvancedPotentially dangerous settingsSHOW ADVANCED OPTIONS = 1 required

Workflow

-- 1. Enable advanced options (required for most settings)
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;

-- 2. Change a setting
EXEC sp_configure 'max server memory (MB)', 24576;
RECONFIGURE WITH OVERRIDE;  -- OVERRIDE only needed for settings that warn

-- 3. Verify change
SELECT name, value, value_in_use, minimum, maximum, description
FROM sys.configurations
WHERE name = 'max server memory (MB)';

-- 4. Most settings take effect immediately; a few require restart
-- Check is_dynamic: 1 = immediate, 0 = requires restart
SELECT name, is_dynamic, is_advanced
FROM sys.configurations
ORDER BY name;

RECONFIGURE validates and applies settings that are is_dynamic = 1. RECONFIGURE WITH OVERRIDE bypasses range validation — use only for settings documented to need it.


sp_configure Cheat Sheet

All important options with recommended values. Options marked Requires restart have is_dynamic = 0.

Memory

OptionDefaultRecommendedNotes
max server memory (MB)2147483647See formula belowMust set. Default is unlimited
min server memory (MB)00Leave at 0; setting > 0 pins memory pages
max worker threads000 = auto-calculated; change only if advised
memory modelN/ALPIM if high-memory serverVia Windows policy, not sp_configure

Parallelism

OptionDefaultRecommendedNotes
max degree of parallelism0See formula below0 = all CPUs; almost never the right setting
cost threshold for parallelism540–50Default of 5 is far too low for modern hardware
max worker threads00See memory section

Query Execution

OptionDefaultRecommendedNotes
optimize for ad hoc workloads01Always enable; reduces single-use plan cache bloat
priority boost00Leave at 0. Setting to 1 causes instability
lightweight pooling00Leave at 0. Fiber mode deprecated and harmful
query governor cost limit000 = no limit; use Resource Governor instead
query wait (s)-1-1-1 = auto (25× CPU time); rarely needs changing

I/O

OptionDefaultRecommendedNotes
backup compression default01Enable compression by default; override per-job
backup checksum default01(2014+) Catch silent corruption at backup time

[!NOTE] SQL Server 2014 backup checksum default was added in SQL Server 2014. Enable it — it has negligible overhead and catches media corruption.

Features

OptionDefaultRecommendedNotes
clr enabled0As neededRequired for CLR objects
clr strict security11(2017+) Do not disable without strong reason
Ole Automation Procedures00Avoid; enables sp_OACreate COM automation
xp_cmdshell00Security risk. Enable only when required, with a proxy account
Ad Hoc Distributed Queries00Required for OPENROWSET/OPENDATASOURCE; enable only if needed
Database Mail XPs01 if using mailRequired for sp_send_dbmail
show advanced options00 after useReset to 0 when done configuring

Network

OptionDefaultRecommendedNotes
remote access11Controls linked servers and remote queries
remote query timeout (s)6006000 = infinite; watch for hung cross-server queries
remote login timeout (s)1010Seconds to wait for remote server login
network packet size (B)40964096Larger (8192+) only for bulk transfer workloads

Locks and Connections

OptionDefaultRecommendedNotes
locks000 = auto; fixed value causes runaway lock table
open objects000 = auto; leave alone
max connections000 = max allowed by OS; rarely needs limiting
fill factor (%)000 = 100% fill; per-index settings are better

Max Server Memory Calculation

SQL Server's memory manager does not properly account for OS and other process needs when max server memory is left at the unlimited default. The OS will page memory under memory pressure, causing severe performance degradation.

Formula

Reserved for OS = MAX(10% of total RAM, minimum floor)
Total RAMReserve for OSReserve floor
≤ 4 GB1 GB
4–16 GB1–2 GB
16–64 GB2–4 GB4 GB
64–256 GB4–8 GB8 GB
256 GB+8–16 GB16 GB

Additionally, reserve memory for:

  • SQL Agent: ~100–200 MB
  • SSIS: 500 MB–2 GB depending on packages
  • Antivirus/monitoring agents: 200–500 MB
  • Other services on the host: whatever they need

Practical T-SQL Calculation

-- Query current physical memory and suggest max server memory
DECLARE @TotalPhysGB DECIMAL(10,2);
SELECT @TotalPhysGB = physical_memory_kb / 1024.0 / 1024.0
FROM sys.dm_os_sys_info;

DECLARE @OsReserveGB DECIMAL(10,2) =
    CASE
        WHEN @TotalPhysGB <= 4   THEN 1.0
        WHEN @TotalPhysGB <= 16  THEN 2.0
        WHEN @TotalPhysGB <= 64  THEN 4.0
        WHEN @TotalPhysGB <= 256 THEN 8.0
        ELSE 16.0
    END;

SELECT
    @TotalPhysGB                                 AS total_ram_gb,
    @OsReserveGB                                 AS os_reserve_gb,
    @TotalPhysGB - @OsReserveGB                  AS suggested_max_server_memory_gb,
    (@TotalPhysGB - @OsReserveGB) * 1024         AS suggested_max_server_memory_mb;

Apply the Setting

-- Example: 32 GB server, reserve 4 GB for OS
-- max server memory = 28 GB = 28672 MB
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;

EXEC sp_configure 'max server memory (MB)', 28672;
RECONFIGURE;

[!WARNING] On Availability Group secondary replicas configured as readable secondaries, consider leaving extra memory headroom — readable secondaries maintain their own version store in tempdb which can balloon under heavy read workloads.

LPIM Interaction

If Locked Pages in Memory (LPIM) is enabled (recommended for large-memory servers), SQL Server will ignore the OS max server memory signal and hold all allocated memory. Set max server memory correctly regardless — LPIM only changes when the OS can forcibly reclaim pages, not whether the limit is respected.


MAXDOP Formula

MAXDOP (max degree of parallelism) controls the maximum number of processor threads used for a single parallel query execution.

Default of 0 means "use all CPUs". This is almost never the right setting because a single runaway parallel query can consume all CPU, starving other queries.

Recommended Formula

Per NUMA node: MAXDOP ≤ number of logical CPUs per NUMA node

Global MAXDOP:
  - ≤ 8 logical CPUs total:  MAXDOP = 0 (all CPUs)
  - 8–16 logical CPUs:       MAXDOP = 8
  - > 16 logical CPUs:       MAXDOP = logical CPUs per NUMA node (max 16)
  - Hyperthreading enabled:  Consider halving the per-node value

[!NOTE] SQL Server 2019 The Azure SQL best practice and Microsoft's own documentation now recommends:

  • OLTP workloads: MAXDOP = 4–8 as a starting point
  • OLAP/reporting: MAXDOP = half the logical CPU count (cap at 16)
  • Mixed workloads: MAXDOP = 4–8, use Resource Governor to override per workload

Calculate MAXDOP Based on NUMA Topology

-- Determine NUMA topology and suggest MAXDOP
SELECT
    numa_node_id,
    COUNT(*) AS logical_cpus_per_node
FROM sys.dm_os_schedulers
WHERE scheduler_id < 255
  AND status = 'VISIBLE ONLINE'
GROUP BY numa_node_id
ORDER BY numa_node_id;

-- Also check current MAXDOP
SELECT name, value_in_use
FROM sys.configurations
WHERE name = 'max degree of parallelism';

Apply

-- Example: 4-core server with no NUMA, set MAXDOP = 4
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;

EXEC sp_configure 'max degree of parallelism', 4;
RECONFIGURE;

Per-Query and Per-Database Override

-- Override per-query
SELECT * FROM Orders WITH (INDEX = IX_OrderDate)
OPTION (MAXDOP 1);  -- force serial

-- Override at database level (SQL Server 2016+)
ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 2;
ALTER DATABASE SCOPED CONFIGURATION FOR SECONDARY SET MAXDOP = 1;

-- Override in Resource Governor (see Resource Governor section)

[!NOTE] SQL Server 2016 ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP was introduced in SQL Server 2016. Use it to tune per-database without changing global settings.


Cost Threshold for Parallelism

The default value of 5 was set in the 1990s for hardware of that era. Modern CPUs execute a "cost 5" query in microseconds. A threshold of 5 means almost every query attempts to go parallel, which:

  • Wastes parallel thread reservation overhead on trivial queries
  • Increases context switching
  • Causes CXPACKET/CXCONSUMER wait accumulation that looks alarming but is spurious

Recommended Value: 40–50

A value of 40–50 is a practical starting point for modern OLTP systems. Some shops run 25 for mixed OLTP/OLAP. Pure OLAP warehouses can go higher (75–100).

The right value is workload-specific. Use this diagnostic to find queries near the boundary:

-- Queries in cache near current threshold where parallelism might flip
SELECT TOP 50
    qs.total_worker_time / qs.execution_count  AS avg_cpu_microseconds,
    qs.total_elapsed_time / qs.execution_count AS avg_duration_microseconds,
    qs.execution_count,
    qp.query_plan,
    SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
        ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text)
          ELSE qs.statement_end_offset END - qs.statement_start_offset)/2)+1) AS query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qs.total_worker_time / qs.execution_count BETWEEN 5000 AND 100000  -- 5ms–100ms CPU
ORDER BY qs.execution_count DESC;

Apply

EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;

EXEC sp_configure 'cost threshold for parallelism', 50;
RECONFIGURE;

Lightweight Pooling / Fiber Mode

-- DO NOT ENABLE
EXEC sp_configure 'lightweight pooling', 0;  -- 0 = thread mode (correct)

Fiber mode (lightweight pooling = 1) switches SQL Server to use Windows fibers instead of threads. Originally intended for specific Itanium workloads that no longer exist. Has known issues with CLR, extended stored procedures, and some features.

[!WARNING] Deprecated Fiber mode is deprecated as of SQL Server 2017 and removed in SQL Server 2022. Enabling it on any version where it exists is not recommended. There is no modern workload where it helps.


Locked Pages in Memory (LPIM)

LPIM allows SQL Server to lock physical pages in memory so the OS cannot page them out. Without LPIM, memory pressure from other processes can cause SQL Server pages to be paged to disk — catastrophic for performance.

LPIM is controlled by Windows User Rights, not sp_configure:

  • Grant Lock pages in memory to the SQL Server service account
  • Requires restart to take effect

Should You Enable LPIM?

ScenarioRecommendation
Dedicated SQL Server with ≥ 32 GB RAMYes, enable LPIM
SQL Server sharing a host with other servicesYes, but set max server memory carefully
Small dev/test instanceNot required
Azure SQL / SQL MINot applicable (managed by platform)

Verify LPIM is Active

SELECT locked_page_allocations_kb
FROM sys.dm_os_process_memory;
-- Non-zero means LPIM is active and pages are locked

[!NOTE] LPIM without correct max server memory is dangerous: SQL Server will consume all RAM and never release it, starving the OS. Set both together.


Optimize for Ad Hoc Workloads

EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;

EXEC sp_configure 'optimize for ad hoc workloads', 1;
RECONFIGURE;

When enabled, SQL Server stores only a small stub in plan cache on first execution of an ad-hoc batch. The full plan is stored on second execution (when it's proven to be reused). This prevents single-use plans from filling the plan cache with plans that will never be reused.

Enable this on every instance, always. There is no downside. The overhead of the stub is negligible; the savings on busy OLTP systems with ORMs generating varied SQL can be dramatic (plan cache from 2 GB to 200 MB is common).

-- Before enabling: check how much of plan cache is single-use
SELECT
    objtype,
    COUNT(*) AS plan_count,
    SUM(size_in_bytes) / 1024 / 1024 AS size_mb,
    SUM(CAST(usecounts AS BIGINT)) AS total_uses
FROM sys.dm_exec_cached_plans
GROUP BY objtype
ORDER BY size_mb DESC;

-- More detail: what % are used only once
SELECT
    COUNT(*) AS total_plans,
    SUM(CASE WHEN usecounts = 1 THEN 1 ELSE 0 END) AS single_use_plans,
    CAST(SUM(CASE WHEN usecounts = 1 THEN 1 ELSE 0 END) AS FLOAT)
        / COUNT(*) * 100 AS pct_single_use
FROM sys.dm_exec_cached_plans
WHERE objtype = 'Adhoc';

Backup Compression Default

EXEC sp_configure 'backup compression default', 1;
RECONFIGURE;

Enables compression for all backups by default (SQL Server 2008+, Standard Edition+). Typically reduces backup size by 50–70% for transaction-heavy OLTP data. Adds CPU overhead during backup (~5–15% depending on data compressibility).

Individual backup jobs can still override:

BACKUP DATABASE MyDB TO DISK = '...'
WITH COMPRESSION;    -- override to force compression

BACKUP DATABASE MyDB TO DISK = '...'
WITH NO_COMPRESSION; -- override to disable compression

[!NOTE] SQL Server 2014 backup checksum default (also added in 2014) validates backup integrity during the backup operation. Enable it alongside backup compression:

EXEC sp_configure 'backup checksum default', 1;
RECONFIGURE;

Database Mail and CLR

-- Enable Database Mail
EXEC sp_configure 'Database Mail XPs', 1;
RECONFIGURE;

-- Enable CLR (required for CLR objects)
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE;

-- CLR strict security (2017+) - do not disable
-- Requires SAFE/EXTERNAL_ACCESS assemblies to be signed
-- Even with clr strict security = 1, use certificate signing rather than TRUSTWORTHY
EXEC sp_configure 'clr strict security', 1;
RECONFIGURE;

[!NOTE] SQL Server 2017 clr strict security = 1 is the default in SQL Server 2017+. It requires all CLR assemblies (even SAFE) to be authorized by a login with UNSAFE ASSEMBLY permission, backed by a server certificate or asymmetric key. Do not set to 0 — use the certificate signing approach instead for any CLR objects you deploy.


Remote Connections and Protocols

-- Ad hoc distributed queries (OPENROWSET/OPENDATASOURCE)
EXEC sp_configure 'Ad Hoc Distributed Queries', 1;
RECONFIGURE;

-- Ole Automation (sp_OACreate) - avoid
EXEC sp_configure 'Ole Automation Procedures', 0;  -- keep disabled

-- xp_cmdshell - use only with proxy account, disable when not in use
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
-- Note: always configure a proxy account for xp_cmdshell (sp_xp_cmdshell_proxy_account)
-- Otherwise it runs as the SQL Server service account

Resource Governor

Resource Governor limits CPU and memory resources for specific workloads, preventing runaway queries from starving other workloads.

Architecture

External Pool (2016+, for R/Python external scripts)
     ↑
Internal Pool (SQL Server itself)
     ↑
[Pool 1] OLTP       [Pool 2] Reporting    [default pool]
     ↑                    ↑
[WG: web-apps]    [WG: reports]    [default workload group]
     ↑
[Classifier Function] → routes incoming sessions to workload groups

Classifier Function

The classifier function is a T-SQL scalar function in the master database that returns a workload group name. Called on every new connection. Must be fast — it runs in every login path.

-- Step 1: Create resource pools
CREATE RESOURCE POOL OltpPool
WITH (
    MIN_CPU_PERCENT = 10,      -- guaranteed minimum CPU %
    MAX_CPU_PERCENT = 80,      -- hard cap: cannot exceed this
    MIN_MEMORY_PERCENT = 5,    -- reserved memory % (from pool budget)
    MAX_MEMORY_PERCENT = 70    -- max memory from SQL Server's budget
);

CREATE RESOURCE POOL ReportPool
WITH (
    MIN_CPU_PERCENT = 0,
    MAX_CPU_PERCENT = 40,
    MIN_MEMORY_PERCENT = 0,
    MAX_MEMORY_PERCENT = 40
);

-- Step 2: Create workload groups
CREATE WORKLOAD GROUP OltpGroup
WITH (
    IMPORTANCE = HIGH,
    MAX_DOP = 4,
    GROUP_MAX_REQUESTS = 200    -- concurrent request limit; 0 = unlimited
)
USING OltpPool;

CREATE WORKLOAD GROUP ReportGroup
WITH (
    IMPORTANCE = LOW,
    MAX_DOP = 8,
    REQUEST_MAX_CPU_TIME_SEC = 300,   -- kill query after 5 minutes CPU time
    REQUEST_MAX_MEMORY_GRANT_PERCENT = 25
)
USING ReportPool;

-- Step 3: Create classifier function
CREATE FUNCTION dbo.rg_classifier()
RETURNS sysname
WITH SCHEMABINDING
AS
BEGIN
    DECLARE @wg sysname = 'default';

    -- Route by application name
    IF APP_NAME() LIKE '%ReportServer%'
        SET @wg = 'ReportGroup';
    ELSE IF APP_NAME() IN ('MyWebApp', 'MobileAPI')
        SET @wg = 'OltpGroup';

    -- Route by login
    IF SUSER_SNAME() = 'etl_service_account'
        SET @wg = 'OltpGroup';

    RETURN @wg;
END;
GO

-- Step 4: Register classifier function
ALTER RESOURCE GOVERNOR
WITH (CLASSIFIER_FUNCTION = dbo.rg_classifier);

-- Step 5: Activate all changes
ALTER RESOURCE GOVERNOR RECONFIGURE;

External Resource Pools (2016+ for R/Python)

CREATE EXTERNAL RESOURCE POOL DataSciencePool
WITH (
    MAX_CPU_PERCENT = 20,
    MAX_MEMORY_PERCENT = 20,
    MAX_PROCESSES = 4
);

ALTER EXTERNAL RESOURCE POOL [default]
WITH (MAX_CPU_PERCENT = 10);

ALTER RESOURCE GOVERNOR RECONFIGURE;

Monitoring Resource Governor

-- Current pool statistics
SELECT
    pool_id, name,
    min_cpu_percent, max_cpu_percent,
    min_memory_percent, max_memory_percent,
    used_memory_kb, target_memory_kb,
    active_worker_count
FROM sys.dm_resource_governor_resource_pools;

-- Workload group statistics
SELECT
    g.name AS workload_group,
    p.name AS resource_pool,
    g.total_request_count,
    g.total_cpu_usage_ms,
    g.request_count,
    g.active_worker_count,
    g.total_queued_request_count
FROM sys.dm_resource_governor_workload_groups g
JOIN sys.dm_resource_governor_resource_pools p
    ON g.pool_id = p.pool_id;

-- Which sessions are in which workload group
SELECT
    s.session_id, s.login_name, s.program_name,
    g.name AS workload_group,
    p.name AS resource_pool,
    r.status, r.cpu_time, r.memory_usage * 8 AS memory_kb
FROM sys.dm_exec_sessions s
JOIN sys.dm_resource_governor_workload_groups g
    ON s.group_id = g.group_id
JOIN sys.dm_resource_governor_resource_pools p
    ON g.pool_id = p.pool_id
LEFT JOIN sys.dm_exec_requests r
    ON s.session_id = r.session_id
WHERE s.is_user_process = 1;

Resource Governor vs MAXDOP vs Query Hints

MechanismScopeGranularityRuntime impact
Global MAXDOPServerAll queriesNo per-query overhead
Database scoped MAXDOPDatabaseAll queries in DBNo per-query overhead
OPTION (MAXDOP N)QueryIndividual queryNone
Resource Governor MAX_DOPWorkload groupWorkload classificationClassifier runs at login
REQUEST_MAX_CPU_TIME_SECWorkload groupLong query killEnforced by RG thread

Database-Scoped Configurations

Introduced in SQL Server 2016, ALTER DATABASE SCOPED CONFIGURATION controls per-database behavior that previously required instance-level changes or query hints.

-- View current database-scoped settings
SELECT name, value, value_for_secondary
FROM sys.database_scoped_configurations
ORDER BY name;

-- Common configurations
ALTER DATABASE SCOPED CONFIGURATION SET MAXDOP = 4;
ALTER DATABASE SCOPED CONFIGURATION FOR SECONDARY SET MAXDOP = 2;

-- Legacy cardinality estimator for one database (all other DBs use new CE)
ALTER DATABASE SCOPED CONFIGURATION SET LEGACY_CARDINALITY_ESTIMATION = ON;

-- Disable parameter sniffing for this database
ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = OFF;  -- use with caution

-- Disable Query Store hints for this database
ALTER DATABASE SCOPED CONFIGURATION SET QUERY_STORE_QUERY_HINTS = OFF;

-- Accelerated database recovery (2019+ Enterprise)
ALTER DATABASE MyDB SET ACCELERATED_DATABASE_RECOVERY = ON;

-- Batch mode on rowstore (enable or disable IQP feature)
ALTER DATABASE SCOPED CONFIGURATION SET BATCH_MODE_ON_ROWSTORE = OFF;

-- DOP feedback (2022+)
ALTER DATABASE SCOPED CONFIGURATION SET DOP_FEEDBACK = ON;

-- CE feedback (2022+)
ALTER DATABASE SCOPED CONFIGURATION SET CE_FEEDBACK = ON;

[!NOTE] SQL Server 2016 Database-scoped configurations were introduced in SQL Server 2016 (compat level 130+). They allow per-database tuning without affecting other databases — prefer them over instance-level trace flags whenever possible.

Key Database-Scoped Settings Reference

SettingDefaultWhen to change
MAXDOP0 (inherits global)Per-database tuning
LEGACY_CARDINALITY_ESTIMATIONOFFAfter compat upgrade regression
PARAMETER_SNIFFINGONRarely — causes "average plan" problem
QUERY_OPTIMIZER_HOTFIXESOFFEnable to get QO fixes without compat change
ACCELERATED_DATABASE_RECOVERYOFFEnable for fast recovery/version store in user DB
PAUSED_RESUMABLE_INDEX_ABORT_DURATION_MINUTES1 dayFor long index builds
DOP_FEEDBACKOFF in 2022Enable if IQP is on and QS is READ_WRITE
CE_FEEDBACKOFF in 2022Enable if IQP is on and QS is READ_WRITE

NUMA Topology and Affinity

Modern servers have multiple NUMA (Non-Uniform Memory Access) nodes. Optimal SQL Server configuration respects NUMA boundaries.

Key Principles

  1. SQL Server auto-detects NUMA and creates scheduler sets per node
  2. Memory allocations prefer local NUMA node (lower latency)
  3. Soft NUMA (2016+) can partition logical CPUs into smaller nodes to reduce hot-spot contention on high-CPU-count servers

View NUMA Topology

-- NUMA nodes
SELECT node_id, node_state_desc, memory_node_id,
       processor_group, cpu_count
FROM sys.dm_os_nodes
WHERE node_state_desc != 'ONLINE DAC';

-- Schedulers per node
SELECT scheduler_id, cpu_id, status, is_online, parent_node_id
FROM sys.dm_os_schedulers
WHERE scheduler_id < 255
ORDER BY parent_node_id, scheduler_id;

-- Memory per NUMA node
SELECT memory_node_id,
       virtual_address_space_reserved_kb / 1024 AS reserved_mb,
       virtual_address_space_committed_kb / 1024 AS committed_mb,
       locked_page_allocations_kb / 1024 AS locked_mb
FROM sys.dm_os_memory_nodes
WHERE memory_node_id < 64;  -- exclude internal nodes

Soft NUMA (2016+)

-- Soft NUMA auto-configuration (2016+, ON by default for servers > 8 cores)
-- Check current state
SELECT name, value_in_use
FROM sys.configurations
WHERE name = 'automatic soft-NUMA disabled';
-- 0 = auto soft-NUMA enabled (recommended)
-- 1 = disabled

-- Set MAXDOP = logical CPUs per soft-NUMA node for best parallelism control

CPU Affinity

CPU/IO affinity masks are rarely needed. Windows scheduler handles CPU allocation efficiently for most workloads. Only configure affinity when:

  • Running multiple SQL Server instances on one host (split CPUs between instances)
  • Isolating specific CPUs for OS use on very large servers
-- Affinity mask is bitmask of CPUs SQL Server can use
-- Use SQL Server Configuration Manager (GUI) for large CPU counts
-- Only T-SQL for <= 64 CPUs:
EXEC sp_configure 'affinity mask', 255;  -- CPUs 0-7 only (binary 11111111)
RECONFIGURE;

Trace Flags Reference

Trace flags modify SQL Server behavior, typically for diagnostics or enabling/disabling specific features. They should be documented when applied and reviewed at each upgrade.

[!NOTE] Many trace flags that were required in older versions are now default behavior in SQL Server 2016+ or are superseded by database-scoped configurations. Always verify whether a trace flag still applies to your version.

Startup Trace Flags

Apply startup flags via SQL Server Configuration Manager → SQL Server Service → Startup Parameters, or via SQLSERVR.EXE -T flag.

-- View active trace flags
DBCC TRACESTATUS(-1);  -- all globally active flags

-- Enable trace flag globally (session)
DBCC TRACEON(4199, -1);   -- -1 means global scope

-- Disable trace flag globally
DBCC TRACEOFF(4199, -1);

Important Trace Flags

FlagEffectVersion Notes
1117Grow all files in filegroup uniformly when one hits autogrowthDefault in 2016+ for tempdb; no longer needed for tempdb on 2016+
1118Force uniform extent allocations (reduces SGAM/GAM contention)Default in 2016+; no longer needed
1204Deadlock participants (less detail than 1222)Use XE xml_deadlock_report instead
1222Deadlock details in XML format in error logPrefer XE system_health or XE deadlock session
2371Dynamic statistics threshold (SQRT formula) on SQL Server 2012–2014Default in 2016+ at compat 130; not needed on 2016+
3226Suppress "BACKUP DATABASE successfully processed" messages in error logUseful for log backup noise; safe to enable
4136Disable parameter sniffing; every query plan uses "average" row estimateAlternative to OPTIMIZE FOR UNKNOWN; use with extreme caution
4199Enable all QO fixes from CUs/SPs that are gated behind the flagConsider QUERY_OPTIMIZER_HOTFIXES database-scoped setting instead
7412Enable lightweight query profiling infrastructureDefault ON in 2019+; not needed
8048Force NUMA CPU-level partitioning of memory objects (NUMA hot-spot fix)Only for 16+ core NUMA servers on older versions; rarely needed
9481Force legacy CE (70) regardless of compat levelPrefer LEGACY_CARDINALITY_ESTIMATION database-scoped setting
9488Reverts specific CE 2014 change (multi-statement TVF estimates)Very specific; only if this exact regression is confirmed
9567Enable parallel plan for AG seeding2016+ only; improves initial AG sync speed
10316Create additional indexes on internal temporal history staging tableOnly if temporal table queries are slow on history table joins
11023Use last known good plan (experimental)SQL Server 2022+; use Query Store forced plans instead

Safe Default Trace Flags

For most production instances, the only trace flag worth enabling at startup is:

  • 3226 — suppress successful backup messages (noise reduction)

Everything else should be handled via database-scoped configuration or resolved at root cause rather than suppressed with a flag.


Monitoring Configuration State

Full Configuration Audit

-- All non-default configurations
SELECT
    name,
    value,
    value_in_use,
    minimum,
    maximum,
    description,
    is_dynamic,
    is_advanced
FROM sys.configurations
WHERE value != value_in_use  -- pending restart required
   OR (value != 0 AND name IN (
       'optimize for ad hoc workloads',
       'backup compression default',
       'backup checksum default',
       'max server memory (MB)',
       'max degree of parallelism',
       'cost threshold for parallelism'
   ))
ORDER BY name;

Pending Restart Configuration

-- Settings changed but not yet in use (requires restart)
SELECT name, value AS configured_value, value_in_use AS current_value
FROM sys.configurations
WHERE value <> value_in_use
ORDER BY name;

Resource Governor Classifier

-- Current RG classifier function
SELECT classifier_function_id, is_enabled
FROM sys.resource_governor_configuration;

-- Full RG topology
SELECT
    p.name AS pool,
    g.name AS workload_group,
    g.importance,
    g.max_dop,
    g.request_max_cpu_time_sec,
    g.request_max_memory_grant_percent,
    p.min_cpu_percent, p.max_cpu_percent,
    p.min_memory_percent, p.max_memory_percent
FROM sys.resource_governor_workload_groups g
JOIN sys.resource_governor_resource_pools p
    ON g.pool_id = p.pool_id
ORDER BY p.name, g.name;

Memory State

-- Memory grant pending (queries waiting for memory)
SELECT * FROM sys.dm_exec_query_resource_semaphores;

-- Buffer pool usage by database
SELECT
    DB_NAME(database_id) AS db_name,
    COUNT(*) * 8 / 1024 AS buffer_pool_mb,
    COUNT(*) AS page_count
FROM sys.dm_os_buffer_descriptors
WHERE database_id > 4
GROUP BY database_id
ORDER BY page_count DESC;

-- Current memory clerks (top consumers)
SELECT TOP 20
    type,
    name,
    memory_node_id,
    pages_kb / 1024 AS mb_allocated
FROM sys.dm_os_memory_clerks
ORDER BY pages_kb DESC;

-- LPIM and page fault rates
SELECT
    physical_memory_in_use_kb / 1024 AS phys_mem_in_use_mb,
    locked_page_allocations_kb / 1024 AS lpim_locked_mb,
    page_fault_count
FROM sys.dm_os_process_memory;

Azure SQL Considerations

SettingAzure SQL DatabaseAzure SQL Managed Instance
max server memoryManaged by platformManaged by platform
max degree of parallelismAvailable via db-scoped configAvailable globally and via db-scoped
cost threshold for parallelismNot exposedExposed via sp_configure
Resource GovernorNot availableAvailable
Trace flagsLimited set via DBCC TRACEONBroader support
LPIMNot applicableNot applicable
Soft NUMANot applicableManaged by platform
optimize for ad hoc workloadsAlways onAvailable
backup compression defaultManaged backups, no manual backupAvailable

For Azure SQL Database, use ALTER DATABASE SCOPED CONFIGURATION for per-database tuning — it is the primary configuration surface available.


Common Patterns

Initial Server Configuration Checklist

-- Recommended initial configuration for a new SQL Server instance
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;

-- 1. Memory: adjust MB value based on RAM calculation above
EXEC sp_configure 'max server memory (MB)', 28672;  -- example: 28 GB on 32 GB server

-- 2. MAXDOP: adjust based on CPU topology
EXEC sp_configure 'max degree of parallelism', 8;   -- example: 8-core server

-- 3. Parallelism threshold
EXEC sp_configure 'cost threshold for parallelism', 50;

-- 4. Plan cache efficiency
EXEC sp_configure 'optimize for ad hoc workloads', 1;

-- 5. Backup quality
EXEC sp_configure 'backup compression default', 1;
EXEC sp_configure 'backup checksum default', 1;

-- 6. Reduce error log noise
DBCC TRACEON(3226, -1);

RECONFIGURE;
EXEC sp_configure 'show advanced options', 0;
RECONFIGURE;

Resource Governor for Reporting Isolation

-- Prevent report queries from taking > 50% CPU
CREATE RESOURCE POOL ReportPool
WITH (MAX_CPU_PERCENT = 50, MAX_MEMORY_PERCENT = 40);

CREATE WORKLOAD GROUP ReportGroup
WITH (
    IMPORTANCE = LOW,
    MAX_DOP = 4,
    REQUEST_MAX_CPU_TIME_SEC = 120  -- kill any report query running > 2 min CPU
)
USING ReportPool;

CREATE FUNCTION dbo.rg_classifier()
RETURNS sysname WITH SCHEMABINDING
AS
BEGIN
    IF APP_NAME() LIKE '%SSRS%' OR APP_NAME() LIKE '%ReportServer%'
        RETURN 'ReportGroup';
    RETURN 'default';
END;
GO

ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = dbo.rg_classifier);
ALTER RESOURCE GOVERNOR RECONFIGURE;

Configuration Drift Detection

-- Compare running config against expected values
DECLARE @expected TABLE (name sysname, expected_value sql_variant);
INSERT @expected VALUES
    ('max server memory (MB)', 28672),
    ('max degree of parallelism', 8),
    ('cost threshold for parallelism', 50),
    ('optimize for ad hoc workloads', 1),
    ('backup compression default', 1);

SELECT
    e.name,
    e.expected_value,
    c.value_in_use AS current_value,
    CASE WHEN e.expected_value = c.value_in_use THEN 'OK' ELSE 'DRIFT' END AS status
FROM @expected e
JOIN sys.configurations c ON c.name = e.name;

Gotchas

  1. Never set max server memory to unlimited on a shared host. The OS will page SQL Server memory, making the server perform as if running on disk.

  2. priority boost = 1 is a trap. Setting SQL Server to high priority starves the OS's own threads (network stack, disk I/O). It was removed from Books Online recommendations in SQL Server 2005 and causes instability — never enable it.

  3. RECONFIGURE WITH OVERRIDE is not a magic incantation. It bypasses range validation but does not bypass logical errors. Use it only for the handful of settings documented to require it (e.g., fill factor = 0).

  4. Changing MAXDOP does not re-plan cached plans. Existing cached plans were compiled with the old MAXDOP. Run DBCC FREEPROCCACHE (with caution) or wait for natural plan expiry.

  5. cost threshold for parallelism affects plan cache invalidation. Raising it from 5 to 50 means previously-parallel cached plans won't change until they're recompiled. Flush cache after changing this setting.

  6. Resource Governor classifier function must always return a valid workload group. If it returns NULL or a non-existent group name, the session goes to the default workload group. Test the classifier before enabling Resource Governor.

  7. Resource Governor changes require ALTER RESOURCE GOVERNOR RECONFIGURE. CREATE/ALTER RESOURCE POOL changes are staged; RECONFIGURE makes them live. Sessions already connected keep their old classification — only new connections are affected.

  8. Trace flags survive restarts only if added to startup parameters. DBCC TRACEON sets the flag for the current instance lifetime but is lost on restart unless added to the startup parameters in Configuration Manager.

  9. sp_configure changes are binary — no per-database override for some settings. Settings like xp_cmdshell and clr enabled are instance-wide. Plan your feature toggles accordingly.

  10. Database-scoped PARAMETER_SNIFFING = OFF is dangerous. It forces the optimizer to use average cardinality estimates for all parameterized queries — the "average plan problem". Use Query Store forced plans or OPTIMIZE FOR UNKNOWN hints for specific sniffing victims instead.

  11. max worker threads = 0 auto-calculates, but the formula can be insufficient on systems with thousands of concurrent connections. If you see high THREADPOOL waits, increasing this value (e.g., to 2000–4000) may help — but investigate the root cause (connection pooling issues, long-running queries) first.

  12. min server memory > 0 prevents SQL Server from releasing memory to the OS. This is almost never the right setting. Leave it at 0.


See Also

  • references/32-performance-diagnostics.md — wait stats, sp_Blitz, query hints
  • references/31-intelligent-query-processing.md — IQP features and database-scoped configs
  • references/34-tempdb.md — tempdb sizing, memory-optimized metadata
  • references/30-query-store.md — plan forcing, DOP/CE feedback
  • references/48-database-mail.md — Database Mail XPs config
  • references/43-high-availability.md — AG readable secondary memory considerations

Sources

references

01-syntax-ddl.md

02-syntax-dql.md

03-syntax-dml.md

04-ctes.md

05-views.md

06-stored-procedures.md

07-functions.md

08-indexes.md

09-columnstore-indexes.md

10-partitioning.md

11-custom-data-types.md

12-custom-defaults-rules.md

13-transactions-locking.md

14-error-handling.md

15-principals-permissions.md

16-security-encryption.md

17-temporal-tables.md

18-in-memory-oltp.md

19-json-xml.md

20-full-text-search.md

21-graph-tables.md

22-ledger-tables.md

23-dynamic-sql.md

24-string-date-math-functions.md

25-null-handling.md

26-collation.md

27-cursors.md

28-statistics.md

29-query-plans.md

30-query-store.md

31-intelligent-query-processing.md

32-performance-diagnostics.md

33-extended-events.md

34-tempdb.md

35-dbcc-commands.md

36-data-compression.md

37-change-tracking-cdc.md

38-auditing.md

39-triggers.md

40-service-broker-queuing.md

41-replication.md

42-database-snapshots.md

43-high-availability.md

44-backup-restore.md

45-linked-servers.md

46-polybase-external-tables.md

47-cli-bulk-operations.md

48-database-mail.md

49-configuration-tuning.md

50-sql-server-agent.md

51-2022-features.md

52-2025-features.md

53-migration-compatibility.md

54-linux-containers.md

SKILL.md

tile.json