咱們偶爾,很是偶爾的狀況下會在一個查詢計劃中看到這樣的警告:算法
大紅叉,好嚇人啊!sql
把鼠標放上去一看顯示這樣的信息app
No join predicateide
直譯過來就是:沒有鏈接謂詞oop
在真實的生產環境下咱們不多能看到這種警告,何時纔出這種警告呢?固然就是~~~沒有鏈接謂詞(汗)的時候,也許這麼解釋起來很找打,可是真實狀況就是這樣。sqlserver
咱們知道,在sqlserver鏈接操做的時候,他的本質實際上就是生成一個笛卡爾積表,那麼鏈接謂詞就是在笛卡爾積表上進行篩選的條件性能
好比咱們寫以下的查詢:測試
select sod.ProductID from sales.SalesOrderDetail sod
join production.product pd
on 1=1大數據
能夠看到,我在on的位置上只寫了on 1=1,實際上這個查詢等同於優化
select sod.productid from
sales.SalesOrderDetail sod ,production.product pd
where 1=1--或者where 1=1這個能夠也是能夠不加的
咱們都知道上面兩種寫法只是生成了一個笛卡爾積表的全集
他們的執行計劃生成是如出一轍的,以下,能夠注意一下上方顯示的查詢語句:
這時,由於兩個表之間出現了沒有任何可供鏈接的謂詞,換句話說就是沒有對笛卡爾積生成的表進行任何篩選,這種查詢可能會帶來巨大的性能損耗,因此發出了no join predicate的警告,而事實也是如此
咱們能夠看到12W對500條數據的表作積後生成的數據量高達6KW條,可想而知這種查詢的消耗有多麼大,因此咱們通常在查詢中必定要注意在作錶鏈接的時候避免這種寫法,也許有人會說,誰會這麼寫查詢?我只能說什麼均可能發生 ,好比鏈接表時是以拼串的形式生成的sql語句,或者用我提到的第二種古老的寫法進行的查詢,都是有可能的。
OK,以上是對no join predicate警告的一個基本說明,可是今天咱們的重點不在這裏
我這篇文章想說的是:有一種狀況下的警告出現的很奇怪,好比下面這個查詢
SELECT sod.SalesOrderID,pd.Name FROM sales.SalesOrderDetail sod
INNER JOIN production.product2 pd
on sod.productid=pd.ProductID
where sod.productid=777
計劃:
這個計劃中就有無鏈接謂詞警告,可是明明寫了ON也有where條件,爲何還會生成這樣的計劃呢,咱們來看一下它是怎麼產生的:
咱們以最開始的查詢開始
注意:product 表就是AdventureWorks2008R2原生的產品表
SELECT sod.SalesOrderID,pd.Name FROM sales.SalesOrderDetail sod
INNER JOIN production.product pd
on sod.productid=pd.ProductID
where sod.productid=777
它的計劃以下:
OK,沒有任何的問題,那麼咱們假設有以下狀況:product 表中的productID並不惟一,也就是說SalesOrderDetail 和product 的productid是多對多的狀況,這種狀況在生產環境中就至關常見了
因此我生成了以下的表:select * into Production.product2 from Production.product
咱們知道,這樣生成的表會把identify列一塊兒生成,上面說過productid應該是不惟一的狀況,因此咱們把idenify屬性去除(過程省略),並生成一個不惟一索引
爲這說明以後發生的問題,咱們再添加一個列,顯示的是產品第一批訂單發生的日期
alter table Production.product2 add firstorder int
update Production.product2 set firstorder=c.SalesOrderDetailID
from Production.product2 a
cross apply(select top 1 SalesOrderDetailID from Sales.SalesOrderDetail
where ProductID=a.ProductID order by ModifiedDate) c
再建立一個索引
create index IX_test on Production.product2 (productid,firstorder,name)
以後運行:
SELECT sod.SalesOrderID,pd.Name FROM sales.SalesOrderDetail sod
INNER JOIN production.product2 pd
on sod.productid=pd.ProductID
where sod.productid=777 option(recompile)
再查看執行計劃
好的,仍是沒有任何問題出現,咱們知道,查詢計劃的生成是依靠統計信息的,因此咱們查看一下777這個鍵值的統計信息:
DBCC SHOW_STATISTICS("Production.product2",IX_test)
能夠看出,777這個值顯示爲一個惟一值(或者說mssql經過統計信息認爲777這個值也許,大概,多是惟一的),以前咱們說過,咱們的場景是多對多,那咱們開始生成重複數據,而且手動刷新統計信息:
insert into Production.product2 select * from Production.product2 where productid=777
update STATISTICS Production.product2 IX_test with fullscan—在sql2014中因爲預估算法有改進,不用更新統計直接執行也能夠重現,可是在直方圖中就看不出來了
再看上面查詢的計劃
噢!變成沒有謂詞的提示了!
統計信息呢,變成這樣了
這是爲何呢?
解釋以下:咱們知道,在查詢處理的過程當中,優化器對查詢有一系列簡化過程,好比代數代入,就是說在
on sod.productid=pd.ProductID
where sod.productid=777
這個條件下,會先經過productid=777把兩個表符合條件的數據篩選出來(爲何是兩個表,由於有sod.productid=pd.ProductID,因此常量參數直接傳遞了),以後再進行inner on的匹配
以前沒有重複數據的時候,因爲product表productid列爲主鍵,給定鍵值只有一條數據,那麼對SalesOrderDetail來講,輸出的數據就是product表單條數據與SalesOrderDetail表的所有匹配數據
而把product進行重複插入後,mssql查覺到product表符合777的數據>1條了(見下圖)
可是鏈接謂詞列被指定常量777替換,但又沒有其它的篩選條件,那麼實際上查詢等同於
SELECT sod.SalesOrderID,pd.Name FROM (select SalesOrderID from sales.SalesOrderDetail where productid=777) sod,
(select Name from production.product2 where productid=777) pd option(recompile)
同時,咱們也知道,nest loop算法的僞碼以下 :
咱們知道,nest loop的僞碼算法是這樣的,它的時間複雜度爲N*M(或者說左錶行數*右錶行數)
for each row in tb1 loop for tb2 loop If match tb2.key= tb1.key then pass the row on to the next step If no match then discard the row end loop end loop
上面的查詢寫法就變成了兩個子結果集直接進行積卡爾笛,但並無任何可供比較的key值,因而產生了no join predicate警告
那麼若是我換一個參數呢?好比productid=1的時候會是什麼狀況?
在本例中,因爲SalesOrderDetail沒有符合productid=1的數據,因此預估行數據就爲1,這時無論product表有多少productid=1的數據,也表現爲輸出一對多的數據,因此也就沒有顯示出no join predicate警告。
其實能夠說,若是查詢計劃裏出現了no join predicate警告,就必需要看一下這個查詢的業務邏輯是否是有問題,有多是輸出大量無效的垃圾數據,而且影響了性能 ,可是這個說法反過來講是不成立的,並非說沒有沒有警告就沒產生重複的垃圾數據。
例如在以前我創建的複合索引是包含了productid和firstorder列
那我查詢的寫法可能會這麼寫
SELECT sod.SalesOrderID ,
pd.Name
FROM Sales.SalesOrderDetail sod
INNER JOIN Production.product2 pd ON
sod.salesorderdetailid= pd.firstorder and
sod.ProductID=pd.ProductID
WHERE pd.ProductID = 777 and pd.firstorder=28 option(recompile)
假設這兩個表的數據量很大,且有至關多的數據,這時就有可能產生行數估值錯誤,出現無謂詞警告,可是有可能業務邏輯並無問題(在本例中沒有出現這種狀況,只是舉例說明)
表不變,但當查詢這麼寫的時候
SELECT sod.SalesOrderID ,
pd.Name
FROM Sales.SalesOrderDetail sod
INNER JOIN Production.product2 pd ON
sod.ProductID=pd.ProductID
and sod.salesorderdetailid= pd.firstorder
WHERE pd.ProductID = 777-- and pd.firstorder=28
option(recompile)
由於兩個數據子集除去productid被常量參數傳遞後不參與匹配後,還須要進一步對sod.salesorderdetailid= pd.firstorder進行匹配,這樣不會出現無鏈接謂詞警告,可是業務邏輯明顯出現了問題,但我估計不會有人把上面查詢修改寫成下面這樣吧……
固然在本篇文章中演示的案例數據量過小,並不能重現這種狀況。有興趣的同窗能夠擴大數據量測試一下。
結論
在進行鏈接時無論左表仍是右表,只要有一個表能夠產生一個惟一性數據(即nest loop的時間複雜度爲1*M或者N*1),這種狀況下即便寫成沒有鏈接謂詞的形式,也不會產生警告符,可是隻要結果爲N*M,且沒有任何可供匹配的鏈接謂詞(能夠被常量傳遞的謂詞不算),則會產生警告。
個人確是在生產環境中實實在在遇到了這種狀況
,因爲涉及公司的業務就不把所有計劃截出來了,可是能夠告訴你們的是,在nested loop下方的那個數據表,其實就存在有相似(productid,firstorder)這樣的一個複合索引,且數據具備惟一性,可是由於統計信息的問題預估行數變成了1.25行,因而產生了無鏈接謂詞警告
那麼究竟這種警告到底須要不須要處理呢?個人見解是:看狀況。
出現了警告,確定是DBA須要關注的,可是不是全部的警告必定就是有問題。
這須要與業務方溝通,究竟是業務邏輯出現了問題?仍是需求如此?又或者只是一個生成計劃時的誤判?
若是隻是統計信息誤判斷生成查詢計劃顯示的警告,但業務邏輯沒有混亂,從我本身遇到的狀況看,並無什麼實質性的性能問題,能夠忽略,若是您有什麼不一樣看法能夠與我聯繫