你能帮我理解这句话吗?
如果没有批量绑定,PL/SQL 会针对每条插入、更新或删除的记录向 SQL 引擎发送一条 SQL 语句,从而导致上下文切换,从而损害性能。
在 Oracle 内部,有一个 SQL 虚拟机 (VM) 和一个 PL/SQL VM。当您需要从一个 VM 移动到另一个 VM 时,会产生上下文转换的成本。单独而言,这些上下文转换相对较快,但是当您进行逐行处理时,它们加起来会占代码花费的时间的很大一部分。当您使用批量绑定时,您可以通过一次上下文转移将多行数据从一个 VM 移动到另一个 VM,从而显着减少上下文转移的次数,从而使您的代码更快。
以显式游标为例。如果我写这样的东西
DECLARE
CURSOR c
IS SELECT *
FROM source_table;
l_rec source_table%rowtype;
BEGIN
OPEN c;
LOOP
FETCH c INTO l_rec;
EXIT WHEN c%notfound;
INSERT INTO dest_table( col1, col2, ... , colN )
VALUES( l_rec.col1, l_rec.col2, ... , l_rec.colN );
END LOOP;
END;
然后每次我执行提取,我
每次我插入一行时,我都在做同样的事情。我承担了将一行数据从 PL/SQL VM 传送到 SQL VM、要求 SQL 执行INSERT
语句,然后再将另一个上下文转移回 PL/SQL 的成本。
如果source_table
有 100 万行,那就是 400 万次上下文转换,这可能占我的代码已用时间的合理比例。另一方面,如果我BULK COLLECT
使用 aLIMIT
为 100 进行 a,我可以通过在每次产生上下文成本时将 100 行数据从 SQL VM 检索到 PL/SQL 中的集合中来消除 99% 的上下文转换每次我在那里发生上下文转移时,转移并插入 100 行到目标表中。
如果可以重写我的代码以使用批量操作
DECLARE
CURSOR c
IS SELECT *
FROM source_table;
TYPE nt_type IS TABLE OF source_table%rowtype;
l_arr nt_type;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT INTO l_arr LIMIT 100;
EXIT WHEN l_arr.count = 0;
FORALL i IN 1 .. l_arr.count
INSERT INTO dest_table( col1, col2, ... , colN )
VALUES( l_arr(i).col1, l_arr(i).col2, ... , l_arr(i).colN );
END LOOP;
END;
现在,每次执行 fetch 时,我都会通过一组上下文转换将 100 行数据检索到我的集合中。每次FORALL
插入时,我都会插入 100 行,并带有一组上下文转换。如果source_table
有 100 万行,这意味着我已经从 400 万个上下文转换变为 40,000 个上下文转换。如果上下文转换占了我的代码运行时间的 20%,那么我已经消除了 19.8% 的运行时间。
您可以增加 的大小LIMIT
以进一步减少上下文转换的数量,但您很快就会遇到收益递减规律。如果您使用LIMIT
1000 而不是 100,您将消除 99.9% 的上下文转换,而不是 99%。但是,这意味着您的集合使用了 10 倍以上的 PGA 内存。在我们的假设示例中,它只会多消除 0.18% 的经过时间。通过消除额外的上下文转换,您很快就会达到您使用的额外内存增加的时间多于节省的时间的地步。一般来说,LIMIT
100 到 1000 之间的某个位置可能是最佳位置。
当然,在这个例子中,消除所有上下文转换并在单个 SQL 语句中完成所有事情会更有效
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
如果您正在对源表中的数据进行某种无法在 SQL 中合理实现的操作,那么首先求助于 PL/SQL 才有意义。
此外,我故意在示例中使用了显式游标。如果您使用隐式游标,则在最新版本的 Oracle 中,您可以隐式获得BULK COLLECT
aLIMIT
为 100 的好处。还有另一个 StackOverflow 问题讨论了隐式和显式游标与批量操作的相对性能优势,这些操作更详细地介绍了这些特定的皱纹。
据我了解,涉及两个引擎,PL/SQL engine 和 SQL Engine。执行一次使用一个引擎的查询比在两者之间切换更有效
例子:
INSERT INTO t VALUES(1)
由 SQL 引擎处理,而
FOR Lcntr IN 1..20
END LOOP
由PL/SQL引擎执行
如果将上面的两个语句结合起来,将 INSERT 放入循环中,
FOR Lcntr IN 1..20
INSERT INTO t VALUES(1)
END LOOP
对于每 (20) 次迭代,Oracle 将在两个引擎之间切换。在这种情况下,建议使用 BULK INSERT,它在整个执行过程中都使用 PL/SQL 引擎