查詢優化器最主要的工作就是接受輸入的SQL以及各種環境參數、配置參數,生成合適的SQL執行計划(Execution Plan)。
Query Optimizer一共經歷了兩個歷史階段:
- RBO: Rule-Based Optimization 基於規則的優化器
- CBO: Cost-Based Optimization 基於代價的優化器
關於RBO與CBO,一個形象的比喻:大數據時代到來以前,做生意或許憑借多年累計 下來的經驗規則(RBO)就能夠很好的做出決策,跟隨市場變化。但是大數據時代,如果做生意還是靠以前憑經驗做決策,而不是靠大數據、數據分析、數據挖掘做決策,那么就有可能做出錯誤的決策。這也就是越來越多的公司對BI、數據挖掘越來越重視的緣故
一、RBO優化器
RBO(Rule Based Optimizer,基於規則的優化器)就是在優化器里面嵌入15種規則,它根據ORACLE指定的優先順序規則,對指定的表進行執行計划的選擇。執行SQL語句符合哪種規則,就按照規則定制出相應的SQL執行計划。比如在規則中,索引的優先級大於全表掃描;RBO是根據可用的訪問路徑以及訪問路徑等級來選擇執行計划,在RBO中,SQL的寫法往往會影響執行計划,它要求開發人 員非常了解RBO的各項細則,菜鳥寫出來的SQL腳本性能可能非常差。由於它他是一種過時呆板的優化器,Oracle官方已不再支持RBO服務。
15種規則及ranking:
- RBO Path 1: Single Row by Rowid
- RBO Path 2: Single Row by Cluster Join
- RBO Path 3: Single Row by Hash Cluster Key with Unique or Primary Key
- RBO Path 4: Single Row by Unique or Primary Key
- RBO Path 5: Clustered Join
- RBO Path 6: Hash Cluster Key
- RBO Path 7: Indexed Cluster Key
- RBO Path 8: Composite Index
- RBO Path 9: Single-Column Indexes
- RBO Path 10: Bounded Range Search on Indexed Columns
- RBO Path 11: Unbounded Range Search on Indexed Columns
- RBO Path 12: Sort Merge Join
- RBO Path 13: MAX or MIN of Indexed Column
- RBO Path 14: ORDER BY on Indexed Column
- RBO Path 15: Full Table Scan
二、CBO優化器
CBO是一種比RBO更加合理、可靠的優化器,它是從ORACLE 8中開始引入,但到ORACLE 9i 中才逐漸成熟。進入Oracle 10g之后,Query Optimizer就已經將CBO(Cost Based Optimizer,基於代價的優化器)作為默認優化器,並且Oracle官方不再支持RBO服務。
它的思路是讓Oracle獲取所有的執行計划的相關信息,利用系統統計量進行各種執行路徑試算,計算各種可能“執行計划”的“代價”,即COST,獲取相對相對成本最低的執行計划,作為實際運行方案。它依賴數據庫對象的統計信息,統計信息的准確與否會影響CBO做出最優的選擇。如果對一次執行SQL時發現涉及對象(表、索引 等)沒有被分析、統計過,那么ORACLE會采用一種叫做動態采樣的技術,
動態統計量收集是Oracle CBO優化器的一種特性。優化器生成執行計划是依據成本cost公式計算出的,如果相關數據表沒有收集過統計量,又要使用CBO的機制,就會引起動態采樣(Dynamic Sampling),。
動態采樣(dynamic sampling)就是在生成執行計划是,以一個很小的采用率現進行統計量收集, 動態的收集表和索引上的一些數據信息。由於采樣率低,采樣過程快但是不精確,而且采樣結果不會進入到數據字典中。
CBO查詢優化器由查詢轉換器(Query Transform)、代價估算器(Estimator)和計划生成器(Plan Generator)組成。
CBO優化器有兩種可選的運行模式:
2.1 FIRST_ROWS(n)
當設置優化器模式為:FIRST_ROWS(n)時,意味着Oracle在執行SQL語句時,優先考慮將結果集中的前n條記錄以最快的速度反饋回 來,而其他結果並不需要同時反饋,也就是說在處理數據的時候,后面的數據可能還沒提取出來,前面的數據已經返回給用戶了,這種需求在網站搜索或者BBS的 分頁上經常看到。比如每次只顯示查詢信息的前20條,這時設置FIRST_ROWS(20)就非常合適。對於分頁操作,越靠前的頁,顯示結果需要的時間將越短。
需要注意的是排序使用的X必須創建有索引,否則CBO會忽略FIRST_ROWS(n)而使用ALL_ROWS.
2.2 ALL_ROWS
CBO的模式為ALL_ROWS時,意味着我們需要Oracle以最快的速度將SQL執行完畢,將結果集全部返回。它和FIRST_ROWS(n) 的區別在於,ALL_ROWS強調整體的執行效率,而FIRST_ROWS(n)強調以最快的速度返回前n條記錄。ALL_ROWS在OLAP系統中使用 的比較多,它的目的在於快速獲取執行結果的最后一條記錄。
三、CHOOSE——兩個時代的過渡
在Oracle 9i一個時期,optimizer_mode的默認參數取值為CHOOSE。那個時期是query optimizer從RBO向CBO轉換的時代,Oracle選擇了漸變式的轉換。由於那時沒有專門的夜間統計量收集作業,很多時候數據表是沒有統計量的。加之動態抽樣技術的不成熟,所以CBO的應用存在一些障礙。在這個時期,Oracle提出了CHOOSE作為優化器默認模式。
CHOOSE是介於自動選擇切換RBO和CBO。如果SQL涉及的數據表中有一個有統計量,那么該SQL就是用CBO優化器。否則就是用RBO。動態采樣技術是在CBO優化器前提下的技術方案。

