一、原理介绍:
pl/sql的主要功能是处理表中的数据,效率的提高主要是在数据的查询上。我们已经知道索引可以提高查询效率。那么,索引的原理是什么呢?
例如,创建表employee(first_name varchar2, last_name varchar2),并在employee.last_name上创建索引ind_e1。我们可以把索引ind_e1看做是另外一张表,它包含2列(last_name, rowid)。
我们发出的sql:update employee set first_name='h' where last_name='g';先从索引ind_e1上查询到该行的rowid,然后再根据rowid直接定位到该行数据所在的数据块和行。
那么很明显,我们直接使用rowid作为查询条件,会比使用索引有更高的执行效率,因为这样会跳过从索引中查询rowid的步骤。下面是一个图示:
下面我们用一个例子来看一下:
二、相关测试:
--0.创建表test1(a varchar2),在test1.a上创建唯一索引ind_test1_a。
过程略
SQL> select a, rowid from test1;
A ROWID
---------- ------------------
1 AAAHhNAALAAAAESAAA
2 AAAHhNAALAAAAESAAB
3 AAAHhNAALAAAAESAAC
---------- ------------------
1 AAAHhNAALAAAAESAAA
2 AAAHhNAALAAAAESAAB
3 AAAHhNAALAAAAESAAC
--1.使用索引的执行计划:
SQL> explain plan
2 for
3 update test1 set a = 22 where a = '2';
2 for
3 update test1 set a = 22 where a = '2';
已解释。
SQL> @%oracle_home%\rdbms\admin\utlxpls
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------
----------------------------------------------------------------------
--------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
--------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | | | |
| 1 | UPDATE | TEST1 | | | |
|* 2 | INDEX UNIQUE SCAN | PK_TEST1_A | | | |
--------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
--------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | | | |
| 1 | UPDATE | TEST1 | | | |
|* 2 | INDEX UNIQUE SCAN | PK_TEST1_A | | | |
--------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
---------------------------------------------------
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------
----------------------------------------------------------------------
2 - access("TEST1"."A"='2')
Note: rule based optimization
已选择15行。
--2.使用rowid的执行计划:
SQL> explain plan
2 for
3 update test1 set a = 11 where rowid = 'AAAHhNAALAAAAESAAA';
2 for
3 update test1 set a = 11 where rowid = 'AAAHhNAALAAAAESAAA';
已解释。
SQL> @%oracle_home%\rdbms\admin\utlxpls
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
----------------------------------------------------------------------------
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | | | |
| 1 | UPDATE | TEST1 | | | |
| 2 | TABLE ACCESS BY USER ROWID| TEST1 | | | |
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
---------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | | | |
| 1 | UPDATE | TEST1 | | | |
| 2 | TABLE ACCESS BY USER ROWID| TEST1 | | | |
---------------------------------------------------------------------------
Note: rule based optimization
已选择10行。
三、使用方法
看起来使用rowid作为查询条件是一个不错的主意。但我们知道,不把某行数据查询出来,是不可能知道它的rowid的,那又在什么情况下使用呢?
情况1:先查询某行,再更新该行时。
有时候,我们需要查询出某条数据供界面显示,然后再对这行数据进行修改。那么,在查询时顺便找到该行的rowid,然后在更新时以rowid作为条件,会有更高的效率。
例如,我们先查询ic01:
select a.*, a.rowid into rec_ic01, r_ic01_rowid from ic01 a where aac001 = '13040100001' and aae002 = '200701';
那么下面这条更新语句会有更高的执行效率:
update ic01 set aic020=2000 where rowid=r_ic01_rowid;
相比较而言,下面这条更新语句效率就比较低了,它使用到了查询语句中的where条件:
update ic01 set aic020=2000 where aac001 = '13040100001' and aae002 = '200701';
情况2:隐含的使用rowid作为查询条件。
有时候,我们打开一个游标(可能有1000行),然后在循环中对每行进行更新处理,这时可以使用“where current of <cursor_name>”作为更新语句的查询条件。能起到和rowid同样的效果。
我们还是看一个例子吧:
SQL> set serveroutput on
SQL> DECLARE
2 --定义游标,因为该游标中的数据需要更新,所以使用for update
3 CURSOR cur_test1 IS
4 SELECT *
5 FROM test1
6 FOR UPDATE;
7 --定义记录类型,使用锚定。这样表test1变化时,不需要修改程序
8 rec_test1 test1%ROWTYPE;
9 BEGIN
10 OPEN cur_test1;
11 LOOP
12 FETCH cur_test1 INTO rec_test1;
13 EXIT WHEN cur_test1%NOTFOUND;
14 --更新游标指针目前指向的test1的数据,使用current of cur_test1
15 UPDATE test1
16 SET a = a + 10
17 WHERE CURRENT OF cur_test1;
18 END LOOP;
19 CLOSE cur_test1;
20 COMMIT;
21
22 --我们使用隐式游标再打开一次,把结果显示出来看看
23 FOR rec_test1_2 IN ( SELECT * FROM test1 ) LOOP
24 dbms_output.put_line(rec_test1_2.a);
25 END LOOP;
26 END;
27 /
11
12
13
SQL> DECLARE
2 --定义游标,因为该游标中的数据需要更新,所以使用for update
3 CURSOR cur_test1 IS
4 SELECT *
5 FROM test1
6 FOR UPDATE;
7 --定义记录类型,使用锚定。这样表test1变化时,不需要修改程序
8 rec_test1 test1%ROWTYPE;
9 BEGIN
10 OPEN cur_test1;
11 LOOP
12 FETCH cur_test1 INTO rec_test1;
13 EXIT WHEN cur_test1%NOTFOUND;
14 --更新游标指针目前指向的test1的数据,使用current of cur_test1
15 UPDATE test1
16 SET a = a + 10
17 WHERE CURRENT OF cur_test1;
18 END LOOP;
19 CLOSE cur_test1;
20 COMMIT;
21
22 --我们使用隐式游标再打开一次,把结果显示出来看看
23 FOR rec_test1_2 IN ( SELECT * FROM test1 ) LOOP
24 dbms_output.put_line(rec_test1_2.a);
25 END LOOP;
26 END;
27 /
11
12
13
PL/SQL 过程已成功完成。
SQL>