SQL Server 2005效能排錯(轉載)

bq_wang發表於2010-07-22
SQL Server 2005效能排錯
SQL Server 技術文章

作者: Sunil Agarwal, Boris Baryshnikov, Tom Davidson, Keith Elmore, Denzil Ribeiro, Juergen Thomas

釋出於: 2005年10 月
適用於: SQL Server 2005

摘要:很少會有偶然的效能下降。設計不良的資料庫或工作負載配置不正確的系統會經常導致效能問題。管理員需要能預先阻止或最小化問題的影響,當管理員遇到問題時,應該診斷問題並採取正確操作來修復問題。本文提供了按部就班的指導,透過使用可用的工具例如SQL Server Profiler,System Monitor和在SQL Server 2005中新的Dynamic Management View來為一般的效能問題診斷和排錯。
 
版權宣告

本文件中的資訊(包括 URL 及其他 Internet 網站參考資料)可能隨時變更,恕不另行通知。使用本文件的全部風險或因此導致的後果均由使用者自行承擔。
本文件僅供參考,Microsoft 對本文件中的資訊不提供任何明示或暗示的保證。
使用者必須遵守所有適用的著作權法。在不限制著作權法所保障的權利下,未經 Microsoft Corporation 書面許可,不得將本文件的任何部分複製、儲存或引入檢索系統,或以任何形式、手段 (電子、機械、影印、錄音等等) 或基於任何目,轉發本文任何部分。
Microsoft 可能擁有本文件主體的涉及的專利、專利使用、商標、著作權或其他智慧財產權。除非在Microsoft書面許可協議中明確提到,否則本文件並不向您提供其中的任何專利、商標、版權或其他智慧財產權。
除非註解,否則這裡描述的樣例公司、企業、產品、域名稱、電子郵件地址、徽標、人員、地點和事件純屬虛構,不要有意或推斷,將其與真實的公司、企業、產品、域名稱、電子郵件地址、徽標、人員、地點或事件相聯絡。
 2005 Microsoft Corporation.  保留所有權利.
Microsoft, Excel, SharePoint, Visual Basic, Visual C++, Visual C#, Visual J#, Visual Studio, 和 Windows 是微軟公司在美國和/或其他國家(地區)的註冊商標或商標。
此處提到的實際公司名稱和產品名稱可能使其所有者的商
目錄
簡介    1
目標    1
方法    1
資源瓶頸    2
解決資源瓶頸的工具    2
CPU 瓶頸    2
過多的編譯和重編譯    3
檢測    3
解決    6
效率低的查詢計劃    6
檢測    7
解決    7
內部查詢的並行    8
檢測    8
解決    10
拙劣遊標使用    11
檢測    11
解決    12
記憶體瓶頸    12
背景    12
虛擬地址空間和實體記憶體    12
Windows地址擴充套件和SQL Server    12
記憶體壓力    13
檢測記憶體壓力    14
外部實體記憶體壓力    14
外部虛擬記憶體壓力    15
內部實體記憶體壓力    15
快取記憶體和記憶體壓力    21
Ring buffers    22
內部虛擬記憶體壓力    25
一般的記憶體錯誤排錯步驟    26
記憶體錯誤    26
I/O 瓶頸    29
解決    31
Tempdb    34
監視tempdb空間    35
磁碟空間問題排錯    35
使用者物件    35
版本儲存    36
內部物件    37
過多的DLL和分配操作    39
解決    41
執行緩慢的查詢    41
阻塞    41
識別長時間的阻塞    43
透過sys.dm_db_index_operational_stats檢視阻塞的每個物件    45
使用SQL waits阻塞對整體效能的影響    48
監視索引的使用    50
總結    52
附錄A: DBCC MEMORYSTATUS 描述    52
附錄B: 阻塞指令碼    52
分析操作的索引統計    54
等待狀態    72


 
簡介
很多客戶偶爾會遇到SQL Server 資料庫效能下降。原因可能涉及從不良好的資料庫設計到不正確的負載配置。作為一個管理員,你應該預先阻止或最小化問題,並當問題發生時,診斷原因並儘可能的做出正確的操作來解決問題。這片白皮書所述的問題通常來源於Microsoft® Corporation 的Customer Support Service(CSS or PSS)部門所遇到的,因為將所有可能的問題都詳盡的分析是不合實際的。我們提供了按部就班的指導,透過使用可用的工具例如SQL Server Profiler,System Monitor和在SQL Server 2005中新的Dynamic Management View來為一般的效能問題診斷和排錯。

目標
.這篇文章的主要目標是提供一套常規的方法透過使用公開的工具在一般的客戶場景中診斷和排錯SQL Server效能問題。
SQL Server 2005在使用者支援上有了很大的提高。核心層(SQL-OS)被重新架構過,內部結構和統計資料透過動態管理檢視(DMVs)暴露為關係型行集。SQL Server 2000透過像sysprocesses這樣的系統表暴露一些資訊,但是有時你需要將SQL Server程式記憶體對映為物理檔案並從中抽取內部結構的相關資訊。這裡有2個主要的問題。第一,客戶不能總是提供物理對映檔案,因為檔案的尺寸原因,並且這個過程很耗時。第二,這將花費更長的時間診斷問題,因為檔案必須傳回Microsoft Corporation來分析。
這帶給我們本文的第二個目標,展示DMVs的優點。DMVs透過除去大多數情況下需要的生成和分析物理對映步驟可以加速除錯的過程。本文儘可能的提供了和SQL Server 2000中同樣問題的比較。DMVs提供為獲取關鍵系統資訊的簡單而熟悉的介面。這些資訊也可以用於監視目的,警告管理員潛在的問題。或者也可以被週期性的收集為以後的分析所用。
方法
這裡有很多降低SQL Server速度的原因。我們使用下列3個主要症狀來診斷問題。
•    資源瓶頸: CPU,記憶體,和I/O瓶頸是在本文中主要涉及的。這裡我們不考慮網路因素。對每種資源瓶頸,我們會描述如何識別問題並闡述可能的原因。例如,記憶體瓶頸可以導致過多的分頁,最後影響效能。
•    Tempdb 瓶頸:因為每個SQL Server 例項只有一個tempdb,這可以產生效能和磁碟空間的瓶頸。不好的應用程式在過多的DDL和DML操作會使tempdb過載。這導致其他在這臺伺服器上執行的不相關的應用程式執行緩慢或失敗。
•    緩慢執行的使用者查詢:現有的查詢效能下降或新的查詢顯示比預期時間更長。這可能有很多原因。例如:
•    改變統計資訊可以導致現有查詢的較差的查詢計劃。
•    製表掃描,降低查詢效能。
•    即使資源利用正常由於阻塞也可以導致應用程式執行緩慢。
過多的阻塞可能是由於不良的應用程式設計或架構設計或者是選擇了錯誤的事務隔離級別的原因。
這些症狀的原因不需要每個都獨立出來。不良的查詢計劃選擇可以使系統資源加重並導致整體效能的下降。所以,如果大表缺失的有用的索引,或查詢最佳化器沒有選擇它,這樣不僅導致查詢緩慢,也會導致將不需要資料頁讀取到記憶體(buffer pool)中在快取中儲存,這樣會加重I/O子系統的壓力。同樣的,頻繁執行查詢的重編譯可以導致CPU的壓力。
資源瓶頸    
接下來的部分討論了CPU,記憶體和I/O子系統資源及這些瓶頸如何產生。(網路問題不在本文的討論範圍)每個資源瓶頸,我們描述瞭如何識別問題並找到原因。例如,一個記憶體瓶頸可以導致過渡分頁,將會影響效能。
在決定你遇到資源瓶頸前,你需要了解在普通環境下資源是如何使用的。你可以使用在這片白皮書中所列出的方法收集有關資源使用的基線資訊(即使你沒有遇到效能問題)。
你也許找到問題是一個資源執行到設計容量並且SQL Server當前的配置不能支援這種負載。為了解決這問題,你也許需要新增處理能力,記憶體或增加I/O的頻寬或網路通道。但是,在你進行下一步之前,理解這種資源瓶頸的通常原因是非常重要的。有一些解決方案不需要新增額外資源,例如重新配置。
解決資源瓶頸的工具
下列工具是在解決特殊效能瓶頸時經常使用的工具。
•    系統監視器(PerfMon):這是Windows所帶的一個工具。更多資訊,請見系統監視器文件。
•    SQL Server Profiler:在SQL Server 2005程式組中的Performance Tools組中可以看到SQL Server Profiler。
•    DBCC 命令:詳細資訊請見SQL Server聯機叢書中和附錄A。
•    DMVs: 詳細資訊請見SQL Server聯機叢書。
CPU 瓶頸
當沒有額外負載,突然發生的CPU瓶頸通常由於沒有查詢計劃,不良的配置或設計因素和不足的硬體資源所引起。在購買更快或更多處理器前,你需要首先確定CPU的最大處理能力頻寬並檢視是否他們都在使用中。
系統監視器一般是確定CPU資源的最好工具。你應該檢視Processor:% Processor Time計數器是否偏高;該計數器值超過80%一般被認為是瓶頸。你也可以使用sys.dm_os_schedulers檢視來監視是否正在執行的任務不是0。非0的值預示有任務需要等待時間切片來執行;這個數值高表明一個CPU瓶頸的徵兆。你可以用下列查詢列出所有的排程器併產看等待執行的任務數量。
select
    scheduler_id,
    current_tasks_count,
    runnable_tasks_count
from
    sys.dm_os_schedulers
where
    scheduler_id < 255
下列查詢給你一個檢視當前快取中最耗費CPU的批或過程高階別檢視。查詢根據具有相同plan handle(意味著屬於同一個批或過程)的語句聚合CPU的呼叫。如果給出的plan handle對應多個語句,你將不得不繼續找到在整個CPU使用中最佔用資源的查詢。
select top 50
    sum(qs.total_worker_time) as total_cpu_time,
    sum(qs.execution_count) as total_execution_count,
    count(*) as  number_of_statements,
    qs.plan_handle
from
    sys.dm_exec_query_stats qs
group by qs.plan_handle
order by sum(qs.total_worker_time) desc
本節剩下的部分將討論一些其他的在SQL Server 中CPU敏感的操作,也有有效的檢查和解決這些問題的方法。
過多的編譯和重編譯
當批或遠端過程呼叫(RPC)被提交到SQL Server,在開始執行前,伺服器檢查查詢計劃的有效性和正確性。如果這些檢查中的一個失敗時,批將被再次編譯聲稱不同的查詢計劃。這種編譯就是所說的重編譯。當伺服器確認當底層資料改變時有更最佳化的查詢計劃時,重編譯確認正確性再完成編譯。編譯是CPU敏感的,因此過多的重編譯將導致在系統中的CPU效能問題。
在SQL Server 2000中,當SQL Server重編譯一個儲存過程,整個儲存過程都被重編譯,不僅是觸發重編譯的語句。SQL Server 2005引入了語句級的儲存過程的重編譯。當SQL Server 2005重編譯儲存過程,只有導致重編譯的語句被編譯-而不是整個儲存過程。這減少了CPU的負載並減少了對例如COMPILE鎖的資源爭用。重編譯可以有多種原因出發,例如:
•    架構變化
•    統計變化
•    延期編譯
•    Set屬性改變
•    臨時表改變
•    儲存過程建立時使用了RECOMPLIE查詢提示或使用了OPTION (RECOMPILE)。
檢測
你可以使用系統監視器(PerfMon)或SQL Trace(SQL Server Profiler)來檢測過多的編譯和重編譯。
系統監視器(Perfmon)
SQL Statistics物件提供了監視重編譯的計數器和傳送到SQL Server例項的請求型別。你必須監視查詢編譯和重編譯的數量以及相關聯的接受的批的數量來找出是否這個編譯是最耗費CPU資源。理想環境下,SQL Recompilations/sec和Batch Request/sec的比值應該很低,除非使用者提交了大量的單獨查詢。
下列顯示了關鍵的資料計數器。
•    SQL Server: SQL Statistics: Batch Requests/sec
•    SQL Server: SQL Statistics: SQL Compilations/sec
•    SQL Server: SQL Statistics: SQL Recompilations/sec
更多資訊請看SQL Server聯機叢書中的 “SQL Statistics Object”
SQL 跟蹤
如果PerfMon計數器顯出了很高的重編譯數量,編譯將在SQL Server中佔用很多的CPU資源。我們將需要檢視Profiler 跟蹤並從中找到找到被重編譯的儲存過程。SQL Server Profiler跟蹤給出我們重編譯原因的資訊。你可以使用下列事件。
SP:Recompile和SQL:StmtRecompile事件類指出了哪個儲存過程和語句被重編譯。當你編譯一個儲存過程,一個事件為這個儲存過程生成,其中每條語句將被編譯。然而,當儲存過程重編譯時,只有導致重編譯的語句被重編譯(在SQL Server 2000中將是整個儲存過程)。下面列出了SP:Recompile事件類更多重要的資料列。特別是EventSubClass資料列決定重編譯的原因。SP:Recompile當儲存過程或觸發器被重編譯被觸發一次,但不會被獨立查詢引發。在SQL Server 2005中,監視SQL:StmtRecompiles也非常有用,該事件類在所有型別的重編譯中都會被觸發,包括批,獨立查詢,儲存過程和觸發器。如下是我們關係的事件中關鍵的資料列:
•    EventClass
•    EventSubClass
•    ObjectID (represents stored procedure that contains this statement)
•    SPID
•    StartTime
•    SqlHandle
•    TextData
更多資訊,請見SQL Server 聯機叢書中“SQL:StmtRecompile Event Class”。
如果你有儲存的跟蹤檔案,你可以使用下列查詢檢視所有捕捉的重編譯事件。
select
    spid,
    StartTime,
    Textdata,
    EventSubclass,
    ObjectID,
    DatabaseID,
    SQLHandle
from
    fn_trace_gettable ( 'e:\recompiletrace.trc' , 1)
where
    EventClass in(37,75,166)
事件類37是 Sp:Recompile, 75 是 CursorRecompile, 166是SQL:StmtRecompile
你可以透過SqlHandle和ObjectID列或其他列將這個查詢的結果分組,也可以檢視是否最多的重編譯型別是儲存過程或其他原因(例如SET選項改變等)。
Showplan XML For Query Compile.
Showplan XML For Query Compile事件發生於Microsoft SQL Server編譯或重編譯一段SQL語句時。該事件有關於語句編譯或重編譯的資訊。資訊包括查詢計劃和過程的物件ID。捕獲這些事件是有效能開銷的,因為它捕獲了每次編譯或重編譯。如果你在系統監視其中看到很高的SQL Compilations/sec計數器值,你應該監視這個事件。透過這些資訊,你可以看到那條語句被頻繁的重編譯。你可以使用這些資訊改變這些語句的引數。這將影響重編譯的數量。
DMVs.
當你使用sys.dm_exec_query_optimizer_info DMV,你可以得到SQL Server花費在最佳化的時間。如果獲取了這個DMV的2個快照,你可以得到在給定的時間段內花費在查詢最佳化的時間。
select *
from sys.dm_exec_query_optimizer_info

counter          occurrence           value                
---------------- -------------------- ---------------------
optimizations    81                   1.0
elapsed time     81                   6.4547820702944486E-2

特別是檢視elapsed time,該時間由於最佳化而產生。因為最佳化過程的時間基本上就是使用者最佳化操作的CPU時間(因為最佳化處理是CPU時間的主要部分),你可以得到一個好的度量,找到那段編譯時間佔用了大量的CPU時間。
其他包含有用資訊的DMV有:
sys.dm_exec_query_stats.
你希望檢視的資料列有 :
•    Sql_handle
•    Total worker time
•    Plan generation number
•    Statement Start Offset
更多資訊請檢視SQL Server 聯機叢書相關主題
sys.dm_exec_query_stats.
特別是plan_generation_num預示了查詢編譯時的次數。下面是示例給你展示了被重編譯次數最多的25個儲存過程。
select *
from sys.dm_exec_query_optimizer_info

select top 25
    sql_text.text,
    sql_handle,
    plan_generation_num,
    execution_count,
    dbid,
    objectid
from
    sys.dm_exec_query_stats a
    cross apply sys.dm_exec_sql_text(sql_handle) as sql_text
where
    plan_generation_num >1
