關於join時顯示no join predicate的那點事

咱們偶爾,很是偶爾的狀況下會在一個查詢計劃中看到這樣的警告:算法

image

大紅叉,好嚇人啊!sql

把鼠標放上去一看顯示這樣的信息app

image

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這個能夠也是能夠不加的

咱們都知道上面兩種寫法只是生成了一個笛卡爾積表的全集

他們的執行計劃生成是如出一轍的,以下,能夠注意一下上方顯示的查詢語句:

imageimage

這時,由於兩個表之間出現了沒有任何可供鏈接的謂詞,換句話說就是沒有對笛卡爾積生成的表進行任何篩選,這種查詢可能會帶來巨大的性能損耗,因此發出了no join predicate的警告,而事實也是如此

image

咱們能夠看到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

計劃:

image

這個計劃中就有無鏈接謂詞警告,可是明明寫了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

 

它的計劃以下:

image

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)

 

再查看執行計劃

image

好的,仍是沒有任何問題出現,咱們知道,查詢計劃的生成是依靠統計信息的,因此咱們查看一下777這個鍵值的統計信息:

DBCC SHOW_STATISTICS("Production.product2",IX_test)

image

能夠看出,777這個值顯示爲一個惟一值(或者說mssql經過統計信息認爲777這個值也許,大概,多是惟一的),以前咱們說過,咱們的場景是多對多,那咱們開始生成重複數據,而且手動刷新統計信息:

insert into Production.product2 select * from Production.product2 where productid=777

update STATISTICS Production.product2 IX_test with fullscan—在sql2014中因爲預估算法有改進,不用更新統計直接執行也能夠重現,可是在直方圖中就看不出來了

再看上面查詢的計劃

image

噢!變成沒有謂詞的提示了!

統計信息呢,變成這樣了

image

 

這是爲何呢?

解釋以下:咱們知道,在查詢處理的過程當中,優化器對查詢有一系列簡化過程,好比代數代入,就是說在

on sod.productid=pd.ProductID
where sod.productid=777

這個條件下,會先經過productid=777把兩個表符合條件的數據篩選出來(爲何是兩個表,由於有sod.productid=pd.ProductID,因此常量參數直接傳遞了),以後再進行inner on的匹配

imageimage

 

 

以前沒有重複數據的時候,因爲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,且沒有任何可供匹配的鏈接謂詞(能夠被常量傳遞的謂詞不算),則會產生警告。

個人確是在生產環境中實實在在遇到了這種狀況

image,因爲涉及公司的業務就不把所有計劃截出來了,可是能夠告訴你們的是,在nested loop下方的那個數據表,其實就存在有相似(productid,firstorder)這樣的一個複合索引,且數據具備惟一性,可是由於統計信息的問題預估行數變成了1.25行,因而產生了無鏈接謂詞警告

那麼究竟這種警告到底須要不須要處理呢?個人見解是:看狀況。

出現了警告,確定是DBA須要關注的,可是不是全部的警告必定就是有問題。

這須要與業務方溝通,究竟是業務邏輯出現了問題?仍是需求如此?又或者只是一個生成計劃時的誤判?

若是隻是統計信息誤判斷生成查詢計劃顯示的警告,但業務邏輯沒有混亂,從我本身遇到的狀況看,並無什麼實質性的性能問題,能夠忽略,若是您有什麼不一樣看法能夠與我聯繫

相關文章
相關標籤/搜索