SQL Server 2005性能排錯白皮書(Part 2)---From MS Customer Support Service部門

Tempdb

Tempdb用於全局存儲內部或用戶對象,臨時表,對象和在SQL Server操作是創建的存儲過程。每鯯QL 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中的信息。

SelectSUM (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_kbFrom sys.dm_db_file_space_usageWhere 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='<user-object>'來找到個別用戶對象使用的。例如,你可以運行下列腳本枚舉所有tempdb對象。

DECLARE userobj_cursor CURSOR FOR select sys.schemas.name + '.' + sys.objects.name from sys.objects, sys.schemaswhere object_id > 100 and type_desc = 'USER_TABLE'and sys.objects.schema_id = sys.schemas.schema_idgoopen userobj_cursorgodeclare @name varchar(256)fetch userobj_cursor into @namewhile (@@FETCH_STATUS = 0) beginexec sp_spaceused @objname = @namefetch userobj_cursor into @name endclose 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_transactionsorder by elapsed_time_seconds DESC

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

transaction_id       transaction_sequence_num elapsed_time_seconds-------------------- ------------------------ --------------------8609                 3                        652320156                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_countfrom sys.dm_db_session_space_usageorder 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 t2where t1.session_id = t2.session_id and t1.session_id >50order by allocated DESC

這是示例的輸出。

session_id allocated            deallocated---------- -------------------- --------------------52         5120                 513651         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_handlefrom (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 t2where 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 timestampdeclare @now datetime select @now = getdate()-- insert data into a table for later analysisinsert into analysis..waiting_tasksselect session_id, wait_duration_ms, resource_description, @nowfrom sys.dm_os_waiting_taskswhere wait_type like ‘PAGE%LATCH_%’ andresource_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=56waiting_task_address session_id exec_context_id wait_duration_ms     wait_typeresource_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('<table_name>') 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 = <dbid> )
            


所有沒有被使用的索引仍可以通過下列語句獲取信息:

            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 = <dbid> )
            order by object_name(object_id) asc
            


在這種情況下,表名稱和索引名稱根據表明排序。

.動態管理視圖的真正目的是在長時間運行時觀察索引的使用情況。它可以提供視圖的快照或查詢結果集,將其存儲,然後每天比較相應的改變。如果你能識別特殊的索引數月沒有使用或者在很長時間沒有使用,你可以最終從數據庫中刪除他們。

總結

更多信息請見:http://www.microsoft.com/sql/

附錄A: DBCC MEMORYSTATUS 描述

這有一些使用DBCC MEMORYSTATUS命令的信息。可是,一些信息也可以使用動態管理視圖(DMVs)獲取。

SQL Server 2000 DBCC MEMORYSTATUS在http://support.microsoft.com/?id=271624中描述

SQL Server 2005 DBCC MEMORYSTATUS 在http://support.microsoft.com/?id=907877中描述

附錄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 -- http://www.microsoft.com/info/cpyright.htm
            --
            -- 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 http://www.microsoft.com/info/cpyright.htm
            --
            -- 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), object=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 http://www.microsoft.com/info/cpyright.htm
            -- 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
            -- http://www.microsoft.com/info/cpyright.htm
            --
            -- 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
            -- http://www.microsoft.com/info/cpyright.htm
            --
            -- 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 http://www.microsoft.com/info/cpyright.htm
            --
            -- 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
            -- http://www.microsoft.com/info/cpyright.htm
            --
            -- 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()
 

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/usher_gml/archive/2009/08/03/4403485.aspx

發佈了25 篇原創文章 · 獲贊 6 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章