インデックスの自動コンパクション

適用対象:Azure SQL DatabaseAzure SQL Managed InstanceAUTDMicrosoft Fabric の SQL データベース

インデックスの自動圧縮を使用すると、インデックスのメンテナンス ジョブに時間と労力を費やすことなく、ストレージ領域、ディスク I/O、CPU、メモリの消費量を削減し、ワークロードのパフォーマンスを向上させることができます。 インデックスの圧縮は継続的に実行され、データベース内のデータの変更に伴うオーバーヘッドが少なくなっています。

一般的な質問に対する回答については、 よく寄せられる質問 (FAQ) を参照してください。

有効化と無効化

インデックスの自動圧縮は、既定では無効になっています。 データベースに対して有効または無効にするには、次のTransact-SQLステートメントを実行します。

  • インデックスの自動圧縮を有効にする:

    ALTER DATABASE [database-name-placeholder]
    SET AUTOMATIC_INDEX_COMPACTION = ON;
    
  • インデックスの自動圧縮を無効にする:

    ALTER DATABASE [database-name-placeholder]
    SET AUTOMATIC_INDEX_COMPACTION = OFF;
    

インデックスの自動圧縮が有効になっているかどうかを確認するには、 sys.databases カタログ ビューを使用します。 たとえば、自動インデックス圧縮が有効になっているデータベースを確認するには、次のクエリを実行します。

SELECT database_id,
       name,
       is_automatic_index_compaction_on
FROM sys.databases;

DATABASEPROPERTYEX 関数を使用して、IsAutomaticIndexCompactionOn プロパティを確認することもできます。

利点と考慮事項

インデックスの自動圧縮には、次の利点があります。

  • インデックスのメンテナンス ジョブを設定して管理する必要はありません。
  • インデックスのメンテナンス ジョブによるリソースの消費量が多くなるのを回避します。
  • これにより、データベース内のデータが変更されたときに発生する可能性がある領域の増加が減少します。
  • クエリのパフォーマンスが向上します。
    • コンパクト インデックスを読み取るクエリの読み取り数が少なくなるため、必要なディスク I/O、CPU、メモリが少なくなります。
    • コンパクト インデックスは、クエリ プランを改善するために選択される可能性が高くなります。

Important

自動コンパクト化は、INSERT、UPDATE、DELETE、または MERGE 文によって最近変更された行を含むデータページにのみ作用します。 その結果、圧縮のオーバーヘッドは、すべてのページを処理するインデックスの再構築またはインデックスの再編成と比較して最小限になります。

圧縮プロセスのオーバーヘッドは最小限ですが、ゼロではありません。 インデックスの自動圧縮を有効にする場合は、次の点を考慮してください。

  • 圧縮プロセスで多数の行が移動すると、トランザクション ログ書き込み I/O の量とトランザクション ログ バックアップのサイズが増加する可能性があります。
  • 圧縮が行われると、1 桁の割合が低い範囲内で CPU 使用率が小さく増加する場合があります。 この増加は一般的ではありません。
  • 索引再編成と同様に、圧縮プロセスは、あるページから別のページに行を移動するために、短期排他 (X) ページ・ロックを獲得します。
    • コンカレンシーへの影響は最小限です。 ページのロックをすぐに取得できない場合、他のクエリやプロセスがブロックされないようにページはスキップされます。
      • クエリで短期的 (ミリ秒) のブロックが発生することがあります。 このブロックは、圧縮プロセスが既にページに対して排他的な短期ロックを行った後に、クエリがページまたは行ロックを取得しようとした場合に発生します。これは一般的ではありません。

どのように機能するのか

インデックスの自動圧縮は、バックグラウンド永続 バージョン ストア (PVS) クリーナー プロセスの一部です。 このプロセスにより、古い行バージョンがデータ ページから定期的に削除されます。 データベースのインデックスの自動圧縮を有効にすると、PVS クリーナーによってインデックスも圧縮されます。

