Share via

Performance Degradation after moving from SQL 2019 to SQL 2025

Mantini, Ronald M 6 Reputation points
2026-05-07T17:39:23.1566667+00:00

I am in the processing of upgrading our SQL environment from 2019 to 2025. We are troubleshooting perf issues in our pre prod env right now before upgrading production and I have a fairly simple query that loads 1.6m rows to a temp table and typically takes around 2-3 minutes to complete. This code has not changed for many years, its part of a larger aggregation and is called hundreds of times during the full run.

When running under SQL 2019 compatibility the query finishes in about 2.5-3mins.

After upgrading to 2025 and updating the db compatibility to 170 (2025) this query runs for over 3 hours, every time. At first I assumed it was due to the upgrade and creating a new plan or similar but I can reproduce this instantly every time simply by changing the Compat level back to 170.

I tested at version 160 (2022) and actually get the best perf, as low as 1 minute.

The from and Joins are below. I have been looking at the execution plans and between 2019 and 2025 they don't look much different, all the slowness is in a Hash Match(right Anti Semi Join). The fastest option (2022 compat) uses a Nested Loops (Left Outer Join) instead.

I'm baffled, so far this is the only piece of code amongst hundreds of queries we have noticed having this problem. There may be more but this one has me stumped so I haven't moved on to the rest yet. Any feedback from someone as to why I am seeing this would be appreciated. I just did the upgrade 2 days ago and started the query store, but I have been flipping compatibility so often I probably need to just let it run for a while, but I can't let it run when its taking 3 hours during this aggregation :).


INTO #TempTable2
FROM DB1.dbo.TABLE1 FRE (NOLOCK)
JOIN DB2.dbo.TABLE2 SI
	ON FRE.fac= SI.fac
JOIN #TempTable1 FSV
	ON FSV.fac= FRE.fac
	AND FSV.trid= FRE.trid
LEFT JOIN DB3.dbo.Table3 AEX  NOLOCK
	ON SI.[Site] = AEX.[Site]
	AND (FRE.obj1 = AEX.obj OR FRE.obj2 = AEX.obj)
	AND FRE.start_time BETWEEN AEX.StartTime AND AEX.EndTime
WHERE AEX.obj IS NULL

SQL Server Database Engine

1 answer

Sort by: Most helpful
  1. Q&A Assist
    2026-05-07T17:39:46.85+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    The behavior matches a query-plan regression caused by optimizer and Cardinality Estimator (CE) changes at higher compatibility levels. The query itself is unchanged, but the CE and optimizer behavior differ between compat 150 (SQL 2019), 160 (SQL 2022), and 170 (SQL 2025), which can lead to different join strategies and dramatic performance differences.

    Key points from the scenario and context:

    1. Compatibility level drives CE and optimizer behavior
      • From SQL Server 2014 onward, new CE and optimizer changes are tied to database compatibility level, not just engine version.
      • Different compat levels can choose different join types and plans for the same query and data. In your case:
        • Compat 150 (2019) → ~2.5–3 minutes
        • Compat 160 (2022) → ~1 minute (Nested Loops join)
        • Compat 170 (2025) → >3 hours (Hash Match Right Anti Semi Join)
      • This is consistent with known patterns where newer CE assumptions and optimizer changes improve many workloads but regress some specific queries.
    2. Plan choice differences are a known cause of regressions after upgrade
      • Documentation highlights that upgrading compatibility level can introduce CE changes and different plan choices that may degrade performance for some queries, even if most workloads improve.
      • The 2016/2017 example in the context (compat 120 vs 130) shows exactly this pattern: a plan flips from parallel to serial and performance collapses for a large result set. The recommended mitigations there apply directly to this scenario as well: revert compat level or force the better plan.
    3. Recommended mitigation pattern Based on the documented guidance for similar regressions:
      1. Run the database at a lower compatibility level as a baseline
      • Since compat 160 gives the best performance for this query, one pragmatic option is to keep the database at 160 on SQL Server 2025 while the regression is investigated.
      • This is explicitly recommended in similar upgrade regressions (e.g., reverting from 160 to 150 when moving to SQL 2022/2025) to confirm that the issue is optimizer/CE-related rather than hardware or configuration.
      1. Use Query Store to identify and fix the regression
      • Enable Query Store (if not already) and let it capture plans at both compat levels.
      • Identify this query in Query Store and compare:
        • Runtime statistics
        • Plans at compat 160 vs 170
      • If the compat 160 plan is consistently better, use Query Store to force that plan while running at compat 170. This is the recommended pattern in the documentation for CE/compat-level regressions.
      1. Alternatively, force the good plan with a hint
      • If Query Store cannot be used (or as a temporary workaround), capture the good plan (from compat 160 or 150) and apply it via USE PLAN query hint in the specific statement. The context explicitly calls out USE PLAN as a way to keep the older, better plan while running at the newer compatibility level.
    4. Broader troubleshooting steps If more queries show regressions as compat 170 is used:
      • Confirm environment parity:
        • Same data, statistics, and indexes between pre-prod and prod.
        • Same hardware characteristics (CPU count, memory, power plan) and SQL Server build level.
      • Compare plans and statistics:
        • Ensure statistics are up to date with similar sampling on all relevant tables.
        • Check whether the new CE assumptions at higher compat levels are leading to misestimates (e.g., join cardinality, filter selectivity) that push the optimizer toward an expensive Hash Match Anti Semi Join.
      • If regressions are limited to a small set of queries, use Query Store plan forcing per-query rather than lowering compat level for the entire database.
    5. Strategy for production rollout
      • Before upgrading production, run representative workloads in pre-prod with:
        • Compat 150 and 160 to establish baselines.
        • Compat 170 with Query Store enabled and capture regressions.
      • For each regressed query, either:
        • Fix the query/indexing if a clear pattern emerges, or
        • Force the known-good plan via Query Store.
      • If regressions are widespread and cannot be mitigated quickly, consider running production at compat 160 on SQL Server 2025 initially, then gradually move to 170 as specific regressions are addressed.

    In summary, the behavior is consistent with a CE/optimizer change at compat 170 that leads to a poor join strategy for this query. The documented and supported mitigations are: temporarily use a lower compatibility level (160), and/or use Query Store or USE PLAN to force the better plan while remaining on SQL Server 2025.


    References:

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.