2つの列を追加するとクエリが応答しない


9

2つの列を選択に追加すると、クエリが応答しません。列のタイプはですnvarchar(2000)。それは少し珍しいです。

  • SQL Serverのバージョンは2014です。
  • プライマリインデックスは1つだけです。
  • レコード全体はわずか1000行です。

これが以前の実行計画です(XML showplan):

ここに画像の説明を入力してください

後の実行計画(XML showplan):

ここに画像の説明を入力してください

これがクエリです:

select top(100)
  Batch_Tasks_Queue.id,
  btq.id,
  Batch_Tasks_Queue.[Parameters], -- this field
  btq.[Parameters]  -- and this field
from
        Batch_Tasks_Queue with(nolock)
    inner join  Batch_Tasks_Queue btq with(nolock)  on  Batch_Tasks_Queue.Start_Time < btq.Start_Time
                            and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
                            and Batch_Tasks_Queue.id <> btq.id                            
                            and btq.Start_Time is not null
                            and btq.State in (3, 4)                          
where
    Batch_Tasks_Queue.Start_Time is not null      
    and Batch_Tasks_Queue.State in (3, 4)
    and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
    and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)

order by
    Batch_Tasks_Queue.Start_Time desc

結果の総数は17行です。ダーティデータ(nolockヒント)は重要ではありません。

これはテーブル構造です:

CREATE TABLE [dbo].[Batch_Tasks_Queue](
    [Id] [int] NOT NULL,
    [OBJ_VERSION] [numeric](8, 0) NOT NULL,
    [Operation_Type] [numeric](2, 0) NULL,
    [Request_Time] [datetime] NOT NULL,
    [Description] [varchar](1000) NULL,
    [State] [numeric](1, 0) NOT NULL,
    [Start_Time] [datetime] NULL,
    [Finish_Time] [datetime] NULL,
    [Parameters] [nvarchar](2000) NULL,
    [Response] [nvarchar](max) NULL,
    [Billing_UserId] [int] NOT NULL,
    [Planned_Start_Time] [datetime] NULL,
    [Input_FileId] [uniqueidentifier] NULL,
    [Output_FileId] [uniqueidentifier] NULL,
    [PRIORITY] [numeric](2, 0) NULL,
    [EXECUTE_SEQ] [numeric](2, 0) NULL,
    [View_Access] [numeric](1, 0) NULL,
    [Seeing] [numeric](1, 0) NULL,
 CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO    
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue]  WITH NOCHECK ADD  CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO

この質問に関するディスカッションは、このチャットルームに移動さました
ポールホワイト9

回答:


15

概要

主な問題は次のとおりです。

  • オプティマイザのプランの選択では、値の均一な分布を想定しています。
  • 適切なインデックスがないということは、次のことを意味します。
    • テーブルのスキャンが唯一のオプションです。
    • 結合は、インデックスのネストされたループ結合ではなく、単純なネストされたループ結合です。単純な結合では、結合述部は結合の内側で押し下げられるのではなく、結合で評価されます。

細部

2つの計画は基本的にかなり似ていますが、パフォーマンスは大きく異なる場合があります。

追加の列を使用して計画する

最初に妥当な時間内に完了しない追加の列を持つものをとります:

スロープラン

興味深い機能は次のとおりです。

  1. ノード0のトップは、返される行を100に制限します。また、オプティマイザーの行の目標を設定するため、計画内のそれより下のすべてが最初の100行をすばやく返すように選択されます。
  2. ノード4のスキャンは、テーブルから、Start_Timenullでなく、State3または4でありOperation_Type、リストされた値の1つである行を検索します。表は一度完全にスキャンされ、各行は前述の述語に対してテストされます。すべてのテストに合格した行のみが並べ替えに流れます。オプティマイザーは、38,283行が適格と推定します。
  3. ノード3での並べ替えは、ノード4でのスキャンからのすべての行を消費し、それらをの順に並べ替えますStart_Time DESC。これは、クエリによって要求された最後の表示順序です。
  4. オプティマイザは、計画全体が100行を返すために、93行(実際には93.2791)をソートから読み取る必要があると推定します(結合の予想される影響を考慮)。
  5. ノード2での入れ子ループ結合は、その内部入力(下の分岐)を94回実行することが期待されています(実際には94.2791)。技術的な理由から、ノード1の停止並列処理交換では追加の行が必要です。
  6. ノード5でのスキャンは、各反復でテーブルを完全にスキャンします。Start_Timenullではなく、State3または4の行を検出します。これにより、各反復で400,875行が生成されると推定されます。94.2791回以上の繰り返しでは、行の合計数は約3800万です。
  7. ノード2でのネストされたループ結合は、結合述語も適用します。そのチェックするOperation_Typeこと、マッチをStart_Timeノードから4未満であるStart_Timeことを、ノード5からStart_Timeノードから5未満であるFinish_Timeノード4から、二つことId値が一致しません。
  8. ノード1のGather Streams(停止並列処理交換)は、100行が生成されるまで、各スレッドからの順序付けされたストリームをマージします。複数のストリームにまたがるマージの順序を維持する性質により、手順5で説明した追加の行が必要になります。

