關鍵詞:tempdb
tempdb全局存儲內部對象,用戶對象,臨時表,臨時對象,以及SQL Server操作創建的存儲過程。每個數據庫實例只有一個tempdb,所以可能存在性能以及磁盤空間瓶頸。各種形式的可用空間及過度餓DDL/DML操作都會導致tempdb負載過重。這會導致運行在服務器上不相干程序運行緩慢或者運行失敗。
tempdb的一些常見通病如下:
--耗完了tempdb的所有存儲空間
--讀取tempdb時的I/O瓶頸造成的查詢運行緩慢。
--過度的DDL操作造成在系統表上的瓶頸。
--分配競爭
在我們開始診斷問題之前,讓我們首先看一下tempdb的空間都用在了哪些地方。可以分成四個主要的類比:
類別 | 描述 |
用戶對象 | 這些是明確地由用戶創建並且在系統類別中進行追蹤。他們包括下面的: |
內部對象 | SQL Server在運行查詢的時候會創建或銷毀許多語句范圍的對象。這些 沒有在系統類別中被追蹤。他們包括以下內容: --工作文件(hash連接) --排序運行 --工作表(游標,池以及臨時的大對象數據類型(LOB)存儲) --這里有兩種情況除外:臨時的大對象存儲是批處理范圍的,另一是游標工作表是會話范圍的 |
版本存儲 | 這個被用來存儲行版本。MARS,在線索引,觸發器,以及快照隔離級別都是基於行版本的。 |
空閑空間 | 這個顯示出了可用於tempdb的磁盤空間 |
總的tempdb空間=用戶對象+內部對象+存儲的版本信息+空閑空間。
這個空閑空間大小跟tempdb性能計數器上空閑空間是一樣的。
監測tempdb空間
提前避免問題的發生總是比出現問題之后再去解決要好的多。你可以使用Free Space in tempdb(kb)性能計數器去監測正在使用的tempdb空間數量。這個計數器以kb為單位追蹤空閑空間。管理員可以使用這個計數器去判斷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_objects_kb internal_objects_kb version_store_kb freespace_kb
320 320 128 6208
請注意這些結果值沒有計算包含在混合事物中的頁。混合事物中的頁可以分配給用戶及內部的事物。
空間問題故障排除
用戶對象,內部對象,以及版本存儲都可以導致tempdb出現空間問題。在這個章節里,我們考慮如何針對這四個類別進行故障排除。
用戶對象
因為用戶對象不專屬於任意指定的會話,你需要參照應用程序創建這個對象的規范去調整tempdb需求的大小。你可以找到各個用戶對象使用的空間通過exec sp_spaceused @objname='<user-object>'. 例如,你可以運行下面的腳本去枚舉所有的tempdb對象。
declare userobj_cur cursor for select sys.schemas.name +'.'+sys.objects.name from sys.object,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 2008提供了一個行版本的框架。當前,下面這些特性使用到了行版本的框架:
--觸發器
--MARS
--在線索引
--行版本控制的隔離級別:需要在數據庫級別上設定一個選項。
更多信息,可以參考row versioning resource usage.
行版本可以在各個會話間共享。在行版本被回收時,創建者並不能控制。你可能需要找到然后停掉那個最長的,正在運行的會阻止行版本清空的事務。下面的查詢返回了最上面的兩個最長時間運行的事務,取決於存儲在版本中心中版本。
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
實例輸出,結果顯示id為8609的事務已經激活了6,523秒。
transaction_id transaction_sequence_num elapsed_time_seconds
8609 3 6523
21056 25 783
因為第二個事務已經激活了一段不太長時間,你或許可以通過停掉第一個事務從而釋放掉版本中心里的一定數量。然而,並沒有一個方式可以列舉出來,一旦釋放后,到底有多少版本空間可以被釋放出來。你獲取需要停掉其它更多的事務從而釋放更多重要的空間。
你可以通過設定針對某一賬戶在tempdb上的版本存儲屬性來緩和這一問題,如果可能的話,消除長時間運行的快照隔離級別的事務或者長時間運行的已提交讀快照隔離級別。你可以使用下面的公式粗略估計下版本存儲所需要的大小。(乘以2的原因是需要計算最壞打算的情況,也就是當兩個最長運行的事務重疊時。)
[Size of version store]=2 * [version store data generated per minute] * [longest running time (minutes) of the transaction]
在所有啟用了基於行版本控制隔離級別的數據庫中,事務的版本存儲數據跟日志數據一樣,都是每分鍾生成一次。然而,這里也有一些例外:僅僅是在記錄更新日志時會有一些不同;並且一個最新的插入的數據行沒有被記錄行版本信息,但是它會被記錄在事務日志中,如果它是一個bulk-logged操作並且恢復模型不是完整回復模型。
你也可以使用Version Generation Rate和Version Cleanup Rate性能計數器去優化你的計算。如果你的Version Cleanup Rate是0,可能是因為一個長時間運行的事務阻礙了版本存儲中心的清理。
順便提一下,在生成一個out-of-tempdb-space錯誤之前,SQL Server 2008會最后一搏嘗試強制清除版本存儲中心。在清除過程中,最長的那個還沒有生成任何行版本的事務會被標記為受害者。這會將它們使用的版本空間釋放出來。錯誤日志會為每個受害者存儲一個編號為3967的消息。如果某個事務被標記為了受害者,它就不能在向版本存儲中心中存儲版本信息,也不可以去讀取存儲在里面的行版本信息。當受害者事務嘗試去讀取行版本信息時,3966號錯誤會生成,並且這個事務會被回滾。如果清除版本存儲中心成功的話,tempdb就會有更多的可用空間。否則,tempdb就會無空間可用。
內部對象
內部對象是為每個語句而創建或銷毀的,這點已經在前面的概述中有所介紹。如果你注意到有很多的tempdb空間被分配,你應該確定下是哪個會話或者任務正在使用這些空間,並且可能的話采取對應的措施。
SQL Server 2008提供了兩個DMVs,sys.dm_db_session_space_usage和sys.dm_db_task_space_usage,去檢測被分配給會話或者任務的空間。盡管任務是運行在會話的上下文中的,但是被會話中的任務使用的空間只能在任務完成之后才會被統計出來。
你可以使用下面的查詢去查找分配內部對象最多的會話。注意,這個查詢僅包含會話中已經完成的任務。
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
在你已經定位到了分配很多內部對象的任務和會話后,你可以找出是哪個T-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
---------------------------------------------------------
sql_handle statement_start_offset
-----------------------------------------------------------------------
0x02000000D490961BDD2A8BE3B0FB81ED67655EFEEB360172 356
52 0 1024 1024
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大小的擴展。這會影響你工作負載的性能。我們推薦你提前微軟tempdb分配一個合適的大小。
過度的DDL及分配操作
tempdb中的兩種資源競爭會導致下面的情況。
創建及銷毀大量的臨時表及表變量會引起在元數據上的競爭。在SQL Server2008中,本地的臨時表及表變量會被緩存並最小化元數據的競爭。然而,下面的情況必須滿足;否則,臨時的對象不會被緩存:
--未創建命名的約束
--臨時表創建以后,影響表的DDL語句卻沒有運行,例如Create Index或者Create Statistics語句
--臨時對象不是通過使用動態SQL創建的,例如:sp_executesql N'create table #t(1 int)'
--臨時對象是在其它的對象中創建的,例如一個存儲過程,觸發器,或者用戶定義的函數;或者臨時對象是由用戶自定義的表值函數返回過來的。
典型地,許多臨時的/工作表是堆形式的;這就會導致,一個insert,delete或者drop操作會在空閑頁空間上導致繁重的競爭。如果大部分的表下月64kb並且為分配及交互分配使用固定的范圍,這會導致在共享全局分配映射(Shared Global Allocation Map)上形成嚴重的競爭。
SQL Server 2008緩存一個數據頁和一個IAM頁去最小化分配時的競爭。工作表緩存被改善。當一個查詢執行計划被緩存以后,計划所需要的工作表不會在多個執行計划中被銷毀掉,但是幾乎會被清除掉。此外,工作表的第一個前九頁被保持了下來。
因為共享全局分配映射(SGAM)和頁空閑空間(PFS)發生在數據文件中的固定間隔里,所以非常容易去查找它們的資源描述。所以,例如,2:1:1代表tempdb中的第一個PFS頁(database-id=2,file-id=1,page-id=1)並且2:1:3代表第一個SGAM頁。SGAM頁發生在每511,232個頁之后,並且每個PFS及SGAM頁貫穿在tempdb的所有文件間。你可以使用這個去查找tempdb中的所有其它的PFS和SGAM頁。任意時間,這些頁上正在等待獲取閂鎖的任務,都可以在sys.dm_os_waiting_tasks中查看。因為閂鎖等待是瞬間變化的,所以你應該經常性地查詢這個表(大概每10秒鍾一次)並且獲取這些數據用於之后的分析。例如,你可以使用下面的查詢去將所有等待tempdb頁的任務載入到waiting_tasks這個用於分析的表中。
--get the current timestamp declare @now datatime select @now=getdate() --insert data into a table for later analysis insert into analysis..waiting_tasks select session_id, waiting_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操作導致的。
你也可以檢測下面的性能監測計數器,從而去檢測任何存在於tempdb對象分配及交互活動上的不正常增長:
--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
解決方案
如果tempdb中的經常是由過度的DDL操作導致的,你應該查看你的應用程序並查看你是否可以最小化那些DDL操作。你可以嘗試下面的建議:
--從SQL Server 2005開始,如果是前面所描述的那些情況,臨時對象會被緩存下來。然而,如果你仍然需要存在明顯的DDL競爭,你需要查看下有哪些臨時對象沒有沒緩存下來以及它們發生在哪里。如果這個對象發生在循環或者存儲過程里,考慮將它們從循環或者存儲過程中移出來。
--檢查查詢計划去查看下是否有些計划創建了許多的臨時對象,線軸,排序或者工作表。你可能需要去消除一些臨時的對象。例如,在一個列上創建一個索引可能會消除排序。
如果經常是由SGAM和PFS導致的,你可以通過下面的方式減輕它:
--增加臨時數據文件以在不同文件或者硬盤上異步分散工作負載。典型地,你想創建多個文件,因為那里有多個CPU。
--使用TF--1118去消除固定范圍的分配。