當肯定了應用性能問題能夠歸結到某一個,或者幾個耗時資源的語句後,對這些語句進行調優,就是數據庫管理員或者數據庫應用程序開發者當仁不讓的職責了。語句調優是和數據庫打交道的必備基本功之一。數據庫
當你面對一個「有問題」的語句時,應該怎麼分析它的問題所在,最後達到優化語句的目的呢?首先要想想,「有問題」的語句「問題」究竟在那裏?也就是說,你要優化的目標是什麼。常見的需求有:緩存
1) 語句須要訪問大量的數據頁面,形成內在壓力、磁盤繁忙等。併發
對於這類問題,所關心的是爲何語句要執行要訪問這麼多數據頁面?是語句的結果集自己就比較大;仍是SQL SERVER沒有辦法有效地seek,而是像大炮打蒼蠅同樣從大量的原始數據裏找出須要返回的結果;仍是由於數據頁面裏有不少碎片,致使SQL SERVER讀了不少頁面,可是每一個頁面裏的數據量很少。這些都是要考慮的因素。性能
2) 在內存沒有壓力的前提下(語句所訪問的頁面都事先緩存在內存裏),語句運行的時間仍是很長。測試
語句的運行時間通常會主要花在這3步上:語句編譯、語句執行和結果集返回。結果集返回的速度和SQL SERVER自身沒有太大關係,因此通常不會在語句調優的時候來考慮。語句調優時要搞清楚編譯和執行各花了多少時間,哪 一段時間有優化的空間,以及怎麼來優化。優化
3) 單個語句執行時間能夠接受,可是苦CPU使用量比較大,多個語句併發執行會形成SQL SERVER CPU高。ui
有些語句單句執行可能一兩秒鐘就能執行完畢,對用戶來說還在可接受的範圍。可是它的CPU間可能也是在一兩秒,甚至更長。若是同時有十幾個用戶在跑一樣的語句,SQL SERVER 就會滿負荷了。語句的CPU時間也分編譯階段和執行階段。優化者要先搞清楚這兩個階段各用了多少CPU資源,而後再看看有沒有優化下降CPU使用量的可能。spa
4) 語句單獨執行看不出有大問題,可是併發執行就容易遇到阻塞和死鎖。code
這個也是語句調優的一個重要任務。不少語句執行速度很快,使用資源量SQL SERVER也可以承受,可是就是容易引發阻塞和死鎖。這種現象每每是因爲應用在某個表或者索引上的併發度特別高,而問題語句申請的鎖數量比較大形成的。固然有時候可使用Query Hint 來強制 SQL SERVER使用粒度比較小的鎖。可是這每每不是最好的解決辦法,也可能解決不了問題。最理想的方法,是經過調整語句運行方式,引導它申請儘量少的、粒度儘量小的鎖。這裏也要作語句調優。blog
在作這些調優的時候,首先要對目標語句作估算,看看它優化的空間有多大。有些語句自己比較簡單,能夠經過調整索引的方法迅速提升性能,這樣的調優是很值得作的。有些語句很是複雜,或者返回的結果集很大,經過調整SQL SERVER這裏的設置,提升性能的空間每每不大。這個時候就要考慮,語句自己是否是可以換一種方法實現。不少時候改一下語句,把一條大的語句拆分紅若干條小的語句,或者去掉一些沒必要要的邏輯,會達到事半功倍的效果
在談論如何作語句調優的具體方法以前,必須先介紹一下最必需的背景知識。不瞭解這些知識 ,作語句調優就只能基本靠猜。所須要的背景知識主要包括理解索引和統計信息,理解什麼是統計和重編譯,而且可以基本讀懂語句的執行計劃。如下爲例子,藉助MS示例數據庫AdventureWordks來介紹。
--測試用例 USE AdventureWorks2008 GO IF OBJECT_ID ('SalesOrderHeader_TEST') IS NOT NULL DROP TABLE dbo.SalesOrderHeader_TEST GO IF OBJECT_ID ('dbo.SalesOrderDetail_TEST') IS NOT NULL DROP TABLE dbo.SalesOrderDetail_TEST GO -- (31465 行受影響) SELECT * INTO dbo.SalesOrderHeader_TEST FROM Sales.SalesOrderHeader -- (121317 行受影響) SELECT * INTO dbo.SalesOrderDetail_TEST FROM Sales.SalesOrderDetail -- 創建彙集索引 CREATE CLUSTERED INDEX SalesOrderHeader_TEST_CL ON dbo.SalesOrderHeader_TEST(SalesOrderID) -- 創建非彙集索引 CREATE NONCLUSTERED INDEX SalesOrderDetail_TEST_NCL ON dbo.SalesOrderDetail_test(SalesOrderID) go
SalesOrderHeader_TEST 裏存放的是每一張訂單的頭信息,包括訂單建立日期、客戶編號、合同編號、銷售員編號等,每一個訂單都有一個單獨的訂單號。在訂單號這個字段上,有一個彙集索引。
SalesOrderDetail_TEST 裏存放的是訂單的詳細內容。一張訂單能夠銷售多個產品給同一個客戶,因此SalesOrderHeader_TEST 和SalesOrderDetail_TEST是一對多的關係。往往詳細內容包括它所屬的訂單編號,它本身在表格裏的惟一編號(SalesOrderDetailID)、產品編號、單價、以及銷售數量等。在這裏,先只在SalesOrderDetailID 上創建一個非彙集索引。
按照AdventureWorks裏原先的數據, header_test 裏面有3萬多條訂單信息,detail裏有12萬多條訂單詳細記錄,基本上一條訂單有3-5條詳細記錄。這是一個正常的分佈。
下面再在 header_test 裏面加入9條訂單記錄,他們的編號是從75124 到75132這是9張特殊的訂單,每張有12萬多條詳細記錄。也就是說 deatil_test裏會有90%的數據屬於這9張訂單。
declare @i int set @i = 1 while @i < 10 begin INSERT INTO [AdventureWorks2008].[dbo].[SalesOrderHeader_TEST] ([RevisionNumber] ,[OrderDate] ,[DueDate] ,[ShipDate] ,[Status] ,[OnlineOrderFlag] ,[SalesOrderNumber] ,[PurchaseOrderNumber] ,[AccountNumber] ,[CustomerID] ,[SalesPersonID] ,[TerritoryID] ,[BillToAddressID] ,[ShipToAddressID] ,[ShipMethodID] ,[CreditCardID] ,[CreditCardApprovalCode] ,[CurrencyRateID] ,[SubTotal] ,[TaxAmt] ,[Freight] ,[TotalDue] ,[Comment] ,[rowguid] ,[ModifiedDate]) SELECT [RevisionNumber] ,[OrderDate] ,[DueDate] ,[ShipDate] ,[Status] ,[OnlineOrderFlag] ,[SalesOrderNumber] ,[PurchaseOrderNumber] ,[AccountNumber] ,[CustomerID] ,[SalesPersonID] ,[TerritoryID] ,[BillToAddressID] ,[ShipToAddressID] ,[ShipMethodID] ,[CreditCardID] ,[CreditCardApprovalCode] ,[CurrencyRateID] ,[SubTotal] ,[TaxAmt] ,[Freight] ,[TotalDue] ,[Comment] ,[rowguid] ,[ModifiedDate] FROM [SalesOrderHeader_TEST] WHERE SalesOrderID = 75123 INSERT INTO [AdventureWorks2008].[dbo].[SalesOrderDetail_TEST] ([SalesOrderID] ,[CarrierTrackingNumber] ,[OrderQty] ,[ProductID] ,[SpecialOfferID] ,[UnitPrice] ,[UnitPriceDiscount] ,[LineTotal] ,[rowguid] ,[ModifiedDate]) SELECT 75123 + @i ,[CarrierTrackingNumber] ,[OrderQty] ,[ProductID] ,[SpecialOfferID] ,[UnitPrice] ,[UnitPriceDiscount] ,[LineTotal] ,[rowguid] ,GETDATE() FROM Sales.SalesOrderDetail SET @i = @i + 1 END GO