SCOTT@PDBORCL> alter session set optimizer_mode=choose; 會話已更改。 SCOTT@PDBORCL> set autotrace traceonly; SCOTT@PDBORCL> exec dbms_stats.delete_table_stats('scott','emp',cascade_columns => true,cascade_indexes => true); PL/SQL 過程已成功完成。 SCOTT@PDBORCL> select ename from emp ; 已選擇 14 行。 執行計划 ---------------------------------------------------------- Plan hash value: 3956160932 ---------------------------------- | Id | Operation | Name | ---------------------------------- | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS FULL| EMP | ---------------------------------- Note ----- - rule based optimizer used (consider using cbo) 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 8 consistent gets 0 physical reads 0 redo size 732 bytes sent via SQL*Net to client 544 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 14 rows processed SCOTT@PDBORCL> exec dbms_stats.gather_table_stats (ownname => 'scott',tabname => 'emp'); PL/SQL 過程已成功完成。 SCOTT@PDBORCL> select ename from emp; 已選擇 14 行。 執行計划 ---------------------------------------------------------- Plan hash value: 3956160932 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 14 | 84 | 3 (0)| 00:00:01 | | 1 | TABLE ACCESS FULL| EMP | 14 | 84 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------- 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 8 consistent gets 0 physical reads 0 redo size 732 bytes sent via SQL*Net to client 544 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 14 rows processed SCOTT@PDBORCL>
四、optimizer_mode的查看與修改
通過優化器參數optimizer_mode,我們可以控制Oracle優化器生成不同模式下的執行計划。
4.1查看optimizer_mode
parameter optimizer_mode
SCOTT@PDBORCL> show parameter optimizer_mode NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ optimizer_mode string ALL_ROWS SCOTT@PDBORCL> select name, value from v$parameter where name='optimizer_mode'; NAME VALUE -------------------------------------------------------------------------------- --------------------------------------- optimizer_mode ALL_ROWS SCOTT@PDBORCL>
4.2修改optimizer_mode
ORACLE 10g 優化器可以從系統級別、會話級別、語句級別三種方式修改優化器模式,非常方便靈活。 其中optimizer_mode可以選擇的值有: first_rows_n,all_rows. 其中first_rows_n又有first_rows_1000, first_rows_100, first_rows_10, first_rows_1
系統級別
可以通過下面語句修改optimizer_mode
alter system set optimizer_mode=all_rows scope=both;
會話級別
會話級別修改優化器模式,只對當前會話有效,其它會話依然使用系統優化器模式。
alter session set optimizer_mode=first_rows_100;
語句級別
語句級別通過使用提示hints來實現。
select /*+ rule */ * from emp;