Sql-Server

sp_cursoropen 選擇了糟糕的執行計劃

  • February 27, 2018

如果我直接在 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 根據兩個過濾條件選擇了一個合理的執行計劃:

好的


另一方面,如果我的應用程序使用游標執行相同的查詢……

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 選擇了一個糟糕的執行計劃:

壞的


我知道我可以通過使用索引提示來解決這個問題。但是,我想了解為什麼會發生這種情況。

我試過了:

  • DBCC FREEPROCCACHE
  • UPDATE STATISTICS auftrag_prod_soll

但這並沒有什麼不同。

我還查看了 prod_soll_über 和 auftrag_produktion 上的兩個索引的直方圖:它們分佈良好,因此 SQL Server 應該能夠推斷出查詢最多會返回幾行,因此,鍵查找和排序操作將比索引掃描快得多。

我還嘗試創建一個包含 auftrag_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 找到好的查詢計劃?

我通過禁用“reihenfolge”索引暫時“修復”了這個問題,但我仍然想了解為什麼會發生這種情況,以便將來避免此類問題。

@p3@p4和的值@p5在呼叫 後保持在其初始值 (4, 1, -1) sp_cursoropen,但一旦我通過刪除 reihenfolge 索引“解決”問題,它們就會切換到 (1, 1, 0) .

即使使用游標,我如何幫助 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_produktion和上進行相等搜尋prod_soll_über,同時還確保匹配的行可以按reihenfolge順序返回:

最優方案

游標

提供的參數用於確定請求的游標類型,以及可選的哪些選項是可接受的。如果請求的類型和選項無效或不可用(由於一系列可能的原因),伺服器可能會更改這些選項(因此是輸出參數)。sp_cursoropen

提供的程式碼請求一個只進的只讀游標,伺服器將其作為動態類型游標提供。有關在靜態和動態樣式計劃之間進行選擇的詳細資訊,請參閱了解 SQL Server Fast_Forward 伺服器游標

當您“修復”問題時,將提供鍵集游標,因為不再可能使用動態計劃(動態游標計劃無法排序)。

您需要指定應用程序所需的游標選項(例如,用於並發性)以及在給定預期用途的情況下恰好最適合性能的任何類型。如果您打算獲取所有行,或者快速獲取一行的計劃實際上不是最佳的,您可能需要指定不同的類型,例如 @P3 = 8 的靜態。如果您想確定,請添加 0x80000(靜態可接受)傳遞一個靜態游標。

根據執行計劃圖像,SQL Server 似乎選擇了一個動態計劃,低估了在謂詞(我假設)與第一行匹配之前需要傳遞給 Key Lookup 的行數:

掃描加查找

請注意從掃描中讀取的大量行。動態計劃可以做的最好的事情是按順序掃描reihenfolge索引。儘管 SQL Server 知道來自統計數據的值的分佈,但它不知道這些值在特定掃描順序中的位置。因此,它猜測動態計劃所涉及的成本,並且恰好比具有阻塞排序運算符的計劃成本更低。

引用自:https://dba.stackexchange.com/questions/198886