对一个很大的分区表进行 UPDATE、DELETE,想要加快执行速度,可以按照分区,在不同的会话中对每个分区单独进行 UPDATE、DELETE。但是对一个很大的非分区表进行 UPDATE、DELETE,如果只在一个会话里面运行 SQL,很容易引发 UNDO 不够,如果会话连接中断,会导致大量数据从 UNDO 回滚,这将是一场灾难。
对于非分区表,我们可以对表按照 ROWID 切片,然后开启多个窗口同时执行 SQL,这样既能加快执行速度,还能减少对 UNDO 的占用。
Oracle 提供了一个内置函数 DBMS_ROWID.ROWID_CREATE()用于生成 ROWID。对于一个非分区表,一个表就是一个段(Segment),段是由多个区(extent)组成,每个区里面的块物理上是连续的。因此,我们可以根据数据字典 DBA_EXTENTS,DBA_OBJECTS关联,然后再利用生成 ROWID 的内置函数人工生成 ROWID。
例如,我们对 SCOTT 账户下 TEST 表按照每个 Extent 进行 ROWID 切片。
select ' and rowid between ' || '''' ||dbms_rowid.rowid_create(1,b.data_object_id,a.relative_fno,a.block_id,0) || '''' || ' and ' || '''' ||dbms_rowid.rowid_create(1,b.data_object_id,a.relative_fno,a.block_id + blocks - 1,999) || ''';'from dba_extents a, dba_objects bwhere a.segment_name = b.object_nameand a.owner = b.ownerand b.object_name = 'TEST'and b.owner = 'SCOTT'order by a.relative_fno, a.block_id;
切片后生成的部分数据如下所示。
and rowid between 'AAASs5AAEAAB+SIAAA' and 'AAASs5AAEAAB+SPAPn';and rowid between 'AAASs5AAEAAB+SQAAA' and 'AAASs5AAEAAB+SXAPn';and rowid between 'AAASs5AAEAAB+SYAAA' and 'AAASs5AAEAAB+SfAPn';and rowid between 'AAASs5AAEAAB+SgAAA' and 'AAASs5AAEAAB+SnAPn';and rowid between 'AAASs5AAEAAB+SoAAA' and 'AAASs5AAEAAB+SvAPn';
假如要执行delete test where object_id>50000000,test 表有 1 亿条数据,要删除其中 5 000 万行数据,我们根据上述方法对表按照 ROWID 切片。
delete testwhere object_id > 50000000and rowid between 'AAASs5AAEAAB+SIAAA' and 'AAASs5AAEAAB+SPAPn';delete testwhere object_id > 50000000and rowid between 'AAASs5AAEAAB+SQAAA' and 'AAASs5AAEAAB+SXAPn';delete testwhere object_id > 50000000and rowid between 'AAASs5AAEAAB+SYAAA' and 'AAASs5AAEAAB+SfAPn';delete testwhere object_id > 50000000and rowid between 'AAASs5AAEAAB+SgAAA' and 'AAASs5AAEAAB+SnAPn';delete testwhere object_id > 50000000and rowid between 'AAASs5AAEAAB+SoAAA' and 'AAASs5AAEAAB+SvAPn';
最后,我们将上述 SQL 在不同窗口中执行,这样就能加快 delete 速度,也能减少对 UNDO 的占用。
上述方法需要手动编辑大量 SQL 脚本,如果表的 Extent 很多,这将带来大工作量。我们可以编写存储过程简化上述操作。
因为存储过程需要访问数据字典,我们需要单独授权查询数据字典权限。
grant select on dba_extents to scott;grant select on dba_objects to scott;CREATE OR REPLACE PROCEDURE P_ROWID(RANGE NUMBER, ID NUMBER) ISCURSOR CUR_ROWID ISSELECT DBMS_ROWID.ROWID_CREATE(1,B.DATA_OBJECT_ID,A.RELATIVE_FNO,A.BLOCK_ID,0) ROWID1,DBMS_ROWID.ROWID_CREATE(1,B.DATA_OBJECT_ID,A.RELATIVE_FNO,A.BLOCK_ID + BLOCKS - 1,999) ROWID2FROM DBA_EXTENTS A, DBA_OBJECTS BWHERE A.SEGMENT_NAME = B.OBJECT_NAMEAND A.OWNER = B.OWNERAND B.OBJECT_NAME = 'TEST'AND B.OWNER = 'SCOTT'AND MOD(A.EXTENT_ID, RANGE) = ID;V_SQL VARCHAR2(4000);BEGINFOR CUR IN CUR_ROWID LOOPV_SQL := 'delete test where object_id > 100 and rowid between :1 and :2';EXECUTE IMMEDIATE V_SQLUSING CUR.ROWID1, CUR.ROWID2;COMMIT;END LOOP;END;
如果要将表切分为 6 份,我们可以在 6 个窗口中依次执行。
beginp_rowid(6, 0);end;/beginp_rowid(6, 1);end;/beginp_rowid(6, 2);end;/beginp_rowid(6, 3);end;/beginp_rowid(6, 4);end;/beginp_rowid(6, 5);end;/
这样就达到了将表按 ROWID 切片的目的。在工作中,大家可以根据自己的具体需求对存储过程稍作修改(阴影部分)。
