批量收集百万行以插入.......缺少行?
Posted
技术标签:
【中文标题】批量收集百万行以插入.......缺少行?【英文标题】:Bulk Collect with million rows to insert.......Missing Rows? 【发布时间】:2015-08-27 09:04:13 【问题描述】:我想将光标中的所有行插入到一个表中。但它没有插入所有行。只有一些行被插入。请帮助 我创建了一个过程 BPS_SPRDSHT,它将输入作为 3 个参数。
PROCEDURE BPS_SPRDSHT(p_period_name VARCHAR2,p_currency_code VARCHAR2,p_source_name VARCHAR2)
IS
CURSOR c_sprdsht
IS
SELECT gcc.segment1 AS company, gcc.segment6 AS prod_seg, gcc.segment2 dept,
gcc.segment3 accnt, gcc.segment4 prd_grp, gcc.segment5 projct,
gcc.segment7 future2, gljh.period_name,gljh.je_source,NULL NULL1,NULL NULL2,NULL NULL3,NULL NULL4,gljh.currency_code Currency,
gjlv.entered_dr,gjlv.entered_cr, gjlv.accounted_dr, gjlv.accounted_cr,gljh.currency_conversion_date,
NULL NULL6,gljh.currency_conversion_rate ,NULL NULL8,NULL NULL9,NULL NULL10,NULL NULL11,NULL NULL12,NULL NULL13,NULL NULL14,NULL NULL15,
gljh.je_category ,NULL NULL17,NULL NULL18,NULL NULL19,tax_code
FROM gl_je_lines_v gjlv, gl_code_combinations gcc, gl_je_headers gljh
WHERE gjlv.code_combination_id = gcc.code_combination_id
AND gljh.je_header_id = gjlv.je_header_id
AND gljh.currency_code!='STAT'
AND gljh.currency_code=NVL (p_currency_code, gljh.currency_code)
AND gljh.period_name = NVL (p_period_name, gljh.period_name)
AND gljh.je_source LIKE p_source_name||'%';
type t_spr is table of c_sprdsht%rowtype;
v_t_spr t_spr :=t_spr();
BEGIN
OPEN c_sprdsht;
LOOP
FETCH c_sprdsht BULK COLLECT INTO v_t_spr limit 50000;
EXIT WHEN c_sprdsht%notfound;
END LOOP;
CLOSE c_sprdsht;
FND_FILE.PUT_LINE(FND_FILE.OUTPUT,'TOTAL ROWS FETCHED FOR SPREADSHEETS- '|| v_t_spr.count);
IF v_t_spr.count > 0 THEN
BEGIN
FORALL I IN v_t_spr.FIRST..v_t_spr.LAST SAVE EXCEPTIONS
INSERT INTO custom.pwr_bps_gl_register
VALUES v_t_spr(i);
EXCEPTION
WHEN OTHERS THEN
l_error_count := SQL%BULK_EXCEPTIONS.count;
fnd_file.put_line(fnd_file.output,'Number of failures: ' || l_error_count);
FOR l IN 1 .. l_error_count LOOP
DBMS_OUTPUT.put_line('Error: ' || l ||
' Array Index: ' || SQL%BULK_EXCEPTIONS(l).error_index ||
' Message: ' || SQLERRM(-SQL%BULK_EXCEPTIONS(l).ERROR_CODE));
END LOOP;
END;
END IF;
fnd_file.put_line(fnd_file.output,'END TIME: '||TO_CHAR (SYSDATE, 'DD-MON-YYYY HH24:MI:SS'));
END BPS_SPRDSHT;
要插入的总行数=568388 插入的行数=48345。
【问题讨论】:
为什么不直接使用 SQL 插入行? 为了提高性能,我使用批量收集,因为有很多行。 @Nikhil 谁告诉你PL/SQL
比普通SQL
快?在 SQL 中执行。
Nikhil 和 David 是对的 ***.com/a/2673550/105282
不仅仅是性能问题,还有代码健壮性问题。在这里,您引入了一个错误,这在基于 SQL 的解决方案中是不可能发生的。
【参考方案1】:
Oracle 使用两个引擎来处理 PL/SQL 代码。所有程序代码都由 PL/SQL 引擎 处理,而所有 SQL 都由 SQL 语句执行器或 SQL 引擎 处理。两个引擎之间的每个上下文切换都会产生开销。
整个 PL/SQL 代码可以用普通的 SQL 编写,这将更快和更少的代码 >.
INSERT INTO custom.pwr_bps_gl_register
SELECT gcc.segment1 AS company,
gcc.segment6 AS prod_seg,
gcc.segment2 dept,
gcc.segment3 accnt,
gcc.segment4 prd_grp,
gcc.segment5 projct,
gcc.segment7 future2,
gljh.period_name,
gljh.je_source,
NULL NULL1,
NULL NULL2,
NULL NULL3,
NULL NULL4,
gljh.currency_code Currency,
gjlv.entered_dr,
gjlv.entered_cr,
gjlv.accounted_dr,
gjlv.accounted_cr,
gljh.currency_conversion_date,
NULL NULL6,
gljh.currency_conversion_rate ,
NULL NULL8,
NULL NULL9,
NULL NULL10,
NULL NULL11,
NULL NULL12,
NULL NULL13,
NULL NULL14,
NULL NULL15,
gljh.je_category ,
NULL NULL17,
NULL NULL18,
NULL NULL19,
tax_code
FROM gl_je_lines_v gjlv,
gl_code_combinations gcc,
gl_je_headers gljh
WHERE gjlv.code_combination_id = gcc.code_combination_id
AND gljh.je_header_id = gjlv.je_header_id
AND gljh.currency_code! ='STAT'
AND gljh.currency_code =NVL (p_currency_code, gljh.currency_code)
AND gljh.period_name = NVL (p_period_name, gljh.period_name)
AND gljh.je_source LIKE p_source_name
||'%';
更新
PL/SQL 中的 **frequent commits* 有利于性能,这是一个神话。
Thomas Kyte 解释得很漂亮here:
频繁的提交——当然,“释放”了撤销——这不变 导致 ORA-1555 和您的进程失败。这对 性能好吗?
频繁提交——当然,“释放”锁——这会抛出 窗外的交易完整性。这对数据来说很棒 诚信对吗?
频繁提交——确保“释放”重做日志缓冲区空间——通过 每次都强制您等待同步写入文件系统- 你等待,等待,等待。我可以看到这将如何“增加 性能”(不是)。哦,是的,重做缓冲区是 在后台刷新
每三秒 当 1/3 满时 1meg 满时会做同样的事情(释放这个资源)而不是让你等待。
频繁提交——没有资源可以释放——撤消就是撤消, 大的旧循环缓冲区。管理 15 对我们来说并不难 千兆瓦或 15 个字节的撤消。锁——嗯,它们是一个属性 数据本身,它在 Oracle 中并不昂贵(它将在 db2、sqlserver、informix 等)拥有十亿个锁与一个锁。 重做日志缓冲区——它不断地照顾自己, 不管你是否承诺。
【讨论】:
@Lalit-Thanks 。我认为纯 SQL 在我的情况下更好更快。 @Nikhil 在真正需要时使用 PL/SQL,否则 SQL 有许多功能可以完成复杂的事情。祝你好运!【参考方案2】:首先让我指出您正在使用的代码中有一个严重的错误:这就是您没有插入所有记录的原因:
BEGIN
OPEN c_sprdsht;
LOOP
FETCH c_sprdsht
BULK COLLECT INTO v_t_spr -- this OVERWRITES your array!
-- it does not add new records!
limit 50000;
EXIT WHEN c_sprdsht%notfound;
END LOOP;
CLOSE c_sprdsht;
每次迭代都会覆盖 v_t_spr 的内容,并使用接下来要读取的 50,000 行。 实际上,您插入的 48345 条记录只是上次迭代期间读取的最后一个块。
“插入”语句应该在同一个循环内:您应该为每读取 50,000 行执行一次插入操作。
你应该这样写的:
BEGIN
OPEN c_sprdsht;
LOOP
FETCH c_sprdsht BULK COLLECT INTO v_t_spr limit 50000;
EXIT WHEN c_sprdsht%notfound;
FORALL I IN v_t_spr.FIRST..v_t_spr.LAST SAVE EXCEPTIONS
INSERT INTO custom.pwr_bps_gl_register
VALUES v_t_spr(i);
...
...
END LOOP;
CLOSE c_sprdsht;
如果您希望将整个表加载到内存中以仅执行一次唯一插入,那么您将不需要任何循环或任何“限制 50000”子句......实际上您可以简单地使用“插入...选择”方法。
现在:不使用“插入 ...服务器上的空间来容纳它们。但如果这是问题所在(单个事务不能有这么多回滚数据),您还应该为每 50,000 个记录块执行一次 COMMIT,否则您的循环将无法解决问题:它只会比“插入...选择”,它会产生相同的“回滚空间不足”错误(现在我不记得确切的错误信息了...)
现在,每 50,000 条记录发出一次提交并不是最好的事情,但是如果您的系统实际上不足以处理所需的回滚空间,那么您就没有其他出路(或者至少我不知道其他出路...)
【讨论】:
如果你的操作没有足够的撤销空间,解决办法是增加撤销空间,而不是在游标内开始提交。 如果您正在处理包含数以百万计记录的表,您的客户可能不愿意仅仅因为您需要将数据从一个表复制到另一个表而购买另外 20.000 美元的存储空间...... @CarloSirna 频繁提交并不是一个好的解决方案。搜索谷歌,你会得到一个很好的解释 Thomas Kyte asktom.oracle.com/pls/asktom/… 我很清楚这些问题。在这里我只是把事情简单化了,因为我的主要重点是指出编程错误。 @Carlo Sirna-我按照你说的尝试了你的方法。但现在它插入了 550000 行,剩下的 18388 行被遗漏了。【参考方案3】:不要使用EXIT WHEN c_sprdsht%NOTFOUND
(这是您丢失行的原因),而是使用EXIT WHEN v_t_spr.COUNT = 0
【讨论】:
limit.COUNT 出错。我认为语法不正确以上是关于批量收集百万行以插入.......缺少行?的主要内容,如果未能解决你的问题,请参考以下文章