クリーナーは、最近挿入、更新、または削除された行を含む各ページにアクセスすると、現在のページに空き領域があるかどうかを確認します。フィル ファクターによって予約されている空き領域は除きます。 その場合、クリーナーは、空き領域に収まる限り、行を次のページから現在のページに移動します。 その後、このプロセスは前方に進み、クリーンアップされるページの後に連続する少数のページ ペアに対して繰り返されます。

行の移動後にページが空になった場合は、割り当てが解除されます。 その結果、データベース内の使用ページの合計数が減少し、 ページ密度 が増加し、記憶域スペース、ディスク I/O、CPU、およびバッファー プール メモリの消費量が減少します。

次の図は、圧縮の前後のインデックス内のデータ ページの概念図を示しています。

インデックスの自動圧縮の前後のデータ ページの概念図。

圧縮プロセスは、データが変更され、古い行バージョンがクリーンアップされるとバックグラウンドで続行されます。

コンパクション プロセスでは、次のような同時実行アクティビティが原因で、一部のページがスキップされる場合があります。

  • ページを使用するアクティブなトランザクション。
  • 進行中のインデックスのビルドまたは再編成。
  • 圧縮操作が進行中です。
  • PVS のサイズが大きい、または PVS からクリーンアップすべき中断されたトランザクションの数が多くなります。
    • PVS クリーンアップは、自動圧縮よりも優先されます。 PVS サイズが 150 GB 以上の場合、または中止されたトランザクションの数が 1,000 以上の場合、圧縮は中断されます。

圧縮プロセスがページをスキップする可能性があるあまり一般的でない理由については、「 拡張イベントを使用して圧縮統計を監視する」を参照してください。

ページがスキップされた場合は、次回 PVS クリーナーによって処理されるときに圧縮が考慮されます。

インデックスの自動圧縮は、システム テーブルと、 msdb以外のシステム データベースでは使用できません。 ページ ロックが無効になっているインデックスは、自動圧縮の対象になりません。

データ ページの詳細については、「 ページとエクステントのアーキテクチャ ガイド」を参照してください。

インデックスの詳細については、「 インデックスのアーキテクチャと設計ガイド」を参照してください。

インデックス圧縮、再編成、再構築のいずれを使うべきか

インデックス保守操作間の違いを考えてみましょう:

考慮事項 レコメンデーション
圧縮は、データベース内のデータが変更されている限り、継続的かつ最小限のオーバーヘッドで発生します。 これらのジョブが提供する可能性のある利点を得るために、インデックスメンテナンスジョブを設定、監視、保守する必要はありません。
インデックス再編成や再構築がすべてのページを処理するのに対し、圧縮プロセスは自動インデックス圧縮を有効にした後、INSERT、UPDATE、DELETE、MERGE 文によって変更されたデータページのみを考慮します。 インデックスのページ密度が既に低い場合は、インデックスの 1 回限りの再編成またはインデックス再構築を実行して、インデックスを増やすことを検討してください。 この 1 回限りの操作は、ページ密度をすぐに高めるための追加の最適化です。 その時点から、自動圧縮により、ユーザー操作なしでインデックスがコンパクトになります。
各インデックス再構築操作では、データ ファイル内に大きな空き領域が必要です。通常、再構築するインデックスまたはパーティションのサイズと同じです。 自動索引圧縮または索引再編成のために、データ・ファイルに空き領域を割り振る必要はありません。
インデックスの再構築やインデックスの再編成とは異なり、圧縮によってインデックスの断片化が減りません。 圧縮後のページ密度の増加は、インデックスの断片化よりも重要です。 ほとんどのワークロードでは、インデックスの断片化が高いほど、クエリのパフォーマンスやリソースの消費量には影響しません。
インデックスの再編成とは異なり、圧縮や再構築はLOBデータをコンパクト化しません。 varchar(max)、nvarchar(max)、varbinary(max)、xml、jsonなどのデータ型の列に格納された大量のLOBデータを削除する場合は、LOB_COMPACTION = ONで一度だけインデックス再編成を実行することを検討してください。
ヒープテーブルにはコンパクト化や再編成は利用できません(ただし、ヒープ上の非クラスタインデックスには両操作が可能です)。 ページ密度が低いヒープや多数のフォワーディングポインタには ALTER TABLE ... REBUILD を検討してください。
インデックスの塗りつぶし係数が 100% 未満で、ページ上のデータ量がフィル ファクターを超えている場合、インデックスの圧縮も再編成もページから行を移動しません。 インデックスの再構築では、新しいページが作成され、充填率に従ってページが満たされます。 ほとんどのワークロードでは、ページ密度を高くすることをお勧めします。 ページ分割を減らすために低いフィルファクターを必要とするワークロードでは、インデックスの再構築が不定期に発生する可能性があります。 再構築を行うと、塗りつぶし係数に一致したページ密度の低いページが作成されます。
インデックスの再構築とは異なり、圧縮ではインデックスの統計は更新されません。 統計の自動更新がワークロードに対して不十分であり、統計を更新するためにインデックスの再構築に依存している場合は、統計更新ジョブと組み合わせて自動圧縮を使用することを検討してください。