order by plan_generation_num desc
更多資訊請見Batch Compilation, Recompilation, and Plan Caching Issues in SQL Server 2005
解決
如果你監測到過多的編譯/重編譯,考慮下列選項。
•    如果重編譯因為SET選項改變而發生,使用SQL Server Profiler確定哪個SET選項被改編。避免在儲存過程中改變SET選項。如果改變最好在連線級別設定。確認在該連線的生存週期內不要改變SET選項。
在臨時表上重編譯的閥值比在普通表上的低。在臨時表上的重編譯時由於統計改變而引起,你可以降臨時表改為使用表變數。表變數的改變不會引起重編譯。這種方法的確定是查詢最佳化器不識別表變數,因為統計不會被建立或維護表變數。這將導致沒有查詢計劃。你可以測試不同的選項,並選擇最好的方法。另外一個選項時使用KEEP PLAN查詢提示。設定臨時表的這個閥值與使用永久表相同。EventSubclass 列預示了在臨時表上的”Statistics Changed”的操作。
•    為避免由於改變統計而產生的重編譯(例如,因為資料統計導致計劃不理想),特別是KEEPFIXED PLAN查詢提示。根據設定的影響,重編譯可以僅因為相關正確的原因(例如,當底層表結構改變導致計劃不再適用),而不根據統計的變化。如果語句引用的表的架構改變時或者表是被標記為sp_recompile的儲存過程,重編譯將發生。
•    關閉自動更新索引統計和表或檢視的統計,防止由於物件的統計改變而產生的重編譯。注意,透過使用這種方法關閉”auto-stats”特性不是一個好的想法。這是因為查詢最佳化器不再為在這些物件上的資料改變而敏感,將導致不良的查詢計劃。使用這種方法僅在嘗試了所有其他選擇之後,做為最後的手段。
•    批應該儘量使用物件全名(例如,dbo.Table1)避免重編譯並避免不明確的物件。
•    為避免由於延期編譯導致的重編譯,不要混雜DML和DDL或從條件結構建立DDL,例如IF語句。
•    執行Database Engine Tuning Advisor(DTA)檢視改變索引是否可以改善編譯時間和查詢的執行時間。
•    檢查是否儲存過程透過WITH RECOMPILE選項建立或使用了RECOMPILE查詢提示。如果過程透過WITH RECOMPILE選項建立,在SQL Server 2005中,如果在過程中特殊的語句需要被重編譯,我們可以利用語句級的RECOMPILE提示。這將避免在每次執行的時候對整個過程重編譯,而同時允許個別語句被編譯。更多有關RECOMPILE提示的資訊,請檢視SQL Server聯機叢書。
效率低的查詢計劃
當為一個查詢生成查詢計劃時,SQL Server查詢最佳化器嘗試選擇一個計劃為查詢提供最快的響應時間。注意最快的查詢時間並不意味最小的I/O開銷,也不意味使用最少的CPU資源-它會在各種資源中平衡。
某些操作型別比其他操作對CPU更敏感。Hash操作和Sort操作掃描他們各自的輸入資料。使用掃描向前讀取(prefetch)時,在需要操作頁面前,頁面幾乎都在快取中。因此可以減少或消除物理I/O操作。這使這些操作的型別將不被物理I/O所限制。與之相比,巢狀迴圈連線有很多索引查詢,如果索引查詢使用很多不同的表以至於頁面不適合快取的大小,將導致生成I/O負載。
最有意義的輸入最佳化用於評估為每中操作生成不同查詢計劃開銷的評估,你可以在Showplan(EstimateRows和EstimateExecution屬性)中看到結果。沒有精確的評估,用於最佳化的主輸入是有缺陷的。
為獲取SQL Server最佳化器如何使用統計的詳細資訊,請檢視 Statistics Used by the Query Optimizer in Microsoft SQL Server 2005 。該白皮書討論了最佳化器如何使用統計,維護和更新統計的最佳實踐,以及一些常見的查詢設計問題。
 
檢測
低效率的查詢計劃通常可以被檢測出來。低效率的查詢計劃可以導致增加CPU的消耗。
查詢sys.dm_exec_query_stats是確定哪個查詢累計使用CPU時間最多的有效方法。
select
    highest_cpu_queries.plan_handle,
    highest_cpu_queries.total_worker_time,
    q.dbid,
    q.objectid,
    q.number,
    q.encrypted,
    q.[text]
from
    (select top 50
        qs.plan_handle,
        qs.total_worker_time
    from
        sys.dm_exec_query_stats qs
    order by qs.total_worker_time desc) as highest_cpu_queries
    cross apply sys.dm_exec_sql_text(plan_handle) as q
order by highest_cpu_queries.total_worker_time desc
還可以選擇,也可以查詢sys.dm_exec_cached_plans並透過使用過濾器查詢可疑的類似於‘%Hash Match%’, ‘%Sort%’這樣CPU敏感的各種操作。
解決
如果你監測到效率低的查詢計劃,考慮下列選項。
•    使用Database Engine Tuning Advisor調節查詢,檢視是否生成對修改索引的建議
•    檢查有問題的評估。
編寫的查詢中使用的更有限制性的WHERE從句是否合適?無限制條件的查詢是資源敏感的。
在查詢中涉及的表上執行UPDATE STATISTICS,檢查是否還有這種問題。
是否查詢使用的構造導致最佳化器不能精確的評估?
考慮是否可以將查詢修改為其他的方法,避免這種問題。
•    如果不能修改架構或查詢,SQL Server 2005有一個新的查詢計劃特性,允許你將指定查詢提示新增到滿足某種文字的查詢中。這可以用在獨立查詢中,也可以用在儲存過程內。例如OPTION(OPTIMIZE FOR)這樣的提示允許你影響評估而忘記所有列出的潛在計劃。其他的提示,類似OPTION(FORCE ORDER)或 OPITON(USE PLAN)允許你改變控制查詢計劃的程度。
內部查詢的並行
當為一個查詢生成執行計劃時,SQL Server最佳化器嘗試為該查詢選擇最快的相應計劃。如果查詢的開銷超過了在cost threshold for parallelism選項中指定的值,並行不會被禁用,最佳化器嘗試生成一個可以用於並行的計劃。並行查詢計劃使用多執行緒處理查詢,每個執行緒分佈在可用的CPU上並同時利用每個CPU的時間資源。最大的並行度可以透過伺服器上的max degree of parallelism選項或每個查詢使用OPTION(MAXDOP)提示限制。
用於執行實際並行度(DOP)的結果——度量有多少執行緒將在給定的操作上並行——是知道執行時才能確定。在執行查詢前,SQL Server 2005決定有多少個排程器未充分利用併為查詢選擇DOP來充分利用剩餘的排程器。一旦一個DOP被選擇了,直到完成,查詢將使用這個選擇的並行度來執行。並行查詢的使用時CPU有一些偏高,但是它在elapsed time上的時間很短。如果沒有其他瓶頸,類似於物理I/O等待,並行計劃將會使用所有處理器的100%資源。
查詢開始執行後,一個關鍵的因素(系統有多空閒)可以導致執行並行計劃的改變。例如,如果查詢執行在空閒時間,伺服器可以選擇使用並行計劃並使用DOP為4,在4個不同的處理器上產生執行緒。一旦這種執行緒開始執行,現存的連線可以提交其他需要大量CPU的查詢。在這種情況,所有不同的執行緒將共享可用的CPU的時間切片,導致更高的查詢持續時間。
透過並行計劃執行不是一定是不好的,並行可以為查詢提供最快的響應時間。然而,給定查詢的響應時間必須與整體的吞吐量和系統其他查詢的響應進行衡量。並行查詢一般最適合批處理和決策支援系統,而不適合一個事務處理環境。
檢測
內部查詢的並行問題可以透過下列方法檢測。
系統監視器(Perfmon)
考慮SQL Server:SQL Statistics – Batch Requests/sec 計數器,並檢視SQL Server聯機叢書中的“SQL Statistics Object”獲取更多資訊。
因為在考慮使用並行計劃前,查詢必須評估開銷超過為並行配置設定的開銷閥值(預設被設定為5),伺服器每秒處理的批小於執行在並行計劃中的批。執行很多並行查詢的伺服器一般配置為較小的每秒批請求數(例如,小於100的值)。
DMVs
在執行的伺服器上,你可以使用下列查詢確認在給定會話中是否可以並行執行任何活動的請求。
select
    r.session_id,
    r.request_id,
    max(isnull(exec_context_id, 0)) as number_of_workers,
    r.sql_handle,
    r.statement_start_offset,
    r.statement_end_offset,
    r.plan_handle
from
    sys.dm_exec_requests r
    join sys.dm_os_tasks t on r.session_id = t.session_id
    join sys.dm_exec_sessions s on r.session_id = s.session_id
where
    s.is_user_process = 0x1
group by
    r.session_id, r.request_id,
    r.sql_handle, r.plan_handle,
    r.statement_start_offset, r.statement_end_offset
having max(isnull(exec_context_id, 0)) > 0
透過這些資訊,查詢的文字可以透過使用sys.dm_exec_sql_text輕鬆獲取,而查詢計劃可以使用sys.dm_exec_cached_plan獲取。
你也可以搜尋符合執行在並行的計劃。這可以透過搜尋快取的計劃來檢視如果關係運算子有Parrallel屬性為非零的值。這些計劃也許可以不執行在平行中,但是他們如果系統不忙,他們也適合這樣做。
--
-- Find query plans that may run in parallel
--
select
    p.*,
    q.*,
    cp.plan_handle
from
    sys.dm_exec_cached_plans cp
    cross apply sys.dm_exec_query_plan(cp.plan_handle) p
    cross apply sys.dm_exec_sql_text(cp.plan_handle) as q
