随着11gR2中/*+APPEND_VALUES*/提示的出现,我怀疑我们将看到该功能被滥用,以及一堆关于为什么它“不起作用”的问题。该文档指出,“直接路径插入可以比传统的 INSERT 快得多。它应该声明的是,它也可能要慢得多(另外,如果未在表或表空间级别强制执行日志记录,则还可能导致备份不可用)。
首先,直接路径插入的工作原理是在现有数据的末尾插入数据,高于高水位线。如果有两个常规插入将数据抛出到一个表中,则它们可以根据需要移动高水位线。会话 1 可以将其移出 10 个块,然后会话 2 可以再移出 2 个块,然后会话 1 再次移动它。高水位线是有关表的元数据。它将存储在一个地方,并且多个会话可能会争用,希望一次更改它。对于传统的刀片,一旦会话调整了它,它就会释放其保持,其他会话可以进行调整。它不需要等待会话提交,并且争用通常不是问题。
在直接路径插入中,插入会移动 HWM,但无法解除对该信息的保留。这是因为它在新旧HWM之间写入的数据是“狡猾的”。它尚未提交,不应将其读入缓冲区缓存。[我怀疑它被写入数据文件,就好像它是提交的,而不是带有锁定标志和事务标识符。这样,它就避免了在随后读取时延迟清理块的需要。如果表上的另一个插入(甚至是更新或合并)需要移动 HWM,则必须等到提交或回滚直接路径事务。这在传统的插入/更新/合并中可能会发生,但总是会发生在另一个直接路径插入中。
在一个会话
中删除表TEST_IA清除中尝试此操作;
创建表TEST_IA (ID 号, val varchar2(4000));
插入 /*+ APPEND */ 到TEST_IA
选择行数 rn, 'b' val 从双连接通过级别 <= 1000;
然后这个在另一个
插入 /*+ APPEND */ 到TEST_IA
选择行 rn, 'b' val 从双连接通过级别 <= 1000;
明白我的意思吗?
如果您有 11gR2,请将 /*+ APPEND_VALUES */ 插入到TEST_IA值 (1,'b');将具有相同的效果。
因此,我预测的第一个失败模式将是多个会话,所有这些会话都尝试APPEND_VALUES到同一个表中,然后将头撞在一起。当开发人员尝试在数据库外部并行化操作时,很可能会发生这种情况。将头撞在一起也可能是合适的解决方案。
我怀疑第二种故障模式会在一定程度上缓解这种情况。完成直接路径插入后,如果在同一会话中尝试该表上的任何其他操作,则会收到“ORA-12838:并行修改对象后无法读取/修改对象”错误消息。我会说错误消息有点误导,但快速谷歌会告诉他们解决方案是在插入后进行提交。您可以将编码人员分成两组,第一组理解交易的概念,第二组不理解。我认为后者的数量正在增加。即使可以提交,您仍可能等待日志同步。
我预测的最终失败模式将是那些认为“啊,我可以在不生成日志数据的情况下进行插入的人。这应该更快。问题在于,元数据更改(移动 HWM)将被记录下来,并且只有创建的内容数据才可能未记录。在下面的脚本中,我比较了在循环中插入单个记录的方法。与传统的零件插入相比,很明显,对于记录尺寸较小的单行直接路径插入,可以生成更多的重做。当我使用较大的记录大小时(将值填充到几千个字符),重做大小相当,但传统路径插入中的重做条目仍然较少。我承认日志记录并不是唯一的性能影响,并且由于绕过缓冲区缓存,性能仍然可以提高,无需在表中查找可用空间等,
删除表TEST_ROW_IAV清除;
删除表TEST_ROW_IA清除;
删除表TEST_ROW_IV清除;
删除表TEST_ROW_I清除;
创建表TEST_ROW_IAV (ID 号, val varchar2(4000));
创建表TEST_ROW_IA (id 号, val varchar2(4000));
创建表TEST_ROW_IV (ID 号, val varchar2(4000));
创建表TEST_ROW_I (id 号, val varchar2(4000));
清除屏幕
声明
光标c_1选择
行数rn,'b'val从双连接通过级别< = 10000;
--
过程 rep_ext (在 varchar2 中p_seg)
是
光标c_e选择
rpad(segment_name,20) segment_name、tablespace_name、
计数(extent_id) cnt、sum(round(bytes/1024)) kb、来自user_extents的和(块) 块
,
其中 segment_name = p_seg
按segment_name分组,tablespace_name;
c_e
循环
c_out dbms_output.put_line(to_char(c_out段名称)||
' '||c_out表空间名称||''||to_char c_out 9990||
' '||to_char c_out.kb,999,990.00||'||to_char(c_out块,'999,990');
结束循环;
结束rep_ext;
--
过程 rep_redo(在 varchar2 中p_text)
是
游标c_r是
选择总和(当名称 = “重做条目”然后值结束时的情况) redo_entries,
求和(当名称 = “重做大小”然后值结束时的情况)
redo_size从 v$mystat s 在 n.statistic# = s.statistic#
中名称在 ('重做条目','重做大小');
dbms_output||在“p_text ||”处开始
to_char(日)||');
对于c_r循环
dbms_output put_行中的c_rec(“条目:”||to_char(c_rec重做条目,'9,999,990')||
' 尺寸:'||to_char(c_rec 999,999,990')
结束循环;
结束rep_redo;
--
开始
提交;
rep_redo(“开始”);
对于c_1循环
中的c_rec,将 /*+ APPEND_VALUES */ 插入到TEST_ROW_IAV值 (c_rec.rn, c_rec.val);
提交;
结束循环;
rep_redo(“在 /*+ APPEND_VALUES */'之后);
--
对于c_1循环
中的c_rec,插入 /*+ APPEND */ 到TEST_ROW_IA选择 c_rec.rn, c_rec.val fr对偶;
提交;
结束循环;
rep_redo(“在 /*+ 附加 */'之后);
--
对于c_1循环
中的c_rec,插入到TEST_ROW_IV值中(c_rec.rn,c_rec.val);
提交;
结束循环;
rep_redo(“插入值之后”);
--
对于c_1循环
中的c_rec,插入到TEST_ROW_I从对偶中选择 c_rec.rn, c_rec.val;
提交;
结束循环;
rep_redo(“插入选择后”);
-
rep_ext(“TEST_ROW_IAV”);
rep_ext(“TEST_ROW_IA”);
rep_ext(“TEST_ROW_IV”);
rep_ext(“TEST_ROW_I”);
--
结束;
/和我的结果:
从14:10:59开始 条目:912 大小:125,628
之后 /*+ APPEND_VAL 14:11:02 条目: 112,547 大小: 15,995,632
之后 /*+ APPEND */ 在 14:11:08 条目: 224,184 大小: 31,863,240
在 14:11:09 的插入值之后 条目: 234,409 大小: 36,723,128
插入后选择在 14:11:11 条目: 244,634 大小: 41,422,384
TEST_ROW_IAV用户 81 81,920.00 10,240
TEST_ROW_IA用户 81 81,920.00 10,240
TEST_ROW_IV 用户 3 192.00 24
TEST_ROW_I 用户 3 192.00 24
APPEND_VALUES(实际上是 APPEND)不适用于单行插入,但即使是小数组也会出现类似的问题。理想情况下,您希望每个插入都保留完整的块,以最大限度地减少未使用(并且可能不可用)的空间。如果将数组大小定位到单个块,您可能会发现在实践中,您会得到块的 75% 与 1 和 1 位块之间的变化(更多的空白空间)。但是,如果每个插入创建一百个块,则不介意最后一个块是否有点空。考虑到这一点,您可能希望考虑至少数千行的数组,并且可能根据行大小和块
大小在数万或数十万行中 再考虑
一个脚本,看看应该如何完成工作。首先是经典的插入,从选择中附加提示。其次,插入一个包含十万行的数组,其中包含append_values提示。最后,直接路径的“10g”方式使用SQL类型从PL / SQL变量插入。你所看到的是,对于这个数据集,在这三者之间没有太多的选择。
删除表TEST_FORALL_IAV清除;
删除表TEST_IA清除;
删除表TEST_TYPE_IA清除;
滴型type_test_ia;
滴型type_tab_test_ia;
创建类型 type_test_ia 是对象 (id 号, val varchar2(4000));
/
创建类型type_tab_test_ia是type_test_ia表;
/
创建表 TEST_FORALL_IAV (id 号, val varchar2(4000));
创建表TEST_IA (id 号, val varchar2(4000));
创建表TEST_TYPE_IA (ID 号, val varchar2(4000));
清除屏幕
声明
光标c_1是
选择行数rn,'b'val从双连接通过级别< = 100000;
TYPE tab_1是按pls_integer c_1%行类型索引的表;
t_1 tab_1;
t_tab type_tab_test_ia;
--
程序rep_ext (p_seg在 varchar2)
中) 是
游标c_e
选择 rpad(segment_name,20) segment_name、tablespace_name、
计数(extent_id) cnt、总和(舍入(字节/1024)) kb、user_extents的和(块) 块
,
其中 segment_name = p_seg
按segment_name分组,tablespace_name;
c_e
循环
c_out dbms_output.put_line(to_char(c_out段名称)||
' '||c_out表空间名称||''||to_char c_out 9990||
' '||to_char c_out.kb,999,990.00||
'||to_char(c_out块,'999,990');
结束循环;
结束rep_ext;
--
过程 rep_redo(p_text varchar2 中)
是
游标c_r选择
总和(当名称 = '重做条目' 然后值结束时的情况) redo_entries,
sum(当名称 = '重做大小' 然后值结束时的情况)
redo_size从 v$mystat s 加入 v$statname n n 在 n.statistic# = s.statistic#
中,其中名称在 ('重做条目','重做大小');
||dbms_output开始
(p_text,20)在“||to_char(日)||');
对于c_r循环
dbms_output put_line 中的c_rec(“条目:”||to_char(c_rec重做条目,'9,999,990')||
' 尺寸:'||to_char(c_rec 999,999,990')
结束循环;
结束rep_redo;
--
开始
提交;
rep_redo(“开始”);
插入 /*+ APPEND */ 到TEST_IA
选择行数 rn, 'b' val 从双连接通过级别 <= 100000;
rep_redo(“附加双重之后”);
--
开放c_1;
将批量收集c_1提取到t_1中;
关闭c_1;
在 1..t_1.count
中的所有 i 插入 /*+ APPEND_VALUES */ 到TEST_FORALL_IAV值 t_1(i);
rep_redo(“追加值之后”);
--
选择施法(收集(type_test_ia(rn,val))作为type_tab_test_ia)
到t_tab
(选择行 rn,“b”val 从双连接中按等级 <= 100000);
-
插入 /*+ 附加 */ 到TEST_TYPE_IA从表中
选择 * (t_tab);
rep_redo(“追加类型之后”);
-
rep_ext(“TEST_IA”);
rep_ext(“TEST_FORALL_IAV”);
rep_ext(“TEST_TYPE_IA”);
结束循环;
/
再说一遍,我的结果
从 14:33:04 开始 条目: 580 大小: 112,392
在 14:33:04 追加双精度之后 条目: 850 大小: 153,468
在 14:33:04 追加值之后 条目: 1,116 大小: 193,836
追加键入 14:33:05 条目: 1,383 大小: 234,432
TEST_IA USER 17 2,048.00 256
TEST_FORALL_IAV 用户 17 2,048.00 256
TEST_TYPE_IA 用户 17 2,048.00 256
参考至:http://blog.sydoracle.com/2010/02/append-values-and-how-not-to-break.html
如有错误,欢迎指正