web-dev-qa-db-ja.com

増分更新後に統計が消える

増分統計を利用する大規模なパーティション化された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!!!!!!'

示されているように、インデックスのすべての統計情報を失うことなく、パーティションをオンラインで再構築することはできません。これは私たちにとって大きなメンテナンスの問題です。 stats incrementalオプションは単一のインデックス再構築構文の一部である必要があるか、onlineオプションがそれをofflineオプションのように適切に処理する必要があるように思われます。

何か不足している場合はお知らせください。

更新:

増分統計の必要性に関する限り:日付ではなく、内部の顧客IDでパーティション分割しています。そのため、新しいクライアントが導入されると(データの大量のバックロード)、パーティションの統計を更新するだけで、この新しい顧客向けに作成される醜い計画をすばやく回避できます。私はそれをバグとしてMicrosoftに提出し、彼らが言わなければならないことを確認し、そのパーティションの統計を再サンプリングするだけのソリューションを採用すると思います。

Connectバグレポート:

増分統計を使用したオンラインインデックスの再構築後、統計は消えます

更新:マイクロソフトはバグであることを確認しています。

21
JasonR

バグかどうかはわかりませんが、 per se ですが、間違いなく興味深い出来事です。オンラインパーティションの再構築は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'
_

いくつかの方法で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';
_

以上のことをすべて述べた後、 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ミリ秒かかります。 個々の統計ページをメインヒストグラムにマージする で発生するオーバーヘッドが原因で、非整列統計が最も遅くなる可能性があります。ただし、パーティションアライン統計オブジェクトを使用して、1つのパーティションを更新し、5ミリ秒でメインヒストグラムBLOBにマージできます。したがって、この時点で、増分統計を持つ管理者は決定に直面しています。従来はパーティションを更新するだけで更新する必要があったため、全体的な統計メンテナンス時間を短縮できます。また、以前のメンテナンス期間と同じ期間にサンプリングされる行が増える可能性があるように、より高いサンプルレートで実験することもできます。前者は、メンテナンスウィンドウで呼吸室を許可し、後者は might 非常に大きなテーブルの統計をプッシュしてより正確な統計に基づいてクエリがより良い計画を得る場所。これは保証ではなく、走行距離が異なる場合があります。

読者は、66ミリ秒がこのテーブルの統計更新時間として苦痛ではないことがわかるので、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ミリ秒。

これまでのところ、統計値を増分しても明確な利点はありません。ただし、 undocumented sys.dm_db_stats_properties_internal() DMV(以下)を利用すると、次のことができます。どのパーティションを更新する必要があるかについての洞察を得ます。パーティション3のデータに変更を加え、受信クエリの統計が新しいことを確認したいとします。オプションは次のとおりです。

  • デフォルトでの非増分更新(自動統計更新のデフォルトの動作も):408ミリ秒。
  • 10%で非インクリメンタルに更新:2441ミリ秒。
  • 増分統計の更新、パーティション3のリサンプル(10%-定義されたサンプルレート):CPU時間= 63 ms、経過時間= 63 ms。

ここで決定を下す必要があります。 63ミリ秒で勝ちましたか。パーティションベースの統計更新、またはサンプルレートをさらに高くするか?増分統計でサンプリングの初期ヒットを50%で取得するとします。

  • 50%で増分統計を更新:経過時間= 16840ミリ秒。
  • 増分統計の更新、パーティション3、リサンプル(50%-新しい更新時間):経過時間= 295ミリ秒。

より多くのデータをサンプリングすることができます(おそらく、パーティションレベルの統計を使用していなくても)データをより正確に推測できるようにオプティマイザーを設定します。これにより、より迅速にこれを行うことができます増分統計。

とはいえ、最後にもう1つ興味深い点があります。統計の同期更新についてはどうですか? autostatsが起動しても、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)
_
17
swasheck