where
    cp.cacheobjtype = 'Compiled Plan' and
    p.query_plan.value('declare namespace p="";
        max(//p:RelOp/@Parallel)', 'float') > 0

一般來說,查詢的持續時間長於CPU時間總量,因為一些時間花費在等待資源上例如鎖或物理I/O。查詢使用CPU時間長於持續時間的唯一場景是當查詢執行在並行計劃例如多執行緒併發使用CPU。注意並不是所有並行查詢將證明這種行為(CPU時間大於持續時間)。
select
    qs.sql_handle,
    qs.statement_start_offset,
    qs.statement_end_offset,
    q.dbid,
    q.objectid,
    q.number,
    q.encrypted,
    q.text
from
    sys.dm_exec_query_stats qs
    cross apply sys.dm_exec_sql_text(qs.plan_handle) as q
where
    qs.total_worker_time > qs.total_elapsed_time
SQL Trace
Look for the following signs of parallel queries, which could be either statements or batches that have CPU time greater than the duration.

select
    EventClass,
    TextData
from
    ::fn_trace_gettable('c:\temp\high_cpu_trace.trc', default)
where
    EventClass in (10, 12)    -- RPC:Completed, SQL:BatchCompleted
    and CPU > Duration/1000    -- CPU is in milliseconds, Duration in microseconds
Or can be Showplans (un-encoded) that have Parallelism operators in them
select
    EventClass,
    TextData
from
    ::fn_trace_gettable('c:\temp\high_cpu_trace.trc', default)
where
    TextData LIKE '%Parallelism%'

解決
任何執行在並行計劃的查詢被查詢最佳化器認為是成本昂貴的,並會超過並行閥值,預設為5(粗略的是在涉及的機器上5秒執行一次)。任何透過上述方法確認的查詢都是以後要調節的候選者。
•    使用Database Engine Tuning Advisor檢視是否任何索引改變,改變索引檢視或分割槽改變能減少查詢的開銷
•    檢查實際值和評估集的重要不同因為評估集在評估查詢開銷中是重要因素。如果找到重要的不同:
如果auto create statistics資料庫設定被禁用,確認在Showplan輸出的Warnings列中沒有MISSING STATS項。
嘗試在關閉評估的表上執行UPDATE STATISTICS。
驗證查詢沒有使用最佳化器無法精確評估的查詢構造,例如多語句表值函式或CLR函式,表值或Transact-SQL變數比較(引數比較是可以的)。
•    評估是否可以使用不同的Transact-SQL語句或表示式將查詢寫的更有效率
拙劣遊標使用
SQL Server 2005之前的SQL Server 版本僅支援在每個連線上有單個活動的操作。一個查詢正在執行或有了結果等待傳送到客戶端時將被認為是活動的。在一些情形中,客戶端應用程式也許需要從結果中讀取並向SQL Server提交其他基於剛剛從結果集中讀取的行的查詢。這在預設的結果集中是不能實現的,因為還有其他等待的結果。一般的解決方法是改變連線屬性是用伺服器端遊標。
當使用伺服器端遊標,資料庫客戶端軟體(OLE DB提供者或ODBC驅動)顯然會封裝客戶端請求在特殊的擴充套件儲存過程中,例如sp_cursoropen,sp_cursorfetch等等。這提到了API遊標(而不是TSQL遊標)。當使用者執行查詢,查詢文字透過sp_cursoropen被髮送到伺服器,請求讀取從sp_cursorfetch指示伺服器進傳送某些數量的行。透過控制獲取行的數量,可以為ODBC驅動或OLE DB提供者快取行。這阻止發生伺服器等待客戶端都區所有傳送的行的情形。因此,伺服器可以在這個連線上接受新的請求。
一次性開啟遊標並獲取1行(或少量行)的應用程式能被網路延時的網路瓶頸影響,特別是在廣域網(WAN)。在有快速網路並有不同使用者連線時,處理很多遊標請求的開銷變得更重要。因為開銷來自於遊標位置的變化來適應在結果集上的位置改變,預請求的處理開銷,類似的處理,伺服器處理1個請求返回100行必處理100不同請求相同的100行但是每次1行更有效率。
檢測
你可以使用下列方法為拙劣遊標使用排錯。
系統監視器(Perfmon)
透過考慮SQL Server:Cursor Manager By Type – Cursor Requests/Sec計數器,你可以透過這個效能計數器知道有多少遊標在系統中使用。系統還有很高的CPU利用率,因為小量的讀取通常會有每秒數百個遊標請求。這裡沒有特殊的計數器告訴你關於獲取的快取大小。
DMVs
接下來的查詢可以用於測定使用API遊標(不是TSQL遊標)連線獲取一行使用的快取大小。它對於大的獲取快取更有效,例如100行。
select
    cur.*
from
    sys.dm_exec_connections con
    cross apply sys.dm_exec_cursors(con.session_id) as cur
where
    cur.fetch_buffer_size = 1
    and cur.properties LIKE 'API%'    -- API cursor (TSQL cursors always have fetch buffer of 1)

SQL 跟蹤
使用包括RPC:Completed事件類的跟蹤用於搜尋sp_cursorfetch語句。第4個引數的值是透過獲取返回的行數。請求返回的最大行數是被指定為與RPC:Starting事件類關聯的引數。
解決
•    確定遊標是完成操作的最佳方法或是否基於集合這種更有效的操作是可行的。
•    當連線到SQL Server 2005,考慮使用多活動結果集(MARS)
•    參考你使用的API文件決定如何指定遊標的獲取快取大小:
ODBC - SQL_ATTR_ROW_ARRAY_SIZE
OLE DB – IRowset::GetNextRows or IRowsetLocate::GetRowsAt
記憶體瓶頸
這部分給出了低快取的條件和對不同記憶體錯誤診斷方法,可能的原因和排錯方法。
背景
引用不同的記憶體資源透過使用簡單的術語記憶體。但是卻有一些記憶體資源型別,對於理解和區分特殊的記憶體資源這是很重要的。
虛擬地址空間和實體記憶體
在Microsoft Windows®,每個程式都有自己的虛擬地址空間(VAS)。程式可用所有虛擬地址有VAS的大小決定。VAS的大小依賴於架構(32位或64位)和作業系統。在排錯的上下文中,理解虛擬地址空間使用記憶體資源,瞭解應用程式可以超出VAS即使在64位平臺只要實體地址可能一直可用,這些很重要。
更多有關於虛擬地址空間,請檢視SQL Server聯機叢書中“Process Address Space”和MSDN中的Virtual Address Space。
Windows地址擴充套件和SQL Server
Windows地址擴充套件(AWE)是允許32位應用程式跨越32位地址限制操作記憶體的API。AWE機制技術上不需要64位平臺。然而它出現了。記憶體頁透過AWE機制涉及在64位平臺上的鎖定頁。
在32位和64位平臺上,記憶體透過AWE機制分配不能分頁出界。這可以有益於應用程式(這是在64位平臺上使用AWE機制的原因)。這也影響了系統和其他應用程式可用RAM總數,這可能是有害的影響。因為這個原因為了使用AWE,Lock Pages in Memory權利必須分配該執行SQL Server的賬號。
從排錯的角度來看,要點是SQL Server快取池使用AWE對映記憶體;然而,只有資料庫(hash過的)頁面可以利用AWE分配記憶體。透過AWE機制記憶體分配將不會在工作管理員或在Process: Private Bytes效能計數器中看到。你需要使用SQL Server特殊的計數器或動態管理檢視來獲取這些資訊。
更多關於AWE對映記憶體的資訊,請在SQL Server聯機叢書中檢視“Managing memory for large databases” 和 “Memory Architecture”以及MSDN中的Large Memory Support
下列表彙總了不同SQL Server 2005支援的最大記憶體(注意特殊的SQL Server版本或Windows可以有不同支援記憶體的限制)




表1
配置    VAS    最大實體記憶體    AWE/locked pages 支援
Native 32-bit on 32-bit OS
with /3GB boot parameter     2 GB
3 GB    64 GB
16 GB    支援
支援
32-bit on x64 OS (WOW)    4 GB    64 GB    支援
32-bit on IA64 OS (WOW)    2 GB    2 GB    不支援
Native 64-bit on x64 OS    8 terabyte    1 terabyte    支援
Native 64-bit on IA64 OS    7 terabyte    1 terabyte    不支援

記憶體壓力
記憶體壓力錶示當可用記憶體數量受到限制。識別SQL Server何時執行在記憶體壓力下將幫助你排除記憶體相關的問題。SQL Server依賴於不同型別的記憶體壓力特徵也不一樣。下表彙總了記憶體壓力型別,和他們潛在的原因。在所有的情況下,你可以更多的會見到超時或顯示的記憶體不足錯誤訊息。

表2
壓力    外部    內部
物理    實體記憶體(RAM)執行值低。這導致系統整理當前執行的工具集,導致整體效能下降。

SQL Server監測到這種條件,依賴於配置,可以減少快取池的目的提交併開始清理內部快取。    SQL Server檢測內部較高的記憶體消耗,導致在不同內部元件間的記憶體重新分配。

內部記憶體壓力可以導致:
•    導致外部記憶體壓力(SQL Server設定地的記憶體使用能力)。
•    改變記憶體設定(例如‘max server memory’)。
•    改變內部元件的記憶體分佈(導致預留的高百分比並從快取池中獲取頁)。
虛擬的    在系統頁面檔案執行在較低值。這樣可以導致系統分配記憶體失敗。不能擴充套件當前的記憶體分配。這可以導致著整個系統響應很慢或者可能導致系統關機。    在VAS執行值低,導致分頁(很多VAS可用,但是被分為小塊)與/或消耗(直接分配,DLL載入到SQL VAS,大量的執行緒)。

SQL Server檢測到這種條件並可以釋放VAS中保留的區域,減少快取池提交的目標並開始收縮快取。
Windows有通知的機制  如果實體記憶體執行在過高或過低的情況下。SQL Server在他的記憶體管理決策中使用這種機制。
一般排錯的步驟顯示在表3中。

表 3
壓力    內部    外部
物理    •    找到主要的系統記憶體消耗元件。
•    嘗試消除消耗(如果可能)。
•    檢查適當的系統RAM和考慮新增額外RAM(通常需要更仔細研究)    •    識別SQL Server內主要的記憶體消耗
•    確認系統配置。
•    進一步操作依賴於研究;檢查負載;可能出現的設計問題;其他的資源瓶頸。
虛擬    •    增加交換檔案大小。
•    檢查主要實體記憶體的使用和外部實體記憶體壓力呼叫步驟。    •    外部實體記憶體壓力呼叫步驟。

工具
下列工具和資源可以用於排錯。
•    記憶體相關的DMVs
•    DBCC MEMORYSTATUS 命令
•    效能計數器: 效能監視器或SQL Server指定物件的DMV
•    工作管理員
•    事件檢視器: 應用程式日誌和系統日誌
檢測記憶體壓力
記憶體壓力自身不會預示問題。記憶體壓力是需要的,但時不是為伺服器以後遇到記憶體錯誤的充分條件。在記憶體壓力下工作將被任務是伺服器的正常操作。然而記憶體壓力的徵兆可以於是伺服器執行已經接近設計容量並且潛在存在記憶體不足的錯誤。在正常執行情況下,這些資訊將作為基線決定以後記憶體不足的原因。
外部實體記憶體壓力
開啟人物管理器的效能檢視,檢查Physical Memory節的Available項的值。如果可用記憶體總數很低,這表現了有外部記憶體壓力。這個準確值依賴於很多因素,然而你可以在當這個值降低到50-100MB開始查詢問題。當這個總數小於10MB時,外部記憶體壓力將表現得很明顯。
相同資訊也可以使用在系統監視器中的Memory: Available Bytes計數器獲取。
如果存在外部記憶體壓力並且你看到了記憶體相關的錯誤,你需要確認在系統中主要的記憶體消耗者。為了這個,考慮Process: Working Set效能計數器或在工作管理員中Process欄中的Mem Usage列,找到最大的記憶體消耗者。
系統中所有使用的實體記憶體可以透過彙總下列計數器獲取。
•    Process 物件,每個程式的 Working Set計數器
•    Memory 物件
•    系統的Cache Bytes計數器
•    未分頁池的Pool Nonpaged Bytes 計數器
•    Available Bytes (等於任務管理其中的Available 值)
如果沒有外部壓力,Process: Private Bytes計數器或在工作管理員中虛擬記憶體將接近工作集的大小(Process: Working Set或工作管理員中的Mem Usage),意味著我們沒有記憶體用於分頁了。
注意工作管理員中的Mem Usage列和相應的效能計數器不能計算透過AWE分配的記憶體。這樣如果使用AWE,資訊將是不完整的。這種情況下,你需要考慮在SQL Server內分配的記憶體來獲取完整的資訊。
你可以使用sys.dm_os_memory_clerks DMV找到SQL Server透過AWE機制分配了多少記憶體,如下所示。

select
    sum(awe_allocated_kb) / 1024 as [AWE allocated, Mb]
from
    sys.dm_os_memory_clerks

注意在SQL Server中,只有當前快取池(’MEMORYCLERK_SQLBUFFERPOOL’型別)使用這種機制並且只能是在使用AWE功能時。透過識別和除去主要實體記憶體使用者(如果有可能)和/或 透過新增更多的記憶體的方法解除外部記憶體壓力一般可以解決與此相關的記憶體問題。
外部虛擬記憶體壓力
你需要確定是否頁面檔案為當前記憶體的分配能提供足夠的空間。為了檢查,可以開啟工作管理員中的效能檢視,並檢查Commit Charge節。如果Total接近於Limit則說明可以被提交的最大數量記憶體沒有擴充套件頁面的空間。注意工作管理員中的Commit Charge Total指出潛在使用的頁面檔案,而不是實際使用值。實際使用的頁面檔案將增加實體記憶體壓力。
同樣可以透過下列技術起獲取相關資訊:Memory: Commit Limit, Paging File: %Usage, Paging File: %Usage Peak。
你可以透過每個程式的Process: Working Set減去Process Private Bytes計數器來評估記憶體總數。
如果Paging File: %Usage Peak(或Peak Commit Charge)很高,檢查系統日誌中是否有指出頁面檔案增長或通知“running low on virtual memory”的資訊。你可能需要增加你的頁面檔案大小。High Paging File: %Usage指出實體記憶體超過要提交的值並也要考慮外部實體記憶體壓力(大量的記憶體需求,沒有足夠的RAM)。
內部實體記憶體壓力
內部記憶體壓力來自於SQL Server自身,應首先透過檢查在快取分佈中的異常來考慮在SQL Server記憶體分佈。通常在SQL Server中快取會佔用最多提交的記憶體。為了確定在快取池中的記憶體總數,我們可以使用DBCC MEMROYSTATUS命令。在Buffer Counts節,可以找到Target值。下列輸出顯示了在伺服器達到正常負載時DBCC MEMORYSTATUS的結果。
 
Buffer Counts                  Buffers
------------------------------ --------------------
Committed                      201120
Target                         201120
Hashed                         166517
Reserved Potential             143388
Stolen Potential               173556
External Reservation           0
Min Free                       256
Visible                        201120
Available Paging File          460640

Target是SQL Server計算出它在不導致分頁時可以提交的8-KB每頁的頁數。Target是被定期的重新計算的來反映記憶體的低或高。在常規服務負載下target頁面過低可能預示出現了外部記憶體壓力。
如果SQL Server佔用了大量的記憶體(透過Process: Private Bytes或 工作管理員中Mem Usage 列顯示),請檢視是否Target的數值。注意,如果啟用AWE,你還要從sys.dm_of_memory_clerks或DBCC MEMORYSTATUS輸出計算AWE分配的總量。
考慮上面的示例(沒有啟用AWE),Target*8KB=1.53GB,而伺服器的Process: Private Bytes大約是1.62GB或快取池用SQL Server佔用了94%的記憶體。注意,如果伺服器沒有過載,Target是應該超過Process: Private Bytes效能計數器報告的數量。
如果Target過低,但是伺服器的Process: Private Bytes或 工作管理員中Mem Usage 值很高,我們也許要面對從快取池外使用記憶體的元件帶來的內部記憶體壓力。被載入到SQL Server程式中的元件,例如COM物件,連線伺服器,擴充套件儲存過程,SQLCLR或其他會從快取池外佔用記憶體。如果不使用SQL Server記憶體介面,將沒有方法跟蹤元件在快取池外佔用的記憶體。
適用於SQL Server記憶體管理機制的元件使用在快取池中分配很少的記憶體。如果分配的大於8KB,這些組將將透過多頁分配器藉口使用快取池外的記憶體。
下列方法可以快速檢查透過多業分配器介面占用的記憶體數量。

-- amount of mem allocated though multipage allocator interface
select sum(multi_pages_kb) from sys.dm_os_memory_clerks

你可以這樣獲得透過多頁分配器分發記憶體的詳細資訊:

select
    type, sum(multi_pages_kb)
from
    sys.dm_os_memory_clerks
where
    multi_pages_kb != 0
group by type
type                                       
 
------------------------------------------ ---------
MEMORYCLERK_SQLSTORENG                     56
MEMORYCLERK_SQLOPTIMIZER                   48
MEMORYCLERK_SQLGENERAL                     2176
MEMORYCLERK_SQLBUFFERPOOL                  536
MEMORYCLERK_SOSNODE                        16288
CACHESTORE_STACKFRAMES                     16
MEMORYCLERK_SQLSERVICEBROKER               192
MEMORYCLERK_SNI                            32

如果透過多頁分配器分發了過大的記憶體( 100-200MB或更多),應該做進一步的研究。
如果你看到了透過多頁分配器 分發的大量記憶體,檢查伺服器的配置並嘗試使用之前或後續的查詢確定哪個元件佔用的最多的記憶體。
如果Target值低,但是在百分比上它佔用了最多的記憶體,請在前面部分中查詢描述外部記憶體壓力的部分(External Physical Memory Pressure),或檢視伺服器記憶體配置引數。
如果你設定了max server memory 和/或min server memory,你應該用這些值和Target值進行比較。max server memory選項限制了在快取池中佔用記憶體的最大值,而伺服器還可以佔用其他的部分。min server memory選項告訴伺服器當小於該值時不能釋放快取池的記憶體。如果Target小於min server memory設定並且伺服器沒有過載,這可能預示伺服器遇到了外部記憶體壓力並且不能獲得這個設定大小的記憶體。它也可能預示著從內部元件的記憶體壓力,就像上面描述的那樣。Target 數值不能超過max server memory選項的設定。
首先,檢查從DBCC MEMORYSTATUS輸出中Stolen頁面數量

Buffer Distribution            Buffers
------------------------------ -----------
Stolen                         32871
Free                           17845
Cached                         1513
Database (clean)               148864
Database (dirty)               259
I/O                            0
Latched                        0

相對於Stolen和Target頁面的高百分比(>75-80%)預示著內部記憶體壓力。
更多關於伺服器元件記憶體分配的資訊可以使用sys.dm_of_memory_clerks DMV獲取。
 
-- amount of memory consumed by components outside the Buffer pool
-- note that we exclude single_pages_kb as they come from BPool
-- BPool is accounted for by the next query
select
    sum(multi_pages_kb
        + virtual_memory_committed_kb
        + shared_memory_committed_kb) as [Overall used w/o BPool, Kb]
from
    sys.dm_os_memory_clerks
where
    type <> 'MEMORYCLERK_SQLBUFFERPOOL'

-- amount of memory consumed by BPool
-- note that currenlty only BPool uses AWE
select
    sum(multi_pages_kb
        + virtual_memory_committed_kb
        + shared_memory_committed_kb
        + awe_allocated_kb) as [Used by BPool with AWE, Kb]
from
    sys.dm_os_memory_clerks
where
    type = 'MEMORYCLERK_SQLBUFFERPOOL'

每個元件的詳細資訊可以透過下列語句獲取(這包括從快取池內和快取池外分配的記憶體)。

declare @total_alloc bigint
declare @tab table (
    type nvarchar(128) collate database_default
    ,allocated bigint
    ,virtual_res bigint
    ,virtual_com bigint
    ,awe bigint
    ,shared_res bigint
    ,shared_com bigint
    ,topFive nvarchar(128)
    ,grand_total bigint
);

-- note that this total excludes buffer pool committed memory as it represents the largest consumer which is normal
select
    @total_alloc =
        sum(single_pages_kb
            + multi_pages_kb
            + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL'
                THEN virtual_memory_committed_kb
                ELSE 0 END)
            + shared_memory_committed_kb)
from
    sys.dm_os_memory_clerks

print
    'Total allocated (including from Buffer Pool): '
    + CAST(@total_alloc as varchar(10)) + ' Kb'

insert into @tab
select
    type
    ,sum(single_pages_kb + multi_pages_kb) as allocated
    ,sum(virtual_memory_reserved_kb) as vertual_res
    ,sum(virtual_memory_committed_kb) as virtual_com
    ,sum(awe_allocated_kb) as awe
    ,sum(shared_memory_reserved_kb) as shared_res
    ,sum(shared_memory_committed_kb) as shared_com
    ,case  when  (
        (sum(single_pages_kb
            + multi_pages_kb
            + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL'
                THEN virtual_memory_committed_kb
                ELSE 0 END)
            + shared_memory_committed_kb))/(@total_alloc + 0.0)) >= 0.05
          then type
          else 'Other'
    end as topFive
    ,(sum(single_pages_kb
        + multi_pages_kb
        + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL'
            THEN virtual_memory_committed_kb
            ELSE 0 END)
        + shared_memory_committed_kb)) as grand_total
from
    sys.dm_os_memory_clerks
group by type
order by (sum(single_pages_kb + multi_pages_kb + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN virtual_memory_committed_kb ELSE 0 END) + shared_memory_committed_kb)) desc

select  * from @tab

注意之前的查詢將Buffer Pool與透過單頁分配器提供給元件的記憶體視為不同的部分。透過下列查詢可以確定在快取池中耗費記憶體最多的10個元件(透過單頁分配器)。

-- top 10 consumers of memory from BPool
select
    top 10 type,
    sum(single_pages_kb) as [SPA Mem, Kb]
from
    sys.dm_os_memory_clerks
group by type
order by sum(single_pages_kb) desc

你通常不會控制內部元件對記憶體的佔用。然而,確定哪個元件佔用最多的記憶體可以幫助對問題的後續研究。
系統監視器(Perfmon)
你也可以透過檢查下列計數器來確定記憶體壓力(SQL Server聯機叢書中有詳細地描述):
SQL Server: Buffer Manager 物件
•    Low Buffer cache hit ratio
•    Low Page life expectancy
•    High number of Checkpoint pages/sec
•    High number Lazy writes/sec
不足的記憶體和I/O開銷經常會導致瓶頸。請檢視本文的I/O 瓶頸部分。
快取記憶體和記憶體壓力
檢視外部和內部記憶體壓力的方法是檢視記憶體中快取記憶體的行為。
SQL Server 2005與SQL Server 2000的快取記憶體設計上有一些細微的不同,其中之一就是統一了快取記憶體的框架。為了從快取記憶體中刪除最近很少使用的項,該框架實現了一套時鐘演算法。現在它使用2支不同的時鐘指標,一個是內部時鐘指標,一個是外部時鐘指標。
內部時鐘指標控制與其他快取記憶體相關的快取大小。當框架預測到快取記憶體要使用到盡頭是它開始移動。
當SQL Server總體上陷入記憶體壓力時,外部時鐘指標開始移動。外部時鐘指標的移動可以導致外部和內部的記憶體壓力。在內部和外部記憶體壓力時不會混亂的移動外部時鐘和內部時鐘。
關於時鐘移動的資訊可以透過sys.dm_os_memory_cache_clock_hands DMV顯示,如下程式碼所示。每個快取記憶體項在內部和外部時鐘指標都有不同的行。如果你看到rounds count和removed all rounds count增加,說明伺服器遇到內部/外部記憶體壓力。
select *
from
    sys.dm_os_memory_cache_clock_hands
where
    rounds_count > 0
    and removed_all_rounds_count > 0

透過如下所示,透過透過sys.dm_os_cache_counters DMV你可以獲得更多關於快取的資訊。
select
    distinct cc.cache_address,
    cc.name,
    cc.type,
    cc.single_pages_kb + cc.multi_pages_kb as total_kb,
    cc.single_pages_in_use_kb + cc.multi_pages_in_use_kb as total_in_use_kb,
    cc.entries_count,
    cc.entries_in_use_count,
    ch.removed_all_rounds_count,
    ch.removed_last_round_count
from
    sys.dm_os_memory_cache_counters cc
    join sys.dm_os_memory_cache_clock_hands ch on (cc.cache_address = ch.cache_address)
/*
--uncomment this block to have the information only for moving hands caches
where
    ch.rounds_count > 0
    and ch.removed_all_rounds_count > 0
*/
order by total_kb desc

注意USERSTORE項,正在使用的頁面數量將不會被報告,因為結果將是NULL。
Ring buffers
更多有意義的記憶體除錯資訊可以透過sys.dm_os_ring_buffers的ring buffers DMV獲取。每個ring buffer保留了之前幾次某種型別的通知。指定ring buffer的詳細資訊將在下面描述。
RING_BUFFER_RESOURCE_MONITOR
你可以使用從資源監視器的通知識別記憶體改變的狀態。在內部,SQL Server有一個監視不同記憶體壓力的架構。當記憶體狀態改變,資源監視器任務生成一個通知。這個通知用於內部元件根據記憶體狀態調整它們記憶體使用並透過sys.dm_os_ring_buffers DMV來暴露,如下列程式碼所示。

select record
from sys.dm_os_ring_buffers
where ring_buffer_type = 'RING_BUFFER_RESOURCE_MONITOR'
結果類似於

   
        RESOURCE_MEMPHYSICAL_LOW
        2
        0
   

   
        1646380
        432388
        0
        0
        26592
        17128
        17624
   

   
        50
        3833132
        3240228
        5732340
        5057100
        2097024
        336760
        0
   



從這些記錄來看,你可以減少伺服器收到的低實體記憶體的通知。你也可以檢視記憶體總量(KB為單位)。你可以透過使用SQL Server的XML能力來查詢這些資訊,例如下列程式碼。

select
    x.value('(//Notification)[1]', 'varchar(max)') as [Type],
    x.value('(//Record/@time)[1]', 'bigint') as [Time Stamp],
    x.value('(//AvailablePhysicalMemory)[1]', 'int') as [Avail Phys Mem, Kb],
    x.value('(//AvailableVirtualAddressSpace)[1]', 'int') as [Avail VAS, Kb]
from
    (select cast(record as xml)
     from sys.dm_os_ring_buffers
     where ring_buffer_type = 'RING_BUFFER_RESOURCE_MONITOR') as R(x)
order by
    [Time Stamp] desc

上面收到了低記憶體的通知,快取池重新計算target。注意target數量保持在指定的min server memory 和max server memory選項限制中。如果快取池中心的提交比當前的提交快取還小,快取池將開始收縮直到外部記憶體壓力被移除。注意,當執行在啟用AWE時SQL Server 2000不是這樣來緩解實體記憶體壓力的。
RING_BUFFER_OOM
下列程式碼示例,展示了ring buffer中包含預示伺服器記憶體不足的記錄。

select record
from sys.dm_os_ring_buffers
where ring_buffer_type = 'RING_BUFFER_OOM'
結果類似於:

    
        FAIL_VIRTUAL_COMMIT
        4096
    


這條記錄告訴我們那個操作失敗了(提交,保留或頁面分配)並表明了請求的記憶體數量。
RING_BUFFER_MEMORY_BROKER and Internal Memory Pressure
當監測到內部記憶體壓力時,為元件在快取池分配記憶體的低記憶體通知將被開啟。開啟低記憶體通知允許從使用快取池的快取記憶體和其他元件中回收頁面。
內部記憶體壓力可以透過調整max server memory選項或當stolen頁面與快取池的比例超過80%時觸發。
內部記憶體壓力通知(‘Shrink’)能透過使用下列程式碼查詢ring buffer的呼叫來發現。

select
    x.value('(//Record/@time)[1]', 'bigint') as [Time Stamp],
    x.value('(//Notification)[1]', 'varchar(100)') as [Last Notification]
from
    (select cast(record as xml)
     from sys.dm_os_ring_buffers
     where ring_buffer_type = 'RING_BUFFER_MEMORY_BROKER') as R(x)
order by
    [Time Stamp] desc

