对于优化器而言,它在解析目标SQL,得到执行计划时至关重要的一点是决定访问数据的方法,即优化器要决定采用什么样的方式和方法去访问目标SQL所需要访问的存储在oracle数据库中的数据。
目标SQL所需要访问的数据一般存储在表里,而oracle访问表中数据的方法有两种:一种直接访问表,另一种是先访问索引,在回表(当然,如果目标sql所要访问的数据只通过访问相关的索引就可以得到,那么此时就不需要回表了)
oracle数据库中直接方位表中数据的方法有两种:一是全表扫描,而是rowid扫描
1. 全表扫描
全扫描时指oracle在访问目标表里数据时,会从该表所占用的第一个区(EXTENT)的第一个块(BLOCK)开始扫描,一直扫描到该表的高水位线(HWM),这段范围内所有的数据块oracle都必须读到。当然oracle会对这期间读到的所有数据施加目标SQL的where条件中指定的条件,最后只返回那些满足过滤条件的数据。
不是全表扫描不好,事实上oracle在做全表扫描操作时会使用多块读,这在目标表的数据量不大时执行效率是非常高的,但全表扫描最大问题就在于走全表扫描的目标sql的执行时间不稳定,不可控,这个执行时间一定会随着目标表数据量的递增而递增。因为随着目标表数据量的递增,它的高水位线会一直不断上涨,所以全表扫描该表时所需要读取的数据块的数量也不断增加,这意味着全表扫描该表时所需要耗费的I/O资源随之不断增加,当然完成对该表的全扫描所需要耗费的时间也会随之增加。另外,对于CBO而言,所要耗费I/O资源不断增加意味着全表扫描的成本值也会随着目标表数据量的递增而递增。
在oracle中,如果对目标表不停地插入数据,当分配给该表的现有空间不足时高水位线就会向上移动,但如果你用delete语句从该表中删除数据,则高水位线并不会随之往下移动。高水位线的这种特性带来的负作用是,即使使用delete语句删除所有数据,高水位线还是再原来的位置,这意味这个全表扫描该表时还是需要扫描该表高水位线下的所有数据块,所以此时对该表的全表扫描操作所耗费的时间与之前相比不会有明细的改善。
2.ROWID扫描
ROWID扫描是指oracle在访问目标表里的数据时,直接通过数据所在的rowid去定位并访问这些数据。rowid表示的是oracle的数据行记录所在物理存储地址,也就是说rowid实际上是和oracle中数据块里的行记录一一对应的。
既然rowid代表的就是表的数据行所在的物理存储地址,那么当oracle知道待访问的数据行所在rowid后,自然就可以根据rowid去直接访问对应表的相关数据行,这就是rowid的定义。
从严格意思来说,oracle中rowid扫描有两层含义:一种是根据用户在sql语句中输入rowid的值直接访问对应的数据行记录;另一种是先去访问相关的索引,然后根据访问索引后得到的rowid再回表去访问对应的行记录。
对oralce中堆表而言,我们可以通过内置的rowid伪劣得到对应行记录所在的rowid的值(注意,这个rowid只是一个伪劣,在实际的表块中并不存在该列)然后我们可以通过dbms_rowid包中的相关方法(dbms_rowid.rowid_relative_fno,dbms_rowid.rowid_block_number和dbms_rowid.rowid_row_number)将上述伪列的值翻译成对应数据行的实际物理存储地址。
SQL> select empno,ename,rowid,dbms_rowid.rowid_relative_fno(rowid)||'_'||dbms_rowid.rowid_block_number(rowid)||'_'||dbms_rowid.rowid_row_number(rowid) location from emp;
EMPNO ENAME ROWID LOCATION
---------- ---------- ------------------ --------------------------------------------------
7369 SMITH AAAVREAAEAAAACXAAA 4_151_0
14 rows selected.
从上述显示内容可以看出,empno为7369的行记录所对应的rowid伪列的值为“AAAVREAAEAAAACXAAA ”,使用dbms_rowid包对该列翻译后的值为4_151_0,这表是empno为7369的行记录实际的物理存储地址位于4号文件第151个数据块的第0行记录(数据块里数据行记录的记录号从0开始算起)
上述rowid伪列的值是可以直接在sql语句中where条件中使用的,这就是oralce中rowid扫描的两层含义中第一种,根据用户在sql语句中输入的rowid的值直接去访问数据行记录。
SQL> select empno,ename from emp where rowid='AAAVREAAEAAAACXAAL';
EMPNO ENAME
---------- ----------
7900 JAMES