非常に非効率なのは、明らかに上記のステップ6と7です。反復ごとにノード5でテーブルを完全にスキャンすることは、オプティマイザが予測するとおりに94回しか発生しない場合、わずかでも妥当です。ノード2での行ごとの最大3,800万の比較セットも、大きなコストです。

重要なのは、値の分布に依存するため、93/94の行の行の目標の推定も間違っている可能性が非常に高いことです。オプティマイザは、より詳細な情報がない場合、均一な分散を想定しています。簡単に言えば、これは、テーブル内の行の1%が適格であると予想される場合、オプティマイザは一致する行を1つ見つけるために100行を読み取る必要があることを意味します。

このクエリを完了するまで実行すると(非常に長い時間がかかる可能性があります)、最終的に100行を生成するために、93/94を超える行をSortから読み取る必要があることがわかります。最悪の場合、100番目の行は、Sortの最後の行を使用して検索されます。ノード4でのオプティマイザの推定値が正しいと仮定すると、これはノード5でスキャンを38,284回実行し、合計で150億行のようなものを実行することを意味します。スキャンの見積もりもオフになっている場合は、さらに多くなる可能性があります。

この実行プランには、インデックスがないという警告も含まれています。

/*
The Query Processor estimates that implementing the following index
could improve the query cost by 72.7096%.

WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([Operation_Type],[State],[Start_Time])
INCLUDE ([Id],[Parameters])

オプティマイザは、テーブルにインデックスを追加するとパフォーマンスが向上するという事実を警告しています。

追加の列なしで計画する

遅い計画

これは基本的に前のプランとまったく同じで、ノード6にインデックススプールとノード5にフィルターが追加されています。重要な違いは次のとおりです。

  1. ノード6のインデックススプールは、イーガースプールです。それは熱心にその下のスキャンの結果を消費し、上キー一時インデックスを構築するOperation_TypeStart_Timeして、Id非キー列として。
  2. ノード2のネストされたループ結合がインデックス結合になりました。結合述部は、ここで評価されない、の代わり当たり反復電流値Operation_TypeStart_TimeFinish_Time、およびIdノード4の走査から外側基準として内側ブランチに渡されます。
  3. ノード7でのスキャンは1回だけ実行されます。
  4. ノード6のインデックススプールはOperation_Type、現在の外部参照値と一致する一時インデックスから行を検索Start_Timeし、Start_TimeとはFinish_Time外部参照によって定義された範囲内にあります。
  5. ノード5のフィルターはId、現在の外部参照値と比較して、インデックススプールの値が不等かどうかをテストしますId

主な改善点は次のとおりです。

  • 内側のスキャンは1回だけ実行されます
  • Operation_TypeStart_Time)にId含まれる列としての一時インデックスにより、インデックスのネストされたループ結合が可能になります。インデックスは、毎回テーブル全体をスキャンするのではなく、反復ごとに一致する行を探すために使用されます。

前と同じように、オプティマイザにはインデックスが見つからないという警告が含まれています。

/*
The Query Processor estimates that implementing the following index
could improve the query cost by 24.1475%.

WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([State],[Start_Time])
INCLUDE ([Id],[Operation_Type])
GO

結論

オプティマイザが一時的なインデックスを作成することを選択したため、追加の列がないプランはより高速です。

追加の列を含む計画では、一時インデックスの作成にかかるコストが高くなります。[Parameters]列はnvarchar(2000)、インデックスの各列に4000バイトまで追加しました。追加のコストは、各実行で一時インデックスを構築してもそれだけでは効果がないことをオプティマイザに確信させるのに十分です。

どちらの場合も、オプティマイザーは永続インデックスがより良い解決策であることを警告します。インデックスの理想的な構成は、より広いワークロードに依存します。この特定のクエリの場合、推奨されるインデックスは妥当な出発点ですが、関連する利点とコストを理解する必要があります。

勧告

このクエリには、考えられる幅広いインデックスが役立ちます。重要なポイントは、ある種の非クラスター化インデックスが必要であることです。提供された情報から、私の意見では合理的な指標は次のようになります:

CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time);

また、クエリを少し整理して[Parameters]、上位100行が(Idキーとして)見つかるまで、クラスター化インデックス内の幅の広い列の検索を遅らせたくなります。

SELECT TOP (100)
    BTQ1.id,
    BTQ2.id,
    BTQ3.[Parameters],
    BTQ4.[Parameters]
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
    ON BTQ2.Operation_Type = BTQ1.Operation_Type
    AND BTQ2.Start_Time > BTQ1.Start_Time
    AND BTQ2.Start_Time < BTQ1.Finish_Time
    AND BTQ2.id != BTQ1.id
    -- Look up the [Parameters] values
JOIN dbo.Batch_Tasks_Queue AS BTQ3
    ON BTQ3.Id = BTQ1.Id
JOIN dbo.Batch_Tasks_Queue AS BTQ4
    ON BTQ4.Id = BTQ2.Id
WHERE
    BTQ1.[State] IN (3, 4)
    AND BTQ2.[State] IN (3, 4)
    AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    -- These predicates are not strictly needed
    AND BTQ1.Start_Time IS NOT NULL
    AND BTQ2.Start_Time IS NOT NULL
ORDER BY
    BTQ1.Start_Time DESC;

どこ[Parameters]の列が必要とされていない、クエリがに簡素化することができます。

SELECT TOP (100)
    BTQ1.id,
    BTQ2.id
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
    ON BTQ2.Operation_Type = BTQ1.Operation_Type
    AND BTQ2.Start_Time > BTQ1.Start_Time
    AND BTQ2.Start_Time < BTQ1.Finish_Time
    AND BTQ2.id != BTQ1.id
WHERE
    BTQ1.[State] IN (3, 4)
    AND BTQ2.[State] IN (3, 4)
    AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
    AND BTQ1.Start_Time IS NOT NULL
    AND BTQ2.Start_Time IS NOT NULL
ORDER BY
    BTQ1.Start_Time DESC;

FORCESEEKヒントは、オプティマイザはインデックス付きネストされたループは、(このタイプではうまく機能しない傾向ハッシュ又は(多対多)マージ別段の参加を選択するオプティマイザのコストベースの誘惑がある計画選択保証するために存在しますクエリは実際に行われます。どちらも最終的には大きな残差になります。ハッシュの場合はバケットごとに多くのアイテムがあり、マージのために多くの巻き戻しが行われます)。

オルタナティブ

クエリ(その特定の値を含む)が読み取りパフォーマンスにとって特に重要である場合は、代わりに2つのフィルター処理されたインデックスを検討します。

CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time)
WHERE 
    Start_Time IS NOT NULL
    AND [State] IN (3, 4)
    AND Operation_Type <> 23
    AND Operation_Type <> 24
    AND Operation_Type <> 25
    AND Operation_Type <> 26
    AND Operation_Type <> 27
    AND Operation_Type <> 28
    AND Operation_Type <> 30;

CREATE NONCLUSTERED INDEX i2
ON dbo.Batch_Tasks_Queue (Operation_Type, [State], Start_Time)
WHERE 
    Start_Time IS NOT NULL
    AND [State] IN (3, 4)
    AND Operation_Type <> 23
    AND Operation_Type <> 24
    AND Operation_Type <> 25
    AND Operation_Type <> 26
    AND Operation_Type <> 27
    AND Operation_Type <> 28
    AND Operation_Type <> 30;

[Parameters]列を必要としないクエリの場合、フィルター処理されたインデックスを使用した推定計画は次のとおりです。

単純なフィルター選択されたインデックスプラン

インデックススキャンは、追加の述語を評価せずに、すべての条件を満たす行を自動的に返します。インデックスのネストされたループ結合の反復ごとに、インデックスシークは2つのシーク操作を実行します。

  1. Operation_Typeand State= 3でシークプレフィックスが一致し、次にStart_Time値の範囲、Id不等式の残余述語をシークします。
  2. Operation_Typeand State= 4でシークプレフィックスが一致し、次にStart_Time値の範囲、Id不等式の残余述語をシークします。

どこ[Parameters]カラムが必要とされ、クエリプランは、単純に各テーブルの100のシングルトンルックアップの最大値を追加します。

追加の列を持つフィルター処理されたインデックスプラン

最後にnumeric、該当する場合ではなく、組み込みの標準整数型の使用を検討する必要があります。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.