昨日クエリでパフォーマンスの問題が発生していましたが、さらに調査したところ、クラスター化された列ストアインデックスでの動作が奇妙であることに気づきました。
テーブルは
CREATE TABLE [dbo].[NetworkVisits](
[SiteId] [int] NOT NULL,
[AccountId] [int] NOT NULL,
[CreationDate] [date] NOT NULL,
[UserHistoryId] [int] NOT NULL
)
インデックス付き:
CREATE CLUSTERED COLUMNSTORE INDEX [CCI_NetworkVisits]
ON [dbo].[NetworkVisits] WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [PRIMARY]
テーブルには現在13億行が含まれており、常に新しい行を挿入しています。私が絶えず言うとき、私はいつも意味します。これは、一度に1行ずつテーブルに挿入するという安定した流れです。
Insert Into NetworkVisits (SiteId, AccountId, CreationDate, UserHistoryId)
Values (@SiteId, @AccountId, @CreationDate, @UserHistoryId)
実行計画 ここ
テーブルから重複する行を削除するために4時間ごとに実行されるスケジュールされたジョブもあります。クエリは次のとおりです。
With NetworkVisitsRows
As (Select SiteId, UserHistoryId, Row_Number() Over (Partition By SiteId, UserHistoryId
Order By CreationDate Asc) RowNum
From NetworkVisits
Where CreationDate > GETUTCDATE() - 30)
DELETE
FROM NetworkVisitsRows
WHERE RowNum > 1
Option (MaxDop 48)
実行計画が貼り付けられました ここ 。
問題を掘り下げていると、NetworkVisits
テーブルに約2000の行グループがあり、そのうちの約800がオープン状態にあり、最大許容数(1048576)に近くないことに気付きました。ここに私が見たものの小さなサンプルがあります:
インデックスで再編成を実行しましたが、1つの行グループを除いてすべて圧縮しましたが、今朝もう一度確認すると、複数の開いている行グループがあります。1つは再編成後に昨日作成されたもので、残りの3つはおおよそ削除の前後に作成されました。ジョブが実行されました:
TableName IndexName type_desc state_desc total_rows deleted_rows created_time
NetworkVisits CCI_NetworkVisits CLUSTERED COLUMNSTORE OPEN 36754 0 2019-12-18 18:30:54.217
NetworkVisits CCI_NetworkVisits CLUSTERED COLUMNSTORE OPEN 172103 0 2019-12-18 20:02:06.547
NetworkVisits CCI_NetworkVisits CLUSTERED COLUMNSTORE OPEN 132628 0 2019-12-19 04:03:10.713
NetworkVisits CCI_NetworkVisits CLUSTERED COLUMNSTORE OPEN 397718 0 2019-12-19 08:02:13.063
これが既存の行グループを使用する代わりに新しい行グループを作成する原因となる可能性があるものを特定しようとしています。
挿入と削除の間にメモリの圧迫や競合が発生している可能性はありますか?この動作はどこかに文書化されていますか?
このサーバーではSQL Server 2017 CU 16 Enterprise Editionを実行しています。
INSERT
はMAXDOP 0、DELETE
はMAXDOP 48です。閉じられた行グループは、最初のBULKLOAD
からのものと、昨日行ったREORG_FORCED
のものです。なので、sys.dm_db_column_store_row_group_physical_stats
のトリム理由は、それぞれREORG
およびNO_TRIM
です。それらを超える閉じた行グループはありません。このテーブルに対して実行されている更新はありません。 INSERTステートメントでは、平均して毎分約520回の実行が行われます。テーブルにパーティションはありません。
私は細流の挿入を知っています。他の場所でも同じことが行われ、複数の開いている行グループで同じ問題が発生することはありません。私たちの疑いは、それが削除に関係していることです。新しく作成された各行グループは、スケジュールされた削除ジョブの時間前後です。削除された行を示すデルタストアは2つだけです。このテーブルから実際に大量のデータを削除することはありません。たとえば、昨日の1回の実行中に、266行を削除しました。
クラスター化列ストアインデックスを持つテーブルに開いている行グループが多数あるのはなぜですか?
これを引き起こす可能性がある多くの異なるシナリオがあります。私は、あなたが望むものであると私が思うあなたの特定のシナリオに取り組むことを支持して、一般的な質問に答えることを渡します。
挿入と削除の間にメモリの圧迫や競合が発生している可能性はありますか?
それはメモリのプレッシャーではありません。 SQL Serverは、列ストアテーブルに単一の行を挿入するときにメモリの許可を要求しません。行がデルタ行グループに挿入されることがわかっているため、メモリの付与は必要ありません。 INSERT
ステートメントごとに102399を超える行を挿入し、25秒の固定メモリ許可タイムアウトに達したときに、予想よりも多くのデルタ行グループを取得することができます。ただし、このメモリ圧力のシナリオは、バルクロード用であり、トリクルロード用ではありません。
DELETE
とINSERT
の間の互換性のないロックは、テーブルで何が表示されているかについてのもっともらしい説明です。本番環境では細流挿入を行わないことに注意してください。ただし、デルタ行グループから行を削除するための現在のロック実装には、UIXロックが必要なようです。これは簡単なデモで確認できます。
最初のセッションで、いくつかの行をデルタストアにスローします。
DROP TABLE IF EXISTS dbo.LAMAK;
CREATE TABLE dbo.LAMAK (
ID INT NOT NULL,
INDEX C CLUSTERED COLUMNSTORE
);
INSERT INTO dbo.LAMAK
SELECT TOP (64000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
2番目のセッションで行を削除しますが、変更はまだコミットしません。
BEGIN TRANSACTION;
DELETE FROM dbo.LAMAK WHERE ID = 1;
sp_whoisactive
ごとのDELETE
のロック:
<Lock resource_type="HOBT" request_mode="UIX" request_status="GRANT" request_count="1" />
<Lock resource_type="KEY" request_mode="X" request_status="GRANT" request_count="1" />
<Lock resource_type="OBJECT" request_mode="IX" request_status="GRANT" request_count="1" />
<Lock resource_type="OBJECT.INDEX_OPERATION" request_mode="S" request_status="GRANT" request_count="1" />
<Lock resource_type="PAGE" page_type="*" request_mode="IX" request_status="GRANT" request_count="1" />
<Lock resource_type="ROWGROUP" resource_description="ROWGROUP: 5:100000004080000:0" request_mode="UIX" request_status="GRANT" request_count="1" />
最初のセッションに新しい行を挿入します。
INSERT INTO dbo.LAMAK
VALUES (0);
2番目のセッションで変更をコミットし、sys.dm_db_column_store_row_group_physical_stats
を確認します。
挿入が変更する行グループに対してIXロックを要求するため、新しい行グループが作成されました。 IXロックはUIXロックと互換性がありません。これは現在の内部実装のようで、おそらくMicrosoftは時間の経過とともにそれを変更するでしょう。
修正方法については、このデータの使用方法を検討する必要があります。データをできるだけ圧縮することが重要ですか? [CreationDate]
列で行グループを適切に削除する必要がありますか?新しいデータが数時間表に表示されなくても問題ありませんか?最長4時間存在するのとは対照的に、重複がテーブルに表示されない場合、エンドユーザーは好むでしょうか。
これらすべての質問に対する答えが、問題に対処するための正しい道を決定します。ここにいくつかのオプションがあります:
1日に1回、列ストアに対してCOMPRESS_ALL_ROW_GROUPS = ON
オプションを指定してREORGANIZE
を実行します。これは、平均して、テーブルがデルタストアの100万行を超えないことを意味します。これは、可能な限り最高の圧縮が必要ない場合、[CreationDate]
列で行グループを削除する必要がない場合、および重複する行を4時間ごとに削除するという現状を維持したい場合に適したオプションです。
DELETE
をINSERT
およびDELETE
ステートメントに分割します。最初のステップとして、削除する行を一時テーブルに挿入し、2番目のクエリでTABLOCKX
を使用してそれらを削除します。これは、データの読み込みパターン(挿入のみ)と、重複の検索と削除に使用する方法に基づいて、1つのトランザクションに存在する必要はありません。数百行を削除すると、[CreationDate]
列が適切に削除されるため、非常に高速になり、最終的にはこのアプローチで取得できます。このアプローチの利点は、その列の日付が現在の日付であると仮定すると、圧縮された行グループの[CreationDate]
の範囲が狭くなることです。欠点は、トリクルインサートの実行が数秒間ブロックされることです。
新しいデータをステージングテーブルに書き込み、X分ごとに列ストアにフラッシュします。フラッシュプロセスの一部として、重複の挿入をスキップできるため、メインテーブルに重複が含まれることはありません。他の利点は、データをフラッシュする頻度を制御できるため、目的の品質の行グループを取得できることです。欠点は、新しいデータが[dbo].[NetworkVisits]
テーブルに表示されなくなることです。テーブルを組み合わせたビューを試すこともできますが、データをフラッシュするプロセスにより、エンドユーザーのデータの一貫したビューが得られるように注意する必要があります(行が非表示になったり、行が2回表示されたりしたくない場合)処理する)。
最後に、テーブルの再設計を検討する必要があるという他の回答には同意しません。平均で1秒あたり9行をテーブルに挿入しているだけで、レートは高くありません。 1つのセッションで、6列の列ストアテーブルに1秒あたり1500のシングルトン挿入を実行できます。周りの数字を見始めたら、テーブルのデザインを変更することをお勧めします。
継続的なトリクルインサートを使用すると、多数の開いているdeltastore行グループが作成される可能性があります。これは、挿入が開始されると、既存の行グループがすべてロックされている場合、新しい行グループが作成されるためです。 階段から列ストアインデックスレベル5:列ストアインデックスへの新しいデータの追加
102,399以下の行の挿入は、「トリクル挿入」と見なされます。これらの行は、利用可能な場合(そしてロックされていない場合)、開いているデルタストアに追加されます。そうでない場合は、それらのために新しいデルタストア行グループが作成されます。
一般に、列ストアインデックスの設計は一括挿入用に最適化されており、トリクル挿入を使用する場合は、定期的にreorgを実行する必要があります。
Microsoftのドキュメントで推奨されている別のオプションは、ステージングテーブル(ヒープ)に細流し、102,400行を超えたら、それらの行をcolumstoreインデックスに挿入することです。 列ストアインデックス-データ読み込みのガイダンスを参照してください
いずれの場合も、大量のデータを削除した後、データが実際に削除され、結果のデルタストア行グループがクリーンアップされるように、列ストアインデックスの再編成をお勧めします。
これは、クラスター化列ストアインデックスのエッジケースのように見え、最終的に、これは現在のMicrosoftの考慮事項の下での[〜#〜] htap [〜#〜]シナリオです。つまり、NCCIはより良いソリューション。ええ、クラスター化されたインデックスでの列ストア圧縮を失うと、ストレージの面で本当に悪いことになると思いますが、メインストレージがデルタストアの場合は、とにかく非圧縮で実行しています。
また: