- Oracle Exadata性能優化
- 石云華 陳浩 饒冰
- 3088字
- 2020-05-21 18:00:31
1.4 串行直接路徑算法
傳統的數據讀取方式是先讀取磁盤,然后把數據加載到數據庫服務器SGA中的BufferCache里。這樣其他的服務進程就可以共享BufferCache中的這些數據,無須再訪問緩慢的磁盤讀取來完成。
直接路徑讀取數據塊方式,是指服務器進程直接讀取數據文件,而不會將數據塊緩存到BufferCache中。這種方式讀取的數據塊會加載到服務器進程的PGA內存中,不會進入BufferCache中。
在Oracle 11g之前,直接路徑讀取主要用于并行查詢中;而從Oracle 11g之后,直接路徑讀取方式不僅可用于并行查詢,在符合某些條件后,串行的全表掃描也可以利用直接路徑讀取方式來完成。
1.4.1 _small_table_threshold參數
在Oracle 11g版本中,如果一條SQL語句進行全表掃描或快速全索引掃描,當表或索引的數據塊數量超過_small_table_threshold參數值時,Oracle有可能會使用直接路徑讀取代替以前的BufferCache讀取方式。
隱含參數_small_table_threshold用來界定“小表”的臨界,當某張表的數據塊個數小于該隱含參數值的,則被認為是“小表”。
對于大表,Oracle認為執行直接路徑讀取的效率會比BufferCache讀取方式高;而對于“小表”,通過BufferCache讀取效率更高。隱含參數_small_table_threshold的默認值為_db_block_buffers參數值的2%,單位為數據塊的個數。
下面通過示例驗證隱含參數_small_table_threshold與_db_block_buffers的關系,見代碼清單1.19。
代碼清單1.19 驗證隱含參數_small_table_threshold與_db_block_buffers的關系

首先獲取當前數據庫_small_table_threshold隱含參數的值和當前數據庫BufferCache中的數據塊個數。
隱含參數_small_table_threshold的大小正好滿足計算公式:
_small_table_threshold=2%?_db_block_buffers=2%?91140=1822.8=~1822
從以上代碼輸出可以看出,當前數據庫小表的閾值基本為_db_block_buffers參數值的2%,也印證了這一隱含參數與BufferCache的關系。在Oracle 9i以前,設置db_block_buffers參數值,即間接設置了BufferCache大?。欢鴱腛racle 9i開始,db_block_buffers變成了隱含參數,通過直接設置db_cache_size參數值,就可以間接得到隱含參數_db_block_buffers的值,db_cache_size參數值近似于_db_block_buffers隱含參數值?db_block_size參數值。
1.4.2 _very_large_object_threshold參數
_very_large_object_threshold是Oracle 11g版本中新引入的隱含參數,主要用來定義Oracle數據庫中的“特大表”。該參數的默認值為500,單位為%。換句話說,“特大表”的大小是BufferCache的500%,也就是5倍。
在互聯網上廣泛流傳一種說法,隱含參數_very_large_object_threshold的單位是MB,默認是500MB。這種說法基本上是錯誤的。

當一張表的大小超過5倍的BufferCache時,則這張表被定義為“特大表”。
1.4.3 串行Direct Path Read算法
當數據庫版本大于11.2.0.3時,串行直接路徑讀取的算法可用如圖1.8來表示。

圖1.8 串行直接路徑讀取算法流程圖
串行直接路徑讀取算法可以簡要概括如下。
■ 如果表的數據塊數量小于“小表”,那么會使用內存讀。
■ 如果表的數據塊數量大于“特大表”,那么會使用直接路徑讀。
■ 如果表的數據塊數量在“小表”與“特大表”之間,并且沒有使用壓縮,則根據已經緩存的數據塊或臟數據塊的比例決定采用哪種讀取方式。如果對象已經緩存的數據塊或臟數據塊比例大于50%,會使用內存讀;如果緩存的數據塊或臟數據塊的比例小于50%,則會使用直接路徑讀。
■ 如果表的數據塊數量在“小表”與“特大表”之間,并且使用了壓縮,則根據已經緩存的數據塊或臟數據塊的比例決定是采用哪種讀取方式。如果對象緩存的數據塊或臟數據塊的比例大于95%,會使用內存讀;當緩存的數據塊或臟數據塊的比例小于95%,則會使用直接路徑讀。
下面通過示例驗證“小表”和“特大表”不開啟并行的情況下,能否進行直接路徑讀取,見代碼清單1.20。
代碼清單1.20 驗證“小表”和“特大表”能否進行直接路徑讀取(1)

獲取當前數據庫中影響大、小表定義的相關參數,根據前面講述的大、小表的計算公式可知,數據塊少于532個的表稱之為“小表”,數據塊多于133000個的表稱之為“特大表”(26600×5)。
代碼清單1.20 驗證“小表”和“特大表”能否進行直接路徑讀?。?)


從以上代碼輸出可以看出,在test用戶下創建了small、middle和big 3張測試表,并收集了這3張測試表的統計信息。test.small表中有100個數據塊,而test.big表中有150000個數據塊。當訪問test.small表時,由于其數據塊個數小于“小表”的閾值,所以沒有進行直接路徑讀取;而當訪問test.big表時,由于其數據塊個數大于“特大表”的閾值,所以會進行直接路徑讀取,即使SQL語句中沒有指定并行。
這里大家是否注意到一個問題,那就是所謂“小表”“特大表”的數據塊個數是來自于統計信息,而不是真實數據塊的使用個數。先留個懸念,后面章節會進行講解。
繼續通過下面的示例來驗證當表對象介于“特大表”與“小表”之間時,數據塊在內存中緩存的比例對直接路徑讀取的影響。所有測試用例繼續沿用上面的測試表,測試過程見代碼清單1.21。
代碼清單1.21 驗證介于“小表”與“特大表”之間的表直接路徑讀?。?)

從以上的代碼輸出可以看出,test.middle表中共有20000個數據塊,其大小介于“特大表”與“小表”之間。首先通過檢查可知,test.middle測試表在BufferCache中緩存了19709個數據塊,也就是有96%左右的數據塊緩存在BufferCache中。接著調用[NSMTIO]事件,分析SQL語句在內存讀取和直接路徑讀取之間如何選擇。
下面直接查看[NSMTIO]事件生成的日志內容,具體如下。
代碼清單1.21 驗證介于“小表”與“特大表”之間的表直接路徑讀?。?)

從[NSMTIO]事件的內容可以看出,該表的大小介于MTT與VLOT之間,也即介于“小表”與“特大表”之間。此時進行數據訪問,則需要進行成本比較,來判斷是進行直接路徑讀取。還是進行內存讀取。由于總共才20000個數據塊,已經緩存了19709個數據塊,所以最終選擇了CACHE_READ,也即內存讀,而沒有選擇直接路徑讀取。
如果清空內存中已經緩存的相應數據塊,對掃描方式的選擇是否會產生影響?接著看下面的內容。
代碼清單1.21 驗證介于“小表”與“特大表”之間的表直接路徑讀取(3)

從以上代碼輸出可以看出,BufferCache中test.middle表相關的緩存已經全部被清除。接著再次調用[NSMTIO]事件來分析執行相同的SQL語句時,掃描方式是否會發生變化。
此時[NSMTIO]事件生成的日志文件如下。
代碼清單1.21 驗證介于“小表”與“特大表”之間的表直接路徑讀?。?)

清空BufferCache緩存,同時執行相同SQL語句后,從生成的[NSMTIO]事件內容可以看出,該表的大小介于MTT與VLOT之間,也即介于“小表”與“特大表”之間;該表共有20000個數據塊,已經緩存了0個;最終選擇了DirectRead,也即直接路徑讀取。
注意:①隨著數據庫版本的不斷更新,串行直接路徑讀取的算法也在不斷地發生變化;②[NSMTIO]事件中的VLOT表示_very_large_object_threshold,STT表示_small_table_threshold,而MTT的值為5?STT,也即5倍的_small_table_threshold,但在11gR2及以上版本中已經廢棄,所以在高版本的數據庫中,設置[NSMTIO]事件進行分析時,會給人一種錯覺。
1.4.4 _direct_read_decision_statistics_driven參數
有一點不知大家注意到了沒有?在前面驗證串行直接路徑讀取算法時使用的示例中,表的數據塊個數取自于dba_tables或user_tables視圖,而不是dba_segments或user_segments視圖,這說明表的數據塊個數多少并不是指表的真實數據塊數量,而是指表的統計信息中的數據塊數量。
從統計信息中獲取數據塊的數量,還是從真實的數據段頭中獲取數據塊的數量,其實是由隱含參數_direct_read_decision_statistics_driven來決定的。此參數默認值為true,表示是從表的統計信息中獲取數據塊的數量來決定是否使用直接路徑讀取。如果將此隱含參數設置為false,那么就會由段頭信息中獲取實際的數據塊數量來決定是否使用直接路徑讀取。
下面通過示例來驗證,見代碼清單1.22。
代碼清單1.22 隱含參數_direct_read_decision_statistics_driven對直接路徑讀取的影響(1)

以100%的比例來收集test.middle測試表的統計信息,可以看出該表的真實數據塊為19891個。在此手動修改該測試表的統計信息,將數據塊個數修改為10,變相地認為這是一張非常小的表,同時將_direct_read_decision_statistics_driven隱含參數設置為true,獲取SQL語句的[NSMTIO]事件。
代碼清單1.22 隱含參數_direct_read_decision_statistics_driven對直接路徑讀取的影響(2)

從該SQL語句的[NSMTIO]事件跟蹤日志可以看出,該對象的數據塊只有10個,遠遠小于“小表”的閾值,所以最終只能NoDirectRead,也即只能進行內存讀。
代碼清單1.22 隱含參數_direct_read_decision_statistics_driven對直接路徑讀取的影響(3)

再次清空測試環境,訪問相同的SQL語句,僅僅是將隱含參數_direct_read_decision_statistics_driven設置成false,也即不從統計信息中獲取數據塊個數,而從數據對象的段頭中獲取真實的數據塊個數,同時獲取SQL語句的[NSMTIO]事件。
代碼清單1.22 隱含參數_direct_read_decision_statistics_driven對直接路徑讀取的影響(4)

從該SQL語句的[NSMTIO]事件跟蹤日志可以看出,該對象介于“小表”與“特大表”之間,其數據塊有19891個,緩存在內存中的數據塊只有1個,所以最終選擇了DirectRead,也即進行直接路徑讀取。
- Java語言程序設計
- 垃圾回收的算法與實現
- AWS Serverless架構:使用AWS從傳統部署方式向Serverless架構遷移
- ASP.NET Core Essentials
- C語言程序設計教程(第2版)
- HTML5+CSS3基礎開發教程(第2版)
- RTC程序設計:實時音視頻權威指南
- Python Tools for Visual Studio
- 編譯系統透視:圖解編譯原理
- Unity Game Development Scripting
- SQL Server從入門到精通(第3版)
- Go語言精進之路:從新手到高手的編程思想、方法和技巧(1)
- Laravel Application Development Blueprints
- Xcode 6 Essentials
- Unity 5.X從入門到精通