web-dev-qa-db-ja.com

sp_cursoropenはひどい実行プランを選択します

(単純な)クエリをSQL Server Management Studioで直接実行すると...

SELECT auftrag_prod_soll.ID 
  FROM auftrag_prod_soll 
 WHERE auftrag_prod_soll.auftrag_produktion = 51621 
   AND auftrag_prod_soll.prod_soll_über = 539363
 ORDER BY auftrag_prod_soll.reihenfolge

...すべてがうまく高速です...

Table 'auftrag_prod_soll'. Scan count 2, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
  CPU time = 0 ms,  elapsed time = 102 ms.

... SQL Serverは、2つのフィルタリング基準に基づいて適切な実行プランを選択するためです。

good


一方、アプリケーションがカーソルを使用して同じクエリを実行した場合...

declare @p1 int
declare @p3 int
set @p3=4
declare @p4 int
set @p4=1
declare @p5 int
set @p5=-1
exec sp_cursoropen @p1 output,N' SELECT auftrag_prod_soll.ID  FROM auftrag_prod_soll  WHERE auftrag_prod_soll.auftrag_produktion =  51621   AND auftrag_prod_soll.prod_soll_über =  539363 ORDER BY auftrag_prod_soll.reihenfolge',@p3 output,@p4 output,@p5 output

exec sp_cursorfetch @p1,2,0,1

exec sp_cursorclose @p1

...パフォーマンスはひどい...

Table 'auftrag_prod_soll'. Scan count 1, logical reads 1118354, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
  CPU time = 1094 ms,  elapsed time = 1231 ms.

... SQL Serverがひどい実行プランを選択するためです。

bad


インデックスヒントを使用することで、これを回避できることを知っています。ただし、これが発生する理由を理解したいのですが

私が試してみました:

  • DBCC FREEPROCCACHE
  • UPDATE STATISTICS auftrag_prod_soll

しかし、違いはありませんでした。

また、prod_soll_überとauftrag_produktionの2つのインデックスのヒストグラムも調べました。これらはよく分散されているため、SQL Serverはクエリが最大で数行を返すことを推測できるはずです。したがって、キールックアップと並べ替え操作はインデックススキャンよりもはるかに高速です。

また、afufrag_produktionとprod_soll_überの両方を含む非クラスター化インデックスを作成しようとしましたが、カーソルの実行プランは変更されませんでした(ただし、直接クエリがさらに高速になりました)。


以下は、関連する場合の完全なテーブル定義です。

CREATE TABLE [auftrag_prod_soll](
    [auftrag_produktion] [int] NULL,
    [losgrößenunabh] [smallint] NOT NULL,
    [stückliste_vorh] [smallint] NOT NULL,
    [erledigt] [smallint] NOT NULL,
    [ext_wert_ueberst] [smallint] NOT NULL,
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [prod_soll_über] [int] NULL,
    [artikel] [int] NULL,
    [gesamtmenge_soll] [float] NULL,
    [produktionstext] [nvarchar](max) NULL,
    [reihenfolge] [int] NULL,
    [reihenfolge_druck] [int] NULL,
    [infkst_unter] [int] NULL,
    [ebene] [smallint] NULL,
    [bezeichnung] [varchar](50) NULL,
    [extern_text] [nvarchar](max) NULL,
    [intern_preis] [float] NULL,
    [intern_wert] [float] NULL,
    [extern_preis] [float] NULL,
    [extern_wert] [float] NULL,
    [extern_proz] [float] NULL,
    [dummyfeld] [varchar](50) NULL,
    [mengeneinheit] [varchar](50) NULL,
    [artikel_art] [smallint] NULL,
    [s_insert] [float] NULL,
    [s_update] [float] NULL,
    [s_user] [varchar](255) NULL,
    [preiseinheit] [float] NULL,
    [memo] [nvarchar](max) NULL,
    [lager_nummer] [int] NULL,
    [zweitmenge] [float] NULL,
    [zweit_einheit] [float] NULL,
    [zweit_mengeneinh] [varchar](50) NULL,
    [kst_preis1] [float] NULL,
    [kst_preis2] [float] NULL,
    [kst_preis3] [float] NULL,
    [kst_preis4] [float] NULL,
    [p_position] [int] NULL,
    [zeilen_status] [int] NULL,
    [fs_adresse_lief] [uniqueidentifier] NULL,
    [t_artikel_stückliste] [int] NULL,
    [div_text1] [varchar](255) NULL,
    [div_text2] [varchar](255) NULL,
    [menge_urspr] [float] NULL,
    [fs_artikel_index] [uniqueidentifier] NULL,
    [s_guid] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
    [gemein_kosten] [float] NULL,
    [fs_leistung] [uniqueidentifier] NULL,
    [sonderlogik_ok_rech] [smallint] NOT NULL,
    [sonderlogik_ok_manuell] [int] NULL,
    [menge_inkl_frei] [float] NULL,
    [art_einheit] [int] NULL,
    [drittmenge] [float] NULL,
 CONSTRAINT [PK__auftrag_prod_sol__50E5F592] PRIMARY KEY CLUSTERED ([ID] ASC)
) 

