按照理解,它應該走全索引掃描,但它卻走了全表掃描。單表的數據量有點大,組成也有點複雜,LOB字段很多,索引有點多,加lob的索引一起有13個。這下性能就差很多,本來預計毫秒級別的操作變成了分鐘。在其他同版本的庫上,索引較少時,會走全索引掃描,但性能也不好,查詢時的一致性讀也很大。
SQL是這樣:select max(updateid),min(updateid) from dbcenter.TABLE_NAME ;
很簡單,而且updateid列上有一個唯一索引。索引也分析過,但現在執行起來卻性能差的很,致命的全表掃描。
首先,使用set autotrace trace exp stat得到真實的執行計劃。
SQL> set timing on
SQL> set autotrace trace exp stat
SQL> set linesize 300
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 373K (1)| 01:14:42 |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
| 2 | TABLE ACCESS FULL| TABLE_NAME | 8665K| 57M| 373K (1)| 01:14:42 |
-------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1700621 consistent gets
1506260 physical reads
0 redo size
602 bytes sent via SQL*Net to client
492 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
從結果中可以看到走的就是全表掃描。從統計值看,也是真正的全表掃描了,從頭掃到尾巴的那種,沒辦法,表中這個字段的值又不是排序的,不全部掃完不知道最大最小值的。
很顯然,這不是最優的結果。我認爲最理想應該是走updateid列的索引,一個索引快速全掃描就行。
猜測,會不會是索引多了不知道如何選擇。在select子句中是不主動選擇索引的?
但是,我使用hint也沒有效果,優化器依然沒有選擇走這個索引。
select/*+index_ffs(TABLE_NAME IDX55021287)*/ MAX(updateid), MIN(updateid) from dbcenter.TABLE_NAME;
Elapsed: 00:03:28.77
Execution Plan
----------------------------------------------------------
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 373K (1)| 01:14:42 |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
| 2 | TABLE ACCESS FULL| TABLE_NAME | 8665K| 57M| 373K (1)| 01:14:42 |
-------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1701902 consistent gets
1497285 physical reads
0 redo size
602 bytes sent via SQL*Net to client
492 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
但是,如果只查max或min時,會走索引。
select MIN(updateid) from dbcenter.TABLE_NAME ;
Execution Plan
----------------------------------------------------------
Plan hash value: 3935799349
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 373K (1)| 01:14:42 |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
| 2 | INDEX FULL SCAN (MIN/MAX)| IDX55021287 | 8665K| 57M| | |
------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
524 bytes sent via SQL*Net to client
492 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
性能也好的很,一致性讀只有3。這樣的結果也很好理解。索引是唯一索引,已經排序好的,求一個最大值,肯定只要掃描索引的開始或者結束部分的數據塊即可。
因此,需要分析一下這個SQL的執行計劃產生的過程。我使用event 10053 trace name context forever ,level 1方法來完成這個操作。
alter system flush shared_pool;
alter session set "_optimizer_search_limit"=15;
oradebug setmypid;
oradebug event 10053 trace name context forever ,level 1;
explain plan for select max(updateid),min(updateid) from dbcenter.TABLE_NAME ;
***************************************
SINGLE TABLE ACCESS PATH
-----------------------------------------
BEGIN Single Table Cardinality Estimation
-----------------------------------------
Table: TABLE_NAME Alias: TABLE_NAME
Card: Original: 8663996 Rounded: 8663996 Computed: 8663996.00 Non Adjusted: 8663996.00
-----------------------------------------
END Single Table Cardinality Estimation
-----------------------------------------
Access Path: TableScan
Cost: 373495.00 Resp: 373495.00 Degree: 0
Cost_io: 372211.00 Cost_cpu: 18442053762
Resp_io: 372211.00 Resp_cpu: 18442053762
******** Begin index join costing ********
****** trying bitmap/domain indexes ******
Access Path: index (FullScan)
Index: IDX242025
resc_io: 25019.00 resc_cpu: 1911171307
ix_sel: 1 ix_sel_with_filters: 1
Cost: 2515.21 Resp: 2515.21 Degree: 0
Access Path: index (FullScan)
Index: IDX94341804
resc_io: 31023.00 resc_cpu: 1953914433
ix_sel: 1 ix_sel_with_filters: 1
Cost: 3115.90 Resp: 3115.90 Degree: 0
Access Path: index (FullScan)
Index: PK_TABLE_NAME
resc_io: 25217.00 resc_cpu: 1912567352
ix_sel: 1 ix_sel_with_filters: 1
Cost: 2535.02 Resp: 2535.02 Degree: 0
Access Path: index (FullScan)
Index: IDX242025
resc_io: 25019.00 resc_cpu: 1911171307
ix_sel: 1 ix_sel_with_filters: 1
Cost: 2515.21 Resp: 2515.21 Degree: 0
****** finished trying bitmap/domain indexes ******
******** End index join costing ********
Best:: AccessPath: TableScan
Cost: 373495.00 Degree: 1 Resp: 373495.00 Card: 8663996.00 Bytes: 0
***************************************
從結果看,優化器在index join costing操作時,並沒有將IDX55021287索引計算進來。
即使我使用了alter session set "_optimizer_search_limit"=15;將限制值從5提升到15也沒有效果。或許,index join costing操作時引入的索引數量不是這個參數控制。
最大最小值的查詢操作,就不應該在SQL中一步完成,應該分步驟實現。很顯然,oracle的查詢重寫沒有那麼智能,沒有將其分開。即使在11g也不行,我測試過了。