基数估计 (CE) 反馈

适用于: SQL Server 2022 (16.x) 及更高版本 Azure SQL 数据库Azure SQL 托管实例

从 SQL Server 2022 (16.x) 开始,基数估算 (CE) 反馈属于智能查询处理功能系列的一部分,用于解决重复查询中因 CE 模型假设错误而导致的次优查询执行计划问题。 从旧版数据库引擎升级时,此方案有助于降低与默认 CE 相关的回归风险。

由于没有一组单一的 CE 模型和假设可以适应多种客户工作负载和数据分布,因此 CE 反馈提供了一个基于查询运行时特征的适应性解决方案。 CE 反馈可识别并使用更适合给定查询和数据分布的模型假设,以提高查询执行计划的质量。 目前,CE 反馈可识估计行数和实际行数相差非常大的计划运算符。 出现重大模型估计错误,并且有可行的替代模型可供尝试时将会应用反馈。

有关其他查询反馈功能,请参阅内存授予反馈并行度 (DOP) 反馈

了解基数估算 (CE) 反馈

基数估算 (CE) 是查询优化器估算在查询计划的每个级别处理的总行数的方法。 SQL Server 中的基数估计主要派生自手动或自动创建索引或统计信息时所创建的直方图。 有时,SQL Server 还使用查询的约束信息和逻辑重写来确定基数。

不同版本的数据库引擎根据数据的分布和查询方式使用不同的 CE 模型假设。 有关详细信息,请参阅 CE 的版本

基数估算 (CE) 反馈功能的实现

基数估算 (CE) 反馈会随着时间推移逐渐学习哪种 CE 模型假设是最优的,然后采用历史上最准确的假设:

  1. CE 反馈识别与模型相关的假设并评估它们对于重复查询是否准确

  2. 如果假设看起来不正确,则使用查询计划测试同一查询的后续执行,该查询计划会调整有影响的 CE 模型假设并验证其是否有帮助。 我们通过查看计划运算符的实际行数与估计行数来识别错误。 并非所有错误都可以通过 CE 反馈中提供的模型变体来纠正。

  3. 如果能够提高计划质量,那么会用一个使用适当的 USE HINT 查询提示 来调整估算模型的查询计划取代旧查询计划,新的查询计划通过 查询存储提示 机制实现。

仅保留经过验证的反馈。 如果调整后的模型假设导致性能回归,则不将 CE 反馈用于该查询。 在这种情况下,用户取消的查询也被视为回归。

基数估算 (CE) 反馈应用场景

基数估算 (CE) 反馈用于解决在使用默认 CE(CE120 或更高版本)时,由错误的 CE 模型假设导致的观察到的回归问题,并且还可以有选择地使用不同的模型假设。 应用场景包括关联、联接包含和优化器行目标。

基数估算 (CE) 反馈相关性

当查询优化器估计给定表或视图上谓词的选择性或满足所述谓词的行数时,它使用关联模型假设。 这些假设可能是指谓词满足以下条件:

  • 完全独立(CE70 的默认设置),其中基数通过乘以所有谓词的选择性来计算。

  • 部分相关(CE120 及更高版本的默认设置),其中基数是通过指数退避法的一种变体计算的,将选择性从最高到最低的谓词进行排序。

  • 完全相关,其中基数通过使用所有谓词中的最小选择率来计算。

以下示例在数据库兼容性设置为 120 或更高时使用部分关联:

USE AdventureWorks2016_EXT;
GO
SELECT AddressID, AddressLine1, AddressLine2
FROM Person.Address
WHERE StateProvinceID = 79 AND City = N'Redmond';
GO

当数据库兼容级别设置为 160 且使用默认相关性时,CE 反馈会根据估计基数与实际行数相比是偏低还是偏高,每次一步地尝试将相关性朝正确的方向调整。 如果实际行数大于估计基数,则使用完全相关性。 如果实际行数小于估计的基数,则使用完全独立性。

有关详细信息,请参阅 CE 的版本

基数估算 (CE) 反馈联接包含

