有些操做符須要較多的內存才能完成操做。例如,SORT、HASH、HAS聚合等。執行計劃經過操做符須要處理數據量的預估值(經過統計信息得到的預估行數)、操做符類型和列大小來得到所須要的內存總量。這個執行計劃的內存總量,也叫作內存授予(Memory Grant)。html
當多併發查詢的環境中,若是 查詢中有不少這種須要大量內存授予的操做符時,併發的執行計劃所需的內存授予總量可能會超過服務器內存總量。SQL Server使用 資源信號量(Resource Semaphore) 來避免這種狀況發生。當內存授予量超過可用內存量時,當前請求內存授予的查詢必須等待其它查詢完成並釋放出它佔用的內存授予量。數據庫
可經過 sys.dm_exec_query_memory_grants 查詢當前查詢內存授予的狀態。當查詢等待內存授予時會產生 Execution Warnings 事件。數組
分配內存授予時能夠會出現兩種狀況:對於查詢的內存授予內存是預先保留出來,而不是按需分配。因此在查詢執執行中可能出現實現使用的內存量比授予理要少的狀況,這部分多來的內存會用作數據緩存。由於有資源信號量的限制,因此過大內存授予量會讓其它的查詢缺乏授予內存而等待。緩存
SQL Server 中有一個類似的概念叫 查詢編譯資源信號量(query compile resource semaphore) 。它的做用機制跟資源信號量同樣,可是它只針對查詢的編譯行爲生效。一般查詢編譯不會成爲系統的瓶頸,若是發生極可能是查詢計劃重用出問題了。服務器
須要注意一點,並非全部查詢都須要內存授予才能執行。包含排序、大型掃描、HASH鏈接和聚合等操做的複雜查詢才須要內存授予。在須要快速響應的系統中(如OLTP),若是發生內存授予問題,應當考慮從數據模型設計上作出調整。OLAP系統中的內存授予致使的延遲,通賞是正常的。併發
關於內存的相關資源:理解SQL Server的查詢內存授予dom
堆表:堆表中的數據是無序的。異步
彙集索引:彙集索引表中的數據是有序的,順序跟彙集索引鍵相關。彙集索引是B-Tree結構。性能
非彙集索引:它的數據是表中數據子集,而且數據是有序的,順序跟索引鍵相關fetch
SQL Server 2012以後,還有列存儲索引的數據組織方式。
前面說到,在執行樹葉級的操做符會訪問實際的數據行。它們訪問數據的操做符一般有三種:
掃描操做符(Scan Operator)
顧名思義,它會訪問目標對象的全部數據行。像 Clustered Index Scan, Nonclustered Index Scan, Table Scan, Remote Index Scan and Remote Scan等操做符,它們都掃描方式,只對目標對象不同。一般掃描成本是很高的,應當將之作爲最後的數據訪問。
查找操做符(Seek Operator)
查找是經過一個(或多個)鍵列定位到一行(或多行)數據。查找能夠實現範圍查找。查找只會出如今B-Tree上,即只有訪問彙集或者非彙集索纔會有查找操做。堆表的數據是無序的,因此辦法經過某個鍵列定位到一行。查找是很是高效的,理想狀況下應當作爲數據訪問的首選方式。
書籤查找操做符(Bookmark Lookup Operator)
經過一種特殊的值(即書籤)定位到數據行的操做符。書籤由數據庫引擎產生,不能人爲指定,一般來自前一個操做符的輸出數據。書籤查找能夠在任何數據組織方式的對象上發生。書籤查找操做符包括:Bookmark Lookup, Row ID Lookup (堆表上的查找)或者 Key Value Lookup (B-Tree上的查找)。
嚴格來講,數據訪問操做符還有 Inserted Scan 和 Deleted Scan ,它們訪問是inserted和deleted 僞表。還有 Log Row Scan ,它從事務日誌讀取數據,而不是表。
還有一個叫範圍掃描的概念。它是Seek操做符根據提供的兩個鍵值,掃描鍵值區間內的數據時的操做。
如今回頭再去看查詢執行過程,咱們就會明白數據訪問操做符是如何驅動整個執行計劃進行迭代的了:執行樹根節點的操做符調用next()並沒着子節點逐級調用next(),直到到達到數據訪問操做符所在葉節點。而這些葉節點操做符經過讀取實際的數據和返回相關數據來實現next()接口。
數據訪問操做符會只從緩存池(Buffer Pool)讀取數據。若是緩存池中沒有所須要的數據,則須要將數據從存儲子系統讀取到緩存池。緩存池的數據被全部查詢共享。SQL Server會盡將盡量多的數據緩存到緩存池,以備使用,直到用盡全部分配給SQL Server的內存量。能夠經過 max server memory 選項控制緩存池的內存使用上限。不管是緩存池,仍是磁盤IO,它們數據請求的都以8KB頁爲單位。
讓咱們來看看掃描(Scan)數據訪問操做符是如何從堆上讀取數據的:
做爲對比,再來看看數據訪問操做如何在B-Tree上訪問數據的:
這個「後續」的意思,檢索B-Tree能夠是雙向的(ASC OR DESC),並且根據查詢提供的鍵值,可能找到徹底匹配鍵值的行,也可能找不到匹配鍵值的行。匹配鍵值的行能夠沒有,可是葉級頁老是存在的,而且頁上的行與鍵值所指向的行在同一個B-Tree查找區間內,因此稱之爲「後續」。「後續」行位於鍵值匹配行的前面仍是後面,由檢索方向決定。檢索方向與建立索引時指定的ASC或者DESC的排序方式是不一樣,前者就是指樹中的檢索方向,或者是指索引行的實際順序。BY Joe .TJ
父級操做符取走返回的數據。
若是操做符用做範圍掃描,則會再次調用next(),讀取已返回行的後一行。操做符會保存前一個返回行的鍵值和位置信息。而後再上一點拒描述的B-Tree檢索過程。若是當前葉級頁的數據已經被取完了,則會定位到下一頁的第一行並返回。索引頁經過雙向鏈表鏈接,每一頁上都會前一頁和下一頁的指針。
父級操做符再次取走返回的數據。
由於範圍掃描會包含範圍結束位置的鍵值,因此當調用next()從當前行移動到後一行,然後一行的鍵值超過告終束位置的鍵值,會返回false。此處的「超過」與B-Tree檢索方向和索引排序有關。
B-Tree操做符除能被重繞(Rewind),還能被從新綁定(Rebind)。重繞會重置操做符的狀態,使其使用一樣的鍵值從新開始查找或者範圍掃描。從新綁定會改變用於查找的鍵值,也就是說以前的結果可能無效了。
掃描操做每次讀取完一頁上的全部數據後,再去讀取下一頁,若是下一頁不在緩存池,就須要等待將頁從磁盤加載到緩存池。若是每讀一頁,都須要等待加載,那性能就太差了。SQL Server 使用預計的機制來優化這種操做。掃描操做符經過異步IO將如今尚未被使用到,可是很快會被用到的頁提早加載到緩存池中來。
嵌套循環中還有一種特殊的預讀叫隨機預讀(Random Prefetching),它是爲了減小查找操做等待而提早將頁加載到緩存池。
預讀通常會預先讀取500頁左右的數據,若是頁連續,則每一次異步IO最多能夠讀取64頁(512KB)的數據,也就是說理想狀況下8次IO能夠完成一次預讀。讀取數據頁時,根據IAM獲取要預計的頁地址,而後把連續的頁合併到同一次IO中。索引頁(指葉級頁),則是經過B-Tree的中間級的索引頁,獲取須要預讀的頁地址,而後合併連續的頁到同一次IO。二者中不連續的頁,會單獨執行IO。這裏的「連續」都指的是邏輯上的連續。 BY Joe .TJ