インデックスの再編成と再構築の詳細については、「 インデックスのメンテナンスを最適化してクエリのパフォーマンスを向上させ、リソースの消費量を削減する」を参照してください。

ページ密度とインデックスの断片化

ページ密度 と インデックスの断片化 は、インデックスによる領域使用量を反映する 2 つのメトリックであり、クエリのパフォーマンスに影響を与える可能性があります。 sys.dm_db_index_physical_stats動的管理機能 (DMF) は、avg_page_space_used_in_percent列とavg_fragmentation_in_percent列でこれらのメトリックをそれぞれ報告します。

圧縮により、同じページに格納する行が増えることでページ密度が向上し、パフォーマンスが向上し、リソースの消費量が削減されます。

インデックスの自動圧縮を有効にすると、インデックスの断片化が高くなる場合があります。 この状態が発生した場合は、次の点を考慮してください。

考慮事項 レコメンデーション
一部の書き込み負荷の高いワークロードでは、圧縮後すぐにページが再び分割される場合があります。 ページ分割により、インデックスの断片化が増加します。 結果としてワークロードのパフォーマンスが影響を受ける場合は、1 回限りインデックスの再構築を使用し、圧縮後のページ分割を減らすために、少し小さな フィル ファクター を使用します。

たとえば、70 ~ 95% の範囲で塗りつぶし係数を設定します。 ただし、不必要に塗りつぶし係数を減らしたり、低く設定したりしないでください。 ほとんどのワークロードでは、フィル ファクターを既定の 100% に設定して、最適なパフォーマンスとリソース使用率を実現します。
圧縮中に空のページの割り当てを解除すると、範囲内のページ番号シーケンスにギャップが生じる可能性 があります。 エクステント内のギャップによりインデックスの断片化が増加し、 先行読み取 り I/O のサイズが小さくなる可能性があります。 先読みの恩恵を受けるワークロードの場合でも、圧縮後に読み取られるページ数が少なくなるため、断片化の増加によるパフォーマンスへの影響は最小限に抑えられます。

ヒント

ほとんどのワークロードでは、ページ密度が高くなる利点は、インデックスの断片化の増加によるパフォーマンスへの影響を上回ります。

インデックスの自動圧縮を監視する

インデックスの自動圧縮の効果を確認するには、ページ数、平均ページ密度、時間の経過に伴う平均インデックスの断片化などの主要なインデックス メトリックを監視できます。 詳細については、 キー インデックス メトリックの決定 の例を参照してください。

sys.dm_db_index_operational_stats動的管理機能を使用して、インデックスの各パーティションの累積圧縮統計を監視できます。 これらの統計には、完了した圧縮試行とスキップされた圧縮試行、移動された行、および割り当て解除されたページが含まれます。 詳細については、 各インデックス パーティションの圧縮統計の表示の例を 参照してください。

拡張イベントを使用して、詳細な圧縮統計を監視することもできます。 詳細については、「 拡張イベントを使用して圧縮統計を監視する 」の例を参照してください。

制限事項

インデックスの自動圧縮プロセスでは、 IN_ROW_DATA アロケーション ユニットに含まれる B ツリー インデックスのリーフ レベルのページのみが考慮されます。 これには、次のページが含まれます。

  • クラスター化インデックスと制約。
  • 非クラスター化インデックスと制約。
  • XML、フルテキスト、空間、列ストアの内部行セットなどの特殊なインデックス型を格納する内部テーブルの B ツリー インデックス。

