如何强制 SQL Server 使用近期数据样本进行查询估算?
结论
SQL Server 不支持在基数估算时优先采样最近插入、最近更新或主键值较高的行。无论是查询级、表级还是数据库级,都没有“近期数据优先采样”这一配置。
SQL Server 通常也不会在每次编译查询时重新抽取数据样本。查询优化器使用已有的统计信息,包括直方图、密度信息和行数等。数据采样发生在创建或更新统计信息时,不是在执行每条查询时。
如果近期数据分布发生变化,导致基数估算出现偏差,可以采用这些办法:
- 及时更新相关统计信息;
- 对关键统计信息使用
FULLSCAN; - 为近期数据创建筛选统计信息;
- 按时间分区后使用增量统计信息;
- 处理参数嗅探和执行计划复用问题;
- 检查查询谓词是否超出统计信息直方图的有效范围。
为什么不能让采样偏向近期数据
统计信息需要描述整个统计对象的数据分布。SQL Server 创建采样统计信息时,会从表或索引中抽取部分数据,再据此构建统计信息。系统没有提供按照插入时间、更新时间或主键大小设置采样权重的接口。
即使只采样最近的数据,得到的统计信息也可能严重误判针对历史数据的查询。优化器需要的是与具体查询谓词相匹配的数据分布,而不是单纯的“最新样本”。
统计信息直方图最多包含 200 个步骤。即使使用 FULLSCAN,也只是扫描全部数据来构建统计信息,并不表示直方图会保存每一个不同值。
首先确认哪个统计信息导致了误差
在实际执行计划中,比较各个关键算子的:
Estimated Number of RowsActual Number of Rows
如果两者差异很大,再检查该算子使用的连接条件、筛选条件和相关列。可以通过 DBCC SHOW_STATISTICS 查看对应的统计信息:
DBCC SHOW_STATISTICS
(
N'dbo.Payments',
N'IX_Payments_PaymentDate'
)
WITH STAT_HEADER, DENSITY_VECTOR, HISTOGRAM;
重点检查:
Updated:统计信息最后更新时间;Rows和Rows Sampled:总行数和采样行数;- 直方图是否覆盖查询使用的近期值;
- 近期数据增长后,统计信息是否仍未触发自动更新;
- 查询条件是否涉及多个强相关列,而统计信息无法准确描述这些列之间的相关性。
也可以查询统计信息属性:
SELECT
s.name AS statistics_name,
sp.last_updated,
sp.rows,
sp.rows_sampled,
sp.modification_counter
FROM sys.stats AS s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS sp
WHERE s.object_id = OBJECT_ID(N'dbo.Payments');
更新关键统计信息
如果报表运行前刚完成大批量付款数据导入,可以在加载结束后更新相关统计信息:
UPDATE STATISTICS dbo.Payments
IX_Payments_PaymentDate;
对于很重要且数据量可控的统计信息,可以使用全表扫描:
UPDATE STATISTICS dbo.Payments
IX_Payments_PaymentDate
WITH FULLSCAN;
也可以指定采样比例:
UPDATE STATISTICS dbo.Payments
IX_Payments_PaymentDate
WITH SAMPLE 50 PERCENT;
FULLSCAN 可以消除抽样误差,但不能解决所有估算问题。多列相关性、参数嗅探、表达式转换和直方图步数限制仍可能造成偏差。对大型表执行 FULLSCAN 还会消耗更多 I/O、CPU 和维护时间,因此不适合不加区分地更新整个数据库的所有统计信息。
如果希望后续更新继续使用指定的采样比例,可以设置 PERSIST_SAMPLE_PERCENT,但是否支持以及具体行为取决于 SQL Server 版本:
UPDATE STATISTICS dbo.Payments
IX_Payments_PaymentDate
WITH SAMPLE 50 PERCENT,
PERSIST_SAMPLE_PERCENT = ON;
这只能固定采样比例,不能让采样偏向近期行。
为近期数据创建筛选统计信息
如果报表总是查询一个相对固定的近期范围,可以创建筛选统计信息,专门描述这部分数据:
CREATE STATISTICS ST_Payments_Recent
ON dbo.Payments (PaymentDate)
WHERE PaymentDate >= '20260901';
如果查询还会按状态筛选,可以针对常用条件创建统计信息:
CREATE STATISTICS ST_Payments_Unreconciled_Date
ON dbo.Payments (PaymentDate)
WHERE ReconciliationStatus = 'Pending';
筛选统计信息比“近期优先采样”更贴合这种需求,因为它直接描述了查询关心的数据子集。
筛选条件中的日期必须是固定边界,不能直接使用 GETDATE() 等动态表达式。因此,滚动时间窗口需要通过维护任务定期重建统计信息,或者改用分区方案。查询谓词还要与筛选条件形成足够明确的匹配,优化器才更容易使用该统计信息。
对按时间增长的大表使用分区和增量统计信息
如果 Payments 表按日期分区,可以考虑启用增量统计信息。近期分区发生变化后,只需更新受影响分区的统计信息,不必频繁扫描整张历史大表。
创建增量统计信息的示例:
CREATE STATISTICS ST_Payments_PaymentDate
ON dbo.Payments (PaymentDate)
WITH INCREMENTAL = ON;
更新指定分区的示例:
UPDATE STATISTICS dbo.Payments
ST_Payments_PaymentDate
WITH RESAMPLE ON PARTITIONS (12);
这种方式要求表采用兼容的分区结构,并且 SQL Server 版本支持增量统计信息。它解决的是大型分区表的统计信息维护成本,并不会改变采样算法,使其偏向新数据。
检查递增键和超出直方图范围的问题
日期列、标识列和持续递增的数值主键经常会遇到这种情况:新插入的值已经超过统计信息直方图记录的最大值。查询这些值时,优化器没有直接的历史分布依据,只能按照基数估算模型进行推断,结果可能明显偏低。
最直接的处理方法是在批量加载完成后更新相应统计信息:
UPDATE STATISTICS dbo.Payments
PK_Payments
WITH FULLSCAN;
UPDATE STATISTICS dbo.Payments
IX_Payments_PaymentDate
WITH FULLSCAN;
如果问题持续出现,还需要确认数据库兼容级别和当前使用的基数估算器。不同基数估算模型对超出直方图范围的值可能采用不同的推断方式,但不应仅为修正一条查询,就贸然更改整个数据库的兼容级别。
排除参数嗅探和计划复用问题
“偶尔出现坏计划”未必是采样造成的。如果同一查询在不同参数下返回的行数相差很大,首次编译时使用的参数可能生成只适合某类数据的执行计划。该计划随后被其他参数复用,就可能出现问题。
可以先使用 OPTION (RECOMPILE) 验证:
SELECT
p.PaymentId,
p.PaymentDate,
p.Amount,
p.ReconciliationStatus
FROM dbo.Payments AS p
WHERE p.PaymentDate >= @StartDate
AND p.PaymentDate < @EndDate
AND p.ReconciliationStatus = @Status
OPTION (RECOMPILE);
重新编译会让优化器根据当前参数和现有统计信息生成计划。这可能缓解参数嗅探问题,但也会增加编译成本。它适合执行频率较低、计算量较大的日报查询,不一定适合高频短查询。
还可以根据实际数据分布评估:
OPTION (OPTIMIZE FOR (@Status = 'Pending'));
或:
OPTION (OPTIMIZE FOR UNKNOWN);
这些提示都不是通用修复方案:
OPTIMIZE FOR会使计划偏向指定参数;OPTIMIZE FOR UNKNOWN使用较为通用的估算,可能无法针对典型参数进行优化;RECOMPILE可以避免复用不合适的计划,但会带来额外的编译开销。
在支持 Query Store 提示的版本中,还可以通过 Query Store 为查询应用提示,而不必修改应用程序 SQL。具体能使用哪些功能,需要结合 SQL Server 版本确认。
建议的处理顺序
对于每日 reconciliation report,可以按以下顺序排查:
- 获取坏计划的实际执行计划,确定哪个算子出现了估算偏差。
- 检查相关统计信息的更新时间、采样行数和修改计数。
- 每日数据加载完成后,只更新与报表条件有关的统计信息。
- 对关键统计信息测试
FULLSCAN,确认问题是否由抽样误差造成。 - 如果报表只访问近期数据或特定状态的数据,考虑使用筛选统计信息或合适的复合索引。
- 如果表按日期分区,评估是否使用增量统计信息。
- 如果坏计划只是偶发,重点检查参数嗅探和计划缓存,不要只关注统计信息采样。
- 使用 Query Store 对比好计划和坏计划,必要时考虑计划强制或 Query Store 提示。
因此,处理方向不是配置“近期数据优先采样”,而是让统计信息覆盖查询真正关心的数据范围,并按照数据加载节奏及时维护统计信息,同时处理可能存在的参数嗅探和计划复用问题。
备注:内容仅供参考。
