对一个很大的分区表进行 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 b
where a.segment_name = b.object_name
and a.owner = b.owner
and 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 test
where object_id > 50000000
and rowid between 'AAASs5AAEAAB+SIAAA' and 'AAASs5AAEAAB+SPAPn';
delete test
where object_id > 50000000
and rowid between 'AAASs5AAEAAB+SQAAA' and 'AAASs5AAEAAB+SXAPn';
delete test
where object_id > 50000000
and rowid between 'AAASs5AAEAAB+SYAAA' and 'AAASs5AAEAAB+SfAPn';
delete test
where object_id > 50000000
and rowid between 'AAASs5AAEAAB+SgAAA' and 'AAASs5AAEAAB+SnAPn';
delete test
where object_id > 50000000
and 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) IS
CURSOR CUR_ROWID IS
SELECT 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) ROWID2
FROM DBA_EXTENTS A, DBA_OBJECTS B
WHERE A.SEGMENT_NAME = B.OBJECT_NAME
AND A.OWNER = B.OWNER
AND B.OBJECT_NAME = 'TEST'
AND B.OWNER = 'SCOTT'
AND MOD(A.EXTENT_ID, RANGE) = ID;
V_SQL VARCHAR2(4000);
BEGIN
FOR CUR IN CUR_ROWID LOOP
V_SQL := 'delete test where object_id > 100 and rowid between :1 and :2';
EXECUTE IMMEDIATE V_SQL
USING CUR.ROWID1, CUR.ROWID2;
COMMIT;
END LOOP;
END;
如果要将表切分为 6 份,我们可以在 6 个窗口中依次执行。
begin
p_rowid(6, 0);
end;
/
begin
p_rowid(6, 1);
end;
/
begin
p_rowid(6, 2);
end;
/
begin
p_rowid(6, 3);
end;
/
begin
p_rowid(6, 4);
end;
/
begin
p_rowid(6, 5);
end;
/
这样就达到了将表按 ROWID 切片的目的。在工作中,大家可以根据自己的具体需求对存储过程稍作修改(阴影部分)。