一个大表,查询其ID的MAX值,ID列上建有唯一键索引,在没有缓存和清空shared pool的情况下其查询需要耗时1分半:
set timing on;
set autot on;
alter system flush buffer_cache;
alter system flush shared_pool;
SQL> select nvl(max(id),0) from mc$asset_audit_access_summary ;
NVL(MAX(ID),0)
————–
10974726
Elapsed: 00:01:33.75
Execution Plan
———————————————————-
Plan hash value: 55939531
——————————————————————————————————————
| Id | Operation | Name | Rows | Bytes | Cost(%CPU)| Time | Pstart| Pstop |
——————————————————————————————————————
| 0 | SELECT STATEMENT | | 1 | 6 | 7372 (3)| 00:01:29 | | |
| 1 | SORT AGGREGATE | | 1 | 6 | | | | |
| 2 | PARTITION RANGE ALL | | 10M| 61M| 7372 (3)| 00:01:29 | 1 | 35 |
| 3 | PARTITION LIST ALL | | 10M| 61M| 7372 (3)| 00:01:29 | 1 | 5 |
| 4 | INDEX FAST FULL SCAN| IDX$ASSET_SUMMARY_TIME | 10M| 61M| 7372 (3)| 00:01:29 | 1 | 175 |
——————————————————————————————————————
Statistics
———————————————————-
19739 recursive calls
0 db block gets
49757 consistent gets
45592 physical reads
0 redo size
524 bytes sent via SQL*Net to client
469 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
139 sorts (memory)
0 sorts (disk)
1 rows processed
可以看到执行计划中索引是走INDEX FAST FULL SCAN的,虽然已经走索引了,但物理读还是很高,因为表格的数据量很大,此时索引的大小也很大,有没有方法提高MAX函数的效率呢,答案是有的,因为首先B树索引是有序的,如果能使索引扫描的时候首先从最大值开始扫描,那不需要读整个索引,最大值就能马上得到了,在Oracle中,可以通过使MAX函数走INDEX FULL SCAN (MIN/MAX)访问路径来实现。
要使得MAX/MIN函数走INDEX FULL SCAN (MIN/MAX),需要满足两个条件:
1、列上有索引
2、列必须是非空
可以看到,第一个条件已经满足,看来问题出在第二条,修改列约束为非空,因为本身列中没有null值,可以直接修改:
alter table MC$ASSET_AUDIT_ACCESS_SUMMARY modify ID not null;
再看执行计划:
set timing on;
set autot on;
alter system flush buffer_cache;
alter system flush shared_pool;
SQL> alter table MC$ASSET_AUDIT_ACCESS_SUMMARY modify ID not null;
Table altered.
Elapsed: 00:00:02.42
SQL> select nvl(max(id),0) from mc$asset_audit_access_summary;
NVL(MAX(ID),0)
————–
10974726
Elapsed: 00:00:00.01
Execution Plan
———————————————————-
Plan hash value: 3287236083
——————————————————————————–
——————-
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————————
| 0 | SELECT STATEMENT | | 1 | 6 |3 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 6 | | |
| 2 | INDEX FULL SCAN (MIN/MAX)| UNQ$ASSET_SUMMARY_ID | 10M| 61M|3 (0)| 00:00:01 |
—————————————————————————————————
Statistics
———————————————————-
352 recursive calls
0 db block gets
450 consistent gets
19 physical reads
0 redo size
524 bytes sent via SQL*Net to client
469 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
7 sorts (memory)
0 sorts (disk)
1 rows processed
可以看到现在访问路径变成了INDEX FULL SCAN (MIN/MAX),在同样的清空缓存和shared pool的情况下,物理读降到了只有19次。查询时间减小至几乎为0。