`
a3mao
  • 浏览: 562948 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

nested loop驱动行源的选择

    博客分类:
  • Work
阅读更多
一般情况下,nested loop驱动行源应该选择小表,或者行数少的行源,但有时候也不一定。我们来看一个使用大表做驱动行源的例子。

1. 创建测试环境

大表test_big,小表test_small,二者通过test_big.object_id=test_small.object_id来关联。其中大表的object_id中会有重复记录,而小表中的object_id是唯一的,两个列上都有索引。

C:\Documents and Settings\yuechao.tianyc>sqlplus test/test

SQL*Plus: Release 10.2.0.1.0 - Production on 星期日 3月 29 22:37:13 2009

Copyright (c) 1982, 2005, Oracle.  All rights reserved.


连接到:
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options

SQL> create table test_big as select * from dba_objects;

表已创建。

SQL> insert into test_big select * from test_big;

已创建50000行。

SQL> /

已创建100000行。

...

SQL> /

已创建6400000行。

SQL> commit;

提交完成。

SQL> create table test_small as select * from dba_objects where rownum < 5001;

表已创建。

SQL> create index ind_test_big on test_big(object_id);

索引已创建。

SQL> create index ind_test_small on test_small(object_id);

索引已创建。


2. 获取执行计划

分别使用大表和小表作为驱动行源,先看一下它们的执行计划:

-- 1. 使用test_big作为驱动行源
-- 执行步骤:(1)全表扫描test_big,得到行源A;(2)将A作为驱动行源,嵌套循环连接索引ind_test_small,得到行源B;(3)将行源B通过索引ind_test_small与表test_small关联,得到结果集。
SQL> explain plan for
  2  select/*+ordered use_nl(test_big,test_small)*/ count(test_big.object_name||test_small.object_name)
  3   from test_big, test_small where test_big.object_id = test_small.object_id;

已解释。

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
Plan hash value: 3591390207

----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                |     1 |   158 |    15M  (1)| 53:19:22 |
|   1 |  SORT AGGREGATE              |                |     1 |   158 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST_SMALL     |     1 |    79 |     2   (0)| 00:00:01 |
|   3 |    NESTED LOOPS              |                |  1247K|   187M|    15M  (1)| 53:19:22 |
|   4 |     TABLE ACCESS FULL        | TEST_BIG       |    14M|  1106M| 39134   (2)| 00:07:50 |
|*  5 |     INDEX RANGE SCAN         | IND_TEST_SMALL |     1 |       |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("test_big"."OBJECT_ID"="test_small"."OBJECT_ID")

Note
-----
   - dynamic sampling used for this statement

已选择21行。

-- 2. 使用test_small作为驱动行源
-- 执行步骤:(1)全表扫描test_small,得到行源A;(2)将A作为驱动行源,嵌套循环连接索引ind_test_big,得到行源B;(3)将行源B通过索引ind_test_big与表test_big关联,得到结果集。
SQL> explain plan for
  2  select/*+ordered use_nl(test_big,test_small)*/ count(test_big.object_name||test_small.object_name)
  3   from test_small, test_big where test_big.object_id = test_small.object_id;

已解释。

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
Plan hash value: 1952886871

----------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |     1 |   158 |  1098K  (1)| 03:39:42 |
|   1 |  SORT AGGREGATE              |              |     1 |   158 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST_BIG     |   257 | 20303 |   226   (0)| 00:00:03 |
|   3 |    NESTED LOOPS              |              |  1247K|   187M|  1098K  (1)| 03:39:42 |
|   4 |     TABLE ACCESS FULL        | TEST_SMALL   |  4854 |   374K|    17   (0)| 00:00:01 |
|*  5 |     INDEX RANGE SCAN         | IND_TEST_BIG |   257 |       |     2   (0)| 00:00:01 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   5 - access("test_big"."OBJECT_ID"="test_small"."OBJECT_ID")

Note
-----
   - dynamic sampling used for this statement

已选择21行。


3. 比较执行时间

比较上面两个不同的执行计划。看起来使用表test_small作为驱动行源更合理一些,它只有50001行,而表test_big有12800000行数据。而实际却不是这样,我们看一下他们各自的执行时间:

SQL> set timing on
-- 使用大表作为驱动行源
SQL> select/*+ordered use_nl(test_big,test_small)*/ count(test_big.object_name||test_small.object_name)
  2  from test_big, test_small where test_big.object_id = test_small.object_id;

COUNT(TEST1.OBJECT_NAME||TEST2.OBJECT_NAME)
-------------------------------------------
                                   12800000

已用时间:  00: 00: 36.70

-- 使用小表作为驱动行源
SQL> select/*+ordered use_nl(test1,test2)*/ count(test1.object_name||test2.object_name)
  2  from test2, test1 where test1.object_id = test2.object_id;

COUNT(TEST1.OBJECT_NAME||TEST2.OBJECT_NAME)
-------------------------------------------
                                   12800000

已用时间:  00: 02: 42.89


我们看到,使用大表作为驱动行源,耗时约36.7秒;而使用小表作为驱动行源,耗时达到2分42.89秒!

4. 原因

其实原因就在与大表的列object_id中有很多重复记录,当使用小表作为驱动行源时,全表扫描test_small的时间虽然很快,但对于test_small中的每一条记录,都对应test_big中的若干条记录,那么就需要根据查到的这些rowid,进行若干次的磁盘I/O来获得大表对应的数据。这里频繁的磁盘I/O就是问题的原因。因为test_big数据量很大,数据不可能存储在内存中。

而当使用大表作为驱动行源时,全表扫描test_big的时间虽然比较长,但每条记录都对应test_small中的一条记录,而且表test_small比较小,其数据及索引数据可以从内存中直接找到。这样耗费的时间就主要是全表扫描test_big的时间了,而这耗费不了太多的时间。在下面的测试中,只耗费了26.14秒:

SQL> select count(*) from test_big;

  COUNT(*)
----------
  12800000

已用时间:  00: 00: 26.14

SQL> set timing off
SQL> explain plan for
  2  select count(*) from test_big;

已解释。

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------
--------------------------------------------------------------------------
Plan hash value: 3224830981

-----------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |     1 | 38982   (2)| 00:07:48 |
|   1 |  SORT AGGREGATE    |          |     1 |            |          |
|   2 |   TABLE ACCESS FULL| TEST_BIG |    14M| 38982   (2)| 00:07:48 |
-----------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement

已选择13行。



5. 备注

这里只是一个小测试,来说明nested loop中,驱动行源的选择要根据实际情况而定,不是一成不变的。其实这个SQL使用hash join是最快的:

SQL> explain plan for
  2  select count(test_big.object_name||test_small.object_name)
  3  from test_big, test_small where test_big.object_id = test_small.object_id;

已解释。

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
Plan hash value: 1810242240

----------------------------------------------------------------------------------
| Id  | Operation           | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |            |     1 |   158 | 39404   (3)| 00:07:53 |
|   1 |  SORT AGGREGATE     |            |     1 |   158 |            |          |
|*  2 |   HASH JOIN         |            |  1247K|   187M| 39404   (3)| 00:07:53 |
|   3 |    TABLE ACCESS FULL| TEST_SMALL |  4854 |   374K|    17   (0)| 00:00:01 |
|   4 |    TABLE ACCESS FULL| TEST_BIG   |    14M|  1106M| 39134   (2)| 00:07:50 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("TEST_BIG"."OBJECT_ID"="TEST_SMALL"."OBJECT_ID")

Note
-----
   - dynamic sampling used for this statement

已选择20行。

SQL> set timing on
SQL> select count(test_big.object_name||test_small.object_name)
  2  from test_big, test_small where test_big.object_id = test_small.object_id;

COUNT(TEST_BIG.OBJECT_NAME||TEST_SMALL.OBJECT_NAME)
---------------------------------------------------
                                            1280000

已用时间:  00: 00: 21.42
分享到:
评论

相关推荐

    MySQL中Nested-Loop Join算法小结

    Block Nested-Loop Join的效率提升在于使用join buffer来存储外部表的行。这样,内部表只需与buffer中的行组合进行比较,而不是与每一行单独比较。对于buffer中的行S和内部表的组合C,扫描内部表t3的次数可以用公式`...

    MySQL JOIN 工作原理浅析1

    当我们需要对两个表 R 和 S 进行连接时,Nested Loop Join 算法会将驱动表 R 中的每一条记录与被驱动表 S 中的每一条记录进行比较,从而得到连接结果。 在 Nested Loop Join 中,驱动表 R 会被作为外层循环,而被...

    oracle表的连接方式

    - 当行源已被排过序时,SORT MERGE JOIN的性能可能优于HASH JOIN。 **优化技巧**: - 使用`USE_MERGE(table_name1 table_name2)`提示来强制使用SORT MERGE JOIN。 - 在全表扫描比索引范围扫描更优的情况下,SORT ...

    Oracle表连接方式

    在nested loop连接中,驱动表满足条件结果集不大,被驱动表的连接字段要有索引,这样就走nested loop。如果驱动表返回记录太多,就不适合nested loops了。如果连接字段没有索引,则适合走hash join。可以用ordered...

    Oracle中表的连接及其调整.

    本文将深入探讨 Oracle 中的三种主要连接方式:嵌套循环连接(Nested Loop)、排序合并连接(Sort Merge)和哈希连接(Hash Join),并结合案例分析其工作原理和优化策略。 1. 嵌套循环连接(Nested Loop) 嵌套...

    oracle执行计划文档

    除了以上概念,Oracle执行计划还包括其他访问方法,如索引扫描(Index Scan)、索引唯一扫描(Index Unique Scan)、索引快速全扫描(Index Fast Full Scan)以及各种类型的连接操作,如嵌套循环(Nested Loop)、...

    44.答疑文章(三):说一说这些好问题1

    在Q1的EXPLAIN结果中,表a作为驱动表,使用Block Nested Loop (BNL) 算法,逐行处理表b并检查JOIN条件。而在Q2中,表b成为驱动表,这可能是因为优化器认为这样更有效率。没有在Extra字段中显示特定的JOIN算法可能...

    Oracle 表三种连接方式使用介绍(sql优化)

    使用`USE_NL(table_name1 table_name2)`提示可以强制执行Nested Loop Join,但最好确保被驱动表的连接字段有索引以提高效率。此外,Nested Loop Join的顺序很重要,驱动表的记录集应尽可能小,以快速获取结果。 2. ...

    基于分布式系统OceanBase的并行连接.pdf

    nested loop join是一种基本的join算法,它通过遍历一个表的所有行,并对另一个表进行匹配查找来完成join操作。这种算法的效率在很大程度上取决于较小表的大小以及连接条件的匹配效率。当涉及到大数据量时,nested ...

    34.到底可不可以使用join?1

    `EXPLAIN`结果展示了使用索引字段`a`进行`JOIN`的过程,这被称为`Index Nested-Loop Join`(NLJ)。 NLJ是一种逐行比较的`JOIN`方法,它首先遍历驱动表,然后对每一行在被驱动表中寻找匹配项。在这个例子中,`JOIN`...

    MYSQL查询、优化原理

    - **基于块的嵌套循环连接(Block Nested Loop Join)**:这是一种改进版本的嵌套循环连接,通过预先加载部分驱动表记录到内存中的 join_buffer 中,然后再对被驱动表进行扫描,以减少 I/O 操作次数。 #### 二、SQL ...

    Oracle中三种表连接算法的总结

    1. 嵌套循环连接 (NESTED LOOP Join)嵌套连接把要处理的数据集分为外循环(驱动数据源)和内循环(被驱动数据源),外循环只执行一次(先执行),内循环执行的次数等于外循环执行的数据集个数。 这种连接的好处是...

    hash join 原理和算法

    相比Nested Loop Join,Hash Join在处理大规模数据时更为高效,因为它不需要在驱动表上建立索引。 **一、Hash Join概述** Hash Join的基本思路是将较小的数据集(称为Build Input,如表S)构建为内存中的哈希表,...

    DB2嵌套循环

    嵌套循环(Nested Loop)是数据库查询执行计划中的一种基本操作,通常发生在连接(JOIN)操作中。当两个或更多表需要通过某个共同的键进行连接时,嵌套循环就会被用到。简单来说,嵌套循环就是遍历一个表的所有行,...

    关于Oracle的SQL性能调整.pdf

    1. 嵌套循环连接(Nested Loop Join):在这种连接方式下,如果表a较小,系统会选择a作为驱动表,对每个a中的key使用表b的索引进行定位查找。这种方法在a非常小而b较大的情况下效率较高,但如果a和b大小相近,性能...

    mysql Join使用以及优化

    然而,如果被驱动表的字段上没有索引,则Join操作将退化为Simple Nested-Loop Join,即需要对被驱动表执行全表扫描,这种方式效率较低,因为需要遍历被驱动表的每一行去匹配驱动表中的行。 在优化Join操作时,合理...

    Lec10_连表算法1

    **Nested Loop Join (NLJ)** 是一种基础的Join算法,它遍历外层表(R表)的每一行,并对内层表(S表)进行逐行比较。当外层表是小表时,NLJ可以减少I/O次数。然而,未优化的NLJ效率低下,因为它可能频繁地刷新缓存。...

    oracle数据库性能调优(3)

    1. 嵌套循环连接(Nested Loop Join):最直接的连接方式,其中一个表作为驱动表(Driver Table),另一个表作为被驱动表(Driven Table)。对于驱动表中的每一行,都会遍历被驱动表的每一行来寻找匹配的行。当被...

Global site tag (gtag.js) - Google Analytics