SQLServer 有3種物理連接:Nested Loop(嵌套循環)、Merge Join(合並聯接)、Hash Join(哈希聯接)。
T-SQL中的inner/left/right/full join等在進行優化的過程中會轉換成上面3種物理連接。
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次匹配。
當外部連接很小,並且內部連接在連接列上有索引時,優化器會傾向使用這種算法。
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)子句定義。
合並聯接本身的速度很快,但如果需要排序操作,選擇合並聯接就會非常費時,然而,數據量很大且能夠從現有B樹索引中獲得預排序的所需數據,則合並聯接通常最快
的可用聯接算法。
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.完整計划優化