Skip to content

Commit 640eb23

Browse files
Merge pull request #344 from erikdarlingdata/fix/rules-13-14-improvements
Rules 13+14: GetRangeThroughConvert detection, lazy spool threshold fix
2 parents 9928aa5 + a7d38ac commit 640eb23

2 files changed

Lines changed: 44 additions & 26 deletions

File tree

Dashboard/Services/PlanAnalyzer.cs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -351,40 +351,49 @@ private static void AnalyzeNode(PlanNode node, PlanStatement stmt)
351351
});
352352
}
353353

354-
// Rule 13: Mismatched data types (GetRangeWithMismatchedTypes)
355-
if (node.PhysicalOp == "Compute Scalar" &&
356-
!string.IsNullOrEmpty(node.DefinedValues) &&
357-
node.DefinedValues.Contains("GetRangeWithMismatchedTypes", StringComparison.OrdinalIgnoreCase))
354+
// Rule 13: Mismatched data types (GetRangeWithMismatchedTypes / GetRangeThroughConvert)
355+
if (node.PhysicalOp == "Compute Scalar" && !string.IsNullOrEmpty(node.DefinedValues))
358356
{
359-
node.Warnings.Add(new PlanWarning
357+
var hasMismatch = node.DefinedValues.Contains("GetRangeWithMismatchedTypes", StringComparison.OrdinalIgnoreCase);
358+
var hasConvert = node.DefinedValues.Contains("GetRangeThroughConvert", StringComparison.OrdinalIgnoreCase);
359+
360+
if (hasMismatch || hasConvert)
360361
{
361-
WarningType = "Data Type Mismatch",
362-
Message = "Implicit conversion due to mismatched data types. The column type does not match the parameter or literal type, forcing SQL Server to convert values at runtime. Fix the parameter type to match the column.",
363-
Severity = PlanWarningSeverity.Warning
364-
});
362+
var reason = hasMismatch
363+
? "Implicit conversion due to mismatched data types. The column type does not match the parameter or literal type, forcing SQL Server to convert values at runtime. Fix the parameter type to match the column."
364+
: "Implicit conversion through CONVERT/CAST on a column. SQL Server must convert values at runtime, which can prevent index seeks. Remove the conversion or add a computed column.";
365+
366+
node.Warnings.Add(new PlanWarning
367+
{
368+
WarningType = "Data Type Mismatch",
369+
Message = reason,
370+
Severity = PlanWarningSeverity.Warning
371+
});
372+
}
365373
}
366374

367375
// Rule 14: Lazy Table Spool unfavorable rebind/rewind ratio
376+
// Rebinds = cache misses (child re-executes), rewinds = cache hits (reuse cached result)
368377
if (node.LogicalOp == "Lazy Spool")
369378
{
370379
var rebinds = node.HasActualStats ? (double)node.ActualRebinds : node.EstimateRebinds;
371380
var rewinds = node.HasActualStats ? (double)node.ActualRewinds : node.EstimateRewinds;
372381
var source = node.HasActualStats ? "actual" : "estimated";
373382

374-
if (rebinds > 100 && (rewinds == 0 || rebinds * 2 >= rewinds))
383+
if (rebinds > 100 && rewinds < rebinds * 5)
375384
{
376-
var severity = rebinds > rewinds
385+
var severity = rewinds < rebinds
377386
? PlanWarningSeverity.Critical
378387
: PlanWarningSeverity.Warning;
379388

380389
var ratio = rewinds > 0
381-
? $"{rewinds / rebinds:F1}x more rewinds (cache hits) than rebinds (cache misses)"
390+
? $"{rewinds / rebinds:F1}x rewinds (cache hits) per rebind (cache miss)"
382391
: "no rewinds (cache hits) at all";
383392

384393
node.Warnings.Add(new PlanWarning
385394
{
386395
WarningType = "Lazy Spool Ineffective",
387-
Message = $"Lazy spool has unfavorable rebind/rewind ratio ({source}): {rebinds:N0} rebinds, {rewinds:N0} rewinds — {ratio}. The spool cache is not providing significant benefit.",
396+
Message = $"Lazy spool has low cache hit ratio ({source}): {rebinds:N0} rebinds, {rewinds:N0} rewinds — {ratio}. The spool cache is not earning its overhead.",
388397
Severity = severity
389398
});
390399
}

Lite/Services/PlanAnalyzer.cs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -351,40 +351,49 @@ private static void AnalyzeNode(PlanNode node, PlanStatement stmt)
351351
});
352352
}
353353

