SQLServer 有3種物理鏈接:Nested Loop(嵌套循環)、Merge Join(合併聯接)、Hash Join(哈希聯接)。算法
T-SQL中的inner/left/right/full join等在進行優化的過程當中會轉換成上面3種物理鏈接。sql
1.Nested Loop(嵌套循環)
SELECT e.BusinessEntityID FROM HumanResources.Employee AS e INNER JOIN Sales.SalesPerson AS s ON e.BusinessEntityID =s.BusinessEntityID
處於上方的輸入叫作外部輸入,處於下方的輸入叫作內部輸入,SalesPerson爲外部輸入,Employee爲內部輸入緩存
外部循環逐行處理外部輸入表,內部循環會針對每一個外部行在內部輸入表中進行搜索,已找出匹配行函數
經過彙集索引查找,返回17行數據,也就是說外部輸入要進行17次匹配。oop
當外部鏈接很小,而且內部鏈接在鏈接列上有索引時,優化器會傾向使用這種算法。性能
2.Merge Join(合併鏈接)優化
SELECT s.Name FROM sales.store AS s JOIN sales.Customer AS c ON s.BusinessEntityID =c.CustomerID WHERE c.TerritoryID=6
合併連接也分爲外部輸入和內部輸入,外部輸入和內部輸入只會執行一次。合併聯接要求兩個輸入都在合併列上排序,而合併列由聯接謂詞的等效(on)子句定義。3d
合併聯接自己的速度很快,但若是須要排序操做,選擇合併聯接就會很是費時,然而,數據量很大且可以從現有B樹索引中得到預排序的所需數據,則合併聯接一般最快code
的可用聯接算法。對象
3.Hash Join(哈希聯接)
SELECT pv.ProductID,v.BusinessEntityID,v.Name FROM Purchasing.ProductVendor pv JOIN Purchasing.Vendor v ON (pv.BusinessEntityID=v.BusinessEntityID) WHERE pv.StandardPrice >$10
哈希聯接有兩種輸入:生成輸入和探測輸入。查詢優化器使用兩個輸入中較小的那個做爲生成輸入
用於多種設置匹配操做:內部聯接、左外部聯接、右外部聯接、和徹底外部聯接,左半聯接、和右半聯接,交集、並集和差別。某種變形能夠進行重複刪除和分組。
二:數據訪問操做:
1.掃描操做將針對整個結構來進行,多是一個堆表、一個彙集索引、和一個非彙集索引。
2.查找操做是從索引中查找所需的數據,不須要掃描整個結構,只發生在彙集索引和非彙集索引上。
堆表:表掃描、不存在查找
彙集索引:彙集索引掃描、彙集索引查找
非彙集索引:索引掃描、索引查找
1.掃描(堆表)
SELECT * FROM dbo.DatabaseLog
2.彙集索引掃描
SELECT * FROM Person.Address
3.非彙集索引掃描(索引掃描)
SELECT AddressID,City,StateProvinceID FROM Person.Address
想知道數據是否有排序,能夠看加框部分,它爲true證實數據已經排序
二:查找(堆表上不存在查找操做,因此查找操做特指索引的操做)
查找操做不須要掃描整個索引,它是經過B-Tree結構來快速定位所需的數據
彙集索引查找
SELECT AddressID,City,StateProvinceID FROM Person.Address WHERE AddressID=12037
非彙集索引掃描:
SELECT AddressID,StateProvinceID FROM Person.Address WHERE StateProvinceID=32
3.書籤(鍵)查找
一個非彙集索引被優化器選爲訪問數據的索引,可是這個索引不能覆蓋全部的列,致使非彙集索引必須藉助彙集索引鍵或者堆上的RID來定位其餘數據
SELECT AddressID,City,StateProvinceID,ModifiedDate FROM Person.Address WHERE StateProvinceID=32
鍵值查找操做符的tooltips:
鍵查找(Key Lookup屬於書籤查找的一種)因爲AddressID不包含在非彙集索引中,查詢又須要用到它,因此須要經過彙集索引來獲取相關的數據。
若是表上沒有相關的彙集索引,會出現RID查找,RID Lookup和Key Lookup統稱爲書籤查找。
不能覆蓋查詢的非彙集索引,因此建立了一個非彙集索引在堆表上,以便進行查詢
--建立非彙集索引
CREATE INDEX IX_Object ON dbo.DatabaseLog(Object)
--RID
SELECT * FROM dbo.DatabaseLog WHERE Object='City'
三:聚合操做:
有兩種操做實現聚合:流聚合(Stream Aggregate)和哈希聚合(Hash Aggregate)
1.排序和哈希
2.流聚合
指使用了聚合函數,但沒有Group By子句並返回一個單一值的查詢。
若是帶有聚合函數,但沒有Group By子句,叫作標量聚合。標量聚合由流聚合操做來返回一個數據。
SELECT AVG(ListPrice) FROM Production.Product
--帶有Group By的聚合
SELECT ProductLine,COUNT(*) FROM Production.Product GROUP BY ProductLine
3.Hash聚合
在執行計劃中以Hash Match做爲物理操做符,當優化器發現一個沒有排序的大表須要聚合,預估只有少許的組時,就會選擇Hash聚合
SELECT TerritoryID,COUNT(*) FROM sales.SalesOrderHeader GROUP BY TerritoryID
哈希聚合是針對未排序的數據,一旦加上索引,是能夠轉換成流聚合的。
CREATE INDEX IX_ContactID ON Sales.SalesOrderHeader(TerritoryID) SELECT TerritoryID,COUNT(*) FROM Sales.SalesOrderHeader GROUP BY TerritoryID
四:檢查統計對象
能夠經過sys.stats目錄視圖來查看某個對象的統計信息
SELECT * FROM sys.stats WHERE object_id=OBJECT_ID('Sales.SalesOrderDetail')
也能夠用DBCC SHOW_STATISRICS命令來顯示某列上的統計信息
DBCC SHOW_STATISTICS('Sales.SalesOrderDetail',UnitPrice)
只須要用如下這個列:
SELECT * FROM sales.SalesOrderDetail WHERE UnitPrice=35
再次執行:
檢查一個非彙集索引上的統計信息,只關注密度信息部分:
DBCC SHOW_STATISTICS('Sales.SalesOrderDetail',IX_SalesOrderDetail_ProductID)
手工計算密度矢量:
SELECT COUNT (DISTINCT ProductID) ,1.0/COUNT (DISTINCT ProductID) FROM Sales.SalesOrderDetail
執行如下語句,看看執行計劃中是否真的使用了266行預估行數
SELECT ProductID,COUNT(1) FROM sales.SalesOrderDetail GROUP BY ProductID
使用如下語句查看對象的統計信息狀況:
SELECT name,auto_created,STATS_DATE(object_id,stats_id) AS update_date FROM sys.stats WHERE object_id=OBJECT_ID(N'表名')
重建索引:
ALTER INDEX ix_ProductID ON dbo.SalesOrderDetail REBUILD
計算列:
SELECT * FROM sales.SalesOrderDetail WHERE OrderQty * UnitPrice>10000
建立一個計算列:
ALTER TABLE sales.SalesOrderDetail ADD cc AS OrderQty * UnitPrice
從新運行上面的語句,預估行數和實際函數相對較接近:
刪除上述列:
ALTER TABLE sales.SalesOrderDetail DROP COLUMN cc
4.過濾索引上的統計信息
這種統計信息不是全表建立的,而是針對一個表的子集建立的,當建立過濾索引時,會自動建立對應的統計信息。
SELECT * FROM Person.Address WHERE City='Los Angeles'
SELECT * FROM Person.Address WHERE StateProvinceID=9
兩個查詢的預估行數和實際行數是同樣的。
組合Where條件以後的執行計劃:
SELECT * FROM person.Address WHERE City='Los Angeles' AND StateProvinceID=9
SQL Server 首先把兩個查詢的結果集行數相乘,而後 除以表中的總行數。
提升預估的準確性,能夠建立一個統計信息
CREATE STATISTICS California ON Person.Address(City) WHERE StateProvinceID=9
清空緩存,執行查詢語句:
DBCC FREEPROCCACHE GO SELECT * FROM person.Address WHERE City='Los Angeles' AND StateProvinceID=9
只返回頭信息的方法查詢:
DBCC SHOW_STATISTICS('Person.Address',California) WITH stat_header
也能夠用下面的語句進行查詢:
SELECT * FROM sys.stats WHERE filter_definition IS NOT NULL
六: 預估數據錯誤:
錯誤預估行數會致使優化器不合適的執行計劃影響性能,能夠經過檢查執行計劃發現。
SET STATISTICS PROFILE ON GO SELECT * FROM Sales.SalesOrderDetail WHERE OrderQty * UnitPrice >10000 GO SET STATISTICS PROFILE OFF
七:優化器工做過程
工做過程:簡化、簡單計劃優化和完整計劃優化
1.簡化,在這個階段,查詢會被重寫,一些邏輯寫法會被重寫成優化器能讀懂的內容
(一):子查詢會被轉換成join
(二):多餘的inner/outer join會被移除
(三):Where條件中的篩選部分會被處理
SELECT pp.ProductID--,ppc.Name FROM Production.Product pp INNER JOIN production.ProductSubcategory pps ON pps.ProductSubcategoryID=pp.ProductSubcategoryID INNER JOIN Production.ProductCategory ppc ON ppc.ProductCategoryID =pps.ProductCategoryID
去掉註釋和不去註釋分別執行,查看執行計劃,註釋了一個字段,會少一個表關聯。
禁用外鍵:
ALTER TABLE Production.ProductSubcategory NOCHECK CONSTRAINT FK_ProductSubcategory_ProductCategory_ProductCategoryID
由於外鍵的做用消失,因此須要引入第三個表
恢復外鍵
ALTER TABLE Production.ProductSubcategory WITH CHECK CHECK CONSTRAINT FK_ProductSubcategory_ProductCategory_ProductCategoryID
2.簡單計劃優化
3.完整計劃優化