次の種類のページは、インデックスの自動圧縮の対象ではありません。

  • ヒープ テーブル内のページ。
  • ROW_OVERFLOW_DATAまたはLOB_DATAアロケーション ユニット内のページ。
  • 列ストア インデックス内の圧縮された行グループのページ。
  • メモリ最適化テーブル内のページ。

よく寄せられる質問 (FAQ)

このセクションでは、インデックスの自動圧縮に関する一般的な質問に回答します。

自動圧縮を有効または無効にするには、再起動または排他データベース アクセスが必要ですか?

No. 圧縮は、 ALTER DATABASE ... SET AUTOMATIC_INDEX_COMPACTION = ... コマンドを実行した後、数分以内に開始または停止します。

クエリのパフォーマンスはどのように変わりますか?

クエリの実行速度が速くなり、使用するディスク I/O、メモリ、CPU が少なくなる傾向があります。 この改善は、インデックスの肥大化を引き起こす重大な書き込みアクティビティがあるワークロードで最も顕著です。

記憶域スペースの使用量はどのように変わりますか?

圧縮により、データ ファイル内の使用領域の増加が減少します。 ただし、 データベースの圧縮とは異なり、割り当てられたデータ ファイルのサイズは縮小されません。

オーバーヘッドはありますか?

ほとんどのワークロードでは、オーバーヘッドは目立ちません。 書き込み負荷の高いワークロードでは、トランザクション ログの I/O とトランザクション ログ バックアップのサイズが増加する場合があります。

ブロックの原因になりますか?

自動圧縮によるブロックは可能性が低いです。 ブロックが発生した場合は、短期的かつ一時的な (ミリ秒) です。

クエリがブロックされている場合は、sys.dm_exec_requestsでヘッド ブロッカーのコマンド を確認します。 コマンドが VERSION_CLEANER_MAIN または VERSION_CLEANER_WORKERされている場合、自動圧縮によってクエリがブロックされる可能性があります。

フィル ファクターを遵守していますか?

自動コンパクトは フィルファクターで予約された空きページスペースを埋めません。 しかし、その予約された領域がすでに前のデータ変更で埋まっている場合、コンパクト化は現在それを解放しません。

インデックスで行またはページの圧縮が使用されている場合は機能しますか?

Yes. 自動圧縮により、インデックス内のページから空の領域が削除されます。 ページ上のデータが圧縮されているかどうかは関係ありません。

ゴーストクリーンアップとどう違うのですか?

ゴースト クリーンアップでは、ソフト削除された行をページから削除し、ページに空の領域を残します。 自動圧縮では、少ないページのデータを統合することで、ページの空き領域を削除します。

自動圧縮が有効になっているときにインデックスの再構築またはインデックスの再編成を実行するとどうなりますか。

自動圧縮では、再構築または再構成中のインデックス (再開可能なインデックス操作の一時停止中のインデックスを含む) がスキップされます。

オートコンパクトはサーバーレスデータベースの自動一時停止を防ぐことができますか?

No. データベースが一時停止した際に自動コンパクト化の対象となるインデックスは、データベース再開後にコンパクト化されます。 自動インデックス圧縮は一時停止したサーバーレスデータベースを再開しません。 詳細は「 自動一時停止」と「自動再開」をご覧ください。

多くのデータを削除して自動コンパクト化を有効にしましたが、ページの密度は依然として低いままです。 Why?

自動コンパクトは、機能が有効になっている間に修正されたページにのみ作用します。 データ削除後にインデックスが自動的に圧縮されるようにするために、まず自動コンパクトを有効にしてからデータを削除してください。

例示

master データベースではなく、ユーザー データベースで次の T-SQL の例を実行します。

主要なインデックス メトリックを決定する