354-
// Rule 13: Mismatched data types (GetRangeWithMismatchedTypes)
355-
if (node.PhysicalOp == "Compute Scalar" &&
356-
!string.IsNullOrEmpty(node.DefinedValues) &&
357-
node.DefinedValues.Contains("GetRangeWithMismatchedTypes", StringComparison.OrdinalIgnoreCase))
354+
// Rule 13: Mismatched data types (GetRangeWithMismatchedTypes / GetRangeThroughConvert)
355+
if (node.PhysicalOp == "Compute Scalar" && !string.IsNullOrEmpty(node.DefinedValues))
358356
{
359-
node.Warnings.Add(new PlanWarning
357+
var hasMismatch = node.DefinedValues.Contains("GetRangeWithMismatchedTypes", StringComparison.OrdinalIgnoreCase);
358+
var hasConvert = node.DefinedValues.Contains("GetRangeThroughConvert", StringComparison.OrdinalIgnoreCase);
359+
360+
if (hasMismatch || hasConvert)
360361
{
361-
WarningType = "Data Type Mismatch",
362-
Message = "Implicit conversion due to mismatched data types. The column type does not match the parameter or literal type, forcing SQL Server to convert values at runtime. Fix the parameter type to match the column.",
363-
Severity = PlanWarningSeverity.Warning
364-
});
362+
var reason = hasMismatch
363+
? "Implicit conversion due to mismatched data types. The column type does not match the parameter or literal type, forcing SQL Server to convert values at runtime. Fix the parameter type to match the column."
364+
: "Implicit conversion through CONVERT/CAST on a column. SQL Server must convert values at runtime, which can prevent index seeks. Remove the conversion or add a computed column.";
365+
366+
node.Warnings.Add(new PlanWarning
367+
{
368+
WarningType = "Data Type Mismatch",
369+
Message = reason,
370+
Severity = PlanWarningSeverity.Warning
371+
});
372+
}
365373
}
366374

367375
// Rule 14: Lazy Table Spool unfavorable rebind/rewind ratio
376+
// Rebinds = cache misses (child re-executes), rewinds = cache hits (reuse cached result)
368377
if (node.LogicalOp == "Lazy Spool")
369378
{
370379
var rebinds = node.HasActualStats ? (double)node.ActualRebinds : node.EstimateRebinds;
371380
var rewinds = node.HasActualStats ? (double)node.ActualRewinds : node.EstimateRewinds;
372381
var source = node.HasActualStats ? "actual" : "estimated";
373382

374-
if (rebinds > 100 && (rewinds == 0 || rebinds * 2 >= rewinds))
383+
if (rebinds > 100 && rewinds < rebinds * 5)
375384
{
376-
var severity = rebinds > rewinds
385+
var severity = rewinds < rebinds
377386
? PlanWarningSeverity.Critical
378387
: PlanWarningSeverity.Warning;
379388

380389
var ratio = rewinds > 0
381-
? $"{rewinds / rebinds:F1}x more rewinds (cache hits) than rebinds (cache misses)"
390+
? $"{rewinds / rebinds:F1}x rewinds (cache hits) per rebind (cache miss)"
382391
: "no rewinds (cache hits) at all";
383392

384393
node.Warnings.Add(new PlanWarning
385394
{
386395
WarningType = "Lazy Spool Ineffective",
387-
Message = $"Lazy spool has unfavorable rebind/rewind ratio ({source}): {rebinds:N0} rebinds, {rewinds:N0} rewinds — {ratio}. The spool cache is not providing significant benefit.",
396+
Message = $"Lazy spool has low cache hit ratio ({source}): {rebinds:N0} rebinds, {rewinds:N0} rewinds — {ratio}. The spool cache is not earning its overhead.",
388397
Severity = severity
389398
});
390399
}

0 commit comments

Comments
 (0)