当查询优化器估计联接谓词和适用的筛选器谓词的选择性时,它将使用包含假设模型。 这些假设包括:

  • 简单包含(CE70 的默认设置)假设联接谓词是完全相关的,即先计算筛选选择性,再将联接选择性纳入计算。

  • 基本包含(CE120 及更高版本的默认设置)假设连接谓词与下游过滤器之间不存在相关性,其中首先计算连接选择性,然后将过滤器选择性纳入考虑。

以下示例在数据库兼容性设置为 120 或更高时使用基础包含:

USE AdventureWorksDW2016_EXT;
GO
SELECT *
FROM dbo.FactCurrencyRate AS f
INNER JOIN dbo.DimDate AS d ON f.DateKey = d.DateKey
WHERE d.MonthNumberOfYear = 7 AND f.CurrencyKey = 3 AND f.AverageRate > 1;
GO

有关详细信息,请参阅 CE 的版本

基数估算 (CE) 反馈和查询优化器行目标

当查询优化器估计执行计划的基数时,它通常假设必须处理所有表中所有符合条件的行。 然而,某些查询模式会导致查询优化器搜索能返回更少行数的执行计划,以减少 I/O。 如果查询通过使用 TOPINEXISTS 关键字、FAST 查询提示或 SET ROWCOUNT 语句,指定了在运行时可能要求的目标行数(行目标),则该行目标将用作查询优化过程的一部分,如以下示例所示:

USE AdventureWorks2016_EXT;
GO
SELECT TOP 1 soh.*
FROM Sales.SalesOrderHeader AS soh
INNER JOIN Sales.SalesOrderDetail AS sod ON soh.SalesOrderID = sod.SalesOrderID;
GO

应用行目标计划时,查询计划中的估计行数会减少,因为查询优化器假设必须处理较少的行数才能达到行目标。

虽然行目标对于某些查询模式是一种有益的优化策略,但如果数据分布不均匀,扫描的页数可能比估算的多,这意味着行目标变得低效。 当检测到这种低效情况时,CE 反馈可禁用行目标扫描并启用查找操作。

在执行计划中,没有专门针对 CE 反馈的属性,但会列出一个与 查询存储 提示对应的属性。 查看 QueryStoreStatementHintSource 是否为 CE feedback

有关基数估算 (CE) 反馈的注意事项

  • 要启用基数估算 (CE) 反馈,请将执行该查询时所连接数据库的兼容级别设置为 160。 对于使用 CE 反馈的每个数据库,必须启用查询存储并处于 READ_WRITE 模式。

  • 若要在数据库级别禁用 CE 反馈,请使用 CE_FEEDBACK数据库范围的配置。 例如,在用户数据库中:

    ALTER DATABASE SCOPED CONFIGURATION
    SET CE_FEEDBACK = OFF;
    
  • 若要在查询级别禁用 CE 反馈,请使用 DISABLE_CE_FEEDBACK 查询提示。

CE 反馈活动可通过 query_feedback_analysisquery_feedback_validation XEvents 查看。

由 CE 反馈设置的提示可通过 sys.query_store_query_hints 目录视图进行跟踪。

可以使用 sys.query_store_plan_feedback 目录视图跟踪反馈信息。

如果查询具有通过查询存储强制执行的查询计划,则 CE 反馈不会用于该查询。

如果查询使用硬编码查询提示或在使用用户设置的查询存储提示,则 CE 反馈不会用于该查询。 有关详细信息,请参阅 查询提示查询存储提示

从 SQL Server 2022 (16.x) 开始,当启用次要副本的查询存储时,CE 反馈对于可用性组中的次要副本不是副本感知的。 CE 反馈目前仅对主要副本有益。 故障转移时,应用于主要副本或次要副本的反馈将会丢失。 从 SQL Server 2025 (17.x) 开始,查询存储可在辅助可用性组副本上使用。 有关详细信息,请参阅可读辅助副本的查询存储

基数估算 (CE) 反馈的持久性

适用于:SQL Server 2022 (16.x) 及更高版本、Azure SQL 数据库和 Azure SQL 托管实例

基数估计 (CE) 反馈可检测应保留行目标优化的场景,并通过以查询存储提示的形式将其保存在查询存储中来维持此更改。 新的优化用于未来的查询执行。 CE 反馈在行目标优化查询模式之外的其他场景中仍然存在,如 反馈场景 中所述。 CE 反馈当前处理 CE 相关性模型使用的谓词选择性应用场景,以及 CE 包含模型处理的联接谓词应用场景。

