我需要优化Oracle SQL中的一个过程,主要使用索引。这是声明:
CREATE OR REPLACE PROCEDURE DEL_OBS(cuantos number) IS
begin
FOR I IN (SELECT * FROM (SELECT * FROM observations ORDER BY DBMS_RANDOM.VALUE)WHERE ROWNUM<=cuantos)
LOOP
DELETE FROM OBSERVATIONS WHERE nplate=i.nplate AND odatetime=i.odatetime;
END LOOP;
end del_obs;
我的计划是创建一个与rownum相关的索引,因为它似乎是用来执行删除的。但我不知道它是否值得。此过程的问题在于它的随机性会导致大量一致的获取。谁能帮我解决这个问题??谢谢:)
注意:我无法更改代码,只能在事后进行改进
使用ROWID
伪列筛选列:
CREATE OR REPLACE PROCEDURE DEL_OBS(
cuantos number
)
IS
BEGIN
DELETE FROM OBSERVATIONS
WHERE ROWID IN (
SELECT rid
FROM (
SELECT ROWID AS rid
FROM observations
ORDER BY DBMS_RANDOM.VALUE
)
WHERE ROWNUM < cuantos
);
END del_obs;
如果表上有索引,那么它可以使用索引快速完全扫描:
SQL 小提琴
Oracle 11g R2 架构设置:
CREATE TABLE table_name ( id ) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 50000;
查询 1:无索引:
DELETE FROM table_name
WHERE ROWID IN (
SELECT rid
FROM (
SELECT ROWID AS rid
FROM table_name
ORDER BY DBMS_RANDOM.VALUE
)
WHERE ROWNUM <= 10000
)
执行计划:
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
----------------------------------------------------------------------------------------
| 0 | DELETE STATEMENT | | 1 | 24 | 123 | 00:00:02 |
| 1 | DELETE | TABLE_NAME | | | | |
| 2 | NESTED LOOPS | | 1 | 24 | 123 | 00:00:02 |
| 3 | VIEW | VW_NSO_1 | 10000 | 120000 | 121 | 00:00:02 |
| 4 | SORT UNIQUE | | 1 | 120000 | | |
| * 5 | COUNT STOPKEY | | | | | |
| 6 | VIEW | | 19974 | 239688 | 121 | 00:00:02 |
| * 7 | SORT ORDER BY STOPKEY | | 19974 | 239688 | 121 | 00:00:02 |
| 8 | TABLE ACCESS FULL | TABLE_NAME | 19974 | 239688 | 25 | 00:00:01 |
| 9 | TABLE ACCESS BY USER ROWID | TABLE_NAME | 1 | 12 | 1 | 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 5 - filter(ROWNUM<=10000)
* 7 - filter(ROWNUM<=10000)
查询 2添加索引:
ALTER TABLE table_name ADD CONSTRAINT tn__id__pk PRIMARY KEY ( id )
查询 3使用索引:
DELETE FROM table_name
WHERE ROWID IN (
SELECT rid
FROM (
SELECT ROWID AS rid
FROM table_name
ORDER BY DBMS_RANDOM.VALUE
)
WHERE ROWNUM <= 10000
)
执行计划:
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
---------------------------------------------------------------------------------------
| 0 | DELETE STATEMENT | | 1 | 37 | 13 | 00:00:01 |
| 1 | DELETE | TABLE_NAME | | | | |
| 2 | NESTED LOOPS | | 1 | 37 | 13 | 00:00:01 |
| 3 | VIEW | VW_NSO_1 | 9968 | 119616 | 11 | 00:00:01 |
| 4 | SORT UNIQUE | | 1 | 119616 | | |
| * 5 | COUNT STOPKEY | | | | | |
| 6 | VIEW | | 9968 | 119616 | 11 | 00:00:01 |
| * 7 | SORT ORDER BY STOPKEY | | 9968 | 119616 | 11 | 00:00:01 |
| 8 | INDEX FAST FULL SCAN | TN__ID__PK | 9968 | 119616 | 9 | 00:00:01 |
| 9 | TABLE ACCESS BY USER ROWID | TABLE_NAME | 1 | 25 | 1 | 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 5 - filter(ROWNUM<=10000)
* 7 - filter(ROWNUM<=10000)
如果无法使用ROWID
在单个 SQL 语句中执行此操作,则可以重写现有过程以使用完全相同的查询,但使用FORALL
语句:
CREATE OR REPLACE PROCEDURE DEL_OBS(cuantos number)
IS
TYPE obs_tab IS TABLE OF observations%ROWTYPE;
begin
SELECT *
BULK COLLECT INTO obs_tab
FROM (
SELECT * FROM observations ORDER BY DBMS_RANDOM.VALUE
)
WHERE ROWNUM<=cuantos;
FORALL i IN 1 .. obs_tab.COUNT
DELETE FROM OBSERVATIONS
WHERE nplate = obs_tab(i).nplate
AND odatetime = obs_tab(i).odatetime;
END del_obs;
您最终需要的是OBSERVATIONS
上的索引,以允许DELETE
具有索引访问权限。
CREATE INDEX cuantos ON OBSERVATIONS(nplate, odatetime);
该过程的执行将导致OBSERVATIONS
表的一个FULL TABLE SCAN
,每个已删除的记录一个INDEX ACCESS
。
对于有限数量的已删除记录,它的行为将类似于其他答案中提出的集合DELETE
;对于大量已删除的记录,经过的时间将与删除次数成线性比例。
对于非三重数量的已删除记录,您必须假定索引未完全在缓冲池中,并且需要大量光盘访问。因此,您将以每秒大约 100 个已删除行结束。
换句话说,要删除 100K 行,大约需要 1/4 小时。
要删除1M 行,您需要 2 3/4 小时。
在此比例中删除任务的第一部分时,您会看到 - 表的FULL SCAN
可以忽略不计,只需几分钟。在这种情况下,获得可接受的响应时间的唯一可能性是将逻辑切换到单个DELETE
语句,如其他答案中提出的那样。
此行为也称为规则:">逐行慢慢">(即循环处理工作正常,但仅对有限数量的记录(。
您可以使用单个delete
语句执行此操作:
delete from observations o
where (o.nplate, o.odatetime) in (select nplace, odatetime
from (select o2.nplate, o2.odatetime
from observations o2
order by DBMS_RANDOM.VALUE
) o2
where rownum <= v_cuantos
);
这通常比对要删除的每一行执行多个查询更快。
试试这个。 在MSSQL上进行测试希望,这样它也可以在Oracle上运行。 请备注状态。
CREATE OR REPLACE PROCEDURE DEL_OBS(cuantos number) IS
begin
DELETE OBSERVATIONS FROM OBSERVATIONS
join (select * from OBSERVATIONS ORDER BY VALUE ) as i on
nplate=i.nplate AND
odatetime=i.odatetime AND
i.ROWNUM<=cuantos;
End DEL_OBS;
既然你说nplate
和odatetime
是observations
的主键,那么我猜问题就在这里:
SELECT * FROM (
SELECT *
FROM observations
ORDER BY DBMS_RANDOM.VALUE)
WHERE ROWNUM<=cuantos;
没有办法阻止它执行observations
的完整扫描,如果这是一个大表,再加上大量的排序。
您需要更改运行的代码。到目前为止,更改代码的最简单方法是更改源代码并重新编译它。
但是,有一些方法可以在不更改源代码的情况下更改执行的代码。 这里有两个:
(1( 使用DBMS_FGAC
添加一个策略来检测您是否在此过程,如果是,则向observations
表添加一个谓词,如下所示:
AND rowid IN
( SELECT obs_sample.rowid
FROM observations sample (0.05) obs_sample)
(2( 使用DBMS_ADVANCED_REWRITE
重写查询更改:
FROM observations
。。自。。
FROM observations SAMPLE (0.05)
在重写策略中使用查询的文本应防止它影响针对observations
表的其他查询。
这些都不容易(一点也不容易(,但如果你真的卡住了,值得一试。