web-dev-qa-db-ja.com

空のテーブルでCCIを作成すると、大量のメモリが付与されるのはなぜですか?

メモリが非常に少ないQAサーバーで、SQL Server 2016の新しいテーブルにクラスター化列ストアインデックスを作成すると、エラー8545が発生しました。

エラー8645:メモリリソースがクエリを実行するのを待機中にタイムアウトが発生しました。クエリを再実行します。

ローカルマシンで要求された大量のメモリ許可を簡単に再現できます。次のコードの場合:

DROP TABLE IF EXISTS dbo.MY_FIRST_FACT_TABLE;

CREATE TABLE dbo.MY_FIRST_FACT_TABLE (
    ID BIGINT NOT NULL,
    COL1 BIGINT NULL,
    COL2 BIGINT NULL,
    COL3 BIGINT NULL,
    COL4 BIGINT NULL,
    COL5 BIGINT NULL,
    COL6 BIGINT NULL,
    COL7 BIGINT NULL,
    COL8 BIGINT NULL,
    COL9 BIGINT NULL,
    COL10 BIGINT NULL,
    STRING1 VARCHAR(100) NULL,
    STRING2 VARCHAR(100) NULL,
    STRING3 VARCHAR(100) NULL,
    STRING4 VARCHAR(100) NULL,
    STRING5 VARCHAR(100) NULL
);

CREATE CLUSTERED COLUMNSTORE INDEX MY_FIRST_CCI ON dbo.MY_FIRST_FACT_TABLE;

約512 MBのメモリ要求が発生します。実際の実行計画では、過度のメモリ付与に関する警告があります。

memory grant

クエリは0 KBのメモリを使用しますが、サーバー上の他のアクティビティによってはタイムアウトになる可能性があります。 SQL Serverが大量のメモリを要求するのはなぜですか?それについて私は何ができますか?

3
Joe Obbish

CCIクエリパフォーマンスのドキュメント にこれに関するヒントがあります。

列ストアインデックスを並列で作成するのに十分なメモリを計画する

列ストアインデックスの作成は、メモリに制約がない限り、デフォルトで並列処理です。インデックスを並行して作成するには、インデックスを連続して作成するよりも多くのメモリが必要です。十分なメモリがある場合、列ストアインデックスの作成には、同じ列にBツリーを構築する場合の1.5倍の時間がかかります。

列ストアインデックスの作成に必要なメモリは、列の数、文字列の列の数、並列度(DOP)、およびデータの特性によって異なります。たとえば、テーブルの行数が100万未満の場合、SQL Serverは1つのスレッドのみを使用して列ストアインデックスを作成します。

その引用は、テーブル内の行数はDOPにとって重要であるが、メモリ許可については重要ではないことを示唆しています。必要なメモリは列の数にも依存するので、列が少ないテーブルでテストして、予想されるメモリ許可の違いをより簡単に確認してみましょう。私はこのテーブルとインデックス定義を使用しています:

DROP TABLE IF EXISTS dbo.MY_SECOND_FACT_TABLE;

CREATE TABLE dbo.MY_SECOND_FACT_TABLE (
    ID BIGINT NOT NULL,
    COL1 BIGINT NULL
);

INSERT INTO dbo.MY_SECOND_FACT_TABLE WITH (TABLOCK) (ID, COL1)
SELECT TOP (0) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), NULL
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

CREATE CLUSTERED COLUMNSTORE INDEX MY_SECOND_CCI ON dbo.MY_SECOND_FACT_TABLE WITH (MAXDOP = 1);

以下は、いくつかのテストの結果です。

╔══════════════════╦════════════════╦═════════════════╗
║ REQUESTED_MAXDOP ║ NUMBER_OF_ROWS ║ MEMORY_GRANT_KB ║
╠══════════════════╬════════════════╬═════════════════╣
║                1 ║              0 ║           89928 ║
║                2 ║              0 ║           89928 ║
║                1 ║        2000000 ║           89928 ║
║                2 ║        2000000 ║          179896 ║
║                1 ║        5000000 ║           89928 ║
║                2 ║        5000000 ║          179896 ║
╚══════════════════╩════════════════╩═════════════════╝

行が0の場合、DOPは1にダウングレードされますが、それ以外の場合、行の数はメモリの付与に影響しないようです。これは明らかに非常に単純化しすぎていますが、SQL Serverは、CCIをDOPごとに一度に1つの行グループを構築し、それに従って文字列辞書を維持します。これは実際には問題なく聞こえますが、メモリ許可はテーブルの行数に比例しませんが、非常に少ない行数のチェックがないと、SQL Serverが過剰なメモリ許可を要求する可能性があります。 CREATE INDEXは非常に高速であるため、ほとんどの場合、過剰なメモリの付与は問題になりませんが、SQL Serverがメモリの付与を待機するかどうかは問題になります。

だから今私はなぜメモリ付与がそれほど大きいのかという考えを持っていますが、それについて何ができるのですか? SQL Server 2014では、テーブル定義の一部としてインデックス定義を定義する構文が導入されました。 テーブルとインデックスの両方を作成する単一のステートメント を書くことは可能です:

INDEX index_name CLUSTERED COLUMNSTORE

適用対象:SQL Server 2014〜SQL Server 2016およびAzure SQLデータベース。

クラスター化列ストアインデックスを使用して、テーブル全体を列形式で格納することを指定します。これには常にテーブルのすべての列が含まれます。行は列ストア圧縮のメリットを得るために編成されているため、データはアルファベット順または数値順に並べ替えられていません。

元のテーブルの構文は次のようになります。

CREATE TABLE dbo.MY_FIRST_FACT_TABLE (
    ID BIGINT NOT NULL,
    COL1 BIGINT NULL,
    COL2 BIGINT NULL,
    COL3 BIGINT NULL,
    COL4 BIGINT NULL,
    COL5 BIGINT NULL,
    COL6 BIGINT NULL,
    COL7 BIGINT NULL,
    COL8 BIGINT NULL,
    COL9 BIGINT NULL,
    COL10 BIGINT NULL,
    STRING1 VARCHAR(100) NULL,
    STRING2 VARCHAR(100) NULL,
    STRING3 VARCHAR(100) NULL,
    STRING4 VARCHAR(100) NULL,
    STRING5 VARCHAR(100) NULL,
    INDEX MY_FIRST_CCI CLUSTERED COLUMNSTORE
);

CREATE TABLEステートメントは実際のプランを生成しないので、これがメモリ許可を減らしたかどうかはすぐにはわかりません。 sys.dm_exec_query_memory_grants DMVを叩きながらテーブルを1000回作成しようとしましたが、結果は得られませんでした。これは、記憶が与えられなかったが証明ではないことを示唆しています。私は query_memory_grant_usageを使用した拡張イベント も試してみましたが、単一のCREATE TABLEステートメントの結果は得られませんでした。

もちろん、テストする最良の方法は、使用可能なメモリが限られている元のワークフロー条件であるCCIを作成することです。私はそれを行い、メモリタイムアウトは発生しませんでした。

新しいテーブルのCREATE TABLEステートメントでCCIを定義することがベストプラクティスであると言えるでしょう。ただし、ワークロードによってこのエラーが発生した場合、CREATE TABLEスクリプトを変更するだけでは、CCIへのデータのロードを開始するときのメモリの問題など、発生する他の問題に対処するにはおそらく不十分です。

3
Joe Obbish