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 根據兩個過濾條件選擇了一個合理的執行計劃:
另一方面,如果我的應用程序使用游標執行相同的查詢……
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 知道來自統計數據的值的分佈,但它不知道這些值在特定掃描順序中的位置。因此,它猜測動態計劃所涉及的成本,並且恰好比具有阻塞排序運算符的計劃成本更低。