web-dev-qa-db-ja.com

クラスター化されたキーを高スループットでテーブルに追加しないようにする必要がありますか

サードパーティのトランザクションデータベースによって作成された監査レコードを格納するテーブルdsStaging.Auditを持つSQL Serverソリューションがあります。これらの監査を使用して、サードパーティシステムからのCRUD操作をSQLデータベースに同期します。

CREATE TABLE [dsStaging].[Audit](
    [SyncExecutionId] [bigint] NOT NULL,
    [AuditDataGuid] [nvarchar](56) NOT NULL,
    [AuditDate] [datetime] NOT NULL,
    [AuditDateTimeZone] [datetimeoffset](3) NULL,
    [AuditEventGroup] [nvarchar](56) NOT NULL,
    [TransactionId] [bigint] NOT NULL,
    [TransactionSequence] [int] NOT NULL,
    .
    ...
    .
 CONSTRAINT [PK_Audit] PRIMARY KEY CLUSTERED 
(
    [SyncExecutionId] ASC,
    [TransactionId] ASC,
    [TransactionSequence] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
    IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

監査が処理されたら、監査レコードを別のテーブルProcessed.Auditに移動して、x日後に削除できるようにします。

CREATE TABLE [Processed].[Audit](
    [SyncExecutionId] [bigint] NOT NULL,
    [AuditDataGuid] [nvarchar](56) NOT NULL,
    [AuditDate] [datetime] NOT NULL,
    [AuditEventGroup] [nvarchar](56) NOT NULL,
    [TransactionId] [bigint] NOT NULL,
    [TransactionSequence] [int] NOT NULL,
    .
    ...
    .
 CONSTRAINT [PK_Processed_Audit] PRIMARY KEY NONCLUSTERED 
(
    [AuditDate] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
    IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

監査をステージングからプロセスに移すことにおける私の主な目的はパフォーマンスです。ステージングテーブルが可能な限り短時間ロックされるようにして、未処理の監査をできるだけ早く処理できるようにする必要があります(ステージングテーブルの監査が少ない=処理がはるかに高速)。

1.5mの監査レコードこのプロセスを実行中毎時約10kのバッチで調べています。

監査の移動プロセスは、約20〜30秒ごとに実行されます。 Processed.Auditレコードを削除するプロセスは1時間ごとに実行され、X日前(通常は約7日)から1時間分の監査を削除します。

  • Processed.Auditテーブルをクラスター化インデックスに変換する必要がありますか?

サポートされる最小バージョン:SQL Server 2012 Standard Edition

6
Drammy

このシナリオでクラスター化インデックスが必要になる主な理由は、次の行です。

プロセスを削除するプロセス。監査レコードは1時間ごとに実行され、x日前(通常は約7日)から1時間分の監査を削除します。

HEAPから行を削除すると、削除によってテーブルロックが取得されるか、削除クエリにWITH (TABLOCK)ヒントが提供されない限り、データページの割り当てが解除されない場合があります。ただし、それが並行性に何をもたらすかは想像できるでしょう。良くない。

RCSIまたはスナップショットアイソレーションを使用している場合、TABLOCKヒントはこの動作を行わないことに注意してください。

これは簡単な例です。小さなテーブルをロードします。

USE tempdb;
SET NOCOUNT ON;

CREATE TABLE dbo.heap
(
    id INT PRIMARY KEY NONCLUSTERED,
    junk VARCHAR(1000)
);

INSERT dbo.heap (
    id, junk )
SELECT TOP 1000 x.n, REPLICATE('A', x.n % 1000)
FROM   (
           SELECT ROW_NUMBER() OVER ( ORDER BY @@ROWCOUNT ) AS n
           FROM   sys.messages AS m ) AS x;

健全性チェッククエリを実行して、ヒープと非クラスター化PKに割り当てられているページ数を把握します。

SELECT   OBJECT_NAME(i.object_id) AS table_name,
         i.name AS index_name,
         MAX(a.used_pages) AS leaf_me_alone
FROM     sys.indexes AS i
JOIN     sys.partitions AS p
ON p.object_id = i.object_id
   AND p.index_id = i.index_id
JOIN     sys.allocation_units AS a
ON a.container_id = p.partition_id
WHERE OBJECT_NAME(i.object_id) = 'heap'
GROUP BY i.object_id, i.index_id, i.name
ORDER BY OBJECT_NAME(i.object_id), i.index_id;

この結果:

table_name  index_name  leaf_me_alone
heap        NULL        74
heap        PK__heap__  7

つまり、ヒープに74ページ、NC PKに7ページです。

シングルトン削除をいくつか実行して、テーブルをクリアします。

DECLARE @i INT = 1;

WHILE @i < 1000
    BEGIN

        DELETE h
        FROM  dbo.heap AS h
        WHERE h.id = @i;

        SET @i += 1;

        PRINT @i;

    END;

健全性チェッククエリを再実行すると、同じ結果が得られます。

さらに悪いことに、ここでテーブルをクエリすると、SQLは ALL OF THOSE BLANK PAGES !を読み取ります。

SET STATISTICS TIME, IO ON 
SELECT *
FROM   dbo.heap AS h;

テーブル「ヒープ」。スキャンカウント1、論理読み取り67

したがって、テーブルが人為的に大きくなるだけでなく、SQLのディスク、メモリ、バックアップ、およびDBCC CHECKDBにも多数の空白ページができるようになりました。

1時間ごとにこのプロセスを通過する約150万の監査レコードを調べています。

ヘヘヘヘ!楽しくない。

ヒープからページの割り当てを解除するためのその他のオプションは次のとおりです。

TRUNCATE TABLE dbo.heap

データを一括削除する必要があるため、これは機能しません。

ALTER TABLE dbo.heap REBUILD;

これは、テーブルのすべての非クラスター化インデックスを同時に再構築するため、そのテーブルサイズでは苦痛です。

テーブルはページを再利用しますか?たぶんちょっとかもしれない。

DECLARE @id_max INT = (SELECT MAX(id) FROM dbo.heap AS h);

INSERT dbo.heap (
    id, junk )
SELECT TOP 5000 x.n + @id_max, REPLICATE('A', x.n % 1000)
FROM   (
           SELECT ROW_NUMBER() OVER ( ORDER BY @@ROWCOUNT ) AS n
           FROM   sys.messages AS m ) AS x;

サニティーチェック:

table_name  index_name  leaf_me_alone
heap        NULL        400
heap        PK__heap__  20

SELECT *クエリ:

テーブル「ヒープ」。スキャンカウント1、論理読み取り392

お役に立てれば!

12
Erik Darling

私はそれを別の方法で行います:

  1. 制約やインデックスのないテーブルdsStaging.Audit
  2. スイッチパーティションまたはsp_renameを使用して、レコードを中央のテーブルに移動します。
  3. 削除処理をサポートするために、中央のテーブルにインデックスを作成します。
  4. 中間テーブルからプロセス監査テーブルにデータを移動します。 (削除先をお勧めします)。
  5. 中央のテーブルをドロップします。終わり。

スイッチパーティション機能を使用して、process.Auditテーブルと中間テーブルからパーティション分割することを検討します。

1
itzik Paz