增量更新后统计信息消失


21

我们有一个使用增量统计信息的大型分区SQL Server数据库。所有索引均按分区对齐。当我们尝试通过分区在线重建分区时,在重建索引之后,所有统计信息都会消失。

下面是一个脚本,用于通过AdventureWorks2014数据库在SQL Server 2014中复制问题。

--Example against AdventureWorks2014 Database

CREATE PARTITION FUNCTION TransactionRangePF1 (DATETIME)
AS RANGE RIGHT FOR VALUES 
(
   '20130501', '20130601', '20130701', '20130801', 
   '20130901', '20131001', '20131101', '20131201', 
   '20140101', '20140201', '20140301'
);
GO

CREATE PARTITION SCHEME TransactionsPS1 AS PARTITION TransactionRangePF1 TO 
(
  [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], 
  [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY], 
  [PRIMARY], [PRIMARY], [PRIMARY]
);
GO

CREATE TABLE dbo.TransactionHistory 
(
  TransactionID        INT      NOT NULL, -- not bothering with IDENTITY here
  ProductID            INT      NOT NULL,
  ReferenceOrderID     INT      NOT NULL,
  ReferenceOrderLineID INT      NOT NULL DEFAULT (0),
  TransactionDate      DATETIME NOT NULL DEFAULT (GETDATE()),
  TransactionType      NCHAR(1) NOT NULL,
  Quantity             INT      NOT NULL,
  ActualCost           MONEY    NOT NULL,
  ModifiedDate         DATETIME NOT NULL DEFAULT (GETDATE()),
  CONSTRAINT CK_TransactionType 
    CHECK (UPPER(TransactionType) IN (N'W', N'S', N'P'))
) 
ON TransactionsPS1 (TransactionDate);


INSERT INTO dbo.TransactionHistory
SELECT * FROM Production.TransactionHistory
--  SELECT * FROM sys.partitions
--  WHERE object_id = OBJECT_ID('dbo.TransactionHistory');

CREATE NONCLUSTERED INDEX IDX_ProductId ON dbo.TransactionHistory (ProductId) 
  WITH (DATA_COMPRESSION = ROW, STATISTICS_INCREMENTAL=ON)  
  ON TransactionsPS1 (TransactionDate)

DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);
PRINT 'Stats are avialable'  

ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = ON , DATA_COMPRESSION = ROW)

PRINT 'After online index rebuild by partition stats are now gone'
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

PRINT 'Rebuild the stats with a rebuild for all paritions (this works)' 
ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = ALL WITH (ONLINE = ON , DATA_COMPRESSION = ROW, 
  STATISTICS_INCREMENTAL = ON)

PRINT 'Stats are back'
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

PRINT 'Works correctly for an offline rebuild by partition'
ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = OFF , DATA_COMPRESSION = ROW)

    --stats still there  
DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);

ALTER INDEX [IDX_ProductId] ON [dbo].[TransactionHistory] REBUILD 
  PARTITION = 9 WITH (ONLINE = ON , DATA_COMPRESSION = ROW)

DBCC SHOW_STATISTICS('dbo.TransactionHistory', IDX_ProductId);
PRINT' stats are gone!!!!!!'

如图所示,我们不能在线分区重建索引,而不会丢失所有索引统计信息。这是我们的主要维护问题。似乎统计增量选项需要成为单个索引重建语法的一部分,或者联机选项需要像脱机选项一样正确处理它。

请让我知道我是否缺少什么?

更新:

就我们对增量统计信息的需求而言:我们正在根据内部客户ID而不是日期进行分区。因此,当引入新客户(大量数据后备负载)时,我们可以简单地更新分区的统计信息,并迅速避免为该新客户创建任何难看的计划。我想我会将其作为错误提交给Microsoft,然后看看他们怎么说,并采用仅对该分区的统计数据重新采样的解决方案。

连接错误报告:

使用增量统计信息重建在线索引后,统计信息消失

更新:Microsoft已经确认这是一个错误。


1
更新:微软今天早上给我发了一封电子邮件,该错误将在未来CU更新来修正对SQL 2014
JasonR

您知道哪个CU修复了该问题,或者他们在该电子邮件中报告了多少KB?试图查看何时修复。
mbourgon

1
可以肯定的是VSTS错误号8046729 KB文章编号3194959,它是CU 9中SQL Server 2014 SP1的一部分。到KB的链接在这里
JasonR

是的,看起来像-上个月刚刚在2016SP1上进行了修复。非常感谢!
mbourgon

