web-dev-qa-db-ja.com

クエリは、必要なインデックスが利用可能な場合でもソートコストを示します

この question はSOからのものであり、フラグが立てられてDBA.SEに移動します。

以下はテストデータです:

--Main Table

 CREATE TABLE [dbo].[LogTable]
    (
      [LogID] [int] NOT NULL
                    IDENTITY(1, 1) ,
      [DateSent] [datetime] NULL,
    )
 ON [PRIMARY]
GO
 ALTER TABLE [dbo].[LogTable] ADD CONSTRAINT [PK_LogTable] PRIMARY KEY CLUSTERED  ([LogID]) ON [PRIMARY]
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent] ON [dbo].[LogTable] ([DateSent] DESC) ON [PRIMARY]
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent_LogID] ON [dbo].[LogTable] ([DateSent] DESC) INCLUDE ([LogID]) ON [PRIMARY]
GO


--Cross table

 CREATE TABLE [dbo].[LogTable_Cross]
    (
      [LogID] [int] NOT NULL ,
      [UserID] [int] NOT NULL
    )
 ON [PRIMARY]
GO
 ALTER TABLE [dbo].[LogTable_Cross] WITH NOCHECK ADD CONSTRAINT [FK_LogTable_Cross_LogTable] FOREIGN KEY ([LogID]) REFERENCES [dbo].[LogTable] ([LogID])
GO
 CREATE NONCLUSTERED INDEX [IX_LogTable_Cross_UserID_LogID]
 ON [dbo].[LogTable_Cross] ([UserID])
 INCLUDE ([LogID])
GO


-- Script to populate them
 INSERT INTO [LogTable]
        SELECT TOP 100000
                DATEADD(day, ( ABS(CHECKSUM(NEWID())) % 65530 ), 0)
        FROM    sys.sysobjects
                CROSS JOIN sys.all_columns


 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                1
        FROM    [LogTable]
        ORDER BY NEWID()

 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                2
        FROM    [LogTable]
        ORDER BY NEWID()

 INSERT INTO [LogTable_Cross]
        SELECT  [LogID] ,
                3
        FROM    [LogTable]
        ORDER BY NEWID()


GO

以下の簡単なクエリを使用すると、

SELECT  DI.LogID              
FROM    LogTable DI              
        INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
        WHERE  DP.UserID = 1  
ORDER BY DateSent DESC

クエリは並べ替えを示します

enter image description here

私が理解しているように、必要なインデックスがあるので、ソートのコストは回避する必要があります

 CREATE NONCLUSTERED INDEX [IX_LogTable_DateSent] ON [dbo].[LogTable] ([DateSent] DESC) ON [PRIMARY]

また、プランを掘り下げると、同じインデックスが使用されていることがわかります。

私の質問は:
1。なぜソートコストがまだ存在するのか
2。正確には何をするのかordered property is true 手段 。

私が行ったいくつかの観察/作業:
1.Orderedプロパティがfalseに設定されています

だから私は以下のようなクエリを書き直しました

   SELECT  DI.LogID              
    FROM    LogTable DI              
            INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
            WHERE  DP.UserID = 1  and di.datesent is not null
    ORDER BY DateSent DESC

上記のように書き換えると、orderedプロパティがtrueになりますが、それでもソートが存在します

最も近い、私が見つけることができるポールホワイトによるこの記事: 2つのインデックスヒントの物語 、この記事では、以下に、ポイントがこの種類が発生する理由を明らかに

非常に大きなテーブルの場合、オプティマイザは、IAM駆動型スキャンが余分な並べ替えで消費されるよりも多くの時間を節約できる可能性があると計算し、順不同スキャン+並べ替えを特徴とするプランが選択されます。これはヒューリスティックな最適化です。オプティマイザはindeの実際の断片化レベルについて何も知りません。

しかし、このクエリは並べ替えを必要とし、IAMスキャンの条件を満たさないはずです

詳細が必要な場合はお知らせください

5
TheGameiswar

クエリプランでは、orderedプロパティをtrueに設定すると、IAM駆動型スキャンが実行されなかったことを意味します。データは、インデックス定義に従って論理的な順序で読み取られました。

ハッシュ結合は順序を保持しません 。最初のクエリにはハッシュ結合があるため、最後に明示的なSORTが必要です。クエリでの並べ替えを回避する1つの方法は、LogTableを外部テーブルとしてネストしたループ結合を行うことです。私のマシンでは、さまざまなヒントを使ってこれを実現できました。

SELECT  DI.LogID              
FROM LogTable DI              
        INNER JOIN LogTable_Cross DP ON DP.LogID = DI.LogID  
        WHERE  DP.UserID = 1  
ORDER BY DateSent DESC
OPTION (MAXDOP 1, LOOP JOIN, FORCE ORDER, NO_PERFORMANCE_SPOOL);

bad query plan

正しいインデックスがLogTable_Crossテーブルで定義されていないため、これは非常に非効率的なクエリプランです。シークはそのテーブルに対して行われますが、UserId列は1つだけです。

bad seek

LogIDのフィルタリングは、結合自体で行われます。探しているクエリプランをより適切にサポートするインデックスを作成できます。

CREATE INDEX IX_LogTable_Cross ON LogTable_Cross (LogID, UserID);

そのインデックスの作成は、ソートなしのクエリプランを保証するものではありません。 SQL Serverでは、並べ替えのあるプランの方がコストが低いと推定される場合があります。ただし、LOOP JOIN以外のすべてのヒントを削除すると、並べ替えなしでかなり効率的なクエリプランが得られます。

okay query plan

5
Joe Obbish