此功能是在 SQL Server 2022 (16.x) 中引入的;但是,此性能增强适用于满足以下条件的查询:数据库兼容级别为 160 或更高,或者使用值为 160 或更高的 QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_n 提示;并且数据库已启用 查询存储,且其处于“读写”状态。

基数估算 (CE) 反馈的已知问题

问题 发现日期 状态 解决日期
在特定条件下为 SQL Server 2022 (16.x) 应用累积更新 8 后,SQL Server 性能降低。 启用 CE 反馈后,您可能会遇到计划缓存内存使用率显著上升,并且 CPU 使用率也会意外增加。 2023 年 12 月 已解决 2024 年 4 月 22 日 (CU 12)

已知问题详细信息

在特定条件下为 SQL Server 2022 应用累积更新 8 后,SQL Server 性能降低

从 SQL Server 2022 (16.x) 累积更新 8 开始,SQL Server 可能会显示 CPU 和内存利用率意外增加。 此外,还可能会观察到 RESOURCE_SEMAPHORE_QUERY_COMPILE 等待的增加。 你可能还会注意到,正在使用的计划缓存对象数量持续增加,并逐渐接近计划缓存限制,而使用 ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHEDBCC FREESYSTEMCACHEDBCC FREEPROCCACHE 等方法手动清除计划缓存也无济于事。 只有少数客户观察到这种行为。

此问题不会影响所有工作负载,具体取决于已生成的不同计划数,以及符合 CE 反馈功能参与资格的计划数。 当 CE 反馈在分析计划运算符中是否存在重大的模型估算错误时,可能会出现被引用的计划在此分析阶段被解除引用的情况。 这种场景会阻止使用常用的最近最少使用 (LRU) 算法将计划从内存中删除。 LRU 机制是 SQL Server 强制实施计划逐出策略的一种方法。 此外,在系统面临内存压力时,SQL Server 也会从内存中移除计划。 当 SQL Server 尝试移除未正确取消引用的计划时,它将无法从计划缓存中移除这些计划,导致缓存继续增长。 不断增长的缓存可能会导致额外的编译,最终会使用更多 CPU 和内存。 有关更多信息,请参阅计划缓存的内部机制

症状:随着时间的推移,SQL 计划或对象计划中正在使用并被标记为“”的计划缓存条目数会增加到 50,000 或更多。 如果观察到计划缓存条目开始接近此级别,并且 CPU 利用率意外增加,则可能说明系统遇到了此问题。 SQL Server 2022 (16.x) 累积更新 12 中提供了修正。 请参阅 KB5033663

要监视系统正在使用的计划缓存条目数,以下示例可用作现有计划缓存条目数的时间点视图。 例如,定期观察标记为“脏”的计划缓存条目的数量是监视这种现象的一种方法。

SELECT CASE WHEN mce.[name] LIKE 'SQL Plan%' THEN 'SQL Plans'
            WHEN mce.[name] LIKE 'Object Plan%' THEN 'Object Plans'
            ELSE '[All other cache stores]'
       END AS PlanType,
       COUNT(*) AS [Number of plans marked to be removed]
FROM sys.dm_os_memory_cache_entries AS mce
     LEFT OUTER JOIN sys.dm_exec_cached_plans AS ecp
         ON mce.memory_object_address = ecp.memory_object_address
WHERE mce.is_dirty = 1
      AND ecp.bucketid IS NULL
GROUP BY CASE WHEN mce.[name] LIKE 'SQL Plan%' THEN 'SQL Plans'
              WHEN mce.[name] LIKE 'Object Plan%' THEN 'Object Plans'
              ELSE '[All other cache stores]'
         END;

另一组查询还会提供与上例相同的信息,同时允许观察其他性能指标。 计划缓存命中率降低,与每秒批处理请求数相关的编译数也随之减少。以下查询可用于一段时间内监视系统。 密切关注 缓存命中率(异常下降)、正在使用的缓存对象(数量增加到接近 50,000 且未见回落),以及 每秒批处理请求数 低于预期,而 每秒编译次数 却在上升。