RING_BUFFER_BUFFER_POOL
ring buffer將包含預示嚴重的快取池失敗的記錄,包括快取池溢位的條件。

select record
from sys.dm_os_ring_buffers
where ring_buffer_type = 'RING_BUFFER_BUFFER_POOL'
結果類似於:

    < BufferPoolFailure id="FAIL_OOM">
        84344
        84350
        20
        20345
        64001
    64001
     BufferPoolFailure >

這條記錄告訴我們出現了什麼樣的失敗 (FAIL_OOM, FAIL_MAP, FAIL_RESERVE_ADJUST, FAIL_LAZYWRITER_NO_BUFFERS) 基當時的快取池狀態。
內部虛擬記憶體壓力
VAS的佔用可以使用sys.dm_os_virtual_address_dump DMV來跟蹤。VAS彙總可以使用下列檢視來查詢。

-- virtual address space summary view
-- generates a list of SQL Server regions
-- showing number of reserved and free regions of a given size
CREATE VIEW VASummary AS
SELECT
    Size = VaDump.Size,
    Reserved =  SUM(CASE(CONVERT(INT, VaDump.Base)^0) WHEN 0 THEN 0 ELSE 1 END),
    Free = SUM(CASE(CONVERT(INT, VaDump.Base)^0) WHEN 0 THEN 1 ELSE 0 END)
FROM
(
    --- combine all allocation according with allocation base, don't take into
    --- account allocations with zero allocation_base
    SELECT
        CONVERT(VARBINARY, SUM(region_size_in_bytes)) AS Size,
        region_allocation_base_address AS Base
    FROM sys.dm_os_virtual_address_dump
    WHERE region_allocation_base_address <> 0x0
    GROUP BY region_allocation_base_address
 UNION  
       --- we shouldn't be grouping allocations with zero allocation base
       --- just get them as is
    SELECT CONVERT(VARBINARY, region_size_in_bytes), region_allocation_base_address
    FROM sys.dm_os_virtual_address_dump
    WHERE region_allocation_base_address  = 0x0
)
AS VaDump
GROUP BY Size

下列查詢可以用於訪問VAS狀態

-- available memory in all free regions
SELECT SUM(Size*Free)/1024 AS [Total avail mem, KB]
FROM VASummary
WHERE Free <> 0

-- get size of largest availble region
SELECT CAST(MAX(Size) AS INT)/1024 AS [Max free size, KB]
FROM VASummary
WHERE Free <> 0

如果最大可用區域小於4MB,我們可能遇到了VAS壓力。SQL Server 2005監視和響應VAS壓力。SQL Server 2000不會監視從VAS帶來的壓力,但是當出現虛擬記憶體不足錯誤是,它會清理快取記憶體。
一般的記憶體錯誤排錯步驟
下列是一些常規步驟,有助於你排除記憶體錯誤。
1.    驗證是否伺服器執行在外部記憶體壓力。如果出現外部記憶體壓力,嘗試先解決它,然後再看是否依然存在這個問題或錯誤。
2.    開始收集效能計數器:SQL Server: Buffer Manager, SQL Server: Memory Manager
3.    確認記憶體配置引數(sp_configure), min memory per query,min/max server memory,awe enabled和  Lock Pages in Memory 權利。觀察不正常的值。糾正配置。提供為SQL Server 2005增加記憶體的理由。
4.    檢查所有可能影響伺服器的非預設sp_configure引數。
5.    檢查內部記憶體壓力。
6.    當你見到記憶體錯誤訊息時,觀察DBCC MEMORYSTATUS輸出和改變的方法。
7.    檢查負載(併發會話數量,併發執行查詢數量)。
記憶體錯誤
701 - There is insufficient system memory to run this query.
原因
這是伺服器記憶體不足的典型現象。它預示了記憶體分配失敗。它可以有多種原因導致,包括在當前工作負載上的記憶體提示。透過增加SQL Server 2005需要的記憶體和一些配置上的設定(例如max server memory選項)使用者可能會看到這種錯誤。通常失敗的事務不是因為這個錯誤。
排錯
不論是否錯誤是持續的和可重複的(同樣狀態)或是隨機的(顯示為隨機的不同狀態),當你看到這個錯誤時你需要研究伺服器記憶體分佈。當這個錯誤出現時,可能導致診斷查詢失敗。可以開始從外部進行評估。接下來的步驟在一般的記憶體錯誤排錯步驟中描述。
可能的解決方案包括:除去外部記憶體壓力;增加max server memory設定。使用下列語句釋放快取DBCC FREESYSTEMCACHE,DBCC FREESESSIONCACHE,或 DBCC FREEPROCCACHE。如果問題還是出現,則應減少工作負載。

802 - There is insufficient memory available in the buffer pool.
原因
這個錯誤不是引起記憶體不足的必要條件。它可能預示快取池記憶體被其他的一些元件使用。在SQL Server 2005中很少出現。
排錯
使用常規的排錯步驟和701錯誤的建議。

8628 - A time out occurred while waiting to optimize the query. Rerun the query.
原因
這個錯誤指出查詢編譯過程失敗因為它不能獲取完成編譯所需的記憶體。當查詢經歷了這種編譯過程,包括解析,代數求解和最佳化,它的記憶體需求會增加。因而查詢將和其他的查詢爭奪記憶體資源。如果查詢超過的預定的超時時間(查詢增加記憶體的佔用) 當等待資源時,這個錯誤會返回。這種情況的最可能的原因是在伺服器上出現很多大型查詢的編譯。
排錯
1.    在常規的排錯步驟後檢視是否伺服器記憶體佔用正常。
2.    檢查負載。確認被不同元件佔用的記憶體數量(具體資訊請參考內部實體記憶體壓力)。
3.    檢查DBCC MEMORYSTATUS輸出的在每個gateway上的等待數量(這些資訊將告訴你是否有其他的查詢在佔用大量記憶體)。

Small Gateway                  Value
------------------------------ --------------------
Configured Units               8
Available Units                8
Acquires                       0
Waiters                        0
Threshold Factor               250000
Threshold                      250000

(6 row(s) affected)

Medium Gateway                 Value
------------------------------ --------------------
Configured Units               2
Available Units                2
Acquires                       0
Waiters                        0
Threshold Factor               12

(5 row(s) affected)

Big Gateway                    Value
------------------------------ --------------------
Configured Units               1
Available Units                1
Acquires                       0
Waiters                        0
Threshold Factor               8

4.    儘可能減少工作負載。

8645 - A time out occurred while waiting for memory resources to execute the query. Rerun the query.
原因
這個錯誤指出在伺服器上有很多記憶體敏感的查詢被同時執行。使用排序(ORDER BY)和連線的查詢可以在執行過程中會佔用大量的記憶體。如果有高度並行或如果查詢操作一個非聯合索引的分割槽表時,查詢記憶體需求會有很大的增加。無法或取所需記憶體資源的查詢在預先確定的超時時間時(預設,超時是25倍的評估查詢時間或sp_configure 設定的’query wait’)將收到這個錯誤。通常,收到這個錯誤的查詢佔用記憶體的那個查詢。
排錯
1.    根據一般步驟評估記憶體的情況。
2.    識別有問題的查詢:確認有大量的在分割槽表上的查詢,檢查是否他們使用非聯合索引,檢查是否有大量包括連線和排序的查詢。
3.    檢查sp_configure引數中degree of parallelism 和min memory per query 。嘗試降低並行度並驗證是否min memory per query沒有設定很高的值。如果設定了很高的值,即使少量的查詢也會佔用指定查詢的數量。
4.    為了找到查詢是否等待RESOURCE_SEMAPHORE,請檢視本白皮書中阻塞的部分。
8651 - Could not perform. the requested operation because the minimum query memory is not available. Decrease the configured value for the 'min memory per query' server configuration option.
原因
原因和8645錯誤類似;它也預示伺服器記憶體過低。min memory per query太高也可能導致這個錯誤。
排錯
1.    根據一般記憶體錯誤排錯步驟。
2.    驗證sp_configure min memory per query 選項設定。
I/O 瓶頸
SQL Server效能非常依賴於I/O子系統。除非你的資料庫適合實體記憶體,SQL Server經常地會有資料庫頁面進出快取池。這樣就發生了實質的I/O流量。同樣,在事務被明確的提交前,日誌記錄需要寫入磁碟。SQL Server為各種目的可以使用tempdb,例如儲存中間結果,排序,保持行的版本或其他。所以好的I/O子系統對於SQL Server效能非常重要。
除了當事務需要回滾時,訪問日誌檔案是非常頻繁的,而包括訪問資料檔案,包括tempdb,是隨機訪問的。所以作為一個通常的規則,為獲取更好的效能,你最好將日誌文分佈不到不同的物理磁碟。本文重點於不是在如何配置你的I/O裝置,而是描述識別你的I/O瓶頸的方法。一旦識別了I/O瓶頸,你也許需要重新配置你的I/O子系統。
如果你有一個慢速的I/O子系統,你的使用者會遇到效能問題,例如很長的響應時間,任務由於超時而中斷。
你可以使用下列效能引數來識別I/O瓶頸。注意,如果你的收集間隔過長,這些平均值可能會傾向於過小。例如,很難透過60秒一次的快照獲取正確的I/O值。所以,你不能依賴於一個計數器來確定瓶頸;要考慮多個技術起來反覆校驗。
•    PhysicalDisk Object: Avg. Disk Queue Length表現在取樣週期中所選擇的物理磁碟佇列中的物理讀和寫平均請求數量。如果你的I/O系統過載,更多的讀/寫操作將被等待。如果在很少使用SQL Server時,你的磁碟佇列長度經常超過2,這樣你可能遇到了I/O瓶頸
•    Avg. Disk Sec/Read 是平均每次從磁碟讀取資料的時間
小於10 ms – 很好
在 10 - 20 ms 之間- 正常
在20 - 50 ms 之間- 緩慢,需要注意
大於 50 ms – 嚴重的I/O 瓶頸
•    Avg. Disk Sec/Write 是平均每次從磁碟讀取資料的時間。請引用之前的指導資料。
•    Physical Disk: %Disk Time 是所選磁碟驅動器用於服務於讀或寫請求的總共時間的百分比。一般推薦是如果該值大於50%,則表現為I/O瓶頸。
•    Avg. Disk Reads/Sec 表現磁碟上讀操作的速度。你需要確認該值小於85%的磁碟設計能力。磁碟訪問時間指數高於能力的85%。
•    Avg. Disk Writes/Sec表現在磁碟上寫操作的速度。確認該值小於85%的磁碟設計能力。磁碟訪問時間指數高於能力的85%。
當使用以上計數器,你在使用RAID配置時,你需要使用下列公式調整結果值。
Raid 0 -- I/Os per disk = (reads + writes) / number of disks
Raid 1 -- I/Os per disk = [reads + (2 * writes)] / 2
Raid 5 -- I/Os per disk = [reads + (4 * writes)] / number of disks
Raid 10 -- I/Os per disk = [reads + (2 * writes)] / number of disks

例如,你有帶有2個物理磁碟的RAID-1系統和下列計數器值。
Disk Reads/sec            80
Disk Writes/sec           70
Avg. Disk Queue Length    5

這種情況下,你遇到了(80 + (2 * 70))/2 = 110 I/Os per disk,你的磁碟佇列長度為5/2=2.5,表明系統接近於I/O瓶頸。
你也可以透過考察鎖等待來識別I/O瓶頸。當資料頁透過讀或寫訪問並且在快取池中頁不可用時,這些鎖等待佔用了大量的物理I/O等待。當頁面沒有在快取池中找到時,一個非同步I/O請求被髮出,I/O的狀態是被選中的。如果I/O已經完成,工作程式處理正常。否則,依賴於請求的型別,它會等待PAGEIOLATCH_EX 或 PAGEIOLATCH_SH。從下列的DMV查詢可以用於找到I/O鎖等待的統計。
Select  wait_type,
        waiting_tasks_count,
        wait_time_ms
from    sys.dm_os_wait_stats  
where    wait_type like 'PAGEIOLATCH%'  
order by wait_type

wait_type       waiting_tasks_count  wait_time_ms   signal_wait_time_ms
-----------------------------------------------------------------------
PAGEIOLATCH_DT  0                    0                    0
PAGEIOLATCH_EX  1230                 791                  11
PAGEIOLATCH_KP  0                    0                    0
PAGEIOLATCH_NL  0                    0                    0
PAGEIOLATCH_SH  13756                7241                 180
PAGEIOLATCH_UP  80                   66                   0

下劃線的是有興趣的鎖等待。當I/O完成時,工作者被放置到可用佇列中。從I/O完成到工作者被實際的排程的時間在signal_wait_time_ms列說明。如果你得waiting_task_counts and wait_time_ms偏離正常值太多,你可以識別為I/O問題。為了這樣,透過使用效能計數器和關鍵DMV建立正常時執行時的效能基線就十分重要。wait_types可以識別是否你的I/O子系統處在瓶頸狀態,但是他們不能提供任何關於物理磁碟遇到問題的資訊。
你可以使用下列DMV查詢找到當前等待的I/O請求。你可以週期性的執行這些請求檢查I/O子系統的健康情況,並找到涉及I/O瓶頸的物理磁碟。
select
    database_id,
    file_id,
    io_stall,
    io_pending_ms_ticks,
    scheduler_address
from    sys.dm_io_virtual_file_stats(NULL, NULL)t1,
        sys.dm_io_pending_io_requests as t2
where    t1.file_handle = t2.io_handle

下面是示例的輸出。它展示當時在給定的資料庫上有3個未決定的I/O 。你可以使用database_id 和 file_id 來找到對映的物理磁碟檔案。io_pending_ms_ticks 表現了所有等待在未決定佇列中的個別的I/O。

Database_id    File_Id io_stall    io_pending_ms_ticks    scheduler_address
----------------------------------------------------------------------
6        1        10804        78            0x0227A040
6        1        10804        78            0x0227A040
6        2        101451    31            0x02720040
解決
當你識別到I/O瓶頸時,你可以下列方法解決:
•    檢查SQL Server的記憶體配置。如果SQL Server配置的記憶體不足,將導致更多的I/O開銷。你可以考察下列計數器來識別記憶體壓力。
•    Buffer Cache hit ratio
•    Page Life Expectancy
•    Checkpoint pages/sec
•    Lazywrites/sec
更多關於記憶體壓力的資訊,請檢視記憶體瓶頸 。
•    增加I/O頻寬。
•    為當前的磁碟陣列新增更多的物理驅動器和/或使用更快的磁碟代替當前的磁碟。這幫助提升讀寫訪問時間。但是新增的磁碟數量不能比當前I/O控制器所支援的數量大。
•    新增快速或額外的I/O控制器。考慮為當前的控制器新增更多的快取(如果有可能)
•    考察執行計劃並檢視那個計劃佔用了更多的I/O。這樣可以找到更好的查詢進化(例如,索引)可以減少I/O。如果缺少索引,你可以執行Database Engine Tuning Advisor找到缺失的索引。

下列DMV查詢能用於找到那個批處理/請求生成了最多的I/O。你將注意到我們沒有說明物理寫入。如果你瞭解資料庫如何工作,這也沒有問題。請求中的DML/DDL並不是直接將資料頁寫入磁碟。只有透過提交事務語句才能觸發將頁面物理寫入磁碟。通常物理寫透過Checkpoint語句完成或透過SQL Server lazy writer完成。下列DMV查詢可以用於找到生成最多I/O的5個請求。調整這些查詢,使他們使用較少的邏輯度來完成操作,這樣可以減少在快取池上的壓力。這允許其他的查詢在快取池中找到重複執行中所需要的重複資料(而不是透過物理I/O完成)。因此整體系統效能被提高了。

select top 5
    (total_logical_reads/execution_count) as avg_logical_reads,
    (total_logical_writes/execution_count) as avg_logical_writes,
    (total_physical_reads/execution_count) as avg_phys_reads,
     Execution_count,
    statement_start_offset as stmt_start_offset,
    sql_handle,
    plan_handle
from sys.dm_exec_query_stats  
order by
 (total_logical_reads + total_logical_writes) Desc

當然你可以改變查詢或取資料的不同視角。例如,在一個查詢中聲稱最多I/O請求的5個請求,你可以透過下列表示式排序:
    (total_logical_reads + total_logical_writes)/execution_count
除此之外,你可能希望透過物理I/O排序或其他的操作。然而,邏輯讀/寫數量來幫助決定是否查詢選擇的計劃被最佳化過。例如,它可以做一個表掃描而不是使用索引。一些查詢,例如使用巢狀迴圈連線可能有很高的邏輯計數器但是能更好的適應快取,因為他們重新訪問了同樣的頁面。
例如:讓我們考察下列2個有2個SQL查詢的批,每個表由1000行並且行大小大於8000(每頁1行)。

批-1
select
    c1,
    c5
from t1 INNER HASH JOIN t2 ON t1.c1 = t2.c4
order by c2

批-2
select * from t1

為了這個示例,在執行DMV查詢前,我們利用下列命名清理了快取池和過程的快取。

checkpoint
dbcc freeproccache
dbcc dropcleanbuffers

這是DMV查詢的輸出。你將注意到2行表現的這2個批。

Avg_logical_reads Avg_logical_writes Avg_phys_reads Execution_count stmt_start_offset
--------------------------------------------------------------------------------------
2794                1                385                1                        0                      
1005                0                0                  1                        146         
               
sql_handle                                         plan_handle
----------------------------------------------------------------------------
0x0200000099EC8520EFB222CEBF59A72B9BDF4DBEFAE2B6BB
x0600050099EC8520A8619803000000000000000000000000
0x0200000099EC8520EFB222CEBF59A72B9BDF4DBEFAE2B6BB
 x0600050099EC8520A8619803000000000000000000000000

你將注意到第2個批只發生了邏輯讀但是沒有物理I/O。這是因為需要的資料已經由第1個查詢快取到了快取記憶體中(有充分的記憶體)。
你可以透過下列查詢或取執行查詢的文字。

   select text
from sys.dm_exec_sql_text(
     0x0200000099EC8520EFB222CEBF59A72B9BDF4DBEFAE2B6BB)

Here is the output.

select
    c1,
    c5
