PLSQL中BULK COLLECT搭配FORALL用法及存储过程优化疑问
问题解答
第一个问题:FORALL逻辑与执行停止时机
- FORALL是Oracle PLSQL提供的批量DML执行语法,本身就是用来替代逐行循环插入的,不需要在内部额外写循环,它会自动遍历
1 .. l_prod_ids.COUNT的所有下标,批量执行对应的INSERT语句,性能远高于普通FOR循环逐行插入。 - 你当前的存储过程逻辑是:每次从游标中批量取最多1万条prod_id,执行完对应批次的插入后,会继续取下一批数据,直到游标中所有符合条件的数据都处理完才会停止,不会插完1万条就直接退出。如果你的需求是最多只插入1万条就停止,需要在代码中额外加插入行数计数,达到1万后主动EXIT退出循环,或者直接在游标查询中加
ROWNUM <= 10000限制。
第二个问题:v_cnt变量与相关判断是否可删除
可以直接删除,原因如下:
- 你当前的v_cnt统计的是批量处理的次数,不是插入的总行数。每次处理1万条批量v_cnt才加1,
MOD(v_cnt, 10000) = 0的判断需要处理满1亿条数据才会触发,几乎不会生效。 - 你代码中循环末尾已经写了无条件的
COMMIT,每处理完一批数据就会提交,那个IF分支的COMMIT逻辑完全冗余,没有实际作用。
代码优化建议
- 提交逻辑优化:删除冗余的v_cnt变量、IF MOD判断代码,保留循环末尾的COMMIT即可。如果需要调整提交频率,建议改为统计插入的总行数,按实际需要的阈值提交。
- INSERT逻辑优化:当前INSERT中两个UNION的查询逻辑重复度极高,可以合并关联条件,用OR替代UNION减少重复查询,也可以用CTE抽离公共逻辑,大幅提升执行效率。
- 语法规范优化:用显式JOIN(INNER JOIN、LEFT JOIN)替代老式的逗号分隔表的关联写法,提升代码可读性,避免漏写关联条件产生笛卡尔积。
- 截断表逻辑说明:
TRUNCATE TABLE是DDL语句,执行后会自动提交所有未提交的事务,且无法回滚,如果你的业务逻辑需要支持插入失败回滚恢复数据,建议改为DELETE语句。 - 固定插入1万条的优化:如果你的需求就是最多只插入1万条数据,可以直接在游标c1的查询中加
ROWNUM <= 10000限制,不需要循环批量处理,一次性取数插入效率更高。
原存储过程代码
create or replace procedure GPU_DATA_EXTRACTOR_TEST(pid_billdate DATE) is v_cnt NUMBER; c_limit CONSTANT PLS_INTEGER DEFAULT 10000; CURSOR c1 IS SELECT DISTINCT intl_prod_id FROM apld_bill_rt abr, acct_bill ab WHERE abr.CHRG_TP = 'INSTALLMENT' AND abr.TAX_CATG_ID = 'NOTAX' AND abr.acct_bill_id = ab.acct_bill_id AND ab.bill_date = pid_billdate; TYPE prod_ids_t IS TABLE OF apld_bill_rt.intl_prod_id%TYPE INDEX BY PLS_INTEGER; l_prod_ids prod_ids_t; begin execute immediate 'truncate table GPU_INV_TEST'; v_cnt := 0; OPEN c1; LOOP FETCH c1 BULK COLLECT INTO l_prod_ids LIMIT c_limit; EXIT WHEN l_prod_ids.COUNT = 0; FORALL indx IN 1 .. l_prod_ids.COUNT INSERT INTO GPU_INV_TEST SELECT AB.ACCT_BILL_ID, AB.BILL_NO, AB.INV_ID, AB.BILL_DATE, ba2.bill_acct_id, ba1.bill_acct_id parent_bill_acct_id, AB.DUE_DATE, PG.CMPG_ID, ABR.NET_AMT, AB.DUE_AMT, P.PROD_NUM, pds.DST_ID, ABR.DESCR, p.intl_prod_id FROM apld_bill_rt abr, acct_bill ab, prod p, FCBSADM.PROD_DST pds, bill_acct_prod bap, bill_acct ba1, bill_acct ba2, prod_cmpg pg WHERE ab.intl_bill_acct_id = ba1.intl_bill_acct_id AND AB.ACCT_BILL_ID = ABR.ACCT_BILL_ID AND ba1.intl_bill_acct_id = ba2.parent_bill_acct_id AND ba2.intl_bill_acct_id = bap.intl_bill_acct_id AND bap.intl_prod_id = abr.intl_prod_id AND ABR.CHRG_TP = 'INSTALLMENT' AND bap.intl_prod_id = pds.intl_prod_id AND bap.intl_prod_id = p.intl_prod_id AND p.intl_prod_id = pg.intl_prod_id(+) AND ABR.intl_prod_id = l_prod_ids(indx) UNION SELECT AB.ACCT_BILL_ID, AB.BILL_NO, AB.INV_ID, AB.BILL_DATE, ba1.bill_acct_id, ba1.bill_acct_id parent_bill_acct_id, AB.DUE_DATE, PG.CMPG_ID, ABR.NET_AMT, AB.DUE_AMT, P.PROD_NUM, pds.DST_ID, ABR.DESCR, p.intl_prod_id FROM apld_bill_rt abr, acct_bill ab, prod p, FCBSADM.PROD_DST pds, bill_acct_prod bap, bill_acct ba1, prod_cmpg pg WHERE ab.intl_bill_acct_id = ba1.intl_bill_acct_id AND AB.ACCT_BILL_ID = ABR.ACCT_BILL_ID --AND ba1.intl_bill_acct_id = ba2.parent_bill_acct_id AND ba1.intl_bill_acct_id = bap.intl_bill_acct_id AND bap.intl_prod_id = abr.intl_prod_id AND ABR.CHRG_TP = 'INSTALLMENT' AND bap.intl_prod_id = pds.intl_prod_id AND bap.intl_prod_id = p.intl_prod_id AND p.intl_prod_id = pg.intl_prod_id(+) AND ABR.intl_prod_id = l_prod_ids(indx); v_cnt := v_cnt + 1; IF MOD (v_cnt, 10000) = 0 THEN COMMIT; END IF; COMMIT; END LOOP; CLOSE c1; end;
内容的提问来源于stack exchange,提问作者vitaminJava
相关产品推荐
相关产品推荐

