zoukankan      html  css  js  c++  java
  • Oracle表连接

    一个普通的语句select * from t1, t2 where t1.id = t2.id and t1.name = 'a'; 这个语句在什么情况下最高效?

    表连接分类:

      1. 嵌套循环连接(Nested Loop Join)

      2. 排序合并连接(Merge Sort Join):PGA 中的 SORT_AREA_SIZE 控制

      3. 哈希连接(Hash Join):PGA中的HASH_AREA_SIZE控制

    示例(基本均是在sql*plus环境下执行):

    -- 准备脚本

    prompt 准备实验环境.....

    drop table t1 cascade constraints purge;

    drop table t2 cascade constraints purge;

    create table t1(

      id       number not null,

      n        number,

      contents varchar2(4000)

    );

    create table t2(

      id       number not null,

      t1_id    number not null,

      n        number,

      contents varchar2(4000)

    );

    execute dbms_random.seed(0);

    insert into t1

    (

           id,

           n,

           contents

    )

    select rownum,

           rownum,

           dbms_random.string('a', 50)

      from dual

    connect by level <= 100

      order by dbms_random.random;

    insert into t2

    (

           id,

           t1_id,

           n,

           contents

    )

    select rownum,

           rownum,

           rownum,

           dbms_random.string('b', 50)

      from dual

    connect by level <= 100000

      order by dbms_random.random;

    commit; 

    -- 修改统计参数为ALL

    alter system set statistics_level=all;

    -- alter session set statistics_level=all; 会话级修改,偶尔失效

    show parameter statistics_level;

    set linesize 1000

    -- 实验开始

    -- NL连接表

    -- 1.1 HINT含义 leading(t1)表示强制先访问t1表,也就是t1表作为驱动表;use_nl表示强制用嵌套循环连接方式

    select /*+leading(t1) use_nl(t2)*/ * from t1, t2 where t1.id = t2.t1_id;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    starts列是表访问的次数,t1表访问了1次,t2表访问了100次

    -- 1.2

    select /*+leading(t1) use_nl(t2)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n in (19, 20);

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1表访问了1次,t2表访问了2次

    -- 1.3

    select /*+leading(t1) use_nl(t2)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1表访问了1次,t2表访问了1次

    -- 1.4

    select /*+leading(t1) use_nl(t2)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 9999999;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1表访问了1次,t2表访问了0次

    NL表连接访问次数最终结论:t1表的查询返回多少条记录,t2表就访问多少次。也就是,在嵌套循环连接中,驱动表返回多少条记录,被驱动表就访问多少次。

    --2.  哈希连接表

    -- 2.1

    select /*+leading(t1) use_hash(t2)*/ * from t1, t2 where t1.id = t2.t1_id;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1 1次, t2 1次

    在HASH连接中,驱动表和被驱动表都只会访问1次或0次

    -- 2.2

    select /*+leading(t1) use_hash(t2)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 9999999;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1 1次,t2 0次

    -- 2.3

    select /*+leading(t1) use_hash(t2)*/ * from t1, t2 where t1.id = t2.t1_id and 1 = 2;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1 0次, t2 0次

    -- 3. 排序合并连接

    select /*+ordered use_merge(t2)*/ * from t1, t2 where t1.id = t2.t1_id;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1 1次, t2 1次

    假设两张表,A和B,如果A作为驱动表,B作为被驱动表,那么,嵌套循环连接,就是先扫描A表,对A表的每一条记录,都扫描一遍B表;

    而排序合并连接是 A表跟B表内的记录同时排序,然后连接; 哈希连接,类似排序合并,建立哈希表来连接。所以当返回少量记录的时候(OLTP系统常用,OLTP多用于电信、金融等系统),嵌套循环连接更高效,而返回大量记录的时候,排序合并连接跟哈希连接更高效。

    各类连接驱动顺序区别

    1.嵌套循环连接

    select /*+leading(t1) use_nl(t2)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select /*+leading(t2) use_nl(t1)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1表先访问:BUFFER 1014,t2只访问1次

    t2表先访问:BUFFER 701K,t1被访问100000次

    所以,嵌套循环连接要特别注意驱动表的顺序,小的结果集先访问,大的结果集后访问

    2. 哈希连接

    select /*+leading(t1) use_hash(t2)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select /*+leading(t2) use_hash(t1)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    t1表先访问:BUFFER 1013,Used_Mem 286K,时间0.04秒

    t2表先访问:BUFFER 1013,Used_Mem 11MB,时间0.01秒

    在哈希连接中,驱动表顺序也很重要

    3.排序合并连接

    select /*+leading(t1) use_merge(t2)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select /*+leading(t2) use_merge(t1)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    无论先访问t1还是先访问t2,效率都一样,执行时间,BUFFER,USED_MEM都一样,这表明,排序合并连接没有驱动表概念。

    综上所述:嵌套循环连接和哈希连接有驱动顺序,驱动表的顺序不同将影响表连接的性能;而排序合并连接没有驱动的概念,无论哪张表在前都无妨。

    嵌套循环连接不需要排序;哈希连接并不排序,消耗内存是用于建议HASH表;排序合并连接需要排序。关于哈希连接和排序合并连接,不要取多余的字段参与排序,因为选择的字段越少,消耗内存的尺寸就越小。

    select /*+leading(t2) use_merge(t1)*/ * from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select /*+leading(t2) use_merge(t1)*/ t1.id from t1, t2 where t1.id = t2.t1_id and t1.n = 19;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    可以看出Used_Mem代表的内存消耗差别很大

    各类连接限制场景

    explain plan for

      select /*+leadind(t1) use_hash(t2)*/ *

         from t1, t2 

      where t1.id <> t2.t1_id and t1.n = 19;

    select * from table(dbms_xplan.display);

    没有按照HINT的提示走HASH连接,而是走了NL连接

    同理,可以试验> 、 < 、like,HASH都不支持。

    explain plan for

      select /*+leadind(t1) use_merge(t2)*/ *

         from t1, t2 

      where t1.id <> t2.t1_id and t1.n = 19;

    select * from table(dbms_xplan.display);

    排序合并连接不支持<>和Like,但是支持>、<

    嵌套循环无限制

    表连接与索引

    select /*+leading(t1) use_nl(t2)*/ * from t1, t2 where t1.id = t2.t1_id;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    如果不使用HINT提示

    select * from t1, t2 where t1.id = t2.t1_id;

    select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    会显示走的是HASH连接,因为ORACLE认为HASH连接更合理,但是此时逻辑读,COST值都差不多

    create index t1_n on t1(n);

    再执行上述语句,看看扫描t1表时BUFFER的差别

    create index t2_t1_id on t2(t1_id);

    再执行上述语句,看看扫描t2表时BUFFER的差别

    最适合NL连接的场景:

      1. 两表关联返回的记录不多,最佳情况是驱动结果集仅返回1条或少量几条记录,而被驱动表仅匹配到1条或少量几条记录,这种情况,即便t1和t2表的记录奇大无比,也是非常迅速的。

      2. 遇到一些不等值查询导致哈希和排序合并连接被限制使用,不得不使用NL连接。

      3.最佳的索引建立位置:驱动表的限制条件所在列有索引,被驱动表的连接条件所在列有索引。原因在于,驱动表的限制条件建立索引是为了缩小扫描驱动表的时 间,如果在驱动表的连接条件建立索引就没有任何意义了,所有列关联到另一个表的所有列,等同于每一条记录都要关联。而驱动表的限制条件建立了索引,只快速 返回1条或几条,然后再等传递给t2表的t1_id列,一般情况下,t2表对应t1表返回的记录并不多,所以t2表的t1_id列建索引是有意义的。

    哈希连接与索引

    连接条件的索引对哈希连接和排序合并连接起不到传递作用,对于哈希连接和排序合并连接来说,索引的连接条件建立索引起不到快速检索的作用,但是限制条件列如果有适合的索引可以快速检索到少量记录,还是可以提升性能的。

    两表关联等值查询,ORACLE一般倾向于走HASH连接,因为HASH连接算法本身比较高效,也比较先进。优化HASH连接可以通过调整PGA中的HASH_AREA_SIZE来优化,如果PGA是自动管理的,那么就增大PGA的大小。

    排序合并连接与索引

    排序合并连接上的连接条件虽然没有检索作用,但是有消除排序的作用,因此可以提高效率。

    SQL>select /*+ordered use_merge(t2)*/

                          *

                  from t1, t2

               where t1.id = t2.t1_id;

    SQL>select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    create index idx_t1 on t1(id);

    create index idx_t1_id on t2(t1_id); -- 上面建立过

    SQL>select /*+ordered use_merge(t2)*/

                          *

                  from t1, t2

               where t1.id = t2.t1_id;

    SQL>select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

    建了两个索引,最终只用到了一个,这是ORACLE排序合并连接的BUG,在11G官方文档里有承认。

    也可以通过增大PGA,优化排序合并连接

  • 相关阅读:
    Java-IO流系列-随机存取文件流
    Java-IO流系列-标准输入输出流
    Java-IO流系列-转换流
    Java-IO流系列-缓冲流
    Java-IO流系列-FileInputStream和FileOutStream
    Java-IO流系列-FileReader与FileWriter
    Java-IO流系列-IO流原理以及流的分类
    windows使用chrome调试ios webView
    页面上多个audio只播放一个
    阿里网盘分享
  • 原文地址:https://www.cnblogs.com/zuo-zijing/p/3964586.html
Copyright © 2011-2022 走看看