from t1 INNER HASH JOIN t2 ON t1.c1 = t2.c4
order by c2

你也可以透過下列語句找到獨立語句的字串。

select
    substring(text,
              (/2),
              ( -)/2)  
from sys.dm_exec_sql_text                (0x0200000099EC8520EFB222CEBF59A72B9BDF4DBEFAE2B6BB)

statement_start_offest 和statement_end_offset的值需要除以2來補償SQL Server使用Unicode來儲存這種資料的。statement_end_offse值為-1指出語句先前到了批的最後。而substring()函式不識別-1,並將其作為非法值。使用( -)/2,代替-1,一個需要輸入64000的值,來確認語句覆蓋了所有的情況。透過這種方法,長時間執行或資源消費語句可以被過濾出大型的儲存過程或批。
同樣的你可以執行下列查詢,找到查詢計劃確定是否當選擇了較差的計劃時有大量的I/O。
select *
from sys.dm_exec_query_plan
    (0x0600050099EC8520A8619803000000000000000000000000)
Tempdb
Tempdb用於全域性儲存內部或使用者物件,臨時表,物件和在SQL Server操作是建立的儲存過程。每個SQL Server 例項只有1個單一的tempdb。它可能是一個效能和磁碟空間的瓶頸。有限可用空間和過多的DDL/DML會使Tempdb超過負載。這能導致執行在同一個伺服器中的其他無關應用變得執行緩慢或失敗。
下面列出一些tempdb的常規問題:
•    Tempdb磁碟空間不足
•    由於tempdb中的I/O瓶頸,導致查詢執行緩慢。這在I/O瓶頸部分提到過。
•    過多DDL操作導致系統表的瓶頸
•    分配爭奪
Before we start diagnosing problems with tempdb, let us first look at how the space in tempdb is used. It can be grouped into four main categories.當開始除錯tempdb問題前,讓我們先看一下在tempdb中如何使用空間。它可以分為4個主要的類別。

