以下のような再帰CTEのパフォーマンスを改善する方法はありますか?結合がROW_NUMBERを使用しているときに、結合にインデックスを追加できるかどうかわかりませんか?
DECLARE @File_Name VARCHAR(8000),
@Disk VARCHAR(5)
SET @File_Name = 'MARSQLUTILITY,AdventureWorksDW_Data'
SET @Disk = 'I:'
--Code to pull out deltas between collected IO stats.
;WITH IOPS ([IO_STALL]
,[IO_STALL_READ_MS]
,[IO_STALL_WRITE_MS]
,[NUM_OF_READS]
,[NUM_OF_WRITES]
,[SIZE_ON_DISK_MB]
,[DBNAME]
,[NAME]
,[FILE_ID]
,[DB_FILE_TYPE]
,[DISK]
,[FILE_LOCATION]
,[TIMESTAMP]
,[ROW])
AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY FILE_LOCATION ORDER BY TIMESTAMP DESC) AS [ROW]
FROM dbo.DISKIOPS
)
--Need to divide by the number of operations in that timeframe to get average wait time per operation.
--SELECT MAX(([IO2].[IO_STALL] - [IO1].[IO_STALL]) / (IO2.NUM_OF_READS + IO2.NUM_OF_WRITES - IO1.NUM_OF_READS - IO1.NUM_OF_WRITES))
SELECT [IO1].[TIMESTAMP],
[IO1].[NAME],
([IO2].[IO_STALL] - [IO1].[IO_STALL]) / (IO2.NUM_OF_READS + IO2.NUM_OF_WRITES - IO1.NUM_OF_READS - IO1.NUM_OF_WRITES) AS Avg_Stall_Per_Operation
FROM IOPS IO1 JOIN IOPS IO2 ON IO1.ROW = (IO2.ROW+1)
WHERE IO1.NAME = IO2.NAME
--Need to make sure not dividing by 0 when there has been no operations
AND (IO2.NUM_OF_READS + IO2.NUM_OF_WRITES - IO1.NUM_OF_READS - IO1.NUM_OF_WRITES) > 0
AND IO1.Disk = @Disk
CTEではインデックスを追加できません。CTEはビューと同様に動作しますが、データベース内の永続オブジェクトではないため、(ビューとは異なり)インデックスを作成できません。
ボトルネックがJOIN操作である場合は、一時テーブルを使用して、JOIN操作を高速化するために使用する列にインデックスを追加する必要があります。
ここで正規化の問題を扱っています。各行にタイムスタンプを追加する代わりに、ID列とタイムスタンプ列を持つ親テーブルを作成し、DISKIOPSテーブルに挿入するときにそのID値を使用します。これにより、「行番号」が永続化され、オンザフライで計算する必要がなくなります。現在、IDは継続的であることが保証されていませんが、ユースケースではおそらく常に継続的です。
テーブルレイアウトを変更できない場合は、クラスター化インデックスをDISKIOPS(FILE_LOCATION、TIMESTAMP)に配置します。これにより、そのクエリのパフォーマンスが向上します。
また、@ MartinSmithがすでに述べたように、NAMEではなくFILE_LOCATIONに参加します。あなたのケースではそれは論理的に同等かもしれませんが、FILE_LOCATIONの結合だけがそのインデックスを使用できます。
最後に、「ゼロによる除算」を防ぐためのwhere句の代わりに、これを使用します。
([IO2].[IO_STALL] - [IO1].[IO_STALL]) / NULLIF((IO2.NUM_OF_READS + IO2.NUM_OF_WRITES - IO1.NUM_OF_READS - IO1.NUM_OF_WRITES),0)
これにより、行全体を除外することなく、さらに重要なことにインデックスの使用を妨げることなく、値がNULLになります。