如果你有两个数据来源如平面文件或表数据并且要将他们合并在一起你将怎么做?如果他们有一个共同的属性如客户ID那么该解决方案应该是很明显合并相关的属性在这个例子中只需合并客户ID就够了如果没有任何共同之处该怎么办呢?唯一的要求就是将数据源中的记录和数据源中的记录进行匹配 并且那个记录去和另一个记录匹配并没有关系那么问题是一个数据源中的每一个纪录如何获得从其他数据源记录的标记
上述问题可以被描述为向一个数据库中加入了不同的或看似无关的数据在先前的文章的文章中涉及如何使用ROWNUM在无关的数据之间创造联系该合并方法的本质是利用甲骨文提供虚拟数据列来建立联系下面的查询可以用来作为CREATE TABLE AS SELECT声明的一部分或作为基于满足加入条件既定目标表的插入
SELECT * FROM
(SELECT ROWNUM AS rownum_a
FROM TABLE_A
) ALIAS_A
(SELECT ROWNUM AS rownum_b
FROM TABLE_B
) ALIAS_B
WHERE ALIAS_Arownum_a = ALIAS_Brownum_b;
假设要合并的记录的数目过大(如数以百万计)这种方法潜在的缺点是什么?那么当一行作为一个记录时又如何了?我们没有真正的控制权决定的查询所返回结果行的顺序直到我们执行查询之前甲骨文是不知道记录的行号的换言之 ROWNUM是在这样的事实上创建的如果你要从两个地方选择数百万行你将支付甲骨文公司为每个记录分配行号(只针对你的查询而不是永远)的时间
让我们监测将两个有万行的表合并到一起的一个会话在这第一个例子中这个数据源已经记录可万个记录表A范围从到及表B范围从至 (即在第一个表中再加入万行) 如果加入后能够完美的保持行的顺序那么有序对将像下面表格这个样子
当我们查看数据时(通过Toad)发现Oracle数据库并不执行一个完美的排序并且相差甚远
该ROWNUM_A和B值一个一个都匹配因为这是我们匹配/合并的注意记录 (和 )是如何同ROWNUM 标记在一起的 所以我们可以推断是甲骨文以同样的方式填补表格之间的空白区块这应该说服你一次甚至永远(如果你至今还不知知道) ROWNUM虚拟数据列已没有意义或与个表中记录的实际顺序无关
创建表的声明追蹤 经过TKPROF 解析后输出结果如下
CREATE TABLE TABLE_ROWNUM AS
SELECT * FROM
(SELECT ID ROWNUM AS rownum_a
FROM TABLE_A)
ALIAS_A
(SELECT ID AS id_b ROWNUM AS rownum_b
FROM TABLE_B)
ALIAS_B
WHERE ALIAS_Arownum_a = ALIAS_Brownum_b
call count cpu elapsed disk query current rows
Parse
Execute
Fetch
total
我们知道一个事实即每个表都有万行在分析了表后 NUM_ROWS值显示为 当与甲骨文本身将通过连续计数报告的值相比较时要小心依靠通过第三方工具检查出的值(包括从USER_TABLES选择NUM_ROWS )为什么会有差异呢?是否是因为分析是基于样本或估算的数据或根据检查到的每个记录?
现在对于合并数据有一个可供选择的办法那就是让我们使用一个真正的列替代虚拟数据列一个自然的选择是创建(在某种意义上)基于序列替代关键字这个办法是为每个表添加一个命名为SEQ的列并且在基于序列号对他们进行更新并且保证每次更新使用相同的起点和相同的增量对一个表更新操作如下所示
SQL> create sequence tab_b;
Sequence created
Elapsed: ::
SQL> update table_b set seq = tab_bnextval;
rows updated
Elapsed: ::
有一件事应该可以立即脱颖而出创造一个合并关键字所花费的时间刚刚超过五分钟或是ROWNUM采取的方法所花费时间的倍这只是对两个表中的一个表所进行操作所花费的时间(第一张表格花费五分钟进行更新) 增加或创建一个合并关键字是必要的如有可能最好在创建表的时候就创建那么比通过ROWNUM做同样的事情所多花费的关键点是什么?
根据新的设置如何进行合并?
CREATE TABLE TABLE_SEQ AS
SELECT * FROM
(SELECT ID SEQ AS seq_a
FROM TABLE_A)
ALIAS_A
(SELECT ID AS id_b SEQ AS seq_b
FROM TABLE_B)
ALIAS_B
WHERE ALIAS_Aseq_a = ALIAS_Bseq_b
call count cpu elapsed disk query current rows
Parse
Execute
Fetch
total
有趣的是既然数据并非如此不同性能也只是略差那么解释计划展示的是什么?使用ROWNUM原始测试我们有
PLAN_TABLE_OUTPUT
Plan hash value:
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
| | CREATE TABLE STATEMENT | | G| G| | M ()| :: |
| | LOAD AS SELECT | TABLE_ROWNUM | | | | | |
|* | HASH JOIN | | G| G| M| K ()| :: |
| | VIEW | | K| M| | ()| :: |
| | COUNT | | | | | | |
| | TABLE ACCESS FULL | TABLE_B | K| K| | ()| :: |
| | VIEW | | K| M| | ()| :: |
| | COUNT | | | | | | |
| | TABLE ACCESS FULL | TABLE_A | K| K| | ()| :: |
Predicate Information (identified by operation id):
access(ALIAS_AROWNUM_A=ALIAS_BROWNUM_B)
基于序列的合并似乎是一个更好的计划
PLAN_TABLE_OUTPUT
Plan hash value:
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
| | CREATE TABLE STATEMENT | | G| G| | M ()| :: |
| | LOAD AS SELECT | TABLE_ROWNUM | | | | | |
|* | HASH JOIN | | G| G| M| K ()| :: |
| | VIEW | | K| M| | ()| :: |
| | COUNT | | | | | | |
| | TABLE ACCESS FULL | TABLE_B | K| K| | ()| :: |
| | VIEW | | K| M| | ()| :: |
| | COUNT | | | | | | |
| | TABLE ACCESS FULL | TABLE_A | K| K| | ()| :: |
Predicate Information (identified by operation id):
access(ALIAS_AROWNUM_A=ALIAS_BROWNUM_B)
虽然这是一个相对较小的数据集你可以明白为什么执行该计划的花费可能会引起误解如果基于序列的表在同一会话中被删除和重新建立创建表重新删除的时间到刚刚超过秒在表面上看第二轮创建的表似乎要快得多但真正要证明的是什么呢?
所要证明的是数据块已经读入缓存从缓存中读取数据块的速度将远远超过从磁盘双方读取的速度(这我们已经知道的事实) 它实际意义是你创建表需要多少时间?这通常是一次性完成如果原始表被删除和重创它的创建时间将大大加快
通过清除共享池和缓存来恢复性能 在ROWNUM和基于序的列情况下所花费的时间分别 秒和秒的在这一点上它可能看起来像是混为一谈但在运行期间其性能级别交换了这也许是事实但不要忘记设置了序列为基础的表格的费用(按时间)
总结
从某种意义上说最为相似的数据集操作系统和平台依赖性(多少行内存和I / O等) 他们可以更快地在不同数据集之间添加一个共同的属性然后在进行合并操作对于较小的数据集也许略高于万行我冒昧地说使用ROWNUM这将永远是比新增一个合并关键字更快即使使用常见的关键创建表的速度更快那么什么时候适当使用ROWNUM ?当在没有共同关键字的情况时你不关心表之间的特殊关联即使是正好就存在这样的事实如果你正在处理相关表他们基于一个共同的属性并且这些关联必须排序你一定不能依赖ROWNUM保持合并表之间的顺序它事关在一个表中具体行是否与第二个表中特定行匹配