Sql-Server

為什麼 SQL Server 找不到查找計劃?這是一個錯誤嗎?

  • April 20, 2020
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')在連接節點上進行測試時。

第二個是申請表(更多資訊在這裡

計劃2

當在嵌套循環連接的外側掃描其中一個表上的索引時,然後在內側使用索引數據使用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在其中一張表上使用時,不考慮簡單的連接替代方案,但優化器另外考慮了應用的修改形式

計劃3

它聯合了兩個搜尋到另一個表上的聚集索引。一種帶有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 在非聚集索引上進行了第一次查找i1a.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錯誤的案例。

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