CREATE NONCLUSTERED INDEX [artikel] ON [auftrag_prod_soll] ([artikel] ASC)
CREATE NONCLUSTERED INDEX [auftrag_produktion] ON [auftrag_prod_soll] ([auftrag_produktion] ASC)
CREATE NONCLUSTERED INDEX [dummyfeld] ON [auftrag_prod_soll] ([dummyfeld] ASC)
CREATE NONCLUSTERED INDEX [fs_adresse_lief] ON [auftrag_prod_soll] ([fs_adresse_lief] ASC)
CREATE NONCLUSTERED INDEX [fs_artikel_index] ON [auftrag_prod_soll] ([fs_artikel_index] ASC)
CREATE NONCLUSTERED INDEX [fs_leistung] ON [auftrag_prod_soll] ([fs_leistung] ASC)
CREATE NONCLUSTERED INDEX [lager_nummer] ON [auftrag_prod_soll] ([lager_nummer] ASC)
CREATE NONCLUSTERED INDEX [prod_soll_über] ON [auftrag_prod_soll] ([prod_soll_über] ASC)
CREATE NONCLUSTERED INDEX [reihenfolge] ON [auftrag_prod_soll] ([reihenfolge] ASC)
CREATE UNIQUE NONCLUSTERED INDEX [s_guid] ON [auftrag_prod_soll] ([s_guid] ASC)
CREATE NONCLUSTERED INDEX [s_insert] ON [auftrag_prod_soll] ([s_insert] ASC)
CREATE NONCLUSTERED INDEX [u_test] ON [auftrag_prod_soll] ([auftrag_produktion] ASC,
    [prod_soll_über] ASC)
CREATE NONCLUSTERED INDEX [zeilen_status] ON [auftrag_prod_soll] ([zeilen_status] ASC)
ALTER TABLE [auftrag_prod_soll] ADD  DEFAULT ((0)) FOR [losgrößenunabh]
ALTER TABLE [auftrag_prod_soll] ADD  DEFAULT ((0)) FOR [stückliste_vorh]
ALTER TABLE [auftrag_prod_soll] ADD  DEFAULT ((0)) FOR [erledigt]
ALTER TABLE [auftrag_prod_soll] ADD  DEFAULT ((0)) FOR [ext_wert_ueberst]
ALTER TABLE [auftrag_prod_soll] ADD  CONSTRAINT [DF__auftrag_p__s_gui__28A2FA0E]  DEFAULT (newid()) FOR [s_guid]
ALTER TABLE [auftrag_prod_soll] ADD  DEFAULT ((0)) FOR [sonderlogik_ok_rech]

カーソルが使用されている場合でも、SQL Serverがgoodクエリプランを見つけられるようにするにはどうすればよいですか?

「reihenfolge」インデックスを無効にすることで、この問題を一時的に「修正」しましたが、今後このような問題が発生しないように、なぜこれが発生するのかを理解したいと考えています。

@p3@p4、および@p5の値は、sp_cursoropenの呼び出し後も初期値(4、1、-1)のままですが、「 reihenfolgeインデックスを削除することで問題を修正し、(1、1、0)に切り替えます。

2
Heinzi

カーソルが使用されている場合でも、SQL Serverが適切なクエリプランを見つけられるようにするにはどうすればよいですか?

文字通り:計画ガイドまたはヒントを使用します。ただし、カーソルが使用されているかどうかに関係なく、SQL Serverに最適なインデックスを提供する方がはるかに優れています。

CREATE INDEX [IX dbo.auftrag_prod_soll auftrag_produktion prod_soll_über reihenfolge] 
ON dbo.auftrag_prod_soll (auftrag_produktion, prod_soll_über, reihenfolge);

これは、インデックス交差と並べ替えプランよりも優れており、スキャンインオーダーとルックアッププランよりもはるかに優れています。このインデックスを使用すると、auftrag_produktionprod_soll_überの両方で等価シークが可能になると同時に、一致する行がreihenfolgeの順序で返されるようになります。

Optimal plan

カーソル

sp_cursoropen に提供されるパラメーターは、要求されるカーソルのタイプを決定し、オプションでどのオプションを使用できるかを決定します。サーバーは、要求されたタイプとオプションが有効でないか、または(さまざまな理由により)使用できない場合、これらのオプション(したがって出力パラメーター)を変更することがあります。

提供されたコードは、サーバーが動的型カーソルとして提供する、順方向専用の読み取り専用カーソルを要求します。静的スタイルプランと動的スタイルプランのどちらを選択するかの詳細については、「 SQL Server Fast_Forward Server Cursors について」を参照してください。

問題を「修正」すると、動的計画が不可能になるため、キーセットカーソルが配信されます(動的カーソル計画ではソートできません)。

アプリケーションが必要とするカーソルオプション(たとえば、同時実行性)と、意図された使用方法を前提として、パフォーマンスに最適なタイプを指定する必要があります。すべての行をフェッチする場合、または1つの行をすばやくフェッチする計画が実際には最適ではない場合、別のタイプを指定する必要がある場合があります。 @ P3 = 8の静的。静的カーソルが確実に配信されるようにするには、0x80000(静的に受け入れ可能)を追加します。

実行プランのイメージに基づいて、SQL Serverは、キールックアップに渡す必要がある行数を過小評価する動的プランを選択するようです。

Scan plus lookup

スキャンから読み取られた多数の行に注目してください。動的計画で実行できる最善の方法は、reihenfolgeインデックスを順番にスキャンすることです。 SQL Serverは統計からの値の分布を認識しますが、特定のスキャン順でのwhereを認識しません。したがって、動的計画に関連するコストを推測し、ブロッキングソート演算子を使用する計画よりもコストが低くなる可能性があります。

7
Paul White 9

なぜこれが起こるのか理解したい。

これが発生する理由は、リテラル値を使用したクエリとパラメーターを使用したクエリの違いにあるようです。インデックスは「十分に分散されている」とおっしゃいましたが、まだいくつかのEdge値が存在しない可能性があり、オプティマイザーは実際の値なしにその飛躍を望んでいません。

カーソルをリテラル値で試して、カーソルの動作を確認しましたか? Management Studioでパラメーターを使用して、その動作を確認しましたか?

0
JefferMC