更正:仅在2016 SP1 CU2中修复。它发生在2016 SP1 CU1上。
mbourgon

Answers:


17

本身不知道这是否是一个错误,但这绝对是一个有趣的事件。联机分区重建是SQL Server 2014中的新增功能,因此可能需要一些内部组件来解决。

这是我对你最好的解释。增量统计信息绝对要求以相同的速率对所有分区进行采样,以便在引擎合并统计信息页面时,可以确信采样分布是可比较的。REBUILD必须以100%的采样率对数据进行采样。无法保证分区9上的100%采样率始终是其余分区的准确采样率。因此,引擎似乎无法合并样本,您最终将获得一个空的统计信息Blob。但是,统计对象仍然存在:

select 
    check_time = sysdatetime(),                         
    schema_name = sh.name,
    table_name = t.name,
    stat_name = s.name,
    index_name = i.name,
    stats_column = index_col(quotename(sh.name)+'.'+quotename(t.name),s.stats_id,1),
    s.stats_id,
    s.has_filter,                       
    s.is_incremental,
    s.auto_created,
    sp.last_updated,    
    sp.rows,
    sp.rows_sampled,                        
    sp.unfiltered_rows,
    modification_counter 
from sys.stats s 
join sys.tables t 
    on s.object_id = t.object_id
join sys.schemas sh
    on t.schema_id = sh.schema_id
left join sys.indexes i 
    on s.object_id = i.object_id
    and s.name = i.name
outer apply sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
where t.name = 'TransactionHistory' and sh.name = 'dbo'

您可以通过多种方式填充斑点:

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE;

要么

UPDATE STATISTICS dbo.TransactionHistory (IDX_ProductId) WITH RESAMPLE ON PARTITIONS (9);

或者,您也可以等待AutoStats使用该对象在查询计划的首次编译时进行更新:

-- look at my creative query
select * 
from dbo.TransactionHistory
where TransactionDate = '20140101';

说到这一切,这启发后由Erin Stellato凸显了后来被视为增量统计的一个重大缺陷。优化程序不会在查询计划生成中使用它们的分区级数据,从而降低了增量统计的假定收益。那么,增量统计的当前好处是什么?我认为,与传统统计数据相比,它们的主要用途是能够以更高的速率更一致地对大型表进行采样。

使用您的示例,情况如下:

set statistics time on;

update statistics dbo.TransactionHistory(IDX_ProductId)
with fullscan;

--SQL Server Execution Times:
--  CPU time = 94 ms,  elapsed time = 131 ms.


update statistics dbo.TransactionHistory(IDX_ProductId)
with resample on partitions(2);

 --SQL Server Execution Times:
 --  CPU time = 0 ms,  elapsed time = 5 ms.

drop index IDX_ProductId On dbo.TransactionHistory;

CREATE NONCLUSTERED INDEX IDX_ProductId ON dbo.TransactionHistory (ProductId) 
  WITH (DATA_COMPRESSION = ROW)  
  ON [PRIMARY]

update statistics dbo.TransactionHistory(IDX_ProductId)
with fullscan;

 --SQL Server Execution Times:
 --  CPU time = 76 ms,  elapsed time = 66 ms.

对增量统计信息进行全扫描统计信息更新需要131毫秒。对非分区对齐的统计信息进行全扫描统计信息更新需要花费66 ms。由于将各个统计页面合并回主直​​方图所产生的开销,最有可能导致不对齐的统计数据变慢。但是,使用分区对齐的统计对象,我们可以在5毫秒内更新一个分区并将其合并回主直​​方图blob。因此,此时具有递增统计信息的管理员将面临一个决定。他们可以通过仅更新传统上需要更新的分区来减少总体统计信息维护时间,或者可以尝试更高的采样率,从而有可能在与先前维护时间段相同的时间内采样更多行。前者在维护窗口中提供了喘息的空间,后者可能将非常大的表上的统计信息推送到可以根据更准确的统计信息查询更好的计划的地方。这不是保证,您的里程可能会有所不同。

读者可以看到,此表上的统计信息更新时间不是66毫秒,因此我尝试对stackexchange数据集进行测试。我下载的最新转储中有6,418,608个帖子(不包括StackOverflow帖子和2012年以来的所有帖子-我这是数据错误)。

我对数据进行了分区,[CreationDate]因为...演示。

