SQLServer 性能調優方法小結

數據庫性能優化的應用場景相當廣泛,但SQL語句與業務聯繫緊密,代碼層面的優化可能需要花費相當多的時間與精力。除了代碼層面,語句執行層面的優化、更佳的SQL語句使用執行計劃、運行在一個穩定高效的環境,同樣是高效也更符合運維的一種優化手段。下面我分享一些SQL Server在配置方面的性能優化思路,從CPU、內存、I/O、執行計劃等層面,內容包含了最大並行度、資源調控器、查詢提示幾個功能的介紹與配置方法。



一、最大並行度(MAXDOP)



1.介紹

最大並行度是指會話可以使用的最大線程數,對於大批量查詢,例如大表的掃描,使用多個線程同時掃描能成倍地提高效率;但是對於小型查詢,例如只修改小表裏一行的內容,則沒必要使用多個線程。



一般情況下,會話最終使用多少個線程是由查詢優化器決定的(可以通過option查詢子句進行干預)。查詢語句提交到SQL Server後會先進行解析,然後進行優化和簡化(例如子查詢轉爲對應連接、優先應用篩選條件),生成一系列執行計劃,最後根據統計信息計算開銷,選擇合適的執行計劃。最終預估的開銷決定了會話使用多少並行度。



在服務器配置選項中,我們能通過“最大並行度”、“並行的開銷閾值”兩個配置進行調整,最大並行度的默認值是0,即不限制並行度,最大能使用到與CPU核數相等的並行度。但是對於明顯有性能問題的系統,則需要考慮調整這個高級選項進行優化:



2.配置方法

  • 檢查/配置“最大並行度”設置



  • 檢查/配置“最大線程數”設置





3.注意事項

以下列舉了一些場景作爲參考:



  • OLTP系統

單純的OLTP系統由高併發的小事務組成,不適合使用太高的並行度,可以將最大並行度設置爲1,即不開啓並行查詢;如果調整後明顯感覺到執行時間太長,應用反應變慢,則可以逐步提高到2、4、8再進行觀察。(對於這類語句執行頻繁的小事務,執行計劃的選擇也是非常重要的優化方向,需要結合語句單獨分析)



  • OLAP系統

單純的OLAP系統由只讀長事務組成,事務執行時間都較長,例如報表統計、歷史數據導出。這類事務的特點是會連接大量表、讀取大量數據、進行大量計算,對於語句執行效率來說並行度越高越好。儘管官方文檔推薦8核以上的服務器也使用並行度8,但在沒達到CPU瓶頸的情況下可以儘可能提高OLAP系統的最大並行度,或者不限制最大並行度。



  • 混合系統

實際中更常見的是讀寫混合的系統,在承載應用寫操作的同時也承載一些小型報表的查詢,這類系統則需要進行反覆的調整以達到最佳的並行度設置:寫操作通常開銷較小,只會用1個並行度;普通的檢索開銷也一般不大,使用較低並行度;報表通常開銷較大,會使用較高並行度。



在CPU資源有限的情況下,配置最大並行度爲1可以保證最關鍵的寫操作能獲得足夠的資源;但如果讀操作需要使用並行來提高效率(maxdop=1時語句執行太慢),可以適當調到2並逐步增加;如果只需要提高那些執行時間很長的查詢,可以提高“並行的開銷閾值”,只讓高開銷的查詢使用並行。



  • “並行的開銷閾值”是一個相對值,沒有單位,默認是5,只能通過一步步測試調整來選用最佳的設置。

  • 最大連接數默認值爲0,但不是沒上限,而是根據CPU核數遞增,官方給出的計算公式爲Default Max Workers + ((logical CPUs - 4) * Workers per CPU)。





例如:一個64核的SQL2016最大線程數默認爲1472,默認最大並行度爲64,如果一個會話引發了阻塞,被阻塞的會話並行度都很高,那麼積累了幾十個會話之後線程數就滿了,這在繁忙的系統上可能只會花幾分鐘的時間。線程數滿了以後新的連接無法建立,應用開始報錯,直到阻塞源消失纔會恢復。



這種時候普通用戶無法連接數據庫,我們可以通過管理員專用通道(DAC)進行連接,在連接實例的名稱前加上admin:即可,例如admin:127.0.0.1,DAC連接只能同時存在1個。線程佔滿的根本原因還是阻塞源的處理,提高最大線程數只是一種無奈之舉。



注:日常運維不建議使用DAC連接,因爲DAC連接有更高的CPU優先級,服務器壓力較大時有可能會搶佔普通線程,引發阻塞。



  • 調整最大並行度的優點是快速,修改配置後無需停機即時生效,但無法進行顆粒度更細的資源分配,而下面的資源調控器則可以做到。



