适用于:SQL Server
Azure SQL 数据库
Azure SQL 托管实例
Azure Synapse Analytics
Microsoft Fabric 预览版中的 SQL 数据库
查询优化器使用统计信息来创建可提高查询性能的查询计划。 对于大多数查询,查询优化器已为高质量查询计划生成必要的统计信息;在某些情况下,需要创建额外的统计信息或修改查询设计,以获得最佳结果。 本文讨论了用于高效使用查询优化统计信息的统计信息概念,并提供相关指南。
组件和概念
统计信息
用于查询优化的统计信息是二进制大型对象 (BLOB),其中包含有关表或索引视图的一列或多列中的值分布的统计信息。 查询优化器使用这些统计信息来估计查询结果中的基数或行数。 通过这些基数估计,查询优化器可以创建高质量的查询计划。 例如,根据你的谓词,查询优化器可以使用基数估计来选择索引查找运算符而不是消耗更多资源的索引扫描运算符,前提是这样做可以提高查询性能。
每个统计信息对象都在包含一个或多个表列的列表上创建,并且包括将值的分布显示在第一列的直方图。 在多列上的统计信息对象也会存储与各列中的值的相关性有关的统计信息。 这些相关性统计信息(或密度)根据列值的不同行数而得出。
Histogram
直方图度量数据集中每个非重复值的出现频率。 查询优化器根据统计信息对象第一个键列中的列值来计算直方图,它选择列值的方法是以统计方式对行进行抽样或对表或视图中的所有行执行完全扫描。 如果直方图是根据一组抽样行创建的,存储的总行数和非重复值总数则为估计值,且不必为整数。
Note
SQL Server 中的直方图仅针对统计信息对象的键列集中的第一列生成。
若要创建直方图,查询优化器将对列值进行排序,计算与每个非重复列值匹配的值数,然后将列值聚合到最多 200 个连续直方图梯级中。 每个直方图梯级都包含一个列值范围,后跟上限列值。 该范围包括介于两个边界值之间的所有可能列值,但不包括边界值自身。 最小排序列值是第一个直方图梯级的上限值。
有关详细信息,SQL Server 通过以下三个步骤从已排序的列值集创建直方图:
- 直方图初始化:在第一步中,处理从排序集的开头开始的一系列值,并收集最多 200 个 range_high_key、equal_rows、range_rows 和 distinct_range_rows 值(在此步骤中,range_rows 和 distinct_range_rows 始终为零)。 当用尽所有输入或已找到 200 个值时,第一步结束。
- 使用存储桶合并进行扫描:在第二步中,按排序顺序处理来自统计信息键的前导列的每个额外值;将每个逐次值添加到最后一个范围或在末尾创建一个新范围(这可能是因为已对输入值排序所致)。 如果创建了一个新范围,则一对现有相邻范围折叠为单个范围。 选择这一对范围,以便更大限度地减少信息丢失。 此方法使用最大差异算法,使直方图中的梯级减至最少,并同时最大化边界值之间的差异。 折叠范围后,梯级数在这整个步骤中保持为 200。
- 直方图合并:在第三步中,如果不会导致大量信息丢失,则可以合并更多范围。 直方图梯级数可以少于非重复值的数目,即使对于边界点少于 200 的列也是如此。 因此,即使该列包含超过 200 个唯一值,直方图具有的梯级数可能仍少于 200。 对于仅包含唯一值的列,则合并直方图至少包含三个步骤。
Note
如果使用样本而不是全扫描生成直方图,则 equal_rows、range_rows、distinct_range_rows 和 average_range_rows 的值是估算的,因此它们不需要是整数。
下面的关系图显示包含六个梯级的直方图。 第一个上限值左侧的区域是第一个梯级。
对于前一个示例中的每个直方图梯级:
粗线表示上限值 (range_high_key) 和上限值的出现次数 (equal_rows)
range_high_key 左侧的纯色区域表示列值范围和每个列值的平均出现次数 (average_range_rows)。 第一个直方图梯级的 average_range_rows 始终是 0。
点线表示用于估计范围中的非重复值总数 (distinct_range_rows) 和范围中的总值数 (range_rows) 的抽样值。 查询优化器使用 range_rows 和 distinct_range_rows 来计算 average_range_rows ,并且不存储采样值。
密度向量
密度 是有关给定列中重复项数或列组合的信息,计算结果为 1/(非重复值数)。 查询优化器使用密度提高根据相同表或索引视图返回多个列的查询的基数估计。 随着密度减小,值的选择性会增大。 例如,在一个代表汽车的表中,很多汽车出自同一制造商,但每辆车都有唯一的车辆识别号 (VIN)。 因为 VIN 的密度比制造商的密度更低,所以 VIN 索引比制造商索引更具选择性。
Note
频率是有关统计信息对象第一个键列中每个非重复值出现次数的信息,其计算公式为 row count * density。 最大频率 1 出现在具有唯一值的列中。
密度向量针对统计信息对象中的列的每个前缀包含一个密度。 例如,如果统计信息对象包含键列 CustomerId、ItemId 和 Price,则根据以下每个列前缀计算密度。
| 列前缀 | 计算密度所基于的对象 |
|---|---|
(CustomerId) |
具有 CustomerId 的匹配值的行 |
(CustomerId、 ItemId) |
具有 CustomerId 和 ItemId 的匹配值的行 |
(CustomerId、、ItemIdPrice) |
具有 CustomerId、ItemId 和 Price 的匹配值的行 |
筛选的统计信息
筛选统计信息可以提高以下从定义完善的数据子集选择数据的查询的查询性能。 筛选的统计信息使用筛选谓词来选择统计信息中包含的数据子集。 与全表统计信息相比,精心设计的筛选统计信息可以改进查询执行计划。 有关筛选器谓词的详细信息,请参阅 CREATE STATISTICS。 若要详细了解何时创建筛选的统计信息,请参阅本文中的何时创建统计信息部分。
统计信息选项
有几个选项会影响何时以及如何创建和更新统计信息。 仅可在数据库级别配置这些选项。
AUTO_CREATE_STATISTICS选项
在自动创建统计信息选项 AUTO_CREATE_STATISTICS 为 ON 时,查询优化器将根据需要在查询谓词中的单独列上创建统计信息,以便改进查询计划的基数估计。 这些单列统计信息在现有统计信息对象中尚未具有直方图的列上创建。 该 AUTO_CREATE_STATISTICS 选项不确定是否为索引创建统计信息。 此选项也不会生成筛选的统计信息。 它严格应用于全表的单列统计信息。
查询优化器通过使用 AUTO_CREATE_STATISTICS 选项创建统计信息时,统计信息名称以 _WA 开头。 可以使用下面的查询来确定查询优化器是否为查询谓词列创建了统计信息。
SELECT OBJECT_NAME(s.object_id) AS object_name,
COL_NAME(sc.object_id, sc.column_id) AS column_name,
s.name AS statistics_name
FROM sys.stats AS s
INNER JOIN sys.stats_columns AS sc
ON s.stats_id = sc.stats_id
AND s.object_id = sc.object_id
WHERE s.name LIKE '_WA%'
ORDER BY s.name;
AUTO_UPDATE_STATISTICS选项
当自动更新统计信息选项 AUTO_UPDATE_STATISTICS 为 ON 时,查询优化器确定统计信息何时可能过期,然后在查询使用统计信息时更新它们。 此操作亦称为“统计信息重新编译”。 统计信息将在修改(通过插入、更新、删除或合并操作更改表或索引视图中的数据分布)后过期。 查询优化器通过计算自最后统计信息更新后行修改的次数并且将行修改次数与某一阈值进行比较,确定统计信息何时可能过期。 阈值基于表基数,表基数可以定义为表或索引视图中的行数。
即使 AUTO_UPDATE_STATISTICS 选项为 OFF,也会根据行修改将统计信息标记为过期。 当 AUTO_UPDATE_STATISTICS 选项为 OFF 时,即使统计信息标记为过期,也不会更新统计信息。 计划继续使用过时的统计信息对象。 将 AUTO_UPDATE_STATISTICS 设置为 OFF 会导致生成并非最佳的查询计划以及查询性能下降。 建议将 AUTO_UPDATE STATISTICS 选项设置为 ON。
在 SQL Server 2014 (12.x) 之前,数据库引擎使用基于计算统计信息时表或索引视图中的行数的重新编译阈值。 无论表是临时还是永久,阈值都不同。
表类型 表基数 (n) 重新编译阈值(修改次数) Temporary n< 6 6 Temporary 6 <= n<= 500 500 Permanent n<= 500 500 临时或永久 n> 500 500 + (0.20 * n) 例如,如果表包含 2 万行,则计算是
500 + (0.2 * 20,000) = 4,500并且统计信息每 4,500 次修改更新一次。自 SQL Server 2016 (13.x) 起,并且在数据库兼容性级别 130 时,数据库引擎还使用递减的动态统计信息重新编译阈值,此阈值根据计算统计信息时的表基数进行调整。 通过此更改,大型表的统计信息会更频繁地更新。 不过,如果数据库兼容性级别低于 130,则应用 SQL Server 2014 (12.x) 阈值。
表类型 表基数 (n) 重新编译阈值(修改次数) Temporary n < 66 Temporary 6 <= n <= 500500 Permanent n <= 500500 临时或永久 n > 500MIN ( 500 + (0.20 * n), SQRT(1,000 * n) )例如,如果表中包含 200 万行,则计算为
500 + (0.20 * 2,000,000) = 400,500和SQRT(1,000 * 2,000,000) = 44,721中较小的一个。 这意味着统计信息每 44,721 次修改更新一次。
Important
在 SQL Server 2008 R2 (10.50.x) 到 SQL Server 2014 (12.x) 中,或者在数据库兼容性级别 120 和更低级别的 SQL Server 2016 (13.x) 和更高版本中,启用跟踪标志 2371,以便 SQL Server 使用降低的动态统计信息更新阈值。
虽然建议对所有场景都启用跟踪标志,但启用跟踪标志 2371 是可选的。 不过,可以遵循以下指导,在预 SQL Server 2016 (13.x) 环境中启用跟踪标志 2371:
- 如果使用的是 SAP 系统,请启用此跟踪。 有关详细信息,请参阅这个有关跟踪标志 2371 的博客。
- 如果由于当前自动更新的触发次数不够频繁而必须依赖夜间作业来更新统计信息,请考虑启用跟踪标志 2371 来将阈值调整为表基数。
查询优化器在编译查询和执行缓存查询计划前,检查是否存在过期的统计信息。 在编译查询之前,查询优化器使用查询谓词中的列、表和索引视图来确定哪些统计信息可能过期。 在执行缓存查询计划之前,数据库引擎会验证查询计划是否引用 up-to日期统计信息。
AUTO_UPDATE_STATISTICS 选项适用于为索引创建的统计信息对象、查询谓词中的单列以及使用 CREATE STATISTICS 语句创建的统计信息。 此选项也适用于筛选的统计信息。
可以使用 sys.dm_db_stats_properties 准确地跟踪在表中更改的行数,并决定是否要手动更新统计信息。
对于内存优化表,AUTO_UPDATE_STATISTICS 始终为 OFF。
AUTO_UPDATE_STATISTICS_ASYNC
异步统计信息更新选项 AUTO_UPDATE_STATISTICS_ASYNC 将确定查询优化器是使用同步统计信息更新还是使用异步统计信息更新。 默认情况下,异步统计信息更新选项为 OFF 状态,并且查询优化器以同步方式更新统计信息。 AUTO_UPDATE_STATISTICS_ASYNC 选项适用于为索引创建的统计信息对象、查询谓词中的单列以及使用 CREATE STATISTICS 语句创建的统计信息。
Note
要在 SQL Server Management Studio 中设置异步统计信息更新选项,需在“数据库属性”窗口的“选项”页中同时将“自动更新统计信息”和“自动异步更新统计信息”选项设置为“True”。
统计信息更新可以是同步(默认)或异步。
通过同步统计信息更新,查询始终以最新统计信息编译和执行。 如果统计信息过期,查询优化器会等待更新的统计信息,然后再编译和执行查询。
通过异步统计信息更新,即使现有统计信息已过期,查询也会使用现有统计信息进行编译。 如果在查询编译时统计信息过期,查询优化器可选择并非最优的查询计划。 统计信息通常会在不久之后更新。 在统计信息更新完成后编译的查询将受益于使用更新的统计信息。
执行更改数据分布的操作(例如截断表或对大部分行执行批量更新)时,考虑使用同步统计信息。 如果在完成操作后未手动更新统计信息,则使用同步统计信息可确保在对更改的数据执行查询之前统计数据是最新的。
在以下情况下,考虑使用异步统计信息来实现可预测性更高的查询响应时间:
你的应用程序频繁执行相同的查询、类似的查询或类似的缓存查询计划。 因为查询优化器可以执行传入查询而不必等待最新的统计信息,所以与同步统计信息更新相比,使用异步统计信息更新时查询响应时间可能具有更高的可预测性。 这样可避免延迟某些查询,而不延迟其他查询。
你的应用程序遇到了客户端请求超时,这些超时是由于一个或多个查询正在等待更新后的统计信息所导致的。 在某些情况下,等待同步统计信息会导致具有严格超时设置的应用程序失败。
Note
不管 AUTO_UPDATE_STATISTICS_ASYNC 选项如何,本地临时表上的统计信息始终会同步更新。 而全局临时表上的统计信息会同步或异步更新,具体取决于为用户数据库设置的 AUTO_UPDATE_STATISTICS_ASYNC 选项。
异步统计信息更新由后台请求执行。 当请求准备好将更新后的统计信息写入数据库时,请求会尝试获取统计信息元数据对象的架构修改锁。 如果另一个会话已经持有同一个对象的锁,则会阻止异步统计信息更新,直到可以获取架构修改锁。 同样,需要获取统计信息元数据对象的架构稳定性 (Sch-S) 锁来编译查询的会话可能会被异步统计信息更新后台会话(此会话已经持有或正在等待获取架构修改锁)阻止。 因此,对于具有非常频繁的查询编译和频繁统计信息更新的工作负荷,使用异步统计信息会增大由于锁定阻止而导致并发问题的可能性。
在 Azure SQL 数据库、Azure SQL 托管实例和 SQL Server 2022 (16.x) 及更高版本中,如果启用 ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY 数据库范围的配置,可使用异步统计信息更新来避免潜在并发问题。 启用此配置后,后台请求将等待获取架构修改(Sch-M)锁,并将更新的统计信息保存在单独的低优先级队列上,从而允许其他请求继续使用现有统计信息编译查询。 在统计信息元数据对象上没有其他会话持有锁后,后台请求将获取其架构修改锁并更新统计信息。 在极少数情况下,如果后台请求在几分钟的超时期内无法获取锁,则会中止异步统计更新,并且在触发另一个自动统计信息更新之前,或手动更新统计信息之前,不会更新统计信息。
Note
ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY 数据库范围的配置选项可在 Azure SQL 数据库、Azure SQL 托管实例和 SQL Server(从 SQL Server 2022 (16.x) 开始)中使用。
AUTO_DROP选项
适用于:Azure SQL 数据库、Azure SQL 托管实例,以及 SQL Server 2022 (16.x) 及更高版本
在低于 SQL Server 2022 (16.x) 的 SQL Server 中,如果统计信息是由用户或第三方工具在用户数据库上手动创建的,则这些统计信息对象可能会阻止或干扰你可能需要的架构更改。
从 SQL Server 2022 (16.x) 开始,默认在所有新的和已迁移的数据库上启用“自动删除”选项。
AUTO_DROP属性允许以一种模式创建统计信息对象,使得后续的架构更改不会被统计信息对象阻碍,而是根据需要删除这些统计信息。 这样,启用了“自动删除”的手动创建统计信息行为类似于自动创建统计信息行为。
在 Azure SQL 数据库、Azure SQL 托管实例和 SQL Server 2022(16.x)及更高版本中,自动创建的统计信息始终像设置了 AUTO_DROP 一样。
Note
尝试设置或取消设置自动创建的统计信息的“自动删除”属性会引发错误。 自动创建的统计信息始终使用自动删除功能。 某些备份在还原时错误地设置了此属性,而这种错误会一直持续,直到下次更新统计信息对象(手动或自动)。 但是,自动创建的统计信息的行为始终类似于自动删除统计信息的行为。 从以前的版本将数据库还原到 SQL Server 2022(16.x),建议对数据库执行 sp_updatestats ,为统计信息自动删除功能设置适当的元数据。
例如,要在 dbo.DatabaseLog 表上手动创建统计信息对象,请执行以下操作:
CREATE STATISTICS [mystats]
ON [dbo].[DatabaseLog]([DatabaseLogID], [PostTime], [DatabaseUser])
WITH AUTO_DROP = ON;
例如,要在 dbo.DatabaseLog 表上更新统计信息对象“自动删除”设置,请执行以下操作:
UPDATE STATISTICS [dbo].[DatabaseLog] ([mystats])
WITH AUTO_DROP = ON;
要评估现有统计信息上的“自动删除”设置,请使用 auto_drop 中的 sys.stats 列:
SELECT object_id,
[name],
auto_drop
FROM sys.stats;
有关详细信息,请参阅 AUTO_DROP。
INCREMENTAL
适用于:SQL Server 2014 (12.x) 及更高版本。
CREATE STATISTICS 的 INCREMENTAL 选项为 ON 时,创建的统计信息是每个分区的统计信息。 为 OFF 时,删除统计信息树并且 SQL Server 重新计算统计信息。 默认为 OFF。 此设置覆盖数据库级别 INCREMENTAL 属性。 有关创建增量统计信息的详细信息,请参阅 CREATE STATISTICS。 有关自动创建每个分区统计信息的详细信息,请参阅 数据库属性(选项页) 和 ALTER DATABASE SET 选项。
在将新分区添加到某个大型表时,应更新统计信息,以便包括这些新分区。 但扫描整个表(FULLSCAN 或 SAMPLE 选项)所需的时间可能会相当长。 此外,因为可能只需要新分区的统计信息,所以,不需要扫描整个表。 该增量选项将为每个分区创建和存储统计信息,并且在更新时,只刷新需要新统计信息的那些分区上的统计信息
如果不支持每个分区统计信息,将忽略该选项并生成警告。 对于以下统计信息类型,不支持增量统计信息:
- 使用与基表不分区对齐的索引创建的统计信息。
- 对 Always On 可读辅助数据库创建的统计信息。
- 对只读数据库创建的统计信息。
- 对筛选的索引创建的统计信息。
- 对视图创建的统计信息。
- 对内部表创建的统计信息。
- 使用空间索引或 XML 索引创建的统计信息。
何时创建统计信息
查询优化器已通过以下方式创建统计信息:
在创建索引时,查询优化器为表或视图上的索引创建统计信息。 在索引的键列上创建这些统计信息。 如果索引是一个筛选索引,则查询优化器将在为该筛选索引指定的行的同一子集上创建筛选统计信息。 有关筛选索引的详细信息,请参阅 “创建筛选索引 ”和 “CREATE INDEX”。
Note
在 SQL Server 2014 (12.x) 及更高版本中,当创建或重新生成已分区索引时,不会通过扫描表中的所有行来创建统计信息。 相反,查询优化器使用默认采样算法来生成统计信息。 在升级具有已分区索引的数据库后,你可能会在直方图数据中注意到这些索引的差异。 此行为更改可能不会影响查询性能。 若要通过扫描表中所有行的方法获得有关已分区索引的统计信息,请使用
CREATE STATISTICS或UPDATE STATISTICS以及FULLSCAN子句。在 AUTO_CREATE_STATISTICS 为 ON 时,查询优化器为查询谓词中的单列创建统计信息。
对于大多数查询,用于创建统计信息的这两种方法就可以确保高质量的查询计划;但在很少情况下,可以通过使用 CREATE STATISTICS 语句创建附加统计信息来改进查询计划。 这些附加统计信息可以捕获查询优化器在为索引或单个列创建统计信息时不考虑的统计关联。 应用程序可能在表数据中具有附加的统计关联,如果在统计信息对象中计入这些关联,可能会启用查询优化器来改进查询计划。 例如,针对数据行子集的筛选统计信息或针对查询谓词列的多列统计信息可改进查询计划。
在使用 CREATE STATISTICS 语句创建统计信息时,我们建议使 AUTO_CREATE_STATISTICS 选项保持为 ON,以便查询优化器继续为查询谓词列定期创建单列统计信息。 有关查询谓词的详细信息,请参阅 “搜索条件”。
在以下任何情况适用时,考虑使用 CREATE STATISTICS 语句创建统计信息:
- 数据库引擎优化顾问建议创建统计信息。
- 查询谓词包含多个相关的列,这些列在同一索引中并不是已经存在的键。
- 查询从数据的子集中选择数据。
- 查询缺少统计信息。
Note
有关特定于内存中 OLTP 相关表和统计信息的信息,请参阅内存优化表的统计信息。
查询谓词包含多个相关列
在某一查询谓词包含具有跨列关系和依赖关系的多列时,针对多列的统计信息可能会改进查询计划。 多列的统计信息包含跨列关联统计信息,称为 密度,在单列统计信息中不可用。 在查询结果依赖于多列之间的数据关系时,密度可以改进基数估计。
如果列已位于同一索引中,则多列统计信息对象已存在,并且不需要手动创建它。 如果列尚未位于同一索引中,可以通过对列创建索引或使用 CREATE STATISTICS 语句来创建多列统计信息。 与统计信息对象相比,它要求更多的系统资源来维护索引。 如果应用程序不需要多列索引,则可以通过创建统计信息对象而不创建索引来节约系统资源。
创建多列统计信息时,统计信息对象定义中的列顺序会影响密度的有效性,以便进行基数估计。 统计信息对象在统计信息对象定义中存储键列的每个前缀的密度。 有关密度的详细信息,请参阅此页中的密度部分。
为了创建用于基数估计的密度,查询谓词中的列必须匹配统计信息对象定义中列的前缀之一。 例如,下面的示例会在列 LastName、MiddleName 和 FirstName 上创建多列统计信息对象。
USE AdventureWorks2022;
GO
IF EXISTS (SELECT name
FROM sys.stats
WHERE name = 'LastFirst'
AND object_ID = OBJECT_ID('Person.Person'))
DROP STATISTICS Person.Person.LastFirst;
GO
CREATE STATISTICS LastFirst
ON Person.Person(LastName, MiddleName, FirstName);
GO
在此示例中,统计信息对象 LastFirst 具有以下列前缀的密度:(LastName)、(LastName, MiddleName) 和 (LastName, MiddleName, FirstName)。 密度不可用于 (LastName, FirstName)。 如果查询使用 LastName 且 FirstName 不使用 MiddleName,则密度不适用于基数估计。
查询从数据的子集中进行选择
在查询优化器为单个列和索引创建统计信息时,它为所有行中的值创建统计信息。 在查询从行的子集中进行选择时,行的这个子集具有唯一的数据分布,筛选统计信息可以改进查询计划。 可以通过使用 CREATE STATISTICS 语句和 WHERE 子句定义筛选器谓词表达式,从而创建筛选过的统计信息。
例如,使用 AdventureWorks2022 时,Production.Product 表中的每个产品属于 Production.ProductCategory 表中的以下四个类别之一:Bikes、Components、Clothing 和 Accessories。 每个类别具有不同的重量数据分布:自行车的重量范围为 13.77 至 30.0,部件的重量范围为 2.12 至 1050.00,并有一些 NULL 值,服装的重量全部为 NULL,附件的重量也为 NULL。
以 Bikes 为例,所有自行车重量的筛选统计信息为查询优化器提供了更准确的统计信息,相较于全表统计或在重量列上不存在的统计信息,可以改善查询计划的质量。 自行车重量列很适合用于筛选统计信息,但如果重量查找数相对较小,则不一定适合于筛选索引。 筛选索引为查找带来的性能提升可能无法弥补在数据库中添加该筛选索引所需的额外维护和存储成本。
以下语句创建针对BikeWeights所有子类别的Bikes筛选统计信息。 筛选的谓词表达式通过使用比较 Production.ProductSubcategoryID IN (1,2,3) 来枚举所有自行车子类别,对自行车进行定义。 谓词不能使用 Bikes 类别名称,因为它存储在 Production.ProductCategory 表中,筛选器表达式中的所有列必须位于同一表中。
USE AdventureWorks2022;
GO
IF EXISTS ( SELECT name FROM sys.stats
WHERE name = 'BikeWeights'
AND object_ID = OBJECT_ID ('Production.Product'))
DROP STATISTICS Production.Product.BikeWeights;
GO
CREATE STATISTICS BikeWeights
ON Production.Product (Weight)
WHERE ProductSubcategoryID IN (1,2,3);
GO
查询优化器可使用 BikeWeights 筛选统计信息来改进以下查询的查询计划,此查询选择重量超过 25 的所有自行车。
SELECT P.Weight AS Weight,
S.Name AS BikeName
FROM Production.Product AS P
INNER JOIN Production.ProductSubcategory AS S
ON P.ProductSubcategoryID = S.ProductSubcategoryID
WHERE P.ProductSubcategoryID IN (1, 2, 3)
AND P.Weight > 25
ORDER BY P.Weight;
GO
查询识别缺少的统计信息
如果错误或其他事件阻止查询优化器创建统计信息,则查询优化器不使用统计信息来创建查询计划。 查询优化器将统计信息标记为缺失,并且在下次执行查询时尝试重新生成统计信息。
在使用 SQL Server Management Studio 以图形方式显示查询的执行计划时,缺少的统计信息将予以警告显示(表名称以红色文本显示)。 另外,使用 SQL Server Profiler 监视“Missing Column Statistics”事件类可以指明何时缺少统计信息。 有关详细信息,请参阅错误和警告事件类别(数据库引擎)。
如果缺少统计信息,则执行以下步骤:
- 确认 AUTO_CREATE_STATISTICS 和 AUTO_UPDATE_STATISTICS 为 ON。
- 验证数据库是否不是只读的。 如果数据库是只读的,则无法保存新的统计信息对象。
- 通过使用 CREATE STATISTICS 语句创建缺少的统计信息。
当有关只读数据库或只读快照的统计信息丢失或变得陈旧时,数据库引擎将创建临时统计信息并在 tempdb 中进行维护。 当数据库引擎创建临时统计信息时,将在统计信息名称后追加后缀 _readonly_database_statistic,以便将临时统计信息与永久统计信息加以区分。 为 SQL Server 生成的统计信息保留后缀 _readonly_database_statistic。 可以在读写数据库上创建和重新生成临时统计信息的脚本。 编写脚本时,Management Studio 将统计信息名称的后缀从 _readonly_database_statistic 更改为 _readonly_database_statistic_scripted。
只有 SQL Server 可以创建和更新临时统计信息。 但是,你可以使用用于永久统计信息的相同工具来删除临时统计信息和监视统计信息属性:
- 使用 DROP STATISTICS 语句删除临时统计信息。
- 使用 sys.stats 和 sys.stats_columns 目录视图监视统计信息。
sys.stats系统目录视图包括is_temporary列,用于指示哪些统计信息是永久的,哪些统计信息是临时的。
因为临时统计信息存储于 tempdb 中,所以重新启动 SQL Server 服务将导致所有临时统计信息消失。
何时更新统计信息
查询优化器确定统计信息何时过期,然后在查询计划需要统计信息时更新它们。 在某些情况下,当 AUTO_UPDATE_STATISTICS 设置为 ON 时,可通过更频繁地更新统计信息来优化查询计划,进而提高查询性能。 可以使用 UPDATE STATISTICS 语句或存储过程 sp_updatestats 来更新统计信息。
更新统计信息可确保查询使用最新的统计信息进行编译。 通过任何过程更新统计信息会导致查询计划自动重新编译。 建议不要过于频繁地手动更新统计信息,因为改进查询计划与重新编译查询所需的时间之间存在性能权衡。 具体的折衷方案取决于你的应用程序。
在使用 UPDATE STATISTICS 或 sp_updatestats 更新统计信息时,我们建议使 AUTO_UPDATE_STATISTICS 保持设置为 ON,以便查询优化器定期更新统计信息。
有关如何更新列、索引、表或索引视图的统计信息的详细信息,请参阅 UPDATE STATISTICS。
有关如何为数据库中的所有用户定义表和内部表更新统计信息的信息,请参阅存储过程 sp_updatestats。
有关自动统计信息更新阈值的详细信息,请参阅 AUTO_UPDATE_STATISTICS 选项。
如果 AUTO_UPDATE_STATISTICS 设置为 OFF,计划重新编译仍可能由于各种其他原因而发生,但由于过时的统计信息更新,不会自动发生。 设置为 OFF 时 AUTO_UPDATE_STATISTICS ,统计信息更新仅通过其他手动计划的进程(例如维护计划)进行。 将 AUTO_UPDATE_STATISTICS 设置为 OFF,可能会导致生成并非最佳的查询计划以及查询性能下降。
检测过期统计信息
若要确定最近一次更新统计信息的时间,请使用 sys.dm_db_stats_properties 或 STATS_DATE 函数。
在以下情况下考虑更新统计信息:
- 查询执行时间很长。
- 在升序或降序键列上发生插入操作。
- 在维护操作后。
有关手动更新统计信息的示例,请参阅 UPDATE STATISTICS。
查询执行时间很长
如果查询响应时间很长或不可预测,则在执行其他故障排除步骤前,确保查询具有最新的统计信息。
在升序或降序键列上发生插入操作
与查询优化器执行的统计信息更新相比,升序或降序键列(例如 IDENTITY 列或实时时间戳列)上的统计信息可能要求更频繁地更新。 插入操作将新值追加到升序或降序键列上。 添加的行数可能过小,以致于无法触发统计信息更新。 如果统计信息未保持最新,并且查询从最近添加的行中进行选择,那么当前统计信息就没有针对这些新值的基数估计。 这会导致基数估计不准确和查询性能低下。
例如,如果统计信息未更新以包含最近销售订单日期的基数估计,则从最近的销售订单日期中选择的查询具有不准确的基数估计值。
在维护操作后
考虑在执行会更改数据分布(例如截断表或对大部分行执行批量插入)的维护过程后更新统计信息。 这可以避免在将来查询等待自动统计信息更新时在查询处理中出现延迟。
重新生成、碎片整理或重新组织索引等作不会更改数据的分布。 因此,在执行 ALTER INDEX REBUILD、 DBCC DBREINDEX、 DBCC INDEXDEFRAG 或 ALTER INDEX REORGANIZE 作后,无需更新统计信息。 查询优化器将在使用 ALTER INDEX REBUILD 或 DBCC DBREINDEX 对表或视图重新生成索引时更新统计信息,但此统计信息更新是重新创建索引的副产品。 查询优化器不会在DBCC INDEXDEFRAG或ALTER INDEX REORGANIZE操作后更新统计信息。
Tip
从 SQL Server 2016 (13.x) SP1 CU4 开始,请使用 CREATE STATISTICS 或 UPDATE STATISTICS 的 PERSIST_SAMPLE_PERCENT 选项,为未显式指定采样百分比的后续统计信息更新设置和保留特定的采样百分比。
自动索引和统计信息管理
使用自适应索引碎片整理等智能解决方案,自动管理一个或多个数据库的索引碎片整理和统计信息更新。 此过程根据碎片级别以及其他参数,自动选择是重新生成索引还是重新组织索引,并使用线性阈值更新统计信息。
高效使用统计信息的查询
某些查询实现(如查询谓词中的局部变量和复杂表达式)可能导致产生并非最佳的查询计划。 遵循有关高效使用统计信息的查询设计准则有助于避免这种情况。 有关查询谓词的详细信息,请参阅 “搜索条件”。
你可以通过应用查询设计指导原则来改进查询计划,这些查询设计指导原则高效地使用统计信息,以便改进在查询谓词中使用的表达式、变量和函数的 基数估计 。 当查询优化器不知道表达式、变量或函数的值时,它不知道在直方图中查找哪个值,因此无法从直方图中检索最佳基数估计值。 相反,查询优化器基于直方图中所有采样行的每个非重复值的平均行数来进行基数估计。 这会导致生成并非最佳的基数估计,并会影响查询性能。 有关直方图的详细信息,请参阅本页的直方图部分或 sys.dm_db_stats_histogram。
下面的准则说明了如何编写查询来通过改进基数估计来改进查询计划。
改进表达式的基数估计
要改进表达式的基数估计,请遵循以下准则:
- 在可能的情况下,简化包含常量的表达式。 在确定基数估计之前,查询优化器不会计算包含常量的所有函数和表达式。 例如,将表达式
ABS(-100)简化为100。 - 如果表达式使用多个变量,则考虑为表达式创建一个计算列,然后对该计算列创建统计信息或索引。 例如,如果你为表达式
WHERE PRICE + Tax > 100创建计算列,则查询谓词Price + Tax可能会具有更好的基数估计。
改进变量和函数的基数估计
要改进变量和函数的基数估计,请遵循以下准则:
如果查询谓词使用局部变量,则考虑重新编写查询以使用参数,而非局部变量。 当查询优化器创建查询执行计划时,不知道局部变量的值。 在查询使用参数时,查询优化器将基数估计用于传递到存储过程的第一个实际参数值。
考虑使用标准表或临时表来保存多语句表值函数 (mstvf) 的结果。 查询优化器不会为多语句表值函数创建统计信息。 使用此方法,查询优化器可对表列创建统计信息并使用这些信息来创建更好的查询计划。
考虑使用标准表或临时表来代替表变量。 查询优化器不会为表变量创建统计信息。 使用此方法,查询优化器可对表列创建统计信息并使用这些信息来创建更好的查询计划。 在确定是使用临时表还是表变量时需要进行一些权衡;与临时表相比,在存储过程中使用表变量会导致存储过程的重新编译次数减少。 根据应用程序,使用临时表来代替表变量可能不会提高性能。
如果存储过程包含使用传入参数的查询,则在查询中使用该参数值之前,应避免在存储过程内更改参数值。 查询的基数估计基于传入的参数值,而非更新的值。 为了避免更改参数值,你可以重新编写查询以使用两个存储过程。
例如,以下存储过程
Sales.GetRecentSales将在@date为@date时更改参数NULL的值。USE AdventureWorks2022; GO IF OBJECT_ID('Sales.GetRecentSales', 'P') IS NOT NULL DROP PROCEDURE Sales.GetRecentSales; GO CREATE PROCEDURE Sales.GetRecentSales @date DATETIME AS BEGIN IF @date IS NULL SET @date = DATEADD(MONTH, -3, (SELECT MAX(ORDERDATE) FROM Sales.SalesOrderHeader)); SELECT * FROM Sales.SalesOrderHeader AS h, Sales.SalesOrderDetail AS d WHERE h.SalesOrderID = d.SalesOrderID AND h.OrderDate > @date; END GO如果第一次调用存储过程
Sales.GetRecentSales时为NULL参数传递了@date,则即使未使用@date = NULL调用查询谓词,查询优化器仍会依据@date = NULL的基数估计来编译存储过程。 此基数估计可能与实际查询结果中的行数差别很大。 因此,查询优化器可能会选择并非最佳的查询计划。 若要避免此情况发生,你可以按如下所示将存储过程重新编写成两个过程:USE AdventureWorks2022; GO IF OBJECT_ID('Sales.GetNullRecentSales', 'P') IS NOT NULL DROP PROCEDURE Sales.GetNullRecentSales; GO CREATE PROCEDURE Sales.GetNullRecentSales @date DATETIME AS BEGIN IF @date IS NULL SET @date = DATEADD(MONTH, -3, (SELECT MAX(ORDERDATE) FROM Sales.SalesOrderHeader)); EXECUTE Sales.GetNonNullRecentSales @date; END GO IF OBJECT_ID('Sales.GetNonNullRecentSales', 'P') IS NOT NULL DROP PROCEDURE Sales.GetNonNullRecentSales; GO CREATE PROCEDURE Sales.GetNonNullRecentSales @date DATETIME AS BEGIN SELECT * FROM Sales.SalesOrderHeader AS h, Sales.SalesOrderDetail AS d WHERE h.SalesOrderID = d.SalesOrderID AND h.OrderDate > @date; END GO
使用查询提示改进基数估计
为了改进局部变量的基数估计,可以将 OPTIMIZE FOR <value> 或 OPTIMIZE FOR UNKNOWN 查询提示与 RECOMPILE 一起使用。 有关详细信息,请参阅查询提示。
对于某些应用程序,每次执行查询时都重新编译查询可能会占用过多时间。 即使不使用 OPTIMIZE FOR 选项,RECOMPILE 查询提示也可以提供帮助。 例如,可以将 OPTIMIZE FOR 选项添加到存储过程 Sales.GetRecentSales,以便指定一个特定日期。 以下示例将 OPTIMIZE FOR 选项添加到 Sales.GetRecentSales 过程。
USE AdventureWorks2022;
GO
IF OBJECT_ID('Sales.GetRecentSales', 'P') IS NOT NULL
DROP PROCEDURE Sales.GetRecentSales;
GO
CREATE PROCEDURE Sales.GetRecentSales
@date DATETIME
AS
BEGIN
IF @date IS NULL
SET @date = DATEADD(MONTH, -3,
(SELECT MAX(ORDERDATE)
FROM Sales.SalesOrderHeader));
SELECT *
FROM Sales.SalesOrderHeader AS h, Sales.SalesOrderDetail AS d
WHERE h.SalesOrderID = d.SalesOrderID AND h.OrderDate > @date
OPTION (OPTIMIZE FOR (@date = '2004-05-01 00:00:00.000'));
END
GO
使用计划指南改进基数估计
对于某些应用程序,查询设计指南可能不适用,因为无法更改查询或 RECOMPILE 查询提示可能会导致过多的重新编译。 你可以使用计划指南来指定 USE PLAN 之类的其他提示,以便在向应用程序供应商调查应用程序变化的同时,控制查询的行为。 有关计划指南的详细信息,请参阅计划指南。
在 Azure SQL 数据库 中,考虑使用查询存储提示来强制计划,而不是计划指南。 有关详细信息,请参阅查询存储提示。
相关内容
- 内存优化表的统计信息
- CREATE STATISTICS (Transact-SQL)
- 更新统计信息(Transact-SQL)
- sp_updatestats(Transact-SQL)
- DBCC SHOW_STATISTICS (Transact-SQL)
- ALTER DATABASE SET 选项 (Transact-SQL)
- DROP STATISTICS (Transact-SQL)
- 创建索引(Transact-SQL)
- ALTER INDEX (Transact-SQL)
- 创建筛选索引
- STATS_DATE(Transact-SQL)
- sys.dm_db_stats_properties(Transact-SQL)
- sys.dm_db_stats_histogram(Transact-SQL)
- sys.stats
- sys.stats_columns(Transact-SQL)
- 自适应索引碎片整理