SE の雑記

SQL Server の情報をメインに Microsoft 製品の勉強内容を日々投稿

非クラスター化インデックスのキー項目の変更時の処理オーバーヘッド

leave a comment

非クラスター化インデックスのキー項目を変更した場合、どのようなオーバーヘッドが発生するかについてまとめておきたいと思います。

テスト用データの作成

最初に、次のようなクエリを実行して、テスト用のデータを作成します。

DROP TABLE IF EXISTS IndexTest
GO


CREATE TABLE IndexTest(
    C1 int identity not null,
    C2 int,
    C3 char(36),
    CONSTRAINT PK_IndexTest PRIMARY KEY CLUSTERED(C1),
    INDEX NCIX_IndexTest_01 NONCLUSTERED(C2) INCLUDE(C3)
)
GO

SET NOCOUNT ON
GO
BEGIN TRAN

DECLARE @cnt int = 1
WHILE (@cnt <= 100)
BEGIN
    INSERT INTO IndexTest VALUES(@cnt % 2, NEWID())
    SET @cnt += 1
END

COMMIT TRAN
GO

 

テーブルの構成はシンプルで、C1 に Identity のプライマリキーを持ち、C2 を非クラスター化インデックスとして、C3 が INCLUDE 項目として指定されています。

このデータに対して C2 (非クラスター化インデックスのキー項目) を変更した場合のオーバーヘッドについて考えてみます。

非クラスター化インデックスのキー項目変更時のオーバーヘッドを確認する

テスト用のデータが生成できたので、実際にオーバーヘッドを確認していきたいと思います。

最初に、非クラスター化インデックスのデータの状態を確認してみます。

次のようなクエリを実行することで、インデックスのページを確認することができます。

SELECT
   OBJECT_NAME(p.object_id) AS object_name,
   p.index_id,
   iau.first_page,
    CAST(CONVERT(varbinary(2), '0x' + SUBSTRING(CONVERT(varchar(14), iau.first_page, 1), 13,2) + SUBSTRING(CONVERT(varchar(14), iau.first_page, 1), 11,2), 1) AS int) AS file_id,
    CAST(CONVERT(varbinary(4), '0x' + SUBSTRING(CONVERT(varchar(14), iau.first_page, 1), 9,2) + SUBSTRING(CONVERT(varchar(14), iau.first_page, 1), 7,2) + 
    SUBSTRING(CONVERT(varchar(14), iau.first_page, 1), 5,2) + SUBSTRING(CONVERT(varchar(14), iau.first_page, 1), 3,2), 1) AS int) AS page_id,
   iau.root_page,
   iau.first_iam_page
FROM sys.system_internals_allocation_units AS iau
INNER JOIN sys.partitions AS p ON iau.container_id = p.hobt_id
WHERE
    p.object_id = OBJECT_ID('IndexTest')
GO

今回は、非クラスター化インデックスを変更しますので、ページ ID「1182456」の情報を確認してみます。

image

DBCC TRACEON(3604)
DBCC PAGE('TESTDB', 1, 1182456,3)
GO

実際のインデックスのデータページを確認すると次のようなデータ構造となっていることが確認できます。

image

今回は赤枠のデータ (C1 = 2) の C2 のデータを、次のクエリで変更してみます。

UPDATE IndexTest SET C2 = 1 WHERE C1 = 2
GO

 

先ほどまでは、C1 = 2 のデータは Row=0 として格納されていましたが、C2 を 1 に変更することでデータの場所が移動され、Row=51 に移動していることが確認できました。

image

この時の、非クラスター化インデックスの内部的な動作ですが、DELETE -> INSERT により、非クラスター化インデックスのレコードが生成されます。

トランザクションログとして生成されたレコードを確認してみましょう。

SELECT [Current LSN], Operation, Context, AllocUnitName, [Transaction Name]
FROM sys.fn_dblog(NULL, NULL)
GO

次の画像の赤枠の箇所が DELETE -> INSERT を行っているログレコードとなります。

image

非クラスター化インデックスに対して DELETE -> INSERT を行っていることが確認できますね。

非クラスター化インデックスの INCLUDE 項目のみを更新した場合はどうでしょうか?

UPDATE IndexTest SET C3 = NEWID() WHERE C1 = 2
GO

INCLUDE の項目を変更した場合には、インデックスに対しての修正のみが発生していることが確認できますね。

INCLUDE の項目の変更については、キー項目の変更よりはオーバーヘッドが少なく処理が実行できています。

image

まとめ

クラスター化インデックスのキー項目とは異なり、非クラスター化インデックスのキー項目は通常のデータに対してインデックスを設定することになるため、キー項目の変更は実際のワークロードでも発生する可能性は高いかと思います。

しかし、非クラスター化インデックスのキー項目を変更した場合には、新しいインデックスレコードの生成が行われるため、キー項目の変更については、オーバーヘッドが大きい処理となります。

非クラスター化インデックスのインデックス長 (インデックスのキー項目ならびに INCLUDE 項目) が大きい場合、キー項目が変更された際の、インデックスの INSERT コストが大きくなる (キー項目変更後のインデックスの INSERT で移動されるデータが多い) はずですので、インデックスに含まれる項目と、キー項目の更新頻度はインデックスを設定する際に、少しは意識しておいた方がよいのではないでしょうか。

Share

Written by Masayuki.Ozawa

3月 15th, 2021 at 11:16 pm

Leave a Reply