Skip to content

Commit ed64dbc

Browse files
Rule 38: suppress when query has explicit MAXDOP 2 hint
When the user explicitly sets OPTION (MAXDOP 2) in the query, the DOP cap is intentional and not the SQL Server Standard Edition batch-mode limitation, so the warning would be misleading. Suppress in that case (both the Standard-Edition-confirmed Warning path and the unknown-edition Info path). Mirrors the existing Rule 3 (Serial Plan) handling for MAXDOP 1 hints. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c8f888c commit ed64dbc

2 files changed

Lines changed: 58 additions & 17 deletions

File tree

src/PlanViewer.Core/Services/PlanAnalyzer.cs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -482,31 +482,39 @@ private static void AnalyzeStatement(PlanStatement stmt, AnalyzerConfig cfg, Ser
482482
if (!cfg.IsRuleDisabled(38) && stmt.DegreeOfParallelism == 2 && stmt.RootNode != null
483483
&& HasBatchModeNode(stmt.RootNode))
484484
{
485-
var editionKnown = !string.IsNullOrEmpty(serverMetadata?.Edition);
486-
if (editionKnown
487-
&& serverMetadata!.Edition!.Contains("Standard", StringComparison.OrdinalIgnoreCase))
485+
// Suppress when the user explicitly set MAXDOP 2 as a query hint — the DOP
486+
// cap is intentional, not the Standard Edition batch-mode limitation.
487+
var hasMaxdop2Hint = !string.IsNullOrEmpty(stmt.StatementText)
488+
&& Regex.IsMatch(stmt.StatementText, @"MAXDOP\s+2\b", RegexOptions.IgnoreCase);
489+
490+
if (!hasMaxdop2Hint)
488491
{
489-
// Server context confirms Standard Edition — check MAXDOP
490-
if (serverMetadata.MaxDop > 2)
492+
var editionKnown = !string.IsNullOrEmpty(serverMetadata?.Edition);
493+
if (editionKnown
494+
&& serverMetadata!.Edition!.Contains("Standard", StringComparison.OrdinalIgnoreCase))
495+
{
496+
// Server context confirms Standard Edition — check MAXDOP
497+
if (serverMetadata.MaxDop > 2)
498+
{
499+
stmt.PlanWarnings.Add(new PlanWarning
500+
{
501+
WarningType = "Standard Edition DOP Limitation",
502+
Message = $"DOP is limited to 2 because SQL Server Standard Edition caps parallelism at 2 when batch mode operators are present, even though MAXDOP is set to {serverMetadata.MaxDop}. Developer or Enterprise Edition would allow higher DOP in the same conditions.",
503+
Severity = PlanWarningSeverity.Warning
504+
});
505+
}
506+
}
507+
else if (!editionKnown)
491508
{
509+
// No server context, or edition unknown (e.g. collection failure) — suspect the limitation
492510
stmt.PlanWarnings.Add(new PlanWarning
493511
{
494512
WarningType = "Standard Edition DOP Limitation",
495-
Message = $"DOP is limited to 2 because SQL Server Standard Edition caps parallelism at 2 when batch mode operators are present, even though MAXDOP is set to {serverMetadata.MaxDop}. Developer or Enterprise Edition would allow higher DOP in the same conditions.",
496-
Severity = PlanWarningSeverity.Warning
513+
Message = "DOP is limited to 2 and the plan uses batch mode operators. This may be caused by the SQL Server Standard Edition limitation, which caps parallelism at 2 when batch mode is in use. If this server runs Standard Edition, Developer or Enterprise Edition would allow higher DOP.",
514+
Severity = PlanWarningSeverity.Info
497515
});
498516
}
499517
}
500-
else if (!editionKnown)
501-
{
502-
// No server context, or edition unknown (e.g. collection failure) — suspect the limitation
503-
stmt.PlanWarnings.Add(new PlanWarning
504-
{
505-
WarningType = "Standard Edition DOP Limitation",
506-
Message = "DOP is limited to 2 and the plan uses batch mode operators. This may be caused by the SQL Server Standard Edition limitation, which caps parallelism at 2 when batch mode is in use. If this server runs Standard Edition, Developer or Enterprise Edition would allow higher DOP.",
507-
Severity = PlanWarningSeverity.Info
508-
});
509-
}
510518
}
511519

512520
// Rules 25 (Ineffective Parallelism) and 31 (Parallel Wait Bottleneck) were removed.

tests/PlanViewer.Core.Tests/PlanAnalyzerTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -989,5 +989,38 @@ public void Rule38_ServerMetadataWithNullEdition_Dop2_BatchMode_EmitsInfo()
989989
Assert.Equal(PlanWarningSeverity.Info, warnings[0].Severity);
990990
}
991991

992+
[Fact]
993+
public void Rule38_StandardEdition_Dop2_BatchMode_MaxDop2QueryHint_NoWarning()
994+
{
995+
// User explicitly set OPTION (MAXDOP 2) — DOP cap is intentional, not the SE limit
996+
var stmt = BuildBatchModeDop2Statement();
997+
stmt.StatementText = "SELECT * FROM dbo.Fact OPTION (MAXDOP 2)";
998+
var plan = BuildSyntheticPlan(stmt);
999+
var metadata = new ServerMetadata
1000+
{
1001+
Edition = "Standard Edition (64-bit)",
1002+
MaxDop = 8
1003+
};
1004+
PlanAnalyzer.Analyze(plan, serverMetadata: metadata);
1005+
1006+
var warnings = stmt.PlanWarnings
1007+
.Where(w => w.WarningType == "Standard Edition DOP Limitation").ToList();
1008+
Assert.Empty(warnings);
1009+
}
1010+
1011+
[Fact]
1012+
public void Rule38_NoServerMetadata_Dop2_BatchMode_MaxDop2QueryHint_NoWarning()
1013+
{
1014+
// Same suppression applies when we don't know the edition either
1015+
var stmt = BuildBatchModeDop2Statement();
1016+
stmt.StatementText = "SELECT * FROM dbo.Fact OPTION (MAXDOP 2)";
1017+
var plan = BuildSyntheticPlan(stmt);
1018+
PlanAnalyzer.Analyze(plan);
1019+
1020+
var warnings = stmt.PlanWarnings
1021+
.Where(w => w.WarningType == "Standard Edition DOP Limitation").ToList();
1022+
Assert.Empty(warnings);
1023+
}
1024+
9921025
#endregion
9931026
}

0 commit comments

Comments
 (0)