在執行任何查詢時,SQL Server都會把數據讀取到內存,在使用完數據之后,數據不會被立即刪除,而是緩存在內存Buffer中,當再次獲取相同的數據時,如果所需數據全部緩存在內存中,那么SQL Server不會產生Disk IO操作(把數據從硬盤導入到內存),而是直接從內存中獲取數據。由於查詢內存中的數據,速度非常快,SQL Server引擎會立即返回查詢結果,緩存數據是是SQL Server的一種性能優化機制。
一,主要的內存消費者
緩存數據需要消耗內存,主要的內存消費者(Memory Consumer)是數據緩存、計划緩存、授予內存和日志緩存,SQL Server還會緩存其他類型的數據,這些數據占比較低,本文不作考慮。
1,數據緩存(Data Cache)
Data Cache是存儲數據頁(Data Page)的緩沖區,當SQL Server需要讀取數據文件(File)中的數據頁(Data Page)時,SQL Server會把整個Page都調入內存(內存中的一個Page叫做buffer),Page是數據訪問的最小單元。
當用戶修改了某個Page上的數據時,SQL Server 會先在內存中修改Buffer,但是不會立即將這個數據葉寫回硬盤,而是等到CheckPoint或lazy Writer進程運行時集中處理。當用戶讀取某個Page后,如果SQL Server沒有內存壓力,它不會在內存中刪除這個Page,因為內存中的數據頁始終存放着數據的最新狀態,如果有其他用戶使用這個Page,SQL Server 不需要從硬盤中讀取一次,節省語句執行的時間。理想情況是SQL Server將用戶需要訪問的所有數據都緩存在內存中,SQL Server 永遠不需要去硬盤讀取數據,只需要在CheckPoint 或 lazy Write運行時把修改過的頁面寫回硬盤即可。
Buffer Pool 用於管理數據緩存,可以從 sys.dm_os_buffer_descriptions 中查詢相關的信息。
2,查詢計划緩存(Query Plan Cache)
計划緩存用於存儲查詢語句和存儲過程的執行計划,便於計划的重用,因為編譯查詢語句產生執行計划是一個非常耗費資源的過程,如果執行計划被緩存起來,下次使用時就不需要重新編譯(Compile)和重新生成,SQL Server引擎會直接復用已緩存的執行計划,可以通過sys.dm_exec_cached_plans 來查詢計划緩存的信息。
對於Ad-Hoc查詢,有一個特殊的優化選項:
執行計划生成后會存儲在plan cache中,如果計划緩存從來都沒有被重用過,這將會造成內存資源的浪費。當執行代碼時,會產生一個hash值,用於匹配計划緩存中的hash值,相同的hash值代表語句是相同的。如果執行一個存儲過程,那么會按照存儲過程名稱來創建hash值;如果是執行Ad-Hoc代碼,那么會按照整個TSQL語句來創建Hash值,只有Ad-Hoc語句中有一點改變,都會產生不同的Hash值,導致執行計划無法重用。針對這類問題,可以考慮使用存儲過程或者參數化的Ad-Hoc。
“針對即席工作負載進行優化”是一個Server級別的性能優化選項,用於提高包含許多臨時批處理的工作負載的計划緩存的效率,如果把該選項設置為True,則數據庫引擎在首次編譯批處理時只保留計划緩存中的一個存根,而不是存儲整個執行計划。當再次調用該批處理時,數據庫引擎識別出該批處理在之前被執行過,進而從計划緩存中刪除該執行計划的存根,並把完全編譯的執行計划添加到計划緩存中。當非參數化的Ad-Hoc查詢較多時,可以避免計划緩存存儲過多的不會被復用的執行計划。
3,授予內存(Granted Memory)
授予內存是已經分配給查詢的那部分內存,可以通過sys.dm_exec_query_memory_grants查看,另外,RESOURCE_SEMAPHORE等待狀態是針對memory grant的,所以,如果在sys.dm_os_wait_stats 中看到這個等待類型存在很久,並且處於前列,說明系統當前無可用的內存分配給查詢,系統很有可能存在內存壓力。
4,Log Cache
在數據被修改時,SQL Server會記錄數據修改的日志,這些日志首先緩存在Log Cache中,然后積累到一定的數量或等很小的時間間隔后,寫入到日志文件中。通常來說,任何一個數據修改,在Log Cache和Buffer Cache中都會有記錄,Log Cache中的數據修改會在checkpoint被觸發時寫入到日志文件中去。
二,查看內存消耗
SQL Server的內存管理是一套完整的機制,只有內存書記員(Memory Clerk)能夠分配內存,Memory Clerk會記錄已經分配內存的數量,任何一個需要使用內存的對象,必須創建自己的Memory Clerk,並使用該Memory clerk來分配內存。
1,查看Memory clerk分配的內存量
每一個對象通過Memory Clerk來分配內存的,內存消費者Buffer Pool的clerk是MEMORYCLERK_SQLBUFFERPOOL,計划緩存的clerk是MEMORYCLERK_SQLQUERYPLAN
select memory_node_id, type, pages_kb, virtual_memory_reserved_kb, virtual_memory_committed_kb, shared_memory_reserved_kb, shared_memory_committed_kb, page_size_in_bytes from sys.dm_os_memory_clerks where type in( 'MEMORYCLERK_SQLQUERYPLAN','MEMORYCLERK_SQLBUFFERPOOL')
2,統計Memory Clerk分配的內存總量
select mc.type,mc.name, sum(mc.pages_kb) as AllocatedPages_KB, sum(mc.virtual_memory_reserved_kb) as VM_Reserved_KB, sum(mc.virtual_memory_committed_kb) as VM_Committed_KB, --sum(mc.shared_memory_reserved_kb) as ShareMem_Reserved_KB, --sum(mc.shared_memory_committed_kb) as ShareMem_Committed_KB, max(mc.page_size_in_bytes)/1024 as SinglePageSize_KB from sys.dm_os_memory_clerks mc group by mc.type,mc.name order by AllocatedPages_KB desc,mc.type,mc.name
消耗內存較大的Clerk是:
- MEMORYCLERK_SQLBUFFERPOOL:是Buffer Pool占用的內存大小
- MEMORYCLERK_SQLQUERYPLAN:是計划緩存占用的內存大小
- OBJECTSTORE_LOCK_MANAGER:鎖結構使用的內存,當發生嚴重的鎖阻塞時,這表明系統中,存儲大量鎖,造成鎖管理占用大量的內存;
- CACHESTORE_OBJCP:觸發器和存儲過程等模塊(Module)的執行計划占用的緩存空間;
- CACHESTORE_SQLCP:動態TSQL語句,即席(Adhoc)查詢和預編譯(Prepared) TSQL的執行計划緩存;
- CACHESTORE_COLUMNSTOREOBJECTPOOL:列存儲索引(ColumnStore Index)占用的緩存
3,查看緩存中的數據頁
當數據頁從硬盤讀取到內存之后,該數據頁被復制到緩沖池(Buffer Pool),供SQL Server重用。每個緩存的數據頁都有一個緩存描述器(Buffer Descriptor),用戶唯一標識內存中的數據頁,在SQL Server實例中緩存的每一個數據頁,都能從 sys.dm_os_buffer_descriptors 查看緩存描述的信息。
select object_name(p.object_id) as object_name ,o.type_desc ,i.name as index_name ,count(0) as buffer_counts ,cast(sum(bd.free_space_in_bytes)/(8*1024.0)/count(0) as decimal(10,4))*100 as free_space_ratio ,sum(cast(bd.is_modified as int)) as dirty_pages ,sum(bd.row_count) as row_counts from sys.allocation_units au inner join sys.dm_os_buffer_descriptors bd on au.allocation_unit_id=bd.allocation_unit_id inner join sys.partitions p on au.container_id=p.hobt_id inner join sys.indexes i on p.object_id=i.object_id and p.index_id=p.index_id inner join sys.objects o on p.object_id=o.object_id where bd.database_id=db_id() and o.type<>N'S' group by p.object_id ,o.type_desc ,i.name order by buffer_counts desc ,object_name
4,查看計划緩存
產生執行計划是十分消耗CPU資源的,SQL Server會在內存的Plan Cache中存儲每個查詢計划(Query Plan),及其占用的內存空間,重用次數等信息。
select cp.objtype,cp.cacheobjtype, sum(cp.size_in_bytes) as TotalSize_B, COUNT(cp.bucketid) as CacheCounts, sum(cp.refcounts) as TotalRefCounts, sum(cp.usecounts) as TotalUseCounts from sys.dm_exec_cached_plans cp group by cp.objtype,cp.cacheobjtype order by TotalSize_B desc
5,查看各個數據占用的內存buffer
select iif(d.database_id=32767,'Resource DB',db_name(d.database_id)) as db ,sum(d.row_count) as row_count ,count(0) as buffer_pages ,count(0)*8/1024 as buffer_mb from sys.dm_os_buffer_descriptors d where d.database_id between 5 and 32767 group by d.database_id order by buffer_mb desc
三,清空緩存
在調優存儲過程性能時,清空緩存是必需的,緩沖池(Buffer Pool)是SQL Server的緩存管理器,包含了SQL Server的絕大部分緩存數據(Cache),例如,執行計划緩存(Plan cache),數據緩存(Data cache)等。
清空緩存常用的命令有如下三個:
CHECKPOINT
DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE
Checkpoint和DBCC DROPCLEANBUFFERS 用於清理數據緩存(Data Cache)中的臟頁(dirty pages)和干凈頁(clean pages),而DBCC FREEPROCCACHE 用於清空所有的計划緩存(Plan Cache)。
1,清空數據緩存
checkpoint 用於將臟頁(Dirty Pages)寫入硬盤,臟頁(Dirty Pages)是指數據頁讀入緩存后,被修改過,導致內存中數據頁和硬盤中的數據頁中的內容不同;干凈頁(Clean Pages)是指數據頁被讀入緩存后,沒有被修改過,所以,內存中的數據頁和硬盤中的數據頁中的內容相同。不管是Dirty pages 還是 Clean pages 都是Data Cache,在性能調優時,都必須從內存中清理掉,否則,查詢性能將忽略掉數據從硬盤加載到內存的IO消耗,影響查詢語句的執行情況。
CHECKPOINT 命令用於產生冷緩存(Cold buffer Cache),該命令將當前數據庫產生的所有臟頁寫入到硬盤,並清理內存buffer;在執行CHECKPOINT命令之后,執行 DBCC DROPCLEANBUFFERS 用於從緩沖池中清空所有的干凈頁。
在性能測試時,使用DBCC DROPCLEANBUFFERS從SQLSERVER的數據緩存池中清除所有的clean緩存數據,需要注意的是該命令只移走干凈的緩存,不移走臟緩存。因此,在執行這個命令前,應該先執行CheckPoint,將所有臟頁寫入磁盤,這樣在運行DBCC RROPCLEANBUFFERS 時,可以保證所有的數據緩存被清理,而不是其中的一部分。
2,清空計划緩存
計划緩存(Plan Cache)用於緩存查詢語句的執行計划,每一條查詢語句在執行之后,其查詢計划都會緩存Plan Cache中。在產品環境中,不要輕易清理掉Plan Cache。如果檢測到某個Plan Cache產生參數嗅探問題,導致性能十分低下,推薦修改查詢語句,重新編譯存儲過程,以單獨刷新該SP的計划緩存。
DBCC FREEPROCCACHE [ ( { plan_handle | sql_handle} ) ]
計划緩存,之前叫做過程緩存(procedure cache),執行DBCC FREEPROCCACHE 命令,釋放所有的計划緩存,這會導致存儲過程,AdHoc查詢等必須重新編譯,產生新的計划緩存。
附:冷緩存,熱緩存,臟緩存和干凈緩存名詞解釋:
- 凈緩存頁(Clean Buffer) 是指內存中未被修改的數據頁,DBCC DROPCLEANBUFFERS 用於從緩沖池(Buffer Pool)移除干凈頁,釋放Buffer。
- 臟緩存頁(Dirty Buffer)是指數據頁在內存中被修改,但是還沒有寫入到硬盤中,導致硬盤中的數據不同於內存,通常情況下,臟頁通過CHECKPOINT進程來自動同步,CHECKPOINT 將臟頁數據寫入到硬盤中,使內存和硬盤文件中的數據保持一致,能夠減少數據還原的時間。
- 冷緩存頁(Cold Buffer)是指,在數據處理階段,最近沒有被使用的緩存頁。
- 熱緩存頁(Hot Buffer)是指,在數據處理階段,最近經常被使用的緩存頁。
參考文檔: