SQL server 內部實現了三種類型的內連接運算,大多數人從來沒有聽說過這些連接類型,因為它們不是邏輯連接也很少被用於代碼中。那么它們什么時候會被用到呢?答案是要依情況而定。這就意味着要依賴於記錄集和索引。查詢優化器總是智能的選擇最優的物理連接類型。我們知道SQL優化器創建一個計划開銷是基於查詢開銷的,並依據此來選擇最佳連接類型。
那查詢優化器究竟是怎樣從內部選擇連接類型的呢?
SQLServer在內部為查詢優化器對連接類型的選擇實現了一些算法,讓我們來看下面的一些練習示例,最后來做總結。
首先我給出一些基本的思想,連接是怎樣工作什么時候工作,優化器又是怎樣決定使用哪種類型的內連接。
· 取決於表大小
· 取決於連接列是否有索引
· 取決於連接列是否排序
測試環境:
內存:4GB
數據庫服務器:SQLServer 2008 (RTM)
-
create table tableA (id int identity ,name varchar(50))
-
declare @i int
-
set @i=0
-
while (@i<100)
-
begin
-
insert into tableA (name)
-
select name from master.dbo.spt_values
-
set @i=@i+1
-
end
-
--select COUNT(*) from dbo.tableA --250600
-
go
-
create table tableB (id int identity ,name varchar(50))
-
declare @i int
-
set @i=0
-
while (@i<100)
-
begin
-
insert into tableB (name)
-
select name from master.dbo.spt_values
-
set @i=@i+1
-
end
-
-- select COUNT(*) from dbo.tableB --250600
-
select * from dbo.tableA A join tableB B
-
on (a.id=b.id)
測試1:大表,沒有索引
現在來創建一個聚族索引:
-
create unique clustered index cx_tableA on tableA (id)
-
create unique clustered index cx_tableB on tableB (id)
測試1:大表,有索引
如果連接中的任何一個表有索引那么將采用Hash Join。我並沒有貼上所有結果截圖,如果你感興趣你可以刪除任何一個表中的索引來做測試。
測試2:中表,沒有索引
首先創建表:
-
create table tableC (id int identity,name varchar(50))
-
insert into tableC (name)
-
select name from master.dbo.spt_values
-
-- select COUNT(*) from dbo.tableC --2506
-
create table tableD (id int identity,name varchar(50))
-
insert into tableD (name)
-
select name from master.dbo.spt_values
-
select * from dbo.tableC C join tableD D
-
on (C.id=D.id)
-
-- select COUNT(*) from dbo.tableD --2506
測試2:中表,有索引
首先還是創建一個聚族索引:
-
create unique clustered index cx_tableC on tableC (id)
-
create unique clustered index cx_tableD on tableD (id)
對於中等大小的表,如果連接中的任何一個表有索引,那么將采用Merge Join。
測試3:小表,沒有索引
-
create table tableE (id int identity,name varchar(50))
-
insert into tableE (name)
-
select top 10 name from master.dbo.spt_values
-
-- select COUNT(*) from dbo.tableE --10
-
create table tableF (id int identity,name varchar(50))
-
insert into tableF (name)
-
select top 10 name from master.dbo.spt_values
-
-- select COUNT(*) from dbo.tableF --10
測試3:小表,有索引
創建聚族索引:
-
create unique clustered index cx_tableE on tableE (id)
-
create unique clustered index cx_tableF on tableF (id)
對於小表,如果任何一個表中有索引,那么將采用Nested Loop Join。
同樣也可以從另一個方向來做比較,比如大表對比中表對比小表。
-
select * from dbo.tableA A join tableC C
-
on (a.id=C.id)
-
-
select * from dbo.tableA A join tableE E
-
on (a.id=E.id)
-
-
select * from dbo.tableC C join tableE E
-
on (C.id=E.id)
在這種情況下若所有或部分表都有索引則采用Nested Loop Join,如果都沒有則使用HashJoin。
當然你也可以強制優化器使用任何一種連接類型,但這並不是一種值得推薦的做法。查詢優化器很智能,能夠動態的選擇最優的一個。這里我只是顯示調用了MergeJoin,所以優化器使用MergeJoin替代本來應使用HashJoin (測試1沒有索引)。
-
select * from dbo.tableA A join tableB B
-
on (A.id=B.id)option (merge join)
-
-
select * from dbo.tableA A inner merge join tableB B
-
on (A.id=B.id)
表1 測試唯一聚族索引
根據上表:
Ø 如果兩個表都沒有索引則查詢優化器內部會選擇Hash Join
Ø 如果兩個表都有索引則內部會選擇Merge Join(大表)/NestedLoop Join(小表)
Ø 如果其中的一個表有索引則查詢優化器內部會選擇Merge Join(中表)/HashJoin(大表)/NestedLoop Join(小表&大表 vs 小表)
表2 測試聚族索引(create
clustered index
cx_tableA on
tableA (id))
|
|||||
Table size |
With index (Both) |
Without Index(Both) |
Either of table has index |
|
|
Big (Both) |
HASH |
HASH |
HASH |
|
|
Medium (Both) |
HASH |
HASH |
HASH |
|
|
Small (Both) |
NESTED LOOP |
NESTED LOOP |
HASH |
|
|
Big Vs Small(medium) |
HASH |
HASH |
HASH |
|
根據上表:
這個測試是在沒有使用唯一聚族索引下完成,可以知道如果創建索引的時候沒有使用UNIQUE關鍵字則無法保證SQLServer會知道這是UNIQUE數據,所以它默認會創建4字節整數來作為唯一標識符。
根據上表如果創建聚族索引沒有使用Unique關鍵字則不會使用MergeJoin。
謝謝@Dave的郵件,現在加上第二個圖表了。
總結:
Merge Join
Merge Join是為那些在連接列上有索引的表,索引可以是聚族索引或者非聚族索引。Merge是這種情況最好的Join類型,需要兩個表都有索引,所以它已經排好序並更容易匹配和返回數據。
Hash Join
Hash Join是為那些沒有索引或者其中任一個有索引的大表。對於這種情況它是最好的Join類型,為什么呢?因為它能夠很好的工作於沒有索引的大表和並行查詢的環境中,並提供最好的性能。大多數人都說它是Join的重型升降機。
Nested Loop Join
Nested Loop Join是為那些有索引的小表或其中人一個有索引的大表。它對那些小表連接,需要循環執行從一個到另一個表的按行比較的情況下工作最好的。
我希望你現在能理解查詢優化器是如何選擇最優的查詢類型。