二、資源調控器(RESOURCE GOVERNOR)



1.介紹

這是一個SQL Server 2008開始的功能,可以通過登錄名(函數user_name())、當前時間(函數getdate())等會話屬性進行篩選,對會話使用的CPU、物理 I/O 和內存進行人爲限制,保證關鍵功能有充足的資源可用。



開啓資源調控器後(默認關閉),會話發出請求會先通過“分類器函數”進行分類,路由到相應的“工作負荷組”,每個工作負荷組都映射到一個“資源池”,再根據資源池中設置的CPU、I/O、內存閾值來決定會話的資源分配。



  • 資源池

可以看作是一個虛擬的SQL Server實例,默認有兩個資源池(內部資源池和默認資源池),支持用戶自行創建;

注:外部資源池定義的是外部進程的資源,如R 服務的rterm.exe、BxlServer.exe,與本次討論的內部資源無關。



  • 工作負荷組

相當於具有分類標準的會話容器,我們可以根據工作負荷組對會話進行聚合監控。每個工作負荷組都只處於一個資源池中,默認有兩個工作負荷組(內部工作負荷組和默認工作負荷組),支持用戶自行創建;



  • 分類

對傳入會話進行分類,分配到工作負荷組。

注:資源調控器不向專用管理員連接 (DAC) 施加任何控制。無需對在內部工作負荷組和資源池中運行的 DAC 查詢進行分類。



2.創建與配置資源調控器

  • 通過圖形界面創建較爲直觀,如需指定I/O相關的限制,則必須腳本創建,可以在圖形界面生成腳本再進行修改。







這裏新建了一個資源池vip_pool、工作負荷組vip_group,最小CPU預留了5%,最大不超過20%,內存無限制,資源池中還創建了工作負荷組vip_group;(注意下方腳本指定了cap_cpu_percent=20,就是說即使系統空閒也不會使用超過20%的CPU)




USE [master]
GO

CREATE RESOURCE POOL [vip_pool] WITH(min_cpu_percent=5, 
    max_cpu_percent=20, 
    min_memory_percent=0, 
    max_memory_percent=100, 
    cap_cpu_percent=20, 
    AFFINITY SCHEDULER = AUTO
, 
    min_iops_per_volume=0, 
    max_iops_per_volume=0)
GO

USE [master]
GO

CREATE WORKLOAD GROUP [vip_group] WITH(group_max_requests=0, 
    importance=Medium, 
    request_max_cpu_time_sec=0, 
    request_max_memory_grant_percent=25, 
    request_memory_grant_timeout_sec=0, 
    max_dop=0) USING [vip_pool], EXTERNAL [default]
GO

  • 新建分類器函數(此處指定了登錄名vip的會話,將路由到工作負荷組vip_group,其餘會話都將在默認的default組)



CREATE FUNCTION [dbo].[rgClassifier]() 
RETURNS sysname 
WITH SCHEMABINDING
AS
BEGIN
     DECLARE @grp_name AS sysname;
     SET @grp_name = 'default';
     IF (USER_NAME()='vip')
   begin
          SET @grp_name = 'vip_group'
      RETURN @grp_name
   end
     RETURN @grp_name;
END
GO
-- Set the classifier function for Resource Governor
ALTER RESOURCE GOVERNOR 
WITH ( 
  CLASSIFIER_FUNCTION = [dbo].[rgClassifier]
)
GO
ALTER RESOURCE GOVERNOR RECONFIGURE

  • 下面是效果演示,這裏使用了一個1億行表與100萬行表連接,手動指定cpu消耗較高的hash join,對比普通登錄名與vip登錄名的執行時長(圖1),並通過性能計數器查看對應的CPU使用情況(圖2,紅線default組,綠線vip_group組):

 







3.注意事項

  • 在所有資源池中,CPU、內存的最小值相加不能超過100,最大值的設置在系統空閒的時候是可以超出的,但是當其他資源池設置了最小值時,則一定會預留出來。如果需要限制資源池不利用空閒資源,要指定cap_cpu_percent;



  • 資源池中I/O的值不能用圖形界面設置,在腳本中配置的值爲min_iops_per_volume、max_iops_per_volume,注意這裏的單位不是%,而是IOPS,設置爲0代表不限制。存儲設備差異較大,難以用數值衡量百分比,如SSD的4K隨機IOPS能達到10萬級別,但機械磁盤通常只有100級別。如果需要對I/O進行設置,請先做好充分測試;



  • 啓/停/修改資源調控器不需要重啓服務器,但用腳本設置後記得運行ALTER RESOURCE GOVERNOR RECONFIGURE使配置生效;





三、查詢提示(Query Hints)



