Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/oracle/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Oracle总是使用哈希连接,即使两个表都很大?_Oracle_Join - Fatal编程技术网

Oracle总是使用哈希连接,即使两个表都很大?

Oracle总是使用哈希连接,即使两个表都很大?,oracle,join,Oracle,Join,我的理解是,只有当两个表中的一个足够小,可以作为哈希表放入内存时,哈希连接才有意义 但当我向oracle提供一个查询时,两个表都有数亿行,oracle仍然提出了一个哈希连接解释计划。即使我用OPT_ESTIMATE(rows=..)提示欺骗了它,它也总是决定使用哈希连接而不是合并排序连接 所以我想知道,在两个表都非常大的情况下,哈希连接是如何实现的 谢谢 Yang散列联接不必将整个表放入内存,只需将与该表的where条件匹配的行装入内存(甚至只需一个散列+rowid,我对此不确定) 因此,当Or

我的理解是,只有当两个表中的一个足够小,可以作为哈希表放入内存时,哈希连接才有意义

但当我向oracle提供一个查询时,两个表都有数亿行,oracle仍然提出了一个哈希连接解释计划。即使我用OPT_ESTIMATE(rows=..)提示欺骗了它,它也总是决定使用哈希连接而不是合并排序连接

所以我想知道,在两个表都非常大的情况下,哈希连接是如何实现的

谢谢
Yang

散列联接不必将整个表放入内存,只需将与该表的where条件匹配的行装入内存(甚至只需一个散列+rowid,我对此不确定)


因此,当Oracle决定影响其中一个表的where条件部分的选择性足够好时(即,必须对很少的行进行散列),它可能更喜欢哈希联接,即使对于非常大的表也是如此。

当所有内容都可以放入内存时,哈希联接显然工作得最好。但这并不意味着当表无法放入内存时,它们仍然不是最好的联接方法。我认为唯一其他的实际连接方法是合并排序连接

如果哈希表无法放入内存,则为合并排序联接对表进行排序也无法放入内存。合并联接需要对两个表进行排序。根据我的经验,对于加入和分组,哈希总是比排序快

但也有一些例外。从:

哈希联接通常比排序合并联接性能更好。然而, 如果两个 存在以下条件:

  The row sources are sorted already.
  A sort operation does not have to be done.

测试

与其创建数亿行,还不如强制Oracle只使用非常少量的内存

此图表显示,即使表太大,无法放入(人为限制的)内存,哈希联接的性能也优于合并联接:


注释

对于性能调优,通常使用字节比使用行数更好。但表的“真实”大小很难测量,这就是图表显示行的原因。大小大约从0.375 MB到14 MB。要仔细检查这些查询是否真的写入磁盘,您可以使用/*+GARGET\u plan\u statistics*/运行它们,然后查询v$sql\u plan\u statistics\u all

我只测试了散列连接和合并排序连接。我没有完全测试嵌套循环,因为join方法在处理大量数据时总是非常慢。作为一种理智的检查,我确实将它与上一次的数据大小进行了一次比较,并且在杀死它之前至少花了几分钟时间

我还测试了不同的_area_大小、有序数据和无序数据,以及连接列的不同区分度(匹配次数越多,CPU限制越大,匹配次数越少,IO限制越大),得到了相对相似的结果

然而,当记忆量小得可笑时,结果就不同了。只有32K排序|散列|区域|大小,合并排序联接明显更快。但是如果你的记忆力太差,你可能会有更重要的问题要担心

还有很多其他的变量要考虑,比如并行性、硬件、Bloom过滤器等等。人们可能已经在这个问题上写过书,我甚至没有测试过一小部分的可能性。但希望这足以证实一个普遍共识,即散列联接最适合大数据


代码

以下是我使用的脚本:

--Drop objects if they already exist
drop table test_10k_rows purge;
drop table test1 purge;
drop table test2 purge;

--Create a small table to hold rows to be added.
--("connect by" would run out of memory later when _area_sizes are small.)
--VARIABLE: More or less distinct values can change results.  Changing
--"level" to something like "mod(level,100)" will result in more joins, which
--seems to favor hash joins even more.
create table test_10k_rows(a number, b number, c number, d number, e number);
insert /*+ append */ into test_10k_rows
    select level a, 12345 b, 12345 c, 12345 d, 12345 e
    from dual connect by level <= 10000;
commit;

--Restrict memory size to simulate running out of memory.
alter session set workarea_size_policy=manual;

--1 MB for hashing and sorting
--VARIABLE: Changing this may change the results.  Setting it very low,
--such as 32K, will make merge sort joins faster.
alter session set hash_area_size = 1048576;
alter session set sort_area_size = 1048576;

--Tables to be joined
create table test1(a number, b number, c number, d number, e number);
create table test2(a number, b number, c number, d number, e number);

--Type to hold results
create or replace type number_table is table of number;

