為什麼 SQL Server 找不到查找計劃?這是一個錯誤嗎?
create table Test1 (Id int not null, H char, primary key (Id), index i1 unique (H)) create table Test2 (Id int not null, H char, primary key (Id), index i2 unique (H)) insert into Test1 values (1, 'A'), (2, 'B') insert into Test2 values (1, 'A'), (2, 'C')
這個查詢失敗
Query processor could not produce a query plan because of the hints defined in this query.
(如果我刪除了forceseek提示然後它會執行但它會掃描兩個表之一 - 即使它非常大)select * from Test1 a with (forceseek) join Test2 b with (forceseek) on a.Id = b.Id where a.H = 'A' or b.H = 'C'
這個等效的查詢執行良好:
select * from Test1 a with (forceseek) join Test2 b with (forceseek) on a.Id = b.Id where a.H = 'A' union select * from Test1 a with (forceseek) join Test2 b with (forceseek) on a.Id = b.Id where b.H = 'C'
並給出計劃
..我不明白為什麼 SQL Server 沒有以最佳方式執行第一個查詢。這是一個已知的問題?它有名字嗎?
對於有問題的原始查詢,優化器通常會考慮(*)兩個主要替代方案(當
FORCESEEK
任何表均未使用提示時)。第一個是簡單的加入
當兩個表中的索引被完全掃描(沒有謂詞),並且謂詞
a.Id = b.Id AND (a.H = 'A' OR b.H = 'C')
在連接節點上進行測試時。第二個是申請表(更多資訊在這裡)
當在嵌套循環連接的外側掃描其中一個表上的索引時,然後在內側使用索引數據使用
b.Id = a.Id
查找謂詞和附加a.H = 'A' OR b.H = 'C'
謂詞查找另一張表上的聚集索引。在 T-SQL 中可以表示為SELECT * FROM Test1 a CROSS APPLY ( SELECT * FROM Test2 b WHERE b.Id = a.Id AND (a.H = 'A' OR b.H = 'C') ) appl
當
FORCESEEK
在其中一張表上使用時,不考慮簡單的連接替代方案,但優化器另外考慮了應用的修改形式它聯合了兩個搜尋到另一個表上的聚集索引。一種帶有
b.Id = a.Id
搜尋謂詞和附加b.H = 'C'
謂詞。而另一個使用啟動謂詞的b.Id = a.Id
過濾器之外的尋找謂詞。a.H = 'A'
在 T-SQL 中可以表示為SELECT * FROM Test1 a CROSS APPLY ( SELECT DISTINCT u.Id, u.H FROM ( SELECT b.Id, b.H FROM Test2 b WHERE b.Id = a.Id AND a.H = 'A' UNION ALL SELECT b.Id, b.H FROM Test2 b WHERE b.Id = a.Id AND b.H = 'C' ) u ) appl
實際上還有更多替代方案(例如,在應用程序的內側使用假離線,或為簡單連接使用不同的物理連接實現,或非聚集索引掃描而不是聚集索引掃描,反之亦然,等等),但是上面的執行計劃形狀很有代表性。
當
FORCESEEK
在兩個表上使用時,不會出現新的替代項。此外,由於兩個表上的尋求要求,應用替代方案在考慮後被拒絕。因此,我認為我們可以說,查詢的原始書面形式的可能實現需要
FORCESEEK
至少對其中一個表放寬要求。您有另一個等效查詢,但不幸的是,在目前版本的查詢優化器中沒有實現這種替代方法。不過,這不是錯誤,只是不完美。
另請注意,您添加
FORCESEEK
以說服優化器在非聚集索引中查找,但在上述情況下,優化器以自己的方式理解它並改為在聚集索引上執行查找。重寫查詢,當它的性能不令人滿意時,是經常嘗試的第一件(也是正確的)事情之一。(*)可以通過分析最終的備忘錄結構和應用的轉換(使用未記錄的跟踪標誌 8615、8619 和 8621)來發現它。
我不明白為什麼 SQL Server 沒有以最佳方式執行第一個查詢。這是一個已知的問題?
並不是說 SQL Server 沒有以最佳方式執行第一個查詢,只是它根本無法執行。提示文件說:
- 如果 FORCESEEK 導致找不到計劃,則返回錯誤 8622。
要了解該查詢引起的問題,讓我們觀察以您想要的方式執行的部分查詢的查詢計劃:
select * from Test1 a with (forceseek) join Test2 b with (forceseek) on a.Id = b.Id where a.H = 'A'
這是生成的查詢計劃:
請注意,SQL Server 在非聚集索引上進行了第一次查找
i1
,a.H = 'A'
結果第二次查找(與連接子句相關的一次on a.Id = b.Id
)可能發生在表的聚集索引上Test2
。我們遇到的問題
where a.H = 'A' or b.H = 'C'
是,如果 SQL Server 在非聚集索引上進行了第一次查找i1
以查找a.H = 'A'
並使用該結果進行第二次查找以查找連接on a.Id = b.Id
,它將消除與子句不匹配的行,on a.Id = b.Id
並且b.H = 'C'
無法正確驗證謂詞,因為某些與其匹配的行可能已被強制連接查找丟棄。由於不會冒險生成錯誤的結果集,SQL Server 會拋出 8622 錯誤。關於優化器不選擇Martin Smith提到的索引聯合計劃,Paul White 說:
具有多個條件的連接
OR
長期以來一直存在問題。多年來,優化器添加了一些新技巧,比如將它們轉換為等價UNION
形式,但可用的轉換是有限的,因此很容易被卡住。(重點補充)因此,您想要的轉換可能不在列表中,如果沒有這種可能性,它根本沒有選項。8622錯誤的案例。