什么是謂詞越界?謂詞越界其實就是SQL語句的查詢條件超出了數據庫統計信息所記錄的范圍。謂詞越界會導致Oracle優化器錯誤的選擇SQL語句的執行計划,導致性能問題。
這里舉一個簡單的例子說明謂詞越界導致優化器選擇了錯誤的執行計划。
create table t1 (col1 number); create index idx_t1 on t1(col1); begin for i in 1..10000 loop insert into t1 values (i); end loop; commit; end; /
這里創建了t1表,並在col1列上創建了索引,並向表里寫入了10000條數據。提供過對t1表收集統計信息,可以得到目前表t1的謂詞情況。
SQL> exec dbms_stats.gather_table_stats('SALP','T1'); SQL> select low_value,high_value from dba_tab_col_statistics where table_name='T1' and owner='SALP'; LOW_VALUE HIGH_VALUE ---------- ---------- C102 C302 SQL> var x number; SQL> exec dbms_stats.convert_raw_value('C102',:x); PL/SQL procedure successfully completed. SQL> select :x from dual; :X ---------- 1 SQL> exec dbms_stats.convert_raw_value('C302',:x); PL/SQL procedure successfully completed. SQL> select :x from dual; :X ---------- 10000
上面用到了一個系統包,把統計信息表里的上下限裸數據轉換成可讀的數值。
在謂詞范圍內的條件查詢的執行計划為
explain plan for select * from t1 where col1 between 1 and 10000; select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 1387720244 ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 10000 | 40000 | 7 (0)| 00:00:01 | |* 1 | INDEX FAST FULL SCAN| IDX_T1 | 10000 | 40000 | 7 (0)| 00:00:01 | ------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("COL1">=1 AND "COL1"<=10000) 13 rows selected.
因為這里的條件包含了t1表內的所有數據,所以采用多塊讀且不需要回表的執行計划是最優的(table access full/index fast full scan),這里實際使用的是index fast full scan。
接下來繼續向t1表寫入數據
begin for i in 10001..10000000 loop insert into t1 values (i); end loop; commit; end; /
在不重新收集統計信息的情況下,檢查表的統計信息
select low_value,high_value from dba_tab_col_statistics where table_name='T1' and owner='SALP'; LOW_VALUE HIGH_VALUE ---------- ---------- C102 C302
現在來進行一次謂詞越界的查詢,使用謂詞條件 col1 between 10001 and 10000000。按道理來說,這種選擇表里99.9%數據的語句應該使用多塊讀且不回表的執行計划(table access full/index fast full scan)。我們來實際試驗一下。
SQL> set timing on; SQL> Select count(*) from t1 where col1 between 10001 and 10000000; COUNT(*) ---------- 9990000 Elapsed: 00:00:11.17 SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID 86kr1tnhns36d, child number 0 ------------------------------------- Select count(*) from t1 where col1 between 10001 and 10000000 Plan hash value: 1970818898 ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 2 (100)| | | 1 | SORT AGGREGATE | | 1 | 4 | | | |* 2 | INDEX RANGE SCAN| IDX_T1 | 1 | 4 | 2 (0)| 00:00:01 | ----------------------------------------------------------------------------
可以看到這條語句執行了11s才出結果,且執行計划選擇的是單塊讀的index range scan,而不是我們期望的多塊讀不回表的兩種執行計划之一且返回的Rows和Bytes出現了嚴重預估錯誤。
我們重新為t1表收集一次統計信息,再次執行同樣的語句並檢查執行計划。
SQL> exec dbms_stats.gather_table_stats('SALP','T1'); PL/SQL procedure successfully completed. Elapsed: 00:00:07.92 SQL> select count(*) from t1 where col1 between 10001 and 10000000; COUNT(*) ---------- 9990000 Elapsed: 00:00:00.31 SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID g47843nv7gsdq, child number 0 ------------------------------------- select count(*) from t1 where col1 between 10001 and 10000000 Plan hash value: 3724264953 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 4434 (100)| | | 1 | SORT AGGREGATE | | 1 | 6 | | | |* 2 | TABLE ACCESS FULL| T1 | 9990K| 57M| 4434 (2)| 00:00:01 | ---------------------------------------------------------------------------
這次走出了我們希望的執行計划(table access full),預估的Rows和Bytes也都正常了,且語句花了310ms就運行完了。
謂詞越界一般會發生在什么場景下?
1 臨時表
這里指的是業務上的臨時表而不是Oracle數據庫本身的temporary table。在某些系統中會根據業務條件創建前台表和后台表,數據先進入前台表,處理完畢后,存入后台表,並用delete語句清理前台表的數據,前台表起到一個臨時表的作用。我們知道,Oracle自動收集統計信息的默認時間窗口是工作日晚上的22點到凌晨2點,或者周末的早上6點到第二天凌晨2點。在自動收集統計信息窗口內,數據庫前台表基本上處於無數據,或者數據量很小的情況,那么產生的統計信息就會和白天實際處理業務數據時有偏差,就有可能發生謂詞越界的情況。
2 巨大表
Oracle觸發自動收集某個表的統計信息的條件是表中修改的數據量超過該表數據總量的10%,假設一個表每天新增1w條數據,一年后這個表變成了365w條數據,那么這意味着這個表需要再過一個多月才會觸發一次自動收集統計信息的作業。那么在這個表上的謂詞查詢,尤其是時間、序列等自增條件上的查詢,就可能發生謂詞越界的情況,影響優化器正確選擇執行計划。