Status: Reviewed (deep-dive v1)
Stack note: Blazor boards call usp_* (SQL-12) with filters — indexes must match sargable predicates and covering needs, or GetOpenBoard / heat maps time out under estate growth.
Pairs with: SQL-03 (index/stats maintenance), SQL-07 / SPIKE-05 (perf triage), SQL-12–15 (proc shape, #temp, dynamic SQL, errors).
Goal: Practical index patterns for board/read procs — not a full indexing encyclopedia.
| Board pattern | Index lean toward |
|---|---|
| Filter by status + order by date | (Status, FirstSeenAt) or filtered IX on open rows |
| Filter by instance then child rows | Leading SqlInstanceId + supporting cols |
| Heat map by RAG / last backup | Supporting IX on status + LastBackupAt |
| Point lookup by PK/id | Clustered / PK enough |
Optional filters (@Id IS NULL OR …) |
May need multiple IX; measure — don’t index every column |
Big #temp stage then join |
Index #temp after load (SQL-13) |
Good (index-friendly):
WHERE a.Status = @Status
AND a.FirstSeenAt >= @Since
Often bad:
WHERE ISNULL(a.Status, N'') = @Status
WHERE CONVERT(date, a.FirstSeenAt) = @Day
WHERE a.Title LIKE N'%' + @q + N'%' -- leading wildcard
WHERE (@Status IS NULL OR a.Status = @Status) -- can be OK; watch plans
Optional-param pattern is still preferred over dynamic SQL (SQL-14) — if the plan goes dumb, try:
OPTION (RECOMPILE) on the board proc when measured, or UI needs a thin column list — help the IX include them:
-- example: open/acked alerts by severity age
CREATE INDEX IX_Alert_Status_FirstSeen
ON dbo.Alert (Status, FirstSeenAt)
INCLUDE (AlertType, EntityKey, Title, SeverityCode, SqlInstanceId, AckedBy, LastSeenAt);
Filtered index when boards mostly show active rows:
CREATE INDEX IX_Alert_Active_FirstSeen
ON dbo.Alert (FirstSeenAt, SeverityCode)
INCLUDE (AlertType, Title, SqlInstanceId, Status)
WHERE Status IN (N'Open', N'Acked');
Rule: Match WHERE + ORDER BY first; INCLUDE what SELECT lists without exploding width.
| Proc / board | Likely keys |
|---|---|
usp_Alert_GetOpenBoard |
Alert(Status, FirstSeenAt) + AlertSeverity(SeverityCode) / SortOrder |
usp_Inventory_GetInstances |
SqlInstance(LastSeenAt), hostname/instance search carefully |
usp_Backup_GetHeatmap |
DB + RAG/last backup timestamps |
usp_Agent_GetFailBoard |
Job fail flags + LastRunAt |
usp_Alert_Evaluate |
Supporting IX for source flags; #Findings indexed if large |
Don’t create ten overlapping indexes “just in case” — add from Query Store / actual plans.
#temp in procsINSERT #Findings (…);
CREATE CLUSTERED INDEX CX ON #Findings (AlertType, EntityKey);
-- then merge/join
Stats on #temp beat naked @table for big stages (SQL-13).
UPDATE STATISTICS on hot warehouse tables (SQL-03) INCLUDE if lookup cost dominates RECOMPILE | Smell | Fix |
|---|---|
| Index per Blazor column | Design for real filters only |
SELECT * in proc |
Narrow list → easier covering |
| Function on column in WHERE | Compute persisted / fix predicate |
| Non-allowlisted dynamic ORDER BY | SQL-14 allowlist — indexes must match real sorts |
| Fixing perf with NOLOCK everywhere | Index + query shape first |
usp_* board has a documented supporting IX (or explicit “PK enough”) SQL Dude — SQL-16 Indexing for Procs v1