Oracle 优化器_访问数据的方法_单表
Oracle 在选择执行计划的时候,优化器要决定用什么方法去访问存储在数据文件中的数据。我们从数据文件中查询到相关记录,有两种方法可以实现:1.直接访问表记录所在位置。2.访问索引,拿到索引中对应的rowid,然后根据rowid 去表中获取相应的数据。(有些情况,不需要再去表中取数据就可以得到相应的结果,那么就会直接返回)。
访问表的方法
全表扫描
全表扫描,Oracle 在取数据库数据的时候,从该表在硬盘上的第一个数据块开始,扫描到该表的最高水位线所在的数据块。在读的时候,会使用多块读的技术,将全表扫描一遍表数据,然后将不满足的数据剔除掉,返回需要的数据。Oracle 全表扫描的速度取决于最高水位线的大小。当表中的高水位线越大,需要消耗的资源(主要是I/O资源)越多。这样一来,耗费的时间也会增加。
高水位:在Oracle中,表是属于表空间的,如果建表的时候,没有设置表空间,那么就会将当前用户的默认空间作为表格所在的表空间。如果一直往表中插入数据,分配给表的空间用完了,高水位线就会向上移动。如果用delete语句删除数据,水位线也不回降下来。这就会导致,有的表虽然只有几条数据,但是全表扫描就会很消耗性能。所以在进行大量的delete操作的时候,需要执行降水位的操作。
ROWID 扫描
rowId 类似于指针的概念。rowid和数据块中的行数据是一一对应的。我们知道某一行对应的rowid后,可以直接通过rowid去直接访问相应数据对应的数据行。Oracle 使用row取数据有两种:1、直接用rowId从取得相应数据。2、根据索引获得rowId,然后取数据。
我们获取rowid的方法很简单,在每行记录中,都有一个Oracle内置的伪列rowId 直接在查询的时候去获取就可以了(注:这里需要将rowId 进行重命名,不然无法返回,不知道是不是笔者个人原因还是都需要这么写)。以emp表为例:
SELECT ENAME ,EMPNO, rowId dataRowId FROM EMP;
查出来的结果如下:
SMITH 7368 AAAtkkAAGAABqYkAAA SMITH 7369 AAAtkkAAGAABqYkAAB ALLEN 7499 AAAtkkAAGAABqYkAAC WARD 7521 AAAtkkAAGAABqYkAAD JONES 7566 AAAtkkAAGAABqYkAAE MARTIN 7654 AAAtkkAAGAABqYkAAF BLAKE 7698 AAAtkkAAGAABqYkAAG CLARK 7782 AAAtkkAAGAABqYkAAH SCOTT 7788 AAAtkkAAGAABqYkAAI KING 7839 AAAtkkAAGAABqYkAAJ TURNER 7844 AAAtkkAAGAABqYkAAK ADAMS 7876 AAAtkkAAGAABqYkAAL JAMES 7900 AAAtkkAAGAABqYkAAM FORD 7902 AAAtkkAAGAABqYkAAN MILLER 7934 AAAtkkAAGAABqYkAAO
这里查出的rowId可以直接作为where条件去进行查询,SQL如下:
SELECT ENAME ,EMPNO, rowId dataRowId FROM EMP where rowId='AAAtkkAAGAABqYkAAA';
执行结果:
这条SQL的执行计划如下:
Plan hash value: 1116584662 ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 22 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY USER ROWID| EMP | 1 | 22 | 1 (0)| 00:00:01 | ----------------------------------------------------------------------------------- Note -----
从Id为1的这一行可以看出,执行计划走的是 BY USER ROWID 这个执行计划。我们对比下通过主键EMPNO去查询得到的执行计划:
执行SQL如下:
SELECT ENAME ,EMPNO, rowId dataRowId FROM EMP where EMPNO='7368';
执行结果跟通过rowId执行得到的执行结果一致:
其执行计划如下:
Plan hash value: 2137789089 --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 | | 1 | COLLECTION ITERATOR PICKLER FETCH| DISPLAY | 8168 | 16336 | 29 (0)| 00:00:01 | --------------------------------------------------------------------------------------------- Note -----
可以看出,通过rowId得到的结果比使用主键进行查找的消耗要小的多,因为主键是先通过主键索引找到rowId,然后进行数据的提取操作,而rowId则是直接从数据文件中提取数据。
访问索引的方法
索引结构(B树索引)
在说通过索引扫描数据之前,先介绍下什么是索引。Oracle数据库中用的最多的是B树索引。B树索引的结构如下图所示:
索引包含两个部分,一部分是索引分支块,另外一部分是叶子块。在数据根据索引进行扫描的时候,可以根据数据的内容,计算得出一个索引的值。然后根据索引值,得到响应的rowId,然后根据rowid,去数据文件取出相应的数据。Oracle 通过索引访问表里的记录的效率并不会随着相关表的数据量的递增而显著降低,所以索引访问数据的时间是基本稳定可控的。
索引唯一性扫描(INDEX UNIQUE SCAN)
索引唯一性扫描,是针对唯一性索引(unique scan)进行的扫描。当它的where条件是等于号的时候,扫描结果至多会返回一条数据记录。例如:sql语句:
select * from emp where EMPNO = 7368
执行计划:
Plan hash value: 2949544139 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 37 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 37 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("EMPNO"=7368) Note
索引范围扫描(INDEX RANGE SCAN)
范围索引扫描,使用于所有类型的B树索引,当扫描对象是唯一性索引时,目标的SQL条件一定是范围条件,例如 where 条件为between、<、> 等。
例如,SQL语句为:
select * from emp where EMPNO > 7933
执行计划为:
Plan hash value: 2787773736 ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 37 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 1 | 37 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | PK_EMP | 1 | | 1 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("EMPNO">7933) Note
通过对比范围索引和唯一索引可以看出,即使使用同样的索引,范围索引也比唯一索引消耗更多的CPU,因为范围索引至少要多一次逻辑读。
索引全扫描(INDEX FULL SCAN)
索引在做全扫描的时候,要求索引不能为空。不然会漏掉null 的字段。索引全扫描在默认情况下,直接从第一个叶子节点,通过叶子节点之间相互的链表指针进行跳转。既能保证数据有序,又避免了对索引真正值的排序操作。
sql语句:
SELECT EMPNO FROM EMP
执行计划 :
Plan hash value: 179099197 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 16 | 64 | 1 (0)| 00:00:01 | | 1 | INDEX FULL SCAN | PK_EMP | 16 | 64 | 1 (0)| 00:00:01 | --------------------------------------------------------------------------- Note
索引快速全扫描(INDEX FAST FULL SCAN)
索引快速扫描和索引全扫描类似,与之相比有如下区别:
1.快速全扫描只适用于CBO。
2.索引快速全扫描可以使用多块读,也可以并发执行。
3.索引快速全扫描结果不一定有序。
索引跳跃式扫描(INDEX SKIP SCAN)
索引跳跃式扫描适用于组合索引。适用场景举例:当前索引有两个列,为:C1,C2。当我们的SQL语句where条件中,没有对C1进行筛选,而是对C2进行了筛选。那么有时候就会出现使用跳跃式索引扫描的情况。Oracle 中索引跳跃扫描适用于前导列可选择性较差,后续列的可选择性又非常好的场景。因为前导列包含的distinct值越少,跳跃次数也就越少,索引效率也就越高。