SQL High CPU is one of the most important performance issues when monitoring SQL Server
High CPU usage in SQL Server issue may be caused by slow query performance, load on server and/or some configuration settings which affects entire application performance also.
Any High SQL Server CPU usage may be caused due to issues related to windows (OS) or SQL Server itself
In this article, we will go through, how to resolve or fix High CPU usage from SQL Server side.
Check Fragmentation on Database tablesFirst check, which are major databases for your SQL instances. Run SP_Helpdb in query optimizer which will provide you information about all database size information as below.

Run the script below to check fragmented table indexes for databases as below
SELECT OBJECT_NAME(ips.OBJECT_ID) ,i.NAME ,ips.index_id ,index_type_desc ,avg_fragmentation_in_percent ,page_count FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'SAMPLED') ips INNER JOIN sys.indexes i ON (ips.object_id = i.object_id) AND (ips.index_id = i.index_id) ORDER BY avg_fragmentation_in_percent DESC
As per output,If avg_fragmentation_in_percent >30% and page_count >1000 then Rebuild Indexes
Alter Index <Index_name> on <Table_Name> RebuildE.g.
Alter Index AK_Product_rowguid on production.product RebuildIf avg_fragmentation_in_percent <30% then Reorganize Indexes
Alter Index <Index_name> on <Table_Name> REORGANIZEE.g.
ALTER INDEX AK_Product_ProductNumber ON Production.Product REORGANIZE Update Statistics for TableTo improve he query execution plan, the query optimizer automatically updates statistics as necessary. When statistics are updated, the query optimizer provides an accurate execution plan which helps to read data more efficiently.
Use the below script to check outdated Statistics for tables of databases.
select tbls.name as table_name, i.name as index_name, (select max(rowcnt) from sysindexes i2 where i.id = i2.id and i2.indid < 2) as rowcnt, convert(DECIMAL(18,8), convert(DECIMAL(18,8),i.rowmodctr) / convert(DECIMAL(18,8),(select max(rowcnt) from sysindexes i2 where i.id = i2.id and i2.indid < 2))) as ModifiedPercent, stats_date( i.id, i.indid ) as lastStatsUpdate from sysindexes i inner join sysobjects tbls on i.id = tbls.id inner join sysusers schemas on tbls.uid = schemas.uid inner join information_schema.tables tl on tbls.name = tl.table_name and schemas.name = tl.table_schema and tl.table_type='BASE TABLE' where 0 < i.indid and i.indid < 255 and table_schema <> 'sys' and i.rowmodctr <> 0
Check the LastUpdated column and update statistics of tables which are not updated since many days as shown above fig.
Use the below script to update statistics in above cases
UPDATE STATISTICS <Table_Name> <Statistics_name> Check value of Max Degree of Parallelism & Cost Threshold for ParallelismWhen SQL Server runs on a computer with more than one processor or CPU, that is the number of processors employed to run a single statement, for each query that has a parallel execution plan. You can use the max degree of parallelism option to limit the number of processors to use for parallel plan execution and to prevent run-away queries from impacting SQL Server performance by using all available CPUs.
If Max degree Parallelism (MaxDop) is zero, means it is taking all processor to run single SQL statement in parallel.
How to check MaxDOP & Cost Threshold for Parallelism valueRight Click SQL Instance > Go to Properties > Advanced

There are two main reasons that we need to consider for High CPU
Cost threshold for parallelism Max degree parallelism Cost threshold for parallelismSet Cost Threshold for Parallelism value to 50
Max degree parallelismHow to decide Max Degree Parallelism value?
Run the below script on your SQL instance, which will advise you, what value of MAXDOP is suitable for your server and set that value
declare @hyperthreadingRatio bit declare @logicalCPUs int declare @HTEnabled int declare @physicalCPU int declare @SOCKET int declare @logicalCPUPerNuma int declare @NoOfNUMA int select @logicalCPUs = cpu_count -- [Logical CPU Count] ,@hyperthreadingRatio = hyperthread_ratio --[Hyperthread Ratio] ,@physicalCPU = cpu_count / hyperthread_ratio -- [Physical CPU Count] ,@HTEnabled = case when cpu_count > hyperthread_ratio then 1 else 0 end -- HTEnabled from sys.dm_os_sys_info option (recompile); select @logicalCPUPerNuma = COUNT(parent_node_id) -- [NumberOfLogicalProcessorsPerNuma] from sys.dm_os_schedulers where [status] = 'VISIBLE ONLINE' and parent_node_id < 64 group by parent_node_id option (recompile); select @NoOfNUMA = count(distinct parent_node_id) from sys.dm_os_schedulers -- find NO OF NUMA Nodes where [status] = 'VISIBLE ONLINE' and parent_node_id < 64 -- Report the recommendations .... select --- 8 or less processors and NO HT enabled case when @logicalCPUs < 8 and @HTEnabled = 0 then 'MAXDOP setting should be : ' + CAST(@logicalCPUs as varchar(3)) --- 8 or more processors and NO HT enabled when @logicalCPUs >= 8 and @HTEnabled = 0 then 'MAXDOP setting should be : 8' --- 8 or more processors and HT enabled and NO NUMA when @logicalCPUs >= 8 and @HTEnabled = 1 and @NoofNUMA = 1 then 'MaxDop setting should be : ' + CAST(@logicalCPUPerNuma / @physicalCPU as varchar(3)) --- 8 or more processors and HT enabled and NUMA when @logicalCPUs >= 8 and @HTEnabled = 1 and @NoofNUMA > 1 then 'MaxDop setting should be : ' + CAST(@logicalCPUPerNuma / @physicalCPU as varchar(3)) else '' end as RecommendationsOnce the above script has been executed, it will provide suggestions for MAXDOP value.