次のクエリは、インデックスのリーフ レベルのページ数、その平均ページ密度、および自動圧縮の対象となるインデックスの page_count、 avg_page_space_used_in_percent、および avg_fragmentation_in_percent 列の断片化をそれぞれ返します。 クエリでは、データベース内のすべてのインデックスの集計にこれらのメトリックを含む合計行も返されます。

SELECT COALESCE (OBJECT_SCHEMA_NAME(ips.object_id), '<Total>') AS schema_name,
       COALESCE (OBJECT_NAME(ips.object_id), '<Total>') AS object_name,
       COALESCE (i.name, '<Total>') AS index_name,
       COALESCE (i.type_desc, '<Total>') AS index_type,
       COALESCE (ips.partition_number, NULL) AS partition_number,
       AVG(ips.avg_page_space_used_in_percent) AS avg_page_space_used_in_percent,
       AVG(ips.avg_fragmentation_in_percent) AS avg_fragmentation_in_percent,
       SUM(ips.record_count) AS record_count,
       SUM(ips.page_count) AS page_count
FROM sys.dm_db_index_physical_stats(DB_ID(), DEFAULT, DEFAULT, DEFAULT, 'SAMPLED') AS ips
     INNER JOIN sys.indexes AS i
         ON ips.object_id = i.object_id
        AND ips.index_id = i.index_id
WHERE i.type_desc IN ('CLUSTERED', 'NONCLUSTERED', 'XML', 'SPATIAL')
      AND ips.index_level = 0
      AND ips.page_count > 0
      AND ips.alloc_unit_type_desc = 'IN_ROW_DATA'
GROUP BY ROLLUP(ips.object_id, i.name, i.type_desc, ips.partition_number)
HAVING ips.object_id IS NULL
       AND ips.object_id IS NULL
       AND i.name IS NULL
       AND i.type_desc IS NULL
       AND ips.partition_number IS NULL
       OR ips.object_id IS NOT NULL
          AND ips.object_id IS NOT NULL
          AND i.name IS NOT NULL
          AND i.type_desc IS NOT NULL
          AND ips.partition_number IS NOT NULL
ORDER BY IIF (ips.object_id IS NULL, 0, 1), page_count DESC;

このクエリでは、ページのサブセットをサンプリングすることで、おおよその結果が返されます。 より正確な結果を得るには、 SAMPLED を DETAILED に変更します。 大規模なデータベースに DETAILED を使用すると、データベース内のすべての有効なインデックスが完全にスキャンされるため、はるかに長い時間がかかる場合があります。 詳細については、sys.dm_db_index_physical_stats に関する記事をご覧ください。

各インデックス パーティションの圧縮統計を表示する

次のクエリを使用して、現在のデータベース内のインデックスの各パーティションの自動圧縮統計を確認できます。

SELECT OBJECT_SCHEMA_NAME(ios.object_id) AS schema_name,
       OBJECT_NAME(ios.object_id) AS object_name,
       i.name AS index_name,
       i.type_desc AS index_type,
       ios.partition_number AS partition_number,
       ios.compaction_attempt_count,
       ios.compaction_complete_count,
       ios.compaction_skip_count,
       ios.compaction_ineligible_count,
       ios.compaction_failure_count,
       ios.compaction_row_move_count,
       ios.compaction_page_deallocation_count
FROM sys.dm_db_index_operational_stats(DB_ID(), DEFAULT, DEFAULT, DEFAULT) AS ios
     INNER JOIN sys.indexes AS i
         ON ios.object_id = i.object_id
        AND ios.index_id = i.index_id
WHERE i.type_desc IN ('CLUSTERED', 'NONCLUSTERED', 'XML', 'SPATIAL')
      AND OBJECT_SCHEMA_NAME(ios.object_id) <> 'sys';

詳細については、「sys.dm_db_index_operational_stats」 を参照してください。

拡張イベントを使用して圧縮統計を監視する

auto_index_compaction_stats拡張イベントを使用して、データベースの圧縮統計を監視できます。 イベントは 10 分ごとに発生します。 これには、インデックスを圧縮するためにページ間で移動された行の数、割り当て解除されたページの数、さまざまな理由でスキップされた圧縮試行の数などのデータが含まれています。 各イベントによって報告される統計は、データベース エンジンの起動以降に累積されます。

