diff --git a/src/PlanViewer.App/PlanViewer.App.csproj b/src/PlanViewer.App/PlanViewer.App.csproj
index dc22182..1ab55a9 100644
--- a/src/PlanViewer.App/PlanViewer.App.csproj
+++ b/src/PlanViewer.App/PlanViewer.App.csproj
@@ -6,7 +6,7 @@
app.manifestEDD.icotrue
- 1.4.1
+ 1.4.2Erik DarlingDarling Data LLCPerformance Studio
diff --git a/src/PlanViewer.Core/Services/PlanAnalyzer.cs b/src/PlanViewer.Core/Services/PlanAnalyzer.cs
index d89458d..c2b2fe6 100644
--- a/src/PlanViewer.Core/Services/PlanAnalyzer.cs
+++ b/src/PlanViewer.Core/Services/PlanAnalyzer.cs
@@ -133,22 +133,72 @@ private static void AnalyzeStatement(PlanStatement stmt, AnalyzerConfig cfg)
{
var reason = stmt.NonParallelPlanReason switch
{
+ // User/config forced serial
"MaxDOPSetToOne" => "MAXDOP is set to 1",
+ "QueryHintNoParallelSet" => "OPTION (MAXDOP 1) hint forces serial execution",
+ "ParallelismDisabledByTraceFlag" => "Parallelism disabled by trace flag",
+
+ // Passive — optimizer chose serial, nothing wrong
"EstimatedDOPIsOne" => "Estimated DOP is 1 (the plan's estimated cost was below the cost threshold for parallelism)",
+
+ // Edition/environment limitations
"NoParallelPlansInDesktopOrExpressEdition" => "Express/Desktop edition does not support parallelism",
+ "NoParallelCreateIndexInNonEnterpriseEdition" => "Parallel index creation requires Enterprise edition",
+ "NoParallelPlansDuringUpgrade" => "Parallel plans disabled during upgrade",
+ "NoParallelForPDWCompilation" => "Parallel plans not supported for PDW compilation",
+ "NoParallelForCloudDBReplication" => "Parallel plans not supported during cloud DB replication",
+
+ // Query constructs that block parallelism (actionable)
"CouldNotGenerateValidParallelPlan" => "Optimizer could not generate a valid parallel plan. Common causes: scalar UDFs, inserts into table variables, certain system functions, or OPTION (MAXDOP 1) hints",
- "QueryHintNoParallelSet" => "OPTION (MAXDOP 1) hint forces serial execution",
+ "TSQLUserDefinedFunctionsNotParallelizable" => "T-SQL scalar UDF prevents parallelism. Rewrite as an inline table-valued function, or on SQL Server 2019+ check if the UDF is eligible for automatic inlining",
+ "CLRUserDefinedFunctionRequiresDataAccess" => "CLR UDF with data access prevents parallelism",
+ "NonParallelizableIntrinsicFunction" => "Non-parallelizable intrinsic function in the query",
+ "TableVariableTransactionsDoNotSupportParallelNestedTransaction" => "Table variable transaction prevents parallelism. Consider using a #temp table instead",
+ "UpdatingWritebackVariable" => "Updating a writeback variable prevents parallelism",
+ "DMLQueryReturnsOutputToClient" => "DML with OUTPUT clause returning results to client prevents parallelism",
+ "MixedSerialAndParallelOnlineIndexBuildNotSupported" => "Mixed serial/parallel online index build not supported",
+ "NoRangesResumableCreate" => "Resumable index create cannot use parallelism for this operation",
+
+ // Cursor limitations
+ "NoParallelCursorFetchByBookmark" => "Cursor fetch by bookmark cannot use parallelism",
+ "NoParallelDynamicCursor" => "Dynamic cursors cannot use parallelism",
+ "NoParallelFastForwardCursor" => "Fast-forward cursors cannot use parallelism",
+
+ // Memory-optimized / natively compiled
+ "NoParallelForMemoryOptimizedTables" => "Memory-optimized tables do not support parallel plans",
+ "NoParallelForDmlOnMemoryOptimizedTable" => "DML on memory-optimized tables cannot use parallelism",
+ "NoParallelForNativelyCompiledModule" => "Natively compiled modules do not support parallelism",
+
+ // Remote queries
+ "NoParallelWithRemoteQuery" => "Remote queries cannot use parallelism",
+ "NoRemoteParallelismForMatrix" => "Remote parallelism not available for this query shape",
+
_ => stmt.NonParallelPlanReason
};
- // Only warn (not info) when the user explicitly forced serial execution
- var isExplicit = stmt.NonParallelPlanReason is "MaxDOPSetToOne" or "QueryHintNoParallelSet";
+ // Actionable: user forced serial, or something in the query blocks parallelism
+ // that could potentially be rewritten. Info: passive (cost too low) or
+ // environmental (edition, upgrade, cursor type, memory-optimized).
+ var isActionable = stmt.NonParallelPlanReason is
+ "MaxDOPSetToOne" or "QueryHintNoParallelSet" or "ParallelismDisabledByTraceFlag"
+ or "CouldNotGenerateValidParallelPlan"
+ or "TSQLUserDefinedFunctionsNotParallelizable"
+ or "CLRUserDefinedFunctionRequiresDataAccess"
+ or "NonParallelizableIntrinsicFunction"
+ or "TableVariableTransactionsDoNotSupportParallelNestedTransaction"
+ or "UpdatingWritebackVariable"
+ or "DMLQueryReturnsOutputToClient"
+ or "NoParallelCursorFetchByBookmark"
+ or "NoParallelDynamicCursor"
+ or "NoParallelFastForwardCursor"
+ or "NoParallelWithRemoteQuery"
+ or "NoRemoteParallelismForMatrix";
stmt.PlanWarnings.Add(new PlanWarning
{
WarningType = "Serial Plan",
Message = $"Query running serially: {reason}.",
- Severity = isExplicit ? PlanWarningSeverity.Warning : PlanWarningSeverity.Info
+ Severity = isActionable ? PlanWarningSeverity.Warning : PlanWarningSeverity.Info
});
}
diff --git a/src/PlanViewer.Web/Pages/Index.razor b/src/PlanViewer.Web/Pages/Index.razor
index 91154d3..67ed2a7 100644
--- a/src/PlanViewer.Web/Pages/Index.razor
+++ b/src/PlanViewer.Web/Pages/Index.razor
@@ -7,6 +7,7 @@
Free SQL Server Query Plan Analysis
Paste or upload a .sqlplan file. Your plan XML never leaves your browser.