增量更新后统计信息消失

Jas*_*onR 21 sql-server statistics partitioning sql-server-2014

我们有一个使用增量统计的大型分区 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!!!!!!'
Run Code Online (Sandbox Code Playgroud)

如图所示,我们无法在不丢失索引的所有统计信息的情况下在线按分区重建索引。这对我们来说是一个主要的维护问题。似乎 stats 增量选项需要成为单索引重建语法的一部分,或者在线选项需要像离线选项一样正确处理它。

如果我遗漏了什么,请告诉我?

更新:

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

连接错误报告:

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

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

swa*_*eck 17

不确定它本身是否是一个错误,但这绝对是一个有趣的事件。在线分区重建是 SQL Server 2014 中的新功能,因此可能有一些内部结构需要对此进行整理。

这是我对你最好的解释。增量统计绝对要求以相同的速率对所有分区进行采样,以便引擎合并统计页面时可以确信采样分布具有可比性。REBUILD必须以 100% 的采样率对数据进行采样。无法保证分区 9 上的 100% 采样率始终是其余分区的准确采样率。因此,看起来引擎似乎无法合并样本,最终会得到一个空的 stats 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'
Run Code Online (Sandbox Code Playgroud)

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

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';
Run Code Online (Sandbox Code Playgroud)

说了这么多,这篇由 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.
Run Code Online (Sandbox Code Playgroud)

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

读者可以看到,66 ms 在这张表上并不是一个痛苦的统计更新时间,所以我尝试在 stackexchange 数据集上设置一个测试。我最近下载的转储中有 6,418,608 个帖子(不包括 StackOverflow 帖子和 2012 年的所有帖子 - 我的数据错误)。

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

以下是一些非常标准的场景的一些时间(100% - 索引重建,默认 - 统计数据自动更新或UPDATE STATISTICS没有指定的采样率:

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

假设我们比这些默认方案更复杂,并决定 10% 的采样率是最低采样率,它应该为我们提供所需的计划,同时将维护时间保持在合理的时间范围内。

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

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

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

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

  • 在 50% 处更新增量统计:已用时间 = 16840 毫秒。
  • 使用重采样更新增量统计,分区 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)
Run Code Online (Sandbox Code Playgroud)