次の T-SQL の例では、auto_index_compaction_statsの イベント データを収集するイベント セッションを作成して開始します。 クエリは、現在のイベントと前のイベントを比較して、10 分間隔ごとに圧縮統計を返します。 クエリでは、一定期間の統計を計算するために少なくとも 2 つのイベントが必要であるため、イベント セッションの開始後、最大 20 分間データが返されない場合があります。

/*
Create and start an event session collecting the auto_index_compaction_stats event
into a ring_buffer target
*/
IF NOT EXISTS (SELECT 1
               FROM sys.dm_xe_database_sessions
               WHERE name = N'automatic_index_compaction')
  BEGIN
      CREATE EVENT SESSION automatic_index_compaction ON DATABASE
      ADD EVENT sqlserver.auto_index_compaction_stats
      ADD TARGET package0.ring_buffer (SET MAX_MEMORY = 1024);

      ALTER EVENT SESSION automatic_index_compaction ON DATABASE STATE = START;
  END

/* Get event data from the ring_buffer target */
DECLARE @EventData AS XML = (SELECT CAST (xst.target_data AS XML) AS TargetData
                             FROM sys.dm_xe_database_session_targets AS xst
                                  INNER JOIN sys.dm_xe_database_sessions AS xs
                                      ON xst.event_session_address = xs.address
                             WHERE xs.name = N'automatic_index_compaction');

/* Return statistics for each 10-minute interval */
WITH compaction_stats_event AS (
    SELECT d.value('@timestamp', 'datetimeoffset') AS timestamp,
           d.value('(data[@name = "database_id"]/value/text())[1]', 'smallint') AS database_id,
           d.value('(data[@name = "compact_attempts"]/value/text())[1]', 'bigint') AS compact_attempts,
           d.value('(data[@name = "compact_completed"]/value/text())[1]', 'bigint') AS compact_completed,
           d.value('(data[@name = "pages_deallocated_compaction"]/value/text())[1]', 'bigint') AS pages_deallocated_compaction,
           d.value('(data[@name = "rows_moved"]/value/text())[1]', 'bigint') AS rows_moved
    FROM @EventData.nodes('/RingBufferTarget/event') AS e(d)
    WHERE e.d.value('@name', 'sysname') = 'auto_index_compaction_stats'
),
timestamp_map AS (
    SELECT database_id,
           timestamp,
           LAG(timestamp) OVER (PARTITION BY database_id ORDER BY timestamp) AS previous_timestamp
    FROM compaction_stats_event
)
SELECT c.timestamp,
       c.database_id,
       c.compact_attempts - p.compact_attempts AS compact_attempts,
       c.compact_completed - p.compact_completed AS compact_completed,
       c.pages_deallocated_compaction - p.pages_deallocated_compaction AS pages_deallocated_compaction,
       c.rows_moved - p.rows_moved AS rows_moved
FROM compaction_stats_event AS c
     INNER JOIN timestamp_map AS tm
         ON c.timestamp = tm.timestamp
        AND c.database_id = tm.database_id
     INNER JOIN compaction_stats_event AS p
         ON tm.previous_timestamp = p.timestamp
        AND tm.database_id = p.database_id
ORDER BY timestamp DESC;

前のクエリを変更して、他のイベント フィールドを含め、さまざまな理由でスキップされた圧縮試行の数など、より多くの統計情報を返すことができます。 auto_index_compaction_stats イベントのすべてのフィールドとその説明を表示するには、次のクエリを使用します。

SELECT name,
       type_name,
       description
FROM sys.dm_xe_object_columns
WHERE object_name = N'auto_index_compaction_stats'
      AND column_type = N'data';

フィードバックを送信する

Microsoftは、インデックスの自動圧縮に関するフィードバックをぜひお聞かせください。 SQL フィードバック フォーラムに新しいアイデアを投稿して、製品フィードバックを送信します。 他のコミュニティ メンバーは、あなたのアイデアや提案に賛成票を投じたり、コメントしたりすることができます。 コミュニティの投票とコメントは、製品の改善Microsoft計画と優先順位付けに役立ちます。