使用者物件    這些物件被使用者會話顯示建立並在系統目錄中被跟蹤。這包括:
•    表和索引
•    全域性臨時表(##t1)和索引
•    本地臨時表(#t1)和索引
•    會話範圍
•    儲存過程範圍內建立
•    表變數(@t1).
•    會話範圍
•    儲存過程範圍內建立
內部物件    這有語句範圍的物件,透過SQL Server處理的查詢建立和銷燬。這些物件不能被系統目錄跟蹤。這包括:
•    工作檔案(hash join)
•    排序
•    工作表 (遊標, 池 和臨時大物件資料型別 (LOB)儲存 )
作為最佳化,當工作表被刪除,一個IAM頁和一個擴充套件被儲存到一個新的工作表。
這有2個例外:臨時LOB儲存是批範圍的和遊標工作表示會話範圍的

版本儲存    用於儲存行版本。MARS,在索引因操作,觸發器和快照隔離級別都是基於行版本。這是SQL Server 2005中新的特性。
可用空間    這表示在tempdb資料庫可用的磁碟空間。

tempdb使用的總空間等於使用者物件加內部物件加版本儲存+可用空間。
這個可用空間等於效能計數器中tempdb的可用空間。
監視tempdb空間
阻止問題更好的方法是隨後馬上解決它。你可以使用下列效能監視器來監視使用中的tempdb空間。
•    Free Space in tempdb (KB).這個計數器以KB為單位跟蹤空閒空間的數量。管理員可以使用這個計數器確定是否tempdb執行在較低的磁碟空間上。
然而,識別前面所定義的不同類別如何使用在tempdb中使用的磁碟空間,是很有興趣的並有效率的。
下列查詢返回了tempdb使用者使用的空間和內部元件物件。當前它提供了僅有tempdb中的資訊。

Select
    SUM (user_object_reserved_page_count)*8 as user_objects_kb,
    SUM (internal_object_reserved_page_count)*8 as internal_objects_kb,
    SUM (version_store_reserved_page_count)*8  as version_store_kb,
    SUM (unallocated_extent_page_count)*8 as freespace_kb
From sys.dm_db_file_space_usage
Where database_id = 2

這裡有一些示例的輸出(空間使用KB為單位)。

user_objets_kb   internal_objects_kb   version_store_kb   freespace_kb
---------------- -------------------- ------------------ ------------
8736               128                    64                    448

注意這些技術不包括其中的混合擴充套件。頁面在混合擴充套件中可以被分配個別使用者和內部物件。
磁碟空間問題排錯
使用者物件,內部物件和儲存的版本可以都可以導致在tempdb中的空間問題。這節我們會考慮針對每個類別如何排錯。
使用者物件
因為使用者物件不被特定的會話擁有,你需要理解建立該物件的應用程式的規範並調整需要的tempdb大小。你可以透過執行exec sp_spaceused @objname=''來找到個別使用者物件使用的。例如,你可以執行下列指令碼列舉所有tempdb物件。
DECLARE userobj_cursor CURSOR FOR
select
     sys.schemas.name + '.' + sys.objects.name
from sys.objects, sys.schemas
where object_id > 100 and
      type_desc = 'USER_TABLE'and
      sys.objects.schema_id = sys.schemas.schema_id
go

open userobj_cursor
go

declare @name varchar(256)
fetch userobj_cursor into @name
while (@@FETCH_STATUS = 0)
begin
    exec sp_spaceused @objname = @name
        fetch userobj_cursor into @name    
end
close userobj_cursor

版本儲存
SQL Server 2005提供了行版本架構用於實現一些特性。如下列出了使用行版本架構的特性。更多關於下列特性的資訊,請參考SQL Server 聯機叢書。
•    觸發器
•    MARS
•    聯機索引
•    基於行版本隔離級別:需要在資料庫級設定選項
行版本需要跨會話共享。當行版本被回收時,行版本的建立者沒有控制權。你需要找到並殺掉阻止行版本清理的執行最長的事務。
下列查詢返回依賴於版本儲存執行最長的2個事務。

select top 2
    transaction_id,
    transaction_sequence_num,
    elapsed_time_seconds
from sys.dm_tran_active_snapshot_database_transactions
order by elapsed_time_seconds DESC

這是示例的輸入,顯示了序列號為3,事務ID為8609的事務已經執行了6523秒。

transaction_id       transaction_sequence_num elapsed_time_seconds
-------------------- ------------------------ --------------------
8609                 3                        6523
20156                25                       783

因為第2個事務執行了相對短的時間,你可以透過殺掉第1個事務來釋放大量的版本儲存。可是,沒有方法能評估透過殺掉進能釋放的版本空間。你也許需要殺掉一些事務來釋放更多的空間。
你可以透過改變用於版本儲存的tempdb屬性或透過儘可能的消除在快照隔離級別的長事務,或在read-committed-snapshot下執行的長查詢來減輕這個問題。你能使用下列公式粗略的評估行版本儲存的大小。
[Size of version store] = 2 * [version store data generated per minute] * [longest running time (minutes) of the transaction]
在所有使用了基於孤立級別行版本,為一個事物每分鐘生成版本儲存的資料和每分鐘生成的日誌一樣。然而這也有一些異常:只有更新的差異部分生成日誌;如果使用了批次匯入操作並且恢復模式不是完全恢復時,新插入的資料行不依賴日誌,則不被記錄版本。
你也可以使用Version Generation Rate 和Version Cleanup Rate效能計數器來調整你的計算。如果Version Cleanup Rate為0,這暗示著有長時間執行的事務阻止版本儲存的清理。
附帶地,在產生tempdb空間不足錯誤前,SQL Server 2005會做最後一次嘗試強制版本儲存收縮。在這個收縮過程中,沒有生成行版本執行最長的事務會被標識為犧牲者。這可以釋放他們使用的版本儲存。在錯誤日誌中為犧牲的事務生成一個訊息3967,它能不再從版本儲存中讀取行版本或建立新的版本儲存。如果收縮版本儲存成功,這樣在tempdb中會有更多的可用空間。否則tempdb將耗盡記憶體。
內部物件
內部物件在每條語句中被建立和銷燬,除非想在前面所描述的。如果你注意到有大量的tempdb空間分配,你將需要了解那個會話或任務佔用了空間,然後進肯能做一些矯正的操作。
SQL Server 2005提供了2個額外的DMV::  sys.dm_db_session_space_usage 和sys.dm_db_task_space_usage 來跟蹤分配給個別會話和任務所用的tempdb空間。儘管任務執行在會話的上下文,當任務完成後,任務使用的空間還會被佔用。你可以使用下列查詢來找到為內部物件分配最多的會話。注意這個查詢只包括在會話中已完成的任務。

select
    session_id,
    internal_objects_alloc_page_count,
    internal_objects_dealloc_page_count
from sys.dm_db_session_space_usage
order by internal_objects_alloc_page_count DESC

你可以使用下列查詢找到分配物件最多的會話,包括正在執行的任務。
 
SELECT
    t1.session_id,
    (t1.internal_objects_alloc_page_count + task_alloc) as allocated,
    (t1.internal_objects_dealloc_page_count + task_dealloc) as    
    deallocated
from sys.dm_db_session_space_usage as t1,
    (select session_id,
        sum(internal_objects_alloc_page_count)
            as task_alloc,
    sum (internal_objects_dealloc_page_count) as
        task_dealloc
      from sys.dm_db_task_space_usage group by session_id) as t2
where t1.session_id = t2.session_id and t1.session_id >50
order by allocated DESC

這是示例的輸出。

session_id allocated            deallocated
---------- -------------------- --------------------
52         5120                 5136
51         16                   0

一旦你隔離出生成大量物件分配的任務或會話,你能找到任務的那條Transact-SQL語句和它的查詢計劃來做更詳細地分析。

select
    t1.session_id,
    t1.request_id,
    t1.task_alloc,
    t1.task_dealloc,
    t2.sql_handle,
    t2.statement_start_offset,
    t2.statement_end_offset,
    t2.plan_handle
from (Select session_id,
             request_id,
             sum(internal_objects_alloc_page_count) as task_alloc,
             sum (internal_objects_dealloc_page_count) as task_dealloc
      from sys.dm_db_task_space_usage
      group by session_id, request_id) as t1,
      sys.dm_exec_requests as t2
where t1.session_id = t2.session_id and
     (t1.request_id = t2.request_id)
order by t1.task_alloc DESC
這是示例的輸出。

session_id request_id  task_alloc           task_dealloc  
---------------------------------------------------------       
52         0           1024                 1024                 
sql_handle                          statement_start_offset
-----------------------------------------------------------------------
0x02000000D490961BDD2A8BE3B0FB81ED67655EFEEB360172   356  

statement_end_offset  plan_handle      
---------------------------------                                                                                     -1                0x06000500D490961BA8C19503000000000000000000000000

你可以利用如下語句透過sql_handle和plan_handle來得到SQL語句和查詢計劃:

select text from sys.dm_exec_sql_text(@sql_handle)
select * from sys.dm_exec_query_plan(@plan_handle)

注意當你訪問查詢計劃時,查詢計劃可能已經不在快取中了。為保證查詢計劃可以使用,你應該需要經常為查詢計劃快取儲存否則該計劃會被清除,同時應該將結果儘可能儲存在表中,這樣用於以後的查詢。
當SQL Server 重新啟動,tempdb大小將初始化到配置的大小,並基於需求增長。這可以導致tempdb的分裂,這樣會招致過多的開銷,包括在資料庫自動增長時分配新的擴充套件的阻塞。這能影響你的工作負載效能。建議你重新分配tempdb到一個適當的大小。
過多的DLL和分配操作
在tempdb中2個原因可以導致這個結果。
•    建立和刪除大量的臨時表和標變數導致在後設資料上的爭奪。在SQL Server 2005中,本地的臨時表和標變數被快取來最小化後設資料的爭用。然而只有下列條件滿足時,表才會被快取。
•    在表中沒有命名的約束。
•    在Create語句後在表中沒有DDL(例如,Create Index和Create Statistics)。
•    典型情況下,大部分臨時/工作表是在堆上;因此插入,刪除或刪除操作能引起在Page Free Space (PFS) 頁面上的嚴重爭用。如果大部分的表小於64KB並且使用了混合擴充套件來分配或處理位置,這能帶來在Share Global Allocation Map(SGAM)頁面上的爭用。SQL Server 2005為本地的臨時表快取一個資料頁和一個IAM頁來最小化分配爭用。這種快取在SQL Server 2000中是使用在工作表上的。
因為SGAM和PFS在資料檔案中分頁出現在固定的間隔,這樣很容易找到他們所用資源的描述。例如,2:1:1表示了在tempdb第1個PFS頁面(database-id為2,file-id為1,page-id為1),2:1:3表示了第1個SGAM頁面。SGAM頁面每511232個頁面出現1次,PFS頁面每8088個頁面出現1次。你能使用這個規則在tempdb的所有檔案中找到所有其他的PFS和SGAM頁面。無論什麼時候,當任務等待佔有所有頁面時,它將在sys.dm_os_waiting_tasks中顯示。因為這種等待是短暫的,你需要頻繁的查詢這個表(大約每10秒一次)並收集這些資料以後分析。例如,你可以使用下列查詢將在tempdb中所有等待頁面的任務載入到在分析資料庫中的waiting_task表。

-- get the current timestamp
declare @now datetime
select @now = getdate()

-- insert data into a table for later analysis
insert into analysis..waiting_tasks
      select
          session_id,
          wait_duration_ms,
          resource_description,
          @now
      from sys.dm_os_waiting_tasks
      where wait_type like ‘PAGE%LATCH_%’ and
            resource_description like ‘2:%’
當需要時你可以看到在tempdb頁面中等待鎖的任務,這樣你可以分析是否歸咎於PFS或SGAM分頁。如果是,這意味著在tempdb上有分配爭用。如果你在tempdb上的其他頁面爭用,如果你確定這個頁面屬於系統表,這意味著由於過度的DDL導致爭用。
你也可以使用下列的效能計數器監視臨時物件分配/定位操作得不正常的增加,

•    SQL Server:Access Methods\Workfiles Created /Sec
•    SQL Server:Access Methods\Worktables Created /Sec
•    SQL Server:Access Methods\Mixed Page Allocations /Sec
•    SQL Server:General Statistics\Temp Tables Created /Sec
•    SQL Server:General Statistics\Temp Tables for destruction
解決
如果是由於過多的DDL操作導致在tempdb爭用,你需要考慮你的應用程式,並檢視是否你能減少DDL操作。你可以嘗試下列建議。
•    如果你使用儲存過程範圍內的臨時表,考慮是否這些表可以移動到儲存過程外。否則每次執行儲存過程將會導致建立/刪除臨時表。
•    檢視查詢計劃,是否一些計劃建立大量的臨時物件,池,排序或工作表。你需要評估一些臨時物件。例如,在一個列上建立一個用於ORDER BY操作的索引可以除去查詢時的排序
如果爭用是由於在SGAM 和PFS頁面上的爭用,你可以透過嘗試下列操作減輕爭用:
•    透過增加tempdb資料檔案將等量負載分佈在所有磁碟和檔案上。理論上,你應該將檔案數量設定為CPU數量等同(主要考慮親和性)。
•    使用TF-1118消除混合擴充套件分配。
執行緩慢的查詢
緩慢或長時間執行的查詢可以佔用過多資源並能導致阻塞查詢。
過多的資源佔用是沒有限制CPU資源的使用,但是也能包括I/O儲存頻寬和記憶體頻寬。即使SQL Server查詢被設計為可以透過合理WHERE子句限制結果集的方法避免整表掃描 ,如果沒有合適的索引支援這個特殊的查詢,他們可能不會按照我們期望的方式執行。同樣,WHERE子句能依賴於使用者輸入被動態的透過應用程式構建。假設存在的索引不能覆蓋所有可能的約束。透過Transact-SQL語句佔用過度的CPU,I/O和記憶體的情況在本白皮書前面已經描述了。
除了缺失索引外,也可能有索引沒有被使用。當所有的索引不得不維護時,這不影響查詢的效能,但是影響DML查詢。
因為等待邏輯鎖和系統資源的狀態會阻塞查詢,查詢也會執行的比較緩慢。阻塞的原因可能是較差的應用程式設計,壞的查詢計劃,缺乏有用的索引和不正確的SQL Server例項配置。
這節主要集中在緩慢查詢的2個原因-阻塞和索引問題。
阻塞
阻塞主要是等待邏輯鎖,例如等待在資源上獲取排他鎖或等待從更低階別的同步結果,例如閂。
當做出一個在已經鎖定的資源上獲得一個不相容的鎖的請求產生時,邏輯鎖等待發生。在特殊的Transact-SQL語句執行時,透過使用鎖可以基於事務隔離級別提供資料一致性的功能,這樣給終端使用者的感覺是SQL Server執行緩慢。當查詢被阻塞時,它不佔用任何系統資源,所以你將發現它執行很長時間但是資源佔用卻很少。更多關於併發控制和阻塞的資訊請檢視SQL Server聯機叢書。
如果你的系統沒有被配置為處理這種負載就會導致等待底層的原始同步。
一般阻塞和等待的場景是:
•    識別阻塞者
•    識別長的阻塞
•    阻塞每個物件
•    頁面閉鎖問題
•    阻塞影響整體效能
如果系統資源(或鎖)當前不能服務於請求,這個SQL Server會話將被置於等待狀態。換句話說,資源有一個等待請求的佇列。DMV能提供任何等待資源的會話的資訊。
SQL Server 2005提供了更詳細和一致的等待資訊,有大約125種等待型別而SQL Server 2000只有76種可用的等待型別。DMV提供的資訊範圍從sys.dm_os_wait_statistics中表現SQL Server全面和積累的等待資訊,到sys.dm_os_waiting_tasks中與會話相關分解的等待資訊。下列DMV提供了詳細的等待某些資源的任務等待佇列。它同樣表現了在系統中所有的等待佇列。例如你可以執行下列查詢找到關於阻塞會話56的詳細資訊。

select * from sys.dm_os_waiting_tasks where session_id=56

waiting_task_address session_id exec_context_id wait_duration_ms     wait_type                                                    resource_address   blocking_task_address blocking_session_id blocking_exec_context_id resource_description
-------------------- ---------- --------------- -------------------- ------------------------------------------------------------ ------------------ --------------------- ------------------- ------------------------ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
0x022A8898           56         0               1103500              LCK_M_S                                                      0x03696820         0x022A8D48            53                  NULL                     ridlock fileid=1 pageid=143 dbid=9 id=lock3667d00 mode=X associatedObjectId=72057594038321152

這個結果顯示了會話56被會話54阻塞了,會話56已經為一個鎖等待了1103500毫秒。
為了找到准許的鎖或等待鎖的會話,你可以使用sys.dm_tran_locks DMV。每行資料展現了傳送到鎖管理器的當前活動的請求。為了有序的鎖,准許請求指出了鎖已經在資源上被准許給請求者。一個等待的請求指出了請求沒有被准許。例如下列查詢顯示會話56被阻塞在資源1:143:3,該資源被會話53的X模式鎖佔有。
select
    request_session_id as spid,
    resource_type as rt,  
    resource_database_id as rdb,
    (case resource_type
      WHEN 'OBJECT' then object_name(resource_associated_entity_id)
      WHEN 'DATABASE' then ' '
      ELSE (select object_name(object_id)
            from sys.partitions
            where hobt_id=resource_associated_entity_id)
    END) as objname,
    resource_description as rd,  
    request_mode as rm,
    request_status as rs
from sys.dm_tran_locks

Here is the sample output
spid     rt           rdb         objname       rd            rm           rs
-----------------------------------------------------------------------------                                                                                                        56    DATABASE       9                               S          GRANT
53    DATABASE       9                                S          GRANT
56    PAGE           9       t_lock      1:143       IS         GRANT
53    PAGE        9       t_lock      1:143       IX         GRANT
53    PAGE           9       t_lock      1:153       IX         GRANT
56   OBJECT       9       t_lock                  IS         GRANT
53   OBJECT       9        t_lock                 IX         GRANT
53    KEY         9        t_lock      (a400c34cb X          GRANT
53    RID         9        t_lock      1:143:3    X          GRANT
56    RID         9        t_lock      1:143:3    S           WAIT

實際上,你能連線上面的2個DMV,就像使用儲存過程sp_block鎖展示的。在圖1種阻塞報告列出了被阻塞的會話和阻塞它的會話。你可以在附錄B中找到sp_block的原始碼。如果你需要新增/刪除在可選擇列表中的列時,你可以根據需求修改儲存過程。可選的@spid引數提供了在鎖請求和阻塞這個spid的會話資訊。

 
圖1: sp_block 報表
在SQL Server 2000中,你能透過下列語句檢視被阻塞的spid資訊。
select * from master..sysprocesses where blocked <> 0.
聯合鎖可以透過儲存過程sp_lock儲存過程。
識別長時間的阻塞
之前我們提到,在SQL Server中阻塞是很正常的,使用邏輯鎖來維護事務一致性的。然而當等待的鎖超過了閥值,它會影響響應時間。為了識別長時間執行的阻塞,你能使用BlockedProcessThreshold配置引數來建立一個使用者配置的服務端阻塞閥值。閥值定義一個秒級的間隔。任何超過閥值的阻塞將出發事件並被SQL Trace捕獲。
例如,1個200秒的阻塞程式閥值可以在SQL Management Studio中配置。例如:
Execute Sp_configure ‘blocked process threshold’, 200  
Reconfigure with override
一旦阻塞處理閥值被建立,下一步是捕獲跟蹤的事件。跟蹤阻塞超過使用者配置的閥值事件可以透過SQL Trace 或Profiler捕獲。
1.    如果使用SQL Trace,使用sp_trace_setevent過程,event_id引數為137
2.    如果使用SQL Server Profiler,選擇Blocked Process Report 事件類(在Error和Warnings物件下),如圖2。

 
圖2: 跟蹤長時間的阻塞和死鎖

注意這是一個輕量級的跟蹤,事件僅在當鎖超過閥值,或發生死鎖時被捕獲。每有200秒的間隔一個鎖被阻塞,1個跟蹤事件被觸發。圖3意味著1個鎖阻塞了600秒,發生了3次跟蹤事件。
 
圖3: Reporting Blocking > block threshold

跟蹤事件包括阻塞者和被阻塞者整個SQL語句。圖中所示”Update Customer”阻塞了”Select from Customer”語句。
透過與SQL Server 2000比較,檢查長期阻塞場景程式碼在Sysprocesses並在後續處理結果。知識庫文章271509包含了一段可以用於監視阻塞的示例程式碼。
透過sys.dm_db_index_operational_stats檢視阻塞的每個物件
新的SQL Server 2005 DMV Sys.dm_db_index_operational_stats提供了全面的索引使用統計,包括阻塞。根據阻塞,它提供了每個表,索引,分割槽的鎖統計的詳細資訊。例如,在給定索引和表上的訪問歷史,鎖數量(row_lock_count),阻塞數量(row_lock_wait_count)和等待時間(row_lock_wait_in_ms)等資訊。這個DMV包括的型別資訊有:
•    佔有的鎖的數量,例如行或頁。
•    阻塞或等待的數量,例如,行或頁。
•    阻塞或等待持續的時間,例如行或頁。
•    頁面上閂的等待。
•    page_latch_wait持續時間:這包括特殊頁上的爭用,升序鍵的插入。在這種情況,熱點是最後的頁面,所以多個寫入這到最後的頁面每次嘗試獲取高階的頁面閂在同樣的時間。這將作為Pagelatch waits暴露。
•    page_io_latch_wait持續的時間:當使用者請求一個不在快取池的頁面時發生的I/O閂。一個慢速的I/O子系統或過多工作的I/O子系統將遇到很高的PageIOlatch等待,這實際上是I/O問題。這個問題被混在於快取清除和缺失索引中。
•    頁面閂等待持續的時間。
除了阻塞相關資訊外,這還有額外的資訊。
•    訪問型別, 包括range, singleton lookups.
•    在頁級的插入,更新,刪除。
•    在頁級之上插入,更新或刪除。在葉上的活動是索引維護。在每個葉級頁面中的第一行有這個級別之上的條目。如果新的頁面被分配到葉級別上,這級別之上將為新的葉級頁面的第一行建立新的項。
•    在葉級別的頁面合併將表現為重新分配的空頁,因為行已經刪除了。
•    索引維護。在葉級上頁面合併就是將空白頁重分配,這導致在葉上行被刪除,因此留下的中間級別頁面是空白的。在葉級頁面的第一行有一個條目在該層上。如果在葉級別刪除了足夠的行,原來包含第一行葉級頁面條目的中間層索引頁面也會是空白的。這導致在葉結點上的合併發生。
這些資訊積累了從例項啟動以來的資訊。資訊不會一直保留直到例項被重新啟動,並且沒有其他的方法可以重置它。這個DMV返回的資料僅在後設資料快取物件表現的堆或索引可用的情況下存在。只要堆和索引的後設資料被載入到了後設資料快取,每個列的這個值將被設定為0。統計是被累加的直到快取物件被從後設資料快取中刪除。然而,你可以週期性的查詢這個表來收集在表中的資訊,用於更進一步的分析。
附錄B定義一套儲存過程可以用於收集索引操作的資料。你能分析你感興趣的時間短的資料。這裡是如何使用定義的儲存過程的步驟。
1.    使用init_index_operational_stats初始化indexstats 表
2.    使用insert_indexstats. 捕獲基線資料
3.    執行負載
4.    透過使用insert_indexstats. 捕獲最後索引統計
5.    為了分析收集的索引統計,執行儲存過程get_indexstats來生成每個索引上鎖的平均數(索引和分割槽的row_lock_count),阻塞和等待。高的blocking %和/或高的平均等待指出設計不好的索引或查詢公式。
這有一些示例展現了你能使用這些儲存過程的型別
•    獲取所有資料庫上使用率最高的5個索引
       exec get_indexstats
            @dbid=-1,
            @top='top 5',
            @columns='index, usage',
            @order='index usage'
•    獲取前5個索引鎖提示(所有列)
     exec get_indexstats
            @dbid=-1,
            @top='top 5',
            @order='index lock promotions',
            @threshold='[index lock promotion attempts] > 0'
•    獲取5個平均行鎖等待大於2ms的獨立查詢,返回行包括wait,scan,singleton資訊。以'singleton lookups'排序
       exec get_indexstats
        @dbid=5,
        @top='top 5',
        @columns='wait,scan,singleton',
        @order='singleton lookups',
        @threshold='[avg row lock wait ms] > 2'
•    獲取所有資料庫行鎖等待大於1,列包含‘avg,wait’,以'row lock wait ms'排序
  exec get_indexstats
            @dbid=-1,
            @top='top 10 ',
            @columns='wait,row',
            @order='row lock wait ms',
             @threshold='[row lock waits] > 1'
•    獲取前5個索引狀態,透過'avg row lock wait ms'排序
       exec get_indexstats
        @dbid=-1,
        @top='top 5',
        @order='avg row lock wait ms'
•    獲取前5個索引狀態,透過'avg page latch wait ms'排序
      exec get_indexstats
        @dbid=-1,
        @top='top 5',
        @order='avg page latch wait ms'
•    獲取前 5%索引狀態, 透過 avg pageio latch waits.
      exec get_indexstats
        @dbid=-1,
        @top='top 3 percent',
        @order='avg pageio latch wait ms',
        @threshold='[pageio latch waits] > 0'
•    獲取所有在資料庫5中的索引狀態並且block% > 0.1,以block%排序。
       exec get_indexstats
        @dbid=-1,
        @top='top 10',
        @order='block %',
        @threshold='[block %] > 0.1'

如圖4示例,阻塞分析報告
   
 
圖4:  阻塞分析報告

SQL Server 2000部提供任何物件或索引的統計利用資訊。
使用SQL waits阻塞對整體效能的影響
SQL Server 2000提供了76種等待型別來提供等待報告。SQL Server 2005提供了多餘100個等待型別來跟蹤應用程式效能。任何時間1個使用者連線在等待時,SQL Server會累加等待時間。例如應用程式請求資源例如I/O,鎖或記憶體,可以等待資源直到可用。這些等待資訊可以跨所有連線將被彙總和分類,所以效能配置可以從給定的負載獲得。因此,SQL等待型別從應用程式負載或使用者觀點識別和分類使用者(或執行緒)等待。
這個查詢列出了在SQL Server中前10位的等待。這些等待時累積的,但是你可以使用DBCC SQLPERF ([sys.dm_os_wait_stats], clear)重置這個計數器。

select top 10 *
from sys.dm_os_wait_stats
order by wait_time_ms desc

下列是輸出,要注意幾個關鍵點:
•    一些等待是正常的例如後臺執行緒的等待,例如lazy writer元件。
•    一些會話為獲取共享鎖等待很長時間
•    訊號等待是在一個工作執行緒獲取對資源訪問到它被拿到CPU上排程執行這段時間。長時間的訊號等待也許意味著很高的CPU爭用。

wait_type     waiting_tasks_count  wait_time_ms     max_wait_time_ms  signal_wait_time_ms   
------------------ -------------------- -------------------- -------------------- -------
LAZYWRITER_SLEEP      415088               415048437            1812                 156
SQLTRACE_BUFFER_FLUSH 103762               415044000            4000                 0
LCK_M_S               6                    25016812             23240921             0
WRITELOG              7413                 86843                187                  406
LOGMGR_RESERVE_APPEND 82                   82000                1000                 0
SLEEP_BPOOL_FLUSH     4948                 28687                31                   15
LCK_M_X               1                    20000                20000                0
PAGEIOLATCH_SH        871                  11718                140                  15
PAGEIOLATCH_UP        755                  9484                 187                  0
IO_COMPLETION         636                  7031                 203                  0

為了分析等待狀態,你需要獲取資料,用於以後分析。附錄B提供了2個示例的儲存過程。
•    Track_waitstats.收集資料渴望的取樣數量和取樣的時間間隔。這裡有一個呼叫的示例
exec dbo.track_waitstats @num_samples=6
    ,@delay_interval=30
    ,@delay_type='s'
    ,@truncate_history='y'
                  ,@clear_waitstats='y'
•    Get_waitstats.分析前面步驟收集到的資料。這有一個呼叫的示例。
exec [dbo].[get_waitstats_2005]
•    Spid執行,需要當前不可用的資源。因為資源不可用,在T0時,它移動到資源等待列表。
•    訊號指出資源是可用的,所以spid在T1時間移動到可執行佇列。
•    Spid等候執行狀態直到T2,同樣的CPU透過可執行佇列處理按順序到達等待。
你可以使用這些儲存過程分析資源等待和訊號等待,使用這些資訊隔離資源爭用。
圖5 顯示了示例的報告。

 
圖5:  等待統計分析報告
圖5中現實的等待狀態分析報告預示了由於阻塞(LCK_M_S)和記憶體分配(RESOURCE_SEMAPHORE)的效能問題。特定的55%的所有等待是等待共享鎖,而43%是由於記憶體請求。阻塞每個物件的分析將是識別主要的爭用點。
監視索引的使用
其他方面的查詢效能與DML查詢,查詢刪除,插入和修改資料相關。在指定表上定義更多索引,在需要資料修改時就需要更多的資源。由於鎖結合持續事務,時間長的修改操作可以損害併發性。因此在應用程式中使用那個索引就變得非常重要。你能計算出是否在資料庫架構上有大量從未使用過的索引存在。
SQL Server 2005提供了新的sys.dm_db_index_usage_stats動態管理檢視顯示哪些索引是使用的,和是否他們被用於使用者查詢或僅用於系統操作。每次執行查詢,在這個檢視中的列將根據用於執行查詢的查詢計劃將會增加。當SQL Server啟動並執行,資料就被收集了。這個DMV中的資料只是儲存在記憶體中的,沒有持久化。所以當SQL Server例項關閉,資料將會丟失。你可以週期性的獲取這個表,並將資料儲存用於以後分析。
在索引操作被分為使用者型別和系統型別。使用者型別引用SELECT和INSERT/DELETE/UPDATE操作。系統型別操作是類似於DBCC這樣的命令或DDL命令或是update statistics。每種類別的語句列被區分為:
•    依靠索引的SEEK操作 (user_seeks or system_seeks)
•    依靠索引的LOOKUP操作(user_lookups or system_lookups)
•    依靠索引的SCAN操作(user_scans or system_scans)
•    依靠索引的UPDATE操作(user_updates or system_updates)
每種訪問索引都會記路最後一次訪問的時間戳。一個索引本身透過3列識別,database_id,object_id和index_id。然而,index_id=0代表是一個堆表,index_id=1代表時集束索引,反之index_id>1但表是非集束索引。
一個整天執行的資料庫應用程式,從sys.dm_db_index_usage_stats中得到的索引訪問資訊列表將增長。
下列是在SQL Server 2005使規則和任務的定義:
•    SEEK: 識別用於訪問資料的B樹結構數量。不論B樹結構只是訪問每級只有少量頁面來獲取一個資料行,還是是表中使用半索引頁面讀取如幾個G資料或百萬行的資料。所以我們希望在這個類別有更多的累計。
•    SCAN: 識別不使用B樹索引獲取資料的資料表數量。沒有任何索引定義的表屬於這種情況。有索引定義但是在執行語句查詢時並沒有使用這些說印的表也屬於這種情況。
•    LOOKUP: 識別在一個集束索引透過’seeking’在一個非集束索引上查詢資料,2個索引都定義在同一張表上。這種場景描述在SQL Server 2000中的書籤查詢。它表現了這樣一個場景,非集束索引被用於訪問表,並且非集束索引沒有覆蓋查詢的列和索引列沒有在WHERE子句定義,SQL Server將使用非集束索引列的user_seeks值加上使用集束索引列的user_lookups值。這個值能變得很高如果多個非集束索引在這個表上定義。如果依靠集束索引的user_seeks值高,user_lookups的數量也會很高,加上一部分user_seeks也是很高,應該透過將非集束索引大量的高於集束索引。
下列DMV查詢可以被用於獲取在所有資料庫中所有物件上關於索引使用資訊。
select object_id, index_id, user_seeks, user_scans, user_lookups
from sys.dm_db_index_usage_stats
order by object_id, index_id

你能看到下列結果:

object_id       index_id    user_seeks    user_scans    user_lookups
------------      ------------- -------------- --------------  -----------------
521690298         1                  0                 251                 123
521690298         2                123                 0                     0

在這種情況有251次查詢的執行直接訪問資料層表而不使用索引。有123次查詢的執行透過使用非集束索引訪問表,但是沒有覆蓋查詢選擇列表或在WHERE子句指定列,因為我們看到了123次在集束索引的lookup訪問。
最有趣的類別著眼於‘user type statement’型別。使用方法指出在‘system category’可以被看作為存在索引的結果。如果索引不存在,它不會更新統計,也不需要檢查一致性。因此分析需要著眼於4列顯示獨立語句的使用或分析使用者應用程式。
為了獲取從上次SQL Server啟動以來,關於指定表沒有使用的索引資訊,這種查詢將在資料庫上下文中執行。

select i.name
from sys.indexes i
where i.object_id=object_id('') and
    i.index_id NOT IN  (select s.index_id
                        from sys.dm_db_index_usage_stats s
                        where s.object_id=i.object_id and     
                        i.index_id=s.index_id and
                        database_id = )
所有沒有被使用的索引仍可以透過下列語句獲取資訊:
select object_name(object_id), i.name
from sys.indexes i
where  i.index_id NOT IN (select s.index_id
                          from sys.dm_db_index_usage_stats s
                          where s.object_id=i.object_id and
                          i.index_id=s.index_id and
                          database_id = )
order by object_name(object_id) asc

在正中情況下,表名稱和索引名稱根據表明排序。
.動態管理檢視的真正目的是在長時間執行時觀察索引的使用情況。它可以提供檢視的快照或查詢結果集,將其儲存,然後每天比較相應的改變。如果你能識別特殊的索引數月沒有使用或者在很長時間沒有使用,你可以最終從資料庫中刪除他們。
總結
更多資訊請見
[url][/url]

附錄A: DBCC MEMORYSTATUS 描述
這有一些使用DBCC MEMORYSTATUS命令的資訊。可是,一些資訊也可以使用動態管理檢視(DMVs)獲取。
SQL Server 2000 DBCC MEMORYSTATUS在[url][/url]中描述
SQL Server 2005 DBCC MEMORYSTATUS 在[url][/url]中描述
附錄B: 阻塞指令碼
附錄提供在本白皮書中引用的儲存過程原始碼列表。你可以根據你的需求修改或裁減這些儲存過程。

sp_block
create proc dbo.sp_block (@spid bigint=NULL)
as
-- This stored procedure is provided "AS IS" with no warranties, and
-- confers no rights.
-- Use of included script. samples are subject to the terms specified at -- [url][/url]
--
-- T. Davidson
-- This proc reports blocks
--    1. optional parameter @spid
--


select
    t1.resource_type,
    'database'=db_name(resource_database_id),
    'blk object' = t1.resource_associated_entity_id,
    t1.request_mode,
    t1.request_session_id,
    t2.blocking_session_id     
from
    sys.dm_tran_locks as t1,
    sys.dm_os_waiting_tasks as t2
where
    t1.lock_owner_address = t2.resource_address and
    t1.request_session_id = isnull(@spid,t1.request_session_id)
 
分析操作的索引統計
這套儲存過程可以用於分析索引的使用。
get_indexstats
create proc dbo.get_indexstats
    (@dbid smallint=-1
    ,@top varchar(100)=NULL
    ,@columns varchar(500)=NULL
    ,@order varchar(100)='lock waits'
    ,@threshold varchar(500)=NULL)
as
--
-- This stored procedure is provided "AS IS" with no warranties, and confers no rights.
-- Use of included script. samples are subject to the terms specified at [url][/url]
--
-- T. Davidson
-- This proc analyzes index statistics including accesses, overhead,
--            locks, blocks, and waits
--
-- Instructions: Order of execution is as follows:
--    (1) truncate indexstats with init_indexstats
--    (2) take initial index snapshot using insert_indexstats
--    (3) Run workload
--    (4) take final index snapshot using insert_indexstats
--    (5) analyze with get_indexstats    

-- @dbid limits analysis to a database
-- @top allows you to specify TOP n
-- @columns is used to specify what columns from
--            sys.dm_db_index_operational_stats will be included in the report
--            For example, @columns='scans,lookups,waits' will include columns
--            containing these keywords
-- @order used to order results
-- @threshold used to add a threshold,
--            example: @threshold='[block %] > 5' only include if blocking is over 5%
--
------  definition of some computed columns returned
-- [blk %] = percentage of locks that cause blocks e.g. blk% = 100 * lock waits / locks
-- [index usage] = range_scan_count + singleton_lookup_count + leaf_insert_count
-- [nonleaf index overhead]=nonleaf_insert_count + nonleaf_delete_count + nonleaf_update_count
-- [avg row lock wait ms]=row_lock_wait_in_ms/row_lock_wait_count
-- [avg page lock wait ms]=page_lock_wait_in_ms/page_lock_wait_count
-- [avg page latch wait ms]=page_latch_wait_in_ms/page_latch_wait_count
-- [avg pageio latch wait ms]=page_io_latch_wait_in_ms/page_io_latch_wait_count
---------------------------------------------------------------------------------------------------
--- Case 1 - only one snapshot of sys.dm_db_operational_index_stats was stored in
---            indexstats. This is an error - return errormsg to user
--- Case 2 - beginning snapshot taken, however some objects were not referenced
---            at the time of the beginning snapshot. Thus, they will not be in the initial
---            snapshot of sys.dm_db_operational_index_stats, use 0 for starting values.
---            Print INFO msg for informational purposes.
--- Case 3 - beginning and ending snapshots, beginning values for all objects and indexes
---            this should be the normal case, especially if SQL Server is up a long time
---------------------------------------------------------------------------------------------------
set nocount on
declare @orderby varchar(100), @where_dbid_is varchar(100), @temp varchar(500), @threshold_temptab varchar(500)
declare @cmd varchar(max),@col_stmt varchar(500),@addcol varchar(500)
declare @begintime datetime, @endtime datetime, @duration datetime, @mincount int, @maxcount int

select @begintime = min(now), @endtime = max(now) from indexstats

if @begintime = @endtime
    begin
        print 'Error: indexstats contains only 1 snapshot of sys.dm_db_index_operational_stats'
        print 'Order of execution is as follows: '
        print '    (1) truncate indexstats with init_indexstats'
        print '    (2) take initial index snapshot using insert_indexstats'
        print '    (3) Run workload'
        print '    (4) take final index snapshot using insert_indexstats'
        print '    (5) analyze with get_indexstats'
        return -99
    end

select @mincount = count(*) from indexstats where now = @begintime
select @maxcount = count(*) from indexstats where now = @endtime

if @mincount < @maxcount
    begin
        print 'InfoMsg1: sys.dm_db_index_operational_stats only contains entries for objects referenced since last SQL re-cycle'
        print 'InfoMsg2: Any newly referenced objects and indexes captured in the ending snapshot will use 0 as a beginning value'
    end

select @top = case
        when @top is NULL then ''
        else lower(@top)
    end,
        @where_dbid_is = case (@dbid)
        when -1 then ''
        else ' and i1.database_id = ' + cast(@dbid as varchar(10))
    end,
--- thresholding requires a temp table
        @threshold_temptab = case
        when @threshold is NULL then ''
        else ' select * from #t where ' + @threshold
    end
--- thresholding requires temp table, add 'into #t' to select statement
select @temp = case (@threshold_temptab)
        when '' then ''
        else ' into #t '
    end
select @orderby=case(@order)
when 'leaf inserts' then 'order by [' + @order + ']'
when 'leaf deletes' then 'order by [' + @order + ']'
when 'leaf updates' then 'order by [' + @order + ']'
when 'nonleaf inserts' then 'order by [' + @order + ']'
when 'nonleaf deletes' then 'order by [' + @order + ']'
when 'nonleaf updates' then 'order by [' + @order + ']'
when 'nonleaf index overhead' then 'order by [' + @order + ']'
when 'leaf allocations' then 'order by [' + @order + ']'
when 'nonleaf allocations' then 'order by [' + @order + ']'
when 'allocations' then 'order by [' + @order + ']'
when 'leaf page merges' then 'order by [' + @order + ']'
when 'nonleaf page merges' then 'order by [' + @order + ']'
when 'range scans' then 'order by [' + @order + ']'
when 'singleton lookups' then 'order by [' + @order + ']'
when 'index usage' then 'order by [' + @order + ']'
when 'row locks' then 'order by [' + @order + ']'
when 'row lock waits' then 'order by [' + @order + ']'
when 'block %' then 'order by [' + @order + ']'
when 'row lock wait ms' then 'order by [' + @order + ']'
when 'avg row lock wait ms' then 'order by [' + @order + ']'
when 'page locks' then 'order by [' + @order + ']'
when 'page lock waits' then 'order by [' + @order + ']'
when 'page lock wait ms' then 'order by [' + @order + ']'
when 'avg page lock wait ms' then 'order by [' + @order + ']'
when 'index lock promotion attempts' then 'order by [' + @order + ']'
when 'index lock promotions' then 'order by [' + @order + ']'
when 'page latch waits' then 'order by [' + @order + ']'
when 'page latch wait ms' then 'order by [' + @order + ']'
when 'pageio latch waits' then 'order by [' + @order + ']'
when 'pageio latch wait ms' then 'order by [' + @order + ']'
else ''
end

if @orderby <> '' select @orderby = @orderby + ' desc'
select
    'start time'=@begintime,
    'end time'=@endtime,
    'duration (hh:mm:ss:ms)'=convert(varchar(50),
    @endtime-@begintime,14),
    'Report'=case (@dbid)
               when -1 then 'all databases'
               else db_name(@dbid)
             end +
        
            case
                when @top = '' then ''
                when @top is NULL then ''
                when @top = 'none' then ''
                else ', ' + @top
            end +
            case
                when @columns = '' then ''
                when @columns is NULL then ''
                when @columns = 'none' then ''
                else ', include only columns containing ' + @columns
            end +
            case(@orderby)
                when '' then ''
                when NULL then ''
                when 'none' then ''
                else ', ' + @orderby
            end +
            case
                when @threshold = '' then ''
                when @threshold is NULL then ''
                when @threshold = 'none' then ''
                else ', threshold on ' + @threshold
            end

select @cmd = ' select i2.database_id, i2.object_id, i2.index_id, i2.partition_number '
select @cmd = @cmd +' , begintime=case min(i1.now) when max(i2.now) then NULL else min(i1.now) end '
select @cmd = @cmd +'     , endtime=max(i2.now) '
select @cmd = @cmd +' into #i '
select @cmd = @cmd +' from indexstats i2 '
select @cmd = @cmd +' full outer join '
select @cmd = @cmd +'     indexstats i1 '
select @cmd = @cmd +' on i1.database_id = i2.database_id '
select @cmd = @cmd +' and i1.object_id = i2.object_id '
select @cmd = @cmd +' and i1.index_id = i2.index_id '
select @cmd = @cmd +' and i1.partition_number = i2.partition_number '
select @cmd = @cmd +' where i1.now >= ''' +  convert(varchar(100),@begintime, 109) + ''''
select @cmd = @cmd +' and i2.now = ''' + convert(varchar(100),@endtime, 109) + ''''
select @cmd = @cmd + ' ' + @where_dbid_is + ' '
select @cmd = @cmd + ' group by i2.database_id, i2.object_id, i2.index_id, i2.partition_number '
select @cmd = @cmd + ' select ' + @top + ' i.database_id, db_name=db_name(i.database_id), bject=isnull(object_name(i.object_id),i.object_id), indid=i.index_id, part_no=i.partition_number '

exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[leaf inserts]=i2.leaf_insert_count -  
         isnull(i1.leaf_insert_count,0)'

select @cmd = @cmd +@addcol
exec dbo.add_column
     @add_stmt=@addcol out,
     @cols_containing=@columns,@col_stmt=' ,
       [leaf deletes]=i2.leaf_delete_count –
       isnull(i1.leaf_delete_count,0)'

select @cmd = @cmd + @addcol

exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[leaf updates]=i2.leaf_update_count - isnull(i1.leaf_update_count,0)'

select @cmd = @cmd + @addcol

exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[nonleaf inserts]=i2.nonleaf_insert_count - isnull(i1.nonleaf_insert_count,0)'

select @cmd = @cmd + @addcol

exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[nonleaf deletes]=i2.nonleaf_delete_count - isnull(i1.nonleaf_delete_count,0)'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[nonleaf updates]=i2.nonleaf_update_count - isnull(i1.nonleaf_update_count,0)'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[nonleaf index overhead]=(i2.nonleaf_insert_count –
isnull(i1.nonleaf_insert_count,0)) + (i2.nonleaf_delete_count - isnull(i1.nonleaf_delete_count,0)) + (i2.nonleaf_update_count - isnull(i1.nonleaf_update_count,0))'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[leaf allocations]=i2.leaf_allocation_count - isnull(i1.leaf_allocation_count,0)'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[nonleaf allocations]=i2.nonleaf_allocation_count - isnull(i1.nonleaf_allocation_count,0)'

select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[allocations]=(i2.leaf_allocation_count - isnull(i1.leaf_allocation_count,0)) + (i2.nonleaf_allocation_count - isnull(i1.nonleaf_allocation_count,0))'

select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[leaf page merges]=i2.leaf_page_merge_count - isnull(i1.leaf_page_merge_count,0)'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[nonleaf page merges]=i2.nonleaf_page_merge_count - isnull(i1.nonleaf_page_merge_count,0)'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[range scans]=i2.range_scan_count - isnull(i1.range_scan_count,0)'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing= @columns,
    @col_stmt=' ,[singleton lookups]=i2.singleton_lookup_count - isnull(i1.singleton_lookup_count,0)'

select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[index usage]=(i2.range_scan_count - isnull(i1.range_scan_count,0)) + (i2.singleton_lookup_count - isnull(i1.singleton_lookup_count,0)) + (i2.leaf_insert_count - isnull(i1.leaf_insert_count,0))'
select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[row locks]=i2.row_lock_count - isnull(i1.row_lock_count,0)'
select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[row lock waits]=i2.row_lock_wait_count - isnull(i1.row_lock_wait_count,0)'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[block %]=cast (100.0 * (i2.row_lock_wait_count - isnull(i1.row_lock_wait_count,0)) / (1 + i2.row_lock_count - isnull(i1.row_lock_count,0)) as numeric(5,2))'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[row lock wait ms]=i2.row_lock_wait_in_ms - isnull(i1.row_lock_wait_in_ms,0)'

select @cmd = @cmd + @addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[avg row lock wait ms]=cast ((1.0*(i2.row_lock_wait_in_ms - isnull(i1.row_lock_wait_in_ms,0)))/(1 + i2.row_lock_wait_count - isnull(i1.row_lock_wait_count,0)) as numeric(20,1))'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[page locks]=i2.page_lock_count - isnull(i1.page_lock_count,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[page lock waits]=i2.page_lock_wait_count - isnull(i1.page_lock_wait_count,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[page lock wait ms]=i2.page_lock_wait_in_ms - isnull(i1.page_lock_wait_in_ms,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column    
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[avg page lock wait ms]=cast ((1.0*(i2.page_lock_wait_in_ms - isnull(i1.page_lock_wait_in_ms,0)))/(1 + i2.page_lock_wait_count - isnull(i1.page_lock_wait_count,0)) as numeric(20,1))'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[index lock promotion attempts]=i2.index_lock_promotion_attempt_count - isnull(i1.index_lock_promotion_attempt_count,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[index lock promotions]=i2.index_lock_promotion_count - isnull(i1.index_lock_promotion_count,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[page latch waits]=i2.page_latch_wait_count - isnull(i1.page_latch_wait_count,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[page latch wait ms]=i2.page_latch_wait_in_ms - isnull(i1.page_latch_wait_in_ms,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[avg page latch wait ms]=cast ((1.0*(i2.page_latch_wait_in_ms - isnull(i1.page_latch_wait_in_ms,0)))/(1 + i2.page_latch_wait_count - isnull(i1.page_latch_wait_count,0)) as numeric(20,1))'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[pageio latch waits]=i2.page_io_latch_wait_count - isnull(i1.page_latch_wait_count,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[pageio latch wait ms]=i2.page_io_latch_wait_in_ms - isnull(i1.page_latch_wait_in_ms,0)'
select @cmd = @cmd +@addcol
exec dbo.add_column
    @add_stmt=@addcol out,
    @cols_containing=@columns,
    @col_stmt=' ,[avg pageio latch wait ms]=cast ((1.0*(i2.page_io_latch_wait_in_ms - isnull(i1.page_io_latch_wait_in_ms,0)))/(1 + i2.page_io_latch_wait_count - isnull(i1.page_io_latch_wait_count,0)) as numeric(20,1))'

select @cmd = @cmd +@addcol
select @cmd = @cmd + @temp
select @cmd = @cmd + ' from #i i '
select @cmd = @cmd + ' left join indexstats i1 on i.begintime = i1.now and i.database_id = i1.database_id and i.object_id = i1.object_id and i.index_id = i1.index_id and i.partition_number = i1.partition_number '

select @cmd = @cmd + ' left join indexstats i2 on i.endtime = i2.now and i.database_id = i2.database_id and i.object_id = i2.object_id and i.index_id = i2.index_id and i.partition_number = i2.partition_number '
select @cmd = @cmd + ' ' + @orderby + ' '
select @cmd = @cmd + @threshold_temptab
exec ( @cmd )
go
insert_indexstats
create proc insert_indexstats (@dbid smallint=NULL,
                               @objid int=NULL,
                               @indid int=NULL,
                               @partitionid int=NULL)
as
--
-- This stored procedure is provided "AS IS" with no warranties, and confers no rights.
-- Use of included script. samples are subject to the terms specified at [url][/url]
-- This stored procedure stores a snapshot of sys.dm_db_index_operational_stats into the table indexstas
-- for later analysis by the stored procedure get_indexstats. Please note that the indexstats table has an additional
-- column to store the timestamp when the snapshot is taken
--
-- T. Davidson
-- snapshot sys.dm_db_index_operational_stats
--
declare @now datetime
select @now = getdate()
insert into indexstats
        (database_id
        ,object_id
        ,index_id
        ,partition_number
        ,leaf_insert_count
        ,leaf_delete_count
        ,leaf_update_count
        ,leaf_ghost_count
        ,nonleaf_insert_count
        ,nonleaf_delete_count
        ,nonleaf_update_count
        ,leaf_allocation_count
        ,nonleaf_allocation_count
        ,leaf_page_merge_count
        ,nonleaf_page_merge_count
        ,range_scan_count
        ,singleton_lookup_count
        ,forwarded_fetch_count
        ,lob_fetch_in_pages
        ,lob_fetch_in_bytes
        ,lob_orphan_create_count
        ,lob_orphan_insert_count
        ,row_overflow_fetch_in_pages
        ,row_overflow_fetch_in_bytes
        ,column_value_push_off_row_count
        ,column_value_pull_in_row_count
        ,row_lock_count
        ,row_lock_wait_count
        ,row_lock_wait_in_ms
        ,page_lock_count
        ,page_lock_wait_count
        ,page_lock_wait_in_ms
        ,index_lock_promotion_attempt_count
        ,index_lock_promotion_count
        ,page_latch_wait_count
        ,page_latch_wait_in_ms
        ,page_io_latch_wait_count
        ,page_io_latch_wait_in_ms,
        now)
select  database_id
        ,object_id
        ,index_id
        ,partition_number
        ,leaf_insert_count
        ,leaf_delete_count
        ,leaf_update_count
        ,leaf_ghost_count
        ,nonleaf_insert_count
        ,nonleaf_delete_count
        ,nonleaf_update_count
        ,leaf_allocation_count
        ,nonleaf_allocation_count
        ,leaf_page_merge_count
        ,nonleaf_page_merge_count
        ,range_scan_count
        ,singleton_lookup_count
        ,forwarded_fetch_count
        ,lob_fetch_in_pages
        ,lob_fetch_in_bytes
        ,lob_orphan_create_count
        ,lob_orphan_insert_count
        ,row_overflow_fetch_in_pages
        ,row_overflow_fetch_in_bytes
        ,column_value_push_off_row_count
        ,column_value_pull_in_row_count
        ,row_lock_count
        ,row_lock_wait_count
        ,row_lock_wait_in_ms
        ,page_lock_count
        ,page_lock_wait_count
        ,page_lock_wait_in_ms
        ,index_lock_promotion_attempt_count
        ,index_lock_promotion_count
        ,page_latch_wait_count
        ,page_latch_wait_in_ms
        ,page_io_latch_wait_count
        ,page_io_latch_wait_in_ms
        ,@now
from sys.dm_db_index_operational_stats(@dbid,@objid,@indid,@partitionid)
go
init_index_operational_stats
CREATE proc dbo.init_index_operational_stats
as
--
-- This stored procedure is provided "AS IS" with no warranties, and
-- confers no rights.
-- Use of included script. samples are subject to the terms specified at
-- [url][/url]
--
-- T. Davidson
--
-- create indexstats table if it doesn't exist, otherwise truncate
--
set nocount on
if not exists (select 1 from dbo.sysobjects where id=object_id(N'[dbo].[indexstats]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
    create table dbo.indexstats (
        database_id smallint NOT NULL
        ,object_id int NOT NULL
        ,index_id int NOT NULL
        ,partition_number int NOT NULL
        ,leaf_insert_count bigint NOT NULL
        ,leaf_delete_count bigint NOT NULL
        ,leaf_update_count bigint NOT NULL
        ,leaf_ghost_count bigint NOT NULL
        ,nonleaf_insert_count bigint NOT NULL
        ,nonleaf_delete_count bigint NOT NULL
        ,nonleaf_update_count bigint NOT NULL
        ,leaf_allocation_count bigint NOT NULL
        ,nonleaf_allocation_count bigint NOT NULL
        ,leaf_page_merge_count bigint NOT NULL
        ,nonleaf_page_merge_count bigint NOT NULL
        ,range_scan_count bigint NOT NULL
        ,singleton_lookup_count bigint NOT NULL
        ,forwarded_fetch_count bigint NOT NULL
        ,lob_fetch_in_pages bigint NOT NULL
        ,lob_fetch_in_bytes bigint NOT NULL
        ,lob_orphan_create_count bigint NOT NULL
        ,lob_orphan_insert_count bigint NOT NULL
        ,row_overflow_fetch_in_pages bigint NOT NULL
        ,row_overflow_fetch_in_bytes bigint NOT NULL
        ,column_value_push_off_row_count bigint NOT NULL
        ,column_value_pull_in_row_count bigint NOT NULL
        ,row_lock_count bigint NOT NULL
        ,row_lock_wait_count bigint NOT NULL
        ,row_lock_wait_in_ms bigint NOT NULL
        ,page_lock_count bigint NOT NULL
        ,page_lock_wait_count bigint NOT NULL
        ,page_lock_wait_in_ms bigint NOT NULL
        ,index_lock_promotion_attempt_count bigint NOT NULL
        ,index_lock_promotion_count bigint NOT NULL
        ,page_latch_wait_count bigint NOT NULL
        ,page_latch_wait_in_ms bigint NOT NULL
        ,page_io_latch_wait_count bigint NOT NULL
        ,page_io_latch_wait_in_ms bigint NOT NULL
        ,now datetime default getdate())
else     truncate table dbo.indexstats
go
add_column
create proc dbo.add_column (
            @add_stmt varchar(500) output,
            @find varchar(100)=NULL,
            @cols_containing varchar(500)=NULL,    
            @col_stmt varchar(max))
as
--
-- This stored procedure is provided "AS IS" with no warranties, and
-- confers no rights.
-- Use of included script. samples are subject to the terms specified at
-- [url][/url]
--
-- T. Davidson
-- @add_stmt is the result passed back to the caller
-- @find is a keyword from @cols_containing
-- @cols_containing is the list of keywords to include in the report
-- @col_stmt is the statement that will be compared with @find.
--            If @col_stmt contains @find, include this statement.
--            set @add_stmt = @col_stmt
--
declare @length int, @strindex int, @EOS bit
if @cols_containing is NULL
    begin
        select @add_stmt=@col_stmt
        return
    end
select @add_stmt = '', @EOS = 0

while @add_stmt is not null and @EOS = 0
            @dbid=-1,
    select @strindex = charindex(',',@cols_containing)
    if @strindex = 0
            select @find = @cols_containing, @EOS = 1
    else
    begin
        select @find = substring(@cols_containing,1,@strindex-1)
        select @cols_containing =      
            substring(@cols_containing,
                      @strindex+1,
                      datalength(@cols_containing) - @strindex)
    end
    select @add_stmt=case
--when @cols_containing is NULL then NULL
    when charindex(@find,@col_stmt) > 0 then NULL
    else ''
    end
end
--- NULL indicates this statement is to be passed back through out parm @add_stmt
if @add_stmt is NULL select @add_stmt=@col_stmt
go
等待狀態
這套儲存過程可以在SQL Server中分析鎖。
track_waitstats_2005
CREATE proc [dbo].[track_waitstats_2005] (
                            @num_samples int=10,
                            @delay_interval int=1,
                            @delay_type nvarchar(10)='minutes',
                            @truncate_history nvarchar(1)='N',
                            @clear_waitstats nvarchar(1)='Y')
as
--
-- This stored procedure is provided "AS IS" with no warranties, and confers no rights.
-- Use of included script. samples are subject to the terms specified at [url][/url]
--
-- T. Davidson
-- @num_samples is the number of times to capture waitstats, default is 10 times
-- default delay interval is 1 minute
-- delaynum is the delay interval - can be minutes or seconds
-- delaytype specifies whether the delay interval is minutes or seconds
-- create waitstats table if it doesn't exist, otherwise truncate
-- Revision: 4/19/05
--- (1) added object owner qualifier
--- (2) optional parameters to truncate history and clear waitstats
set nocount on
if not exists (select 1
               from sys.objects
               where object_id = object_id ( N'[dbo].[waitstats]') and
               OBJECTPROPERTY(object_id, N'IsUserTable') = 1)
    create table [dbo].[waitstats]
        ([wait_type] nvarchar(60) not null,
        [waiting_tasks_count] bigint not null,
        [wait_time_ms] bigint not null,
        [max_wait_time_ms] bigint not null,
        [signal_wait_time_ms] bigint not null,
        now datetime not null default getdate())

If lower(@truncate_history) not in (N'y',N'n')
    begin
        raiserror ('valid @truncate_history values are ''y'' or ''n''',16,1) with nowait    
    end
If lower(@clear_waitstats) not in (N'y',N'n')
    begin
        raiserror ('valid @clear_waitstats values are ''y'' or ''n''',16,1) with nowait    
    end
If lower(@truncate_history) = N'y'
    truncate table dbo.waitstats

If lower (@clear_waitstats) = N'y'
    -- clear out waitstats
    dbcc sqlperf ([sys.dm_os_wait_stats],clear) with no_infomsgs

declare @i int,
        @delay varchar(8),
        @dt varchar(3),
        @now datetime,
        @totalwait numeric(20,1),
        @endtime datetime,
        @begintime datetime,
        @hr int,
        @min int,
        @sec int

select @i = 1
select @dt = case lower(@delay_type)
    when N'minutes' then 'm'
    when N'minute' then 'm'
    when N'min' then 'm'
    when N'mi' then 'm'
    when N'n' then 'm'
    when N'm' then 'm'
    when N'seconds' then 's'
    when N'second' then 's'
    when N'sec' then 's'
    when N'ss' then 's'
    when N's' then 's'
    else @delay_type
end

if @dt not in ('s','m')
begin
    raiserror ('delay type must be either ''seconds'' or ''minutes''',16,1) with nowait
    return
end
if @dt = 's'
begin
    select @sec = @delay_interval % 60, @min = cast((@delay_interval / 60) as int), @hr = cast((@min / 60) as int)
end
if @dt = 'm'
begin
    select @sec = 0, @min = @delay_interval % 60, @hr = cast((@delay_interval / 60) as int)
end
select @delay= right('0'+ convert(varchar(2),@hr),2) + ':' +
    + right('0'+convert(varchar(2),@min),2) + ':' +
    + right('0'+convert(varchar(2),@sec),2)

if @hr > 23 or @min > 59 or @sec > 59
begin
    select 'delay interval and type: ' + convert (varchar(10),@delay_interval) + ',' + @delay_type + ' converts to ' + @delay
    raiserror ('hh:mm:ss delay time cannot > 23:59:59',16,1) with nowait
    return
end
while (@i <= @num_samples)
begin
    select @now = getdate()
    insert into [dbo].[waitstats] (
                        [wait_type],
                        [waiting_tasks_count],
                        [wait_time_ms],
                        [max_wait_time_ms],
                        [signal_wait_time_ms],
                        now)    
            select
                [wait_type],
                [waiting_tasks_count],
                [wait_time_ms],
                [max_wait_time_ms],
                [signal_wait_time_ms],
                @now
            from sys.dm_os_wait_stats

    insert into [dbo].[waitstats] (
                        [wait_type],
                        [waiting_tasks_count],
                        [wait_time_ms],
                        [max_wait_time_ms],
                        [signal_wait_time_ms],
                        now)    
            select
                'Total',
                sum([waiting_tasks_count]),
                sum([wait_time_ms]),
                0,
                sum([signal_wait_time_ms]),
                @now
            from [dbo].[waitstats]
            where now = @now

    select @i = @i + 1
    waitfor delay @delay
end
--- create waitstats report
execute dbo.get_waitstats_2005
go
exec dbo.track_waitstats @num_samples=6
    ,@delay_interval=30
    ,@delay_type='s'
    ,@truncate_history='y'
    ,@clear_waitstats='y'
get_waitstats_2005
CREATE proc [dbo].[get_waitstats_2005] (
                @report_format varchar(20)='all',
                @report_order varchar(20)='resource')
as
-- This stored procedure is provided "AS IS" with no warranties, and
-- confers no rights.
-- Use of included script. samples are subject to the terms specified at
-- [url][/url]
--
-- this proc will create waitstats report listing wait types by
-- percentage.
--     (1) total wait time is the sum of resource & signal waits,
--            @report_format='all' reports resource & signal
--    (2) Basics of execution model (simplified)
--        a. spid is running then needs unavailable resource, moves to
--         resource wait list at time T0
--        b. a signal indicates resource available, spid moves to
--         runnable queue at time T1
--        c. spid awaits running status until T2 as cpu works its way
--         through runnable queue in order of arrival
--    (3) resource wait time is the actual time waiting for the
--        resource to be available, T1-T0
--    (4) signal wait time is the time it takes from the point the
--        resource is available (T1)
--        to the point in which the process is running again at T2.
--        Thus, signal waits are T2-T1
--    (5) Key questions: Are Resource and Signal time significant?
--        a. Highest waits indicate the bottleneck you need to solve
--          for scalability
--        b. Generally if you have LOW% SIGNAL WAITS, the CPU is
--         handling the workload e.g. spids spend move through
--         runnable queue quickly
--        c. HIGH % SIGNAL WAITS indicates CPU can't keep up,
--         significant time for spids to move up the runnable queue
--         to reach running status
--     (6) This proc can be run when track_waitstats is executing
--
-- Revision 4/19/2005
-- (1) add computation for CPU Resource Waits = Sum(signal waits /
--                                    total waits)
-- (2) add @report_order parm to allow sorting by resource, signal
--     or total waits
--
set nocount on

declare @now datetime,
        @totalwait numeric(20,1),
        @totalsignalwait numeric(20,1),
        @totalresourcewait numeric(20,1),
        @endtime datetime,@begintime datetime,
        @hr int,
        @min int,
        @sec int

if not exists (select 1
                from sysobjects
                where id = object_id ( N'[dbo].[waitstats]') and
                      OBJECTPROPERTY(id, N'IsUserTable') = 1)
begin
        raiserror('Error [dbo].[waitstats] table does not exist',
                 16, 1) with nowait
        return
end

if lower(@report_format) not in ('all','detail','simple')
    begin
        raiserror ('@report_format must be either ''all'',
                    ''detail'', or ''simple''',16,1) with nowait
        return
    end
if lower(@report_order) not in ('resource','signal','total')
    begin
        raiserror ('@report_order must be either ''resource'',
            ''signal'', or ''total''',16,1) with nowait
        return
    end
if lower(@report_format) = 'simple' and lower(@report_order) <> 'total'
    begin
        raiserror ('@report_format is simple so order defaults to ''total''',
                        16,1) with nowait
        select @report_order = 'total'
    end


select  
    @now=max(now),
    @begintime=min(now),
    @endtime=max(now)
from [dbo].[waitstats]
where [wait_type] = 'Total'

--- subtract waitfor, sleep, and resource_queue from Total
select @totalwait = sum([wait_time_ms]) + 1, @totalsignalwait = sum([signal_wait_time_ms]) + 1
from waitstats
where [wait_type] not in (
        'CLR_SEMAPHORE',
        'LAZYWRITER_SLEEP',
        'RESOURCE_QUEUE',
        'SLEEP_TASK',
        'SLEEP_SYSTEMTASK',
        'Total' ,'WAITFOR',
        '***total***') and
    now = @now

select @totalresourcewait = 1 + @totalwait - @totalsignalwait

-- insert adjusted totals, rank by percentage descending
delete waitstats
where [wait_type] = '***total***' and
now = @now

insert into waitstats
select
    '***total***',
    0,@totalwait,
    0,
    @totalsignalwait,
    @now

select 'start time'=@begintime,'end time'=@endtime,
       'duration (hh:mm:ss:ms)'=convert(varchar(50),@endtime-@begintime,14),
       'report format'=@report_format, 'report order'=@report_order

if lower(@report_format) in ('all','detail')
begin
----- format=detail, column order is resource, signal, total. order by resource desc
    if lower(@report_order) = 'resource'
        select [wait_type],[waiting_tasks_count],
            'Resource wt (T1-T0)'=[wait_time_ms]-[signal_wait_time_ms],
            'res_wt_%'=cast (100*([wait_time_ms] -
                    [signal_wait_time_ms]) /@totalresourcewait as numeric(20,1)),
            'Signal wt (T2-T1)'=[signal_wait_time_ms],
            'sig_wt_%'=cast (100*[signal_wait_time_ms]/@totalsignalwait as numeric(20,1)),
            'Total wt (T2-T0)'=[wait_time_ms],
            'wt_%'=cast (100*[wait_time_ms]/@totalwait as numeric(20,1))
        from waitstats
        where [wait_type] not in (
                'CLR_SEMAPHORE',
                'LAZYWRITER_SLEEP',
                'RESOURCE_QUEUE',
                'SLEEP_TASK',
                'SLEEP_SYSTEMTASK',
                'Total',
                'WAITFOR') and
                now = @now
        order by 'res_wt_%' desc

----- format=detail, column order signal, resource, total. order by signal desc
    if lower(@report_order) = 'signal'
        select    [wait_type],
                [waiting_tasks_count],
                'Signal wt (T2-T1)'=[signal_wait_time_ms],
                'sig_wt_%'=cast (100*[signal_wait_time_ms]/@totalsignalwait as numeric(20,1)),
                'Resource wt (T1-T0)'=[wait_time_ms]-[signal_wait_time_ms],
                'res_wt_%'=cast (100*([wait_time_ms] -
                        [signal_wait_time_ms]) /@totalresourcewait as numeric(20,1)),
                'Total wt (T2-T0)'=[wait_time_ms],
                'wt_%'=cast (100*[wait_time_ms]/@totalwait as numeric(20,1))
        from waitstats
        where [wait_type] not in (
                    'CLR_SEMAPHORE',
                    'LAZYWRITER_SLEEP',
                    'RESOURCE_QUEUE',
                    'SLEEP_TASK',
                    'SLEEP_SYSTEMTASK',
                    'Total',
                    'WAITFOR') and
                    now = @now
        order by 'sig_wt_%' desc

----- format=detail, column order total, resource, signal. order by total desc
    if lower(@report_order) = 'total'
        select
            [wait_type],
            [waiting_tasks_count],
            'Total wt (T2-T0)'=[wait_time_ms],
            'wt_%'=cast (100*[wait_time_ms]/@totalwait as numeric(20,1)),
            'Resource wt (T1-T0)'=[wait_time_ms]-[signal_wait_time_ms],
            'res_wt_%'=cast (100*([wait_time_ms] -
                    [signal_wait_time_ms]) /@totalresourcewait as numeric(20,1)),
            'Signal wt (T2-T1)'=[signal_wait_time_ms],
            'sig_wt_%'=cast (100*[signal_wait_time_ms]/@totalsignalwait as numeric(20,1))
        from waitstats
        where [wait_type] not in (
                'CLR_SEMAPHORE',
                'LAZYWRITER_SLEEP',
                'RESOURCE_QUEUE',
                'SLEEP_TASK',
                'SLEEP_SYSTEMTASK',
                'Total',
                'WAITFOR') and
                now = @now
        order by 'wt_%' desc
end
else
---- simple format, total waits only
    select
        [wait_type],
        [wait_time_ms],
        percentage=cast (100*[wait_time_ms]/@totalwait as numeric(20,1))
    from waitstats
    where [wait_type] not in (
                    'CLR_SEMAPHORE',
                    'LAZYWRITER_SLEEP',
                    'RESOURCE_QUEUE',
                    'SLEEP_TASK',
                    'SLEEP_SYSTEMTASK',
                    'Total',
                    'WAITFOR') and
                now = @now
    order by percentage desc


---- compute cpu resource waits
select
    'total waits'=[wait_time_ms],
    'total signal=CPU waits'=[signal_wait_time_ms],
    'CPU resource waits % = signal waits / total waits'=
            cast (100*[signal_wait_time_ms]/[wait_time_ms] as numeric(20,1)),
    now
from [dbo].[waitstats]
where [wait_type] = '***total***'
order by now
go


declare @now datetime
select @now = getdate()
select getdate()

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6517/viewspace-668681/,如需轉載,請註明出處,否則將追究法律責任。

相關文章