查詢提示可以對當前語句的執行計劃進行干涉,但在通常情況下,查詢優化器選擇的執行計劃已經足夠高效,只推薦利用查詢提示進行性能分析,或者用在一些特殊的語句上。



1.表提示(WITH子句)

這裏只介紹一些常用項,詳細使用方法參考官方文檔:https://docs.microsoft.com/zh-cn/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver15



  • NOLOCK

查詢不加表S鎖,可能造成髒讀,不強調一致性的報表類語句可以使用,防止讀表時阻塞寫操作,很常用。但對於AlwaysOn輔助副本,即使加了WITH(NOLOCK)也會在庫級別添加SCH-S鎖防止數據庫被修改,這個鎖會阻塞redo線程,引發主從延遲,最根本的解決方法還是優化語句,避免單個語句長時間執行。



同類的提示還有HOLDLOCK、PAGLOCK、ROWLOCK、TABLOCK、UPDLOCK、XLOCK等,適用於各種需要保證結果一致性的地方。



  • INDEX(<index_name>)

強制使用特定的索引,不推薦用,如果索引被刪除則語句會執行失敗,查詢優化器沒走最佳索引通常是另有原因,例如統計信息偏差太大、計算開銷的誤差太大。



同類的提示還有FORCESEEK、FORCESCAN,不推薦用在生產,理由同上。

WITH子句用法是加在表名後,例如:

select * from msdb.dbo.sysjobs as a with(nolock)



2.OPTION子句

這裏只介紹一些常用項,詳細使用方法參考官方文檔:https://docs.microsoft.com/zh-cn/sql/t-sql/queries/hints-transact-sql-query?view=sql-server-ver15 



  • MAXDOP

指定當前語句的最大並行度。

第一節提到可以在整個實例層面配置最大並行度,對於單個查詢則可以使用查詢提示OPTION(MAXDOP 1)來覆蓋全局設置。



  • QUERYTRACEON

僅對當前查詢開啓追蹤標誌,會覆蓋全局設置。

例如OPTION(QUERYTRACEON 8649)可以將並行開銷閾值降爲0,即強制使用並行計劃,更多追蹤標誌參考官方文檔:https://docs.microsoft.com/zh-cn/sql/t-sql/database-console-commands/dbcc-traceon-trace-flags-transact-sql?view=sql-server-ver15



  • RECOMPILE

強制重新生成執行計劃。

生成執行計劃會帶來額外的消耗,對於大型的查詢語句,帶來的收益可能遠大於重新生成執行計劃的開銷;但對於執行頻繁的小查詢,還有其他查詢提示可以干預執行計劃的生成,不推薦使用。



  • FAST <integer_value>

快速返回前N行,然後查詢會繼續執行直至生成完整的結果。



  • MAXRECURSION <integer_value>

指定當前查詢的最大遞歸數,覆蓋全局設置,防止進入無限循環。



  • OPTIMIZE FOR

針對特定參數生成執行計劃,需要詳細統計業務訪問的構成,一般不建議干涉執行計劃。



當緩存中存在有效的執行計劃時,語句會直接沿用現有的執行計劃來避免生成執行計劃的性能消耗,但對於參數化的語句,例如存儲過程內的語句,每次的參數可能不一樣,但執行計劃會使用同一個,而這個執行計劃是根據第一次執行的時候傳入的參數選擇的,未必對於其他參數也是最優解。



例如下方語句,則參數@city_name使用值'Seattle'而非初始值,參數@postal_code使用統計數據而非初始值




CREATE PROCEDURE dbo.RetrievePersonAddress
@city_name NVARCHAR(30), 
 @postal_code NVARCHAR(15)
AS
SELECT * FROM Person.Address 
WHERE City = @city_name AND PostalCode = @postal_code 
OPTION ( OPTIMIZE FOR (@city_name = 'Seattle', @postal_code UNKNOWN) );
 

注:存儲過程sp_create_plan_guide也可以達到類似的效果,具體用法參考官方文檔:https://docs.microsoft.com/zh-cn/sql/relational-databases/system-stored-procedures/sp-create-plan-guide-transact-sql?view=sql-server-ver15



  • MERGE JOIN

強制語句使用MERGE JOIN,僅適用性能優化排查,或者明確使用場景的語句。

同類型的還有:

{ HASH | ORDER } GROUP

{ CONCAT | HASH | MERGE } UNION

{ LOOP | MERGE | HASH } JOIN

FORCE ORDER



  • 表提示也可以寫在OPTION子句中,官方文檔中有例子。

OPTION子句用法是加在整個查詢語句後,例如:



select * from msdb.dbo.sysjobs as a with(nolock) join msdb.dbo.sysjobhistory as b with(nolock) on a.job_id = b.job_id where a.[name] = 'syspolicy_purge_history'
option(maxdop 1)



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章