Repository navigation
plan_correction: stage the Query Store views to #temps instead of joining them live - #2765
Conversation
…ning them live #2673 applied half the sp_QuickieStore rule here -- it staged sys.dm_db_tuning_recommendations into #pm_plan_correction_recs so the statistics-free DMV stopped driving a plan-store scan -- but the shipping SELECT went on LEFT JOINing sys.query_store_query, sys.query_store_query_text and sys.query_store_plan directly, which is the naive-catalog-view join the pattern exists to avoid, paid per database, per server, every cycle. Stage the Query Store side the same way sp_QuickieStore does (plan -> query -> text): #pm_plan_correction_plans keyed by the (query_id, last_good_plan_id) pair the final join needs, #pm_plan_correction_queries by query_id, and #pm_plan_correction_query_text by the queries temp's query_text_id. The shipping SELECT joins the three temps, so each catalog view is read exactly once, by a seek keyed off a materialised row count, and never appears on the JOIN side of the shipping query. Output columns and their order are byte-identical to before (the reader ordinals follow the SELECT list), and the LEFT semantics are preserved: a recommendation whose plan has aged out of Query Store still lands with NULL Query Store columns rather than being dropped. Fixes #2764. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Reviewed the diff against
No security, missing-index-DMV, or performance concerns. |
…onitor into fix/plan-correction-qs-materialize # Conflicts: # CHANGELOG.md
ReviewTraced the join-semantics claim by hand: for every staged temp ( Style checks against CONTRIBUTING.md: AS on every new table alias, column_name = expression aliasing, aligned AND, the SELECT 1/0 EXISTS idiom (consistent with ~40 other uses across the codebase, e.g. the upgrades/* scripts), explicit DROP TABLE IF EXISTS guards plus unconditional drops at the tail -- all match. One thing I checked rather than flagged: none of the three new SELECT ... INTO statements carry OPTION(RECOMPILE), and CONTRIBUTING.md is explicit that a statement added to an existing batch needs its own hint. But this batch's existing precedent (the #pm_plan_correction_recs staging SELECT, and the shipping SELECT itself per #2759/#2760) already omits it on the same reasoning: static literal SQL with no runtime parameters, and since the batch references #temp tables that are dropped/recreated fresh every single call, SQL Server can't reuse a cached plan across executions anyway -- the object dependency forces an effective recompile every time. The new statements are consistent with that existing rationale, so I don't think this is a real gap, just flagging that I considered it. No correctness, security, or performance regressions found. The 1/0 EXISTS pattern, temp-table lifecycle, and staging keys all check out against the shipping join. |
Fixes #2764.
Problem
[#2673] applied half of the
sp_QuickieStorerule to this collector: it stagedsys.dm_db_tuning_recommendationsinto#pm_plan_correction_recs, so the statistics-free DMV stopped driving a plan-store scan. But the shipping SELECT inPayloadBodyTextwent right on joining the catalog views live:That is the naive-catalog-view join the pattern exists to avoid, paid per database, per server, every cycle. [#2762] removed a pointless
OPTION(RECOMPILE)from the same query — cleanup, not this.Fix
Stage the Query Store side the way
sp_QuickieStoredoes (#query_store_plan->#query_store_query->#query_store_query_text). Three temps pull only the rows the already-small recommendation set names:#pm_plan_correction_planssys.query_store_plan(query_id, last_good_plan_id)from the recs — the exact pair the final join needs#pm_plan_correction_queriessys.query_store_queryquery_idfrom the recs#pm_plan_correction_query_textsys.query_store_query_textquery_text_idfrom the queries tempThe shipping SELECT then LEFT JOINs the three temps. Each catalog view is read exactly once, by a seek keyed off a materialised row count, and never appears on the JOIN side of the shipping query.
What did not change
dev— verified by extracting the shipping SELECT list from both1ca4f299and this branch and comparing: identical, 38 columns. The reader ordinals inReadfollow that list, so nothing downstream shifts.QueryStoreJoins_AreLeft_SoAnAgedOutPlanCannotDropTheRecommendationstill pins this, now following the joins to the temps.OPTION(RECOMPILE)reintroduced.Tests
New
QueryStoreViews_AreStagedToTemps_NeverJoinedLivepins the shape: noJOIN sys.query_store_*anywhere in the body, each view read exactly once into its temp, the staging keys, and every temp dropped (guard + tail).Verification scope — stated plainly
PerformanceMonitor.CollectorsandLite.Testsboth build clean on macOS (0 errors; no new warnings from the touched files).1ca4f299's pre-fix SQL text: 7/7 RED. They fail without this change, which is what makes them worth having.get_collector_cost(collector_name=plan_correction)once this ships.