set serveroutput on;

--
--Compare hash and merge joins for different data sizes.
--
declare
    v_hash_seconds number_table := number_table();
    v_average_hash_seconds number;
    v_merge_seconds number_table := number_table();
    v_average_merge_seconds number;

    v_size_in_mb number;
    v_rows number;
    v_begin_time number;
    v_throwaway number;

    --Increase the size of the table this many times
    c_number_of_steps number := 40;
    --Join the tables this many times
    c_number_of_tests number := 5;

begin
    --Clear existing data
    execute immediate 'truncate table test1';
    execute immediate 'truncate table test2';

    --Print headings.  Use tabs for easy import into spreadsheet.
    dbms_output.put_line('Rows'||chr(9)||'Size in MB'
        ||chr(9)||'Hash'||chr(9)||'Merge');

    --Run the test for many different steps
    for i in 1 .. c_number_of_steps loop
        v_hash_seconds.delete;
        v_merge_seconds.delete;
        --Add about 0.375 MB of data (roughly - depends on lots of factors)
        --The order by will store the data randomly.
        insert /*+ append */ into test1
        select * from test_10k_rows order by dbms_random.value;

        insert /*+ append */ into test2
        select * from test_10k_rows order by dbms_random.value;

        commit;

        --Get the new size
        --(Sizes may not increment uniformly)
        select bytes/1024/1024 into v_size_in_mb
        from user_segments where segment_name = 'TEST1';

        --Get the rows.  (select from both tables so they are equally cached)
        select count(*) into v_rows from test1;
        select count(*) into v_rows from test2; 

        --Perform the joins several times
        for i in 1 .. c_number_of_tests loop
            --Hash join
            v_begin_time := dbms_utility.get_time;
            select /*+ use_hash(test1 test2) */ count(*) into v_throwaway
            from test1 join test2 on test1.a = test2.a;
            v_hash_seconds.extend;
            v_hash_seconds(i) := (dbms_utility.get_time - v_begin_time) / 100;

            --Merge join
            v_begin_time := dbms_utility.get_time;
            select /*+ use_merge(test1 test2) */ count(*) into v_throwaway
            from test1 join test2 on test1.a = test2.a;
            v_merge_seconds.extend;
            v_merge_seconds(i) := (dbms_utility.get_time - v_begin_time) / 100;
        end loop;

        --Get average times.  Throw out first and last result.
        select ( sum(column_value) - max(column_value) - min(column_value) ) 
            / (count(*) - 2)
        into v_average_hash_seconds
        from table(v_hash_seconds);

        select ( sum(column_value) - max(column_value) - min(column_value) ) 
            / (count(*) - 2)
        into v_average_merge_seconds
        from table(v_merge_seconds);

        --Display size and times
        dbms_output.put_line(v_rows||chr(9)||v_size_in_mb||chr(9)
            ||v_average_hash_seconds||chr(9)||v_average_merge_seconds);

    end loop;
end;
/
——如果对象已经存在,则删除它们
升降台测试_10k_行清洗;
降表test1吹扫;
滴台试验2吹扫;
--创建一个小表以容纳要添加的行。
--(“connect by”将在以后当面积较小时耗尽内存。)
--变量:或多或少不同的值可以更改结果。改变
--“level”到类似“mod(level,100)”的值将导致更多的连接,这
--似乎更喜欢散列连接。
创建表test_10k_行(a号、b号、c号、d号、e号);
将/*+追加*/插入测试行
选择级别a、12345 b、12345 c、12345 d、12345 e
从双连接到分层连接
所以我想知道,在两个表都非常大的情况下,哈希连接是如何实现的

这将在多个过程中完成:读取驱动表并将其分块散列,对前导表进行多次扫描

这意味着在
O(N^2)
内存有限的情况下,哈希连接的伸缩性为
O(N)
(当然不需要排序),而在上,真正的大型表合并的性能优于哈希连接。然而,这些表应该非常大,这样单次读取的好处将超过非顺序访问的缺点,并且您将需要来自它们的所有数据(通常是聚合的)

考虑到现代服务器上的
RAM
大小,我们讨论的是在非常大的数据库上构建非常大的报告,这需要花费数小时,而不是您在日常生活中真正看到的

当输出记录集受
rownum
限制时,
MERGE JOIN
也可能有用。但这意味着连接的输入应该已经被排序,这意味着它们都被索引,这意味着
嵌套循环
也可用,这是优化器通常选择的,因为当连接条件是选择性的时,这更有效

在当前的实现中,
MERGE-JOIN
总是扫描,
NESTED-LOOPS
总是查找,而这两种方法的更智能组合(由统计数据支持)将是首选

您可能想在我的博客中阅读这篇文章:


解释计划是否显示索引范围扫描?您是正确的,使用哈希联接进行完整表扫描将消耗大量内存。请将完整的解释计划输出张贴在