以下是一些标准情况下的时间安排(100%-重建索引,默认-统计信息自动更新或UPDATE STATISTICS没有指定采样率:

  • 使用Fullscan创建非增量统计信息:CPU时间= 23500毫秒,经过的时间= 22521毫秒。
  • 使用全扫描创建增量统计信息:CPU时间= 20406毫秒,经过的时间= 15413毫秒。
  • 使用默认采样率更新非增量统计信息:CPU时间= 406毫秒,经过的时间= 408毫秒。
  • 使用默认采样率更新增量统计信息:CPU时间= 453 ms,经过的时间= 507 ms。

假设我们比这些默认方案要复杂得多,并已决定10%的采样率是可以使我们获得所需计划的最低速率,同时又将维护时间保持在合理的时间范围内。

  • 使用样本10%更新非增量统计信息:CPU时间= 2344毫秒,经过的时间= 2441毫秒。
  • 使用示例10%更新增量统计信息:CPU时间= 2344毫秒,经过的时间= 2388毫秒。

到目前为止,拥有递增统计信息并没有明显的好处。但是,如果我们利用未记录的 sys.dm_db_stats_properties_internal() DMV(如下),则可以深入了解您可能想要更新的分区。假设我们对分区3中的数据进行了更改,并希望确保统计信息对于传入的查询是最新的。这是我们的选择:

  • 默认情况下更新非增量(也是自动统计信息更新的默认行为):408毫秒。
  • 以10%更新非增量:2441 ms。
  • 使用重采样(10%-我们定义的采样率)更新增量统计信息,分区3:CPU时间= 63毫秒,经过时间= 63毫秒。

这是我们需要做出决定的地方。我们是否赢得了63毫秒的胜利。基于分区的统计信息更新,还是我们将采样率提高了?假设我们愿意在增量统计信息上以50%的比例获得初始抽样:

  • 将增量统计信息更新为50%:经过的时间= 16840 ms。
  • 使用重采样更新增量统计信息,分区3(50%-我们的新更新时间):经过时间= 295毫秒。

我们能够采样更多的数据,也许可以设置优化器以对我们的数据做出更好的猜测(即使它还没有使用分区级别的统计信息),并且现在我们可以更快地执行此操作增量统计。

不过,最后一件有趣的事情要弄清楚。同步统计信息更新如何?即使自动调节器启动,仍保留50%的采样率吗?

我从分区3删除了数据,并在CreationDate上运行了一个查询,然后进行了检查,然后使用下面的相同查询来检查费率。保留了50%的采样率。

因此,长话短说:增量统计信息可以通过适当的思考和初步设置工作而成为有用的工具。但是,您必须知道要解决的问题,然后才需要适当解决。如果您得到的基数估计不正确,则可以通过战略抽样率和一些投资干预措施来获得更好的计划。但是,由于所使用的直方图是单个合并的统计信息页面,而不是分区级别的信息,因此您只能获得一小部分好处。如果您在维护窗口中感到痛苦,那么也许增量统计信息可以为您提供帮助,但这可能需要您设置高度干预的维护干预流程。而不管,

  • 使用索引未与基本表进行分区对齐的索引创建的统计信息。
  • 在AlwaysOn可读辅助数据库上创建的统计信息。
  • 在只读数据库上创建的统计信息。
  • 在过滤索引上创建的统计信息。
  • 基于视图创建的统计信息。
  • 在内部表上创建的统计信息。
  • 使用空间索引或XML索引创建的统计信息。

希望这可以帮助

select 
    sysdatetime(),                          
    schema_name = sh.name,
    table_name = t.name,
    stat_name = s.name,
    index_name = i.name,
    leading_column = index_col(quotename(sh.name)+'.'+quotename(t.name),s.stats_id,1),
    s.stats_id,
    parition_number = isnull(sp.partition_number,1),
    s.has_filter,                       
    s.is_incremental,
    s.auto_created,
    sp.last_updated,    
    sp.rows,
    sp.rows_sampled,                        
    sp.unfiltered_rows,
    modification_counter = coalesce(sp.modification_counter, n1.modification_counter) 
from sys.stats s 
join sys.tables t 
    on s.object_id = t.object_id
join sys.schemas sh
    on t.schema_id = sh.schema_id
left join sys.indexes i 
    on s.object_id = i.object_id
        and s.name = i.name
cross apply sys.dm_db_stats_properties_internal(s.object_id, s.stats_id) sp
outer apply sys.dm_db_stats_properties_internal(s.object_id, s.stats_id) n1
where n1.node_id = 1
    and (
            (is_incremental = 0)
               or
            (is_incremental = 1 and sp.partition_number is not null)
         )
    and t.name = 'Posts'
    and s.name like 'st_posts%'
order by s.stats_id,isnull(sp.partition_number,1)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.