--SQL Plan (Adhoc and Prepared plans)
SELECT CASE WHEN [counter_name] = 'Cache Hit Ratio' THEN 'Cache Hit Ratio'
            WHEN [counter_name] = 'Cache Object Counts' THEN 'Cache Object Counts'
            WHEN [counter_name] = 'Cache Objects in use' THEN 'Cache Objects in use'
            WHEN [counter_name] = 'Cache Pages' THEN 'Cache Pages'
       END AS [SQLServer:Plan Cache (SQL Plans)],
       CASE WHEN [counter_name] = 'Cache Hit Ratio' THEN NULL
            ELSE FORMAT(cntr_value, '#,###')
       END AS [Counter Value],
       CASE WHEN [counter_name] = 'Cache Hit Ratio' THEN
                 FORMAT(TRY_CONVERT (DECIMAL (5, 2), (cntr_value * 1.0 / NULLIF ((SELECT cntr_value
                     FROM sys.dm_os_performance_counters
                     WHERE [object_name] LIKE '%:Plan Cache%'
                           AND [counter_name] = 'Cache Hit Ratio Base'
                           AND instance_name LIKE 'SQL Plan%'), 0))), '0.00%')
       END AS [SQL Plan Cache Hit Ratio]
FROM sys.dm_os_performance_counters
WHERE [object_name] LIKE '%:Plan Cache%'
      AND [counter_name] IN ('Cache Hit Ratio', 'Cache Object Counts', 'Cache Objects in use', 'Cache Pages')
      AND instance_name LIKE 'SQL Plan%'
ORDER BY [counter_name];

--Module/Stored procedure based plans
SELECT CASE WHEN [counter_name] = 'Cache Hit Ratio' THEN 'Cache Hit Ratio'
            WHEN [counter_name] = 'Cache Object Counts' THEN 'Cache Object Counts'
            WHEN [counter_name] = 'Cache Objects in use' THEN 'Cache Objects in use'
            WHEN [counter_name] = 'Cache Pages' THEN 'Cache Pages'
       END AS [SQLServer:Plan Cache (Object Plans)],
       CASE WHEN [counter_name] = 'Cache Hit Ratio' THEN NULL
            ELSE FORMAT(cntr_value, '#,###')
       END AS [Counter Value],
       CASE WHEN [counter_name] = 'Cache Hit Ratio' THEN
                 FORMAT(TRY_CONVERT (DECIMAL (5, 2), (cntr_value * 1.0 / NULLIF ((SELECT cntr_value
                     FROM sys.dm_os_performance_counters
                     WHERE [object_name] LIKE '%:Plan Cache%'
                           AND [counter_name] = 'Cache Hit Ratio Base'
                           AND instance_name LIKE 'Object Plan%'), 0))), '0.00%')
       END AS [SQL Plan Cache Hit Ratio]
FROM sys.dm_os_performance_counters
WHERE [object_name] LIKE '%:Plan Cache%'
      AND [counter_name] IN ('Cache Hit Ratio', 'Cache Object Counts', 'Cache Objects in use', 'Cache Pages')
      AND instance_name LIKE 'Object Plan%'
ORDER BY [counter_name];

SELECT CASE WHEN [counter_name] = 'Batch Requests/sec' THEN 'Batch Requests/sec'
            WHEN [counter_name] = 'SQL Compilations/sec' THEN 'SQL Compilations/sec'
       END AS [SQLServer:SQL Statistics],
       FORMAT(cntr_value, '#,###') AS [Counter Value]
FROM sys.dm_os_performance_counters
WHERE [object_name] LIKE '%:SQL Statistics%'
      AND counter_name IN ('Batch Requests/sec', 'SQL Compilations/sec');

解决方法

如果系统在应用累积更新 12 KB5033663 后继续遇到上述症状,则可能是在数据库级别禁用了 CE 反馈功能。

要回收此问题占用的计划缓存内存,需要重新启动 SQL Server 实例。 禁用 CE 反馈功能后,可以执行此重启操作。 若要在数据库级别禁用 CE 反馈,请使用 CE_FEEDBACK数据库范围的配置。 例如,在用户数据库中:

ALTER DATABASE SCOPED CONFIGURATION
SET CE_FEEDBACK = OFF;

反馈和报告问题

有关反馈或问题,请发送电子邮件至 [email protected]