1. 程式人生 > >SqlServer中的UNION操作符在合並數據時去重的原理以及UNION運算符查詢結果默認排序的問題

SqlServer中的UNION操作符在合並數據時去重的原理以及UNION運算符查詢結果默認排序的問題

復數 class 如果 參與 不一致 數據合並 try tle 不用

原文:SqlServer中的UNION操作符在合並數據時去重的原理以及UNION運算符查詢結果默認排序的問題

本文出處:http://www.cnblogs.com/wy123/p/7884986.html

周圍又有人在討論UNION和UNION ALL,對於UNION和UNION ALL,網上說的最多的就是性能問題(實在不想說出來這句話:UNION ALL比UNION快)
其實根本不想炒UNION和UNION ALL這碗剩飯了,
每次看到網上說用這個不用那個,列舉的一條一條的那種文章,只要看到說UNION ALL比UNION性能好的就……

對於合並的結果集,UNION是去重的,UNION ALL是不去重的,去重與不去重是兩個目的,分別由UNION和UNION ALL實現
兩個作用(功能)不同的東西,放一起比性能有什麽意義?
這種問題真的是無聊至極,就好比“足球場上的某個中後衛和某個前腰哪個能力更強”一樣沒有可比性,
他們的作用本身就是不同的,難道說中後衛能力不行,把他撤下來,用一個牛逼的前腰球員替代中後衛,或者是前腰能力不行,撤下他用牛逼的中後衛替代?
這是在功能上的區別,至於性能,我個人認為對比起來沒有任何意義。
如果非要放一起比的話,做同樣的數據合並,
UNION因為要去重,相對UNION ALL來說,(相對)當然會耗費更多的資源(耗費的資源多少跟性能無關,做的事情多,當然需要更多的資源)
但是一定要弄清楚,合並數據的時候,到底要不要去掉重復數據,這是最終結果對與錯的問題,不是性能問題!

這裏不討論UNION和UNION ALL的性能了,
從另外一個點入手來發起問題
UNION與UNION ALL最大的區別就是UNION會去重,那麽問題就來了,這個去重是怎麽實現的?去重會對查詢的默認順序集產生什麽影響?

UNION去重的實現

測試一下UNION運算符去重的實現原理

create table TestUnion1
(    
    
    Id1 INT PRIMARY KEY,
    Id2 tinyint,
    Name varchar(100)
);
create table TestUnion2
(
    Id1 INT PRIMARY
KEY, Id2 tinyint, Name varchar(100) ); insert into TestUnion1 values (500,9,aaa) insert into TestUnion1 values (700,3,ccc) insert into TestUnion1 values (200,7,eee) insert into TestUnion2 values (300,2,bbb) insert into TestUnion2 values (800,8,ddd) insert into TestUnion2 values
(100,5,fff) --TestUnionALL1和TestUnionALL2中相同的數據 insert into TestUnion1 values (600,6,xxx) insert into TestUnion2 values (600,6,xxx)

UNION在去重的過程中,使用的執行計劃是Merge Join,UNION ALL是不去重的,同樣步驟對應的執行計劃是Concatenation

技術分享圖片

這裏UNION的去重動作是通過merge實現,這裏的merge join並不是表與表之間的merge join
這裏可以看出來,UNION產生的merge與 inner join產生的Merge的作用是有差異的

技術分享圖片

對於UNION的去重的這一動作,去當然不是說只有merge join一種,這裏只不過是兩個結果的數據都剛好有序才采用merge join來去重罷了

如果查詢字段的順序的第一個字段是聚集索引(或者主鍵),,正如上文提到的,UNION的雙方就會以merge的方式區中
如果查詢字段的順序非聚集索引,UNION的過程是現將兩個結果集合並起來(上文提到的Concatenation),然後再做sort排序去重

  技術分享圖片


UNION之後結果集的最終排序結果

UNION之後結果集的最終排序結果跟查詢字段的順序有關,
如果查詢字段的順序的第一個字段是聚集索引(或者主鍵),正如上文提到的,UNION的雙方就會以merge的方式區中
如果查詢字段的順序的第一個字段是非聚集索引字段,UNION的過程是現將兩個結果集合並起來(上文提到的Concatenation),然後再做sort排序去重
如下的實例能說說明這個問題,當查詢字段的順序發生變化之後,兩者的執行計劃完全不一致。

技術分享圖片技術分享圖片

或者再看一個case,當Name在最前面的時候,最終的結果就是按照name排序。

技術分享圖片

  可能有人會懷疑是不是數據量太小了,是不是巧合,這裏可以加大測試數據庫,在查詢條件中,讓非聚集索引參與到運算之中

create table TestUnion1
(    
    
    Id1 INT PRIMARY KEY,
    Id2 tinyint,
    Name varchar(100),
    CreateDate datetime
);


create table TestUnion2
(
    Id1 INT PRIMARY KEY,
    Id2 tinyint,
    Name varchar(100),
    CreateDate datetime
);


begin tran
    declare @i int = 0
    while @i<1000000
    begin
        insert into TestUnion1 values (@i,rand()*200,newid(),getdate()-rand()*1000)
        insert into TestUnion2 values (@i,rand()*200,newid(),getdate()-rand()*1000)
        set @i=@i+1
    end
commit


create index idx_CreateDate on TestUnion1(CreateDate)
create index idx_CreateDate on TestUnion2(CreateDate)

參考下圖,一旦查詢結果集不是按照查詢字段聚集索引排序的話,
比如這裏走的是createDate時間字段的索引,執行計劃都是先按照普通的方式合並結果集,也即Concatenation
然後在利用Sort(Distinct)的方式排序去重,對於去重的結果的最終的排序,跟查詢結果的第一個字段有關,且結果總是按照查詢的第一個字段排序的。

技術分享圖片技術分享圖片

  換一種查詢字段的順序方式,看一下結果,仍舊是按照查序列的第一個字段排序的

技術分享圖片

  UNION運算符在去重的時候,
  如果查詢字段的第一個字段是聚集索引,那麽會用merge join的方式合並+去重。
  如果查詢字段的第一個字段不是非聚集索引,那麽首先會將兩個(或者多個)結果集進行普通的合並,最後通過Sort Distinct的方式去重。  
  且UNION運算之後的默認排序方式,受查詢字段前後的方式影響。 

總結:

  UNION和UNION ALL的作用是不一樣的,放在一起比性能沒有任何意義,真不想趟這趟渾水。
  合並結果集,需要去重就用UNION,不需要去重就用UNION ALL,如果兩個結果集中沒有重復的結果集,就用UNION ALL,
  這純粹是需求驅動的,而不是UNION和UNION ALL的性能問題。

多撤一句:
曾經大晚上接到一個面試電話,沒有任何開場白,第一句話是“我們電話面試一下可以嗎”,答曰可以,第二句話就是“UNION和UNION ALL的區別是什麽,有沒有性能差異”。
真的不希望再去對UNION和UNION ALL的性能上做討論。

SqlServer中的UNION操作符在合並數據時去重的原理以及UNION運算符查詢結果默認排序的問題