试图合并-分析

今天遇到一个View Merging 的案例

具体SQL如下:

SELECT /*+ INDEX(d CUST_ASSOC_DNORM_IDX2) */
   DISTINCT e.geo_id, d.ctrl_perd cust_ctrl_perd, d.parnt_id cust_id
FROM
( -- customer list
     SELECT s.node_id   cust_id
     ,      s.node_id_2 geo_id
     FROM   idwsu3.smrt_node_val s
     WHERE  s.cube_id = 'SCE07'
      AND   s.col_name_2 = 'GEO_ID'
      AND   s.col_name   = 'CUST_ID'
   ) e,
   idwsu3.cust_assoc_dnorm d
WHERE d.strct_code='898'
  AND d.ctrl_perd  IN ( SELECT ctrl_perd
                              FROM   idwsu3.ref_ctrl
                              WHERE fact_type_code = 'BS'
                              AND   stage_id IN (30,31)
                              AND   strct_code = '898'
                            )
  AND d.dsend_id = e.cust_id;

这个SQL跑了6个多小时还没跑完

执行计划如下:

SQL> explain plan for SELECT /*+ INDEX(d CUST_ASSOC_DNORM_IDX2) */
  2     DISTINCT e.geo_id, d.ctrl_perd cust_ctrl_perd, d.parnt_id cust_id
  3  FROM
  4  ( -- customer list
  5       SELECT s.node_id   cust_id
  6       ,      s.node_id_2 geo_id
  7       FROM   idwsu3.smrt_node_val s
  8       WHERE  s.cube_id = 'SCE07'
  9        AND   s.col_name_2 = 'GEO_ID'
 10        AND   s.col_name   = 'CUST_ID'
 11     ) e,
 12     idwsu3.cust_assoc_dnorm d
 13  WHERE d.strct_code='898'
 14    AND d.ctrl_perd  IN ( SELECT ctrl_perd
 15                                FROM   idwsu3.ref_ctrl
 16                                WHERE fact_type_code = 'BS'
 17                                AND   stage_id IN (30,31)
 18                                AND   strct_code = '898'
 19                              )
 20    AND d.dsend_id = e.cust_id;

Explained.

Elapsed: 00:00:13.10
SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 535292439

-------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name                  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                       |     1 |    88 |    57   (4)| 00:00:01 |       |       |
|   1 |  HASH UNIQUE                          |                       |     1 |    88 |    57   (4)| 00:00:01 |       |       |
|   2 |   NESTED LOOPS                        |                       |     1 |    88 |    56   (2)| 00:00:01 |       |       |
|   3 |    NESTED LOOPS                       |                       |     1 |    53 |     6   (0)| 00:00:01 |       |       |
|   4 |     INLIST ITERATOR                   |                       |       |       |            |       |       |       |
|   5 |      TABLE ACCESS BY INDEX ROWID      | REF_CTRL              |     1 |    19 |     3   (0)| 00:00:01 |       |       |
|*  6 |       INDEX UNIQUE SCAN               | REF_CTRL_PK           |     1 |       |     2   (0)| 00:00:01 |       |       |
|   7 |     PARTITION RANGE ITERATOR          |                       |     1 |    34 |     3   (0)| 00:00:01 |   KEY |   KEY |
|   8 |      TABLE ACCESS BY LOCAL INDEX ROWID| CUST_ASSOC_DNORM      |     1 |    34 |     3   (0)| 00:00:01 |   KEY |   KEY |
|*  9 |       INDEX RANGE SCAN                | CUST_ASSOC_DNORM_IDX2 |     1 |       |     2   (0)| 00:00:01 |   KEY |   KEY |
|  10 |    PARTITION RANGE SINGLE             |                       |     1 |    35 |    50   (2)| 00:00:01 |    51 |    51 |
|* 11 |     INDEX RANGE SCAN                  | SMRT_NODE_VAL_IDX2    |     1 |    35 |    50   (2)| 00:00:01 |    51 |    51 |
-------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   6 - access("FACT_TYPE_CODE"='BS' AND ("STAGE_ID"=30 OR "STAGE_ID"=31) AND "STRCT_CODE"='898')
   9 - access("D"."STRCT_CODE"='898' AND "D"."CTRL_PERD"="CTRL_PERD")
       filter("D"."CTRL_PERD"="CTRL_PERD")
  11 - access("S"."CUBE_ID"='SCE07' AND "S"."COL_NAME"='CUST_ID' AND "D"."DSEND_ID"="S"."NODE_ID" AND
              "S"."COL_NAME_2"='GEO_ID')
       filter("S"."COL_NAME_2"='GEO_ID' AND "D"."DSEND_ID"="S"."NODE_ID")

28 rows selected.

这个SQL用到的表如下(带星号表示用了索引):

OWNER                TABLE_NAME                       Size(Mb) PARTITIONED          DEGREE          NUM_ROWS
-------------------- ------------------------------ ---------- -------------------- ---------- -------------
IDWSU3               *REF_CTRL                      .038383484 NO                            1       1118
IDWSU3               REF_CTRL                       .038383484 NO                            1       1118
IDWSU3               *SMRT_NODE_VAL                 148.379308 YES                           1       4714757
IDWSU3               *CUST_ASSOC_DNORM               88388.443 YES                           1     975600000

可以看到表CUST_ASSOC_DNORM非常大,有88G,一般我都是遇到加HINT的问题SQL,直接去掉HINT,执行计划如下:

SQL> explain plan for SELECT
  2     DISTINCT e.geo_id, d.ctrl_perd cust_ctrl_perd, d.parnt_id cust_id
  3  FROM
  4  ( -- customer list
  5       SELECT s.node_id   cust_id
  6       ,      s.node_id_2 geo_id
  7       FROM   idwsu3.smrt_node_val s
  8       WHERE  s.cube_id = 'SCE07'
  9        AND   s.col_name_2 = 'GEO_ID'
 10        AND   s.col_name   = 'CUST_ID'
 11     ) e,
 12     idwsu3.cust_assoc_dnorm d
 13  WHERE d.strct_code='898'
 14    AND d.ctrl_perd  IN ( SELECT  ctrl_perd
 15                                FROM   idwsu3.ref_ctrl
 16                                WHERE fact_type_code = 'BS'
 17                                AND   stage_id IN (30,31)
 18                                AND   strct_code = '898'
 19                              )
 20    AND d.dsend_id = e.cust_id;

Explained.

Elapsed: 00:00:53.97
SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3859030211

-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name                | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                     |     1 |    88 |    56   (4)| 00:00:01 |       |       |
|   1 |  HASH UNIQUE                    |                     |     1 |    88 |    56   (4)| 00:00:01 |       |       |
|   2 |   NESTED LOOPS                  |                     |     1 |    88 |    55   (2)| 00:00:01 |       |       |
|   3 |    NESTED LOOPS                 |                     |     1 |    53 |     5   (0)| 00:00:01 |       |       |
|   4 |     INLIST ITERATOR             |                     |       |       |            |          |       |       |
|   5 |      TABLE ACCESS BY INDEX ROWID| REF_CTRL            |     1 |    19 |     3   (0)| 00:00:01 |       |       |
|*  6 |       INDEX UNIQUE SCAN         | REF_CTRL_PK         |     1 |       |     2   (0)| 00:00:01 |       |       |
|   7 |     PARTITION RANGE ITERATOR    |                     |     1 |    34 |     2   (0)| 00:00:01 |   KEY |   KEY |
|*  8 |      INDEX RANGE SCAN           | CUST_ASSOC_DNORM_PK |     1 |    34 |     2   (0)| 00:00:01 |   KEY |   KEY |
|   9 |    PARTITION RANGE SINGLE       |                     |     1 |    35 |    50   (2)| 00:00:01 |    51 |    51 |
|* 10 |     INDEX RANGE SCAN            | SMRT_NODE_VAL_IDX2  |     1 |    35 |    50   (2)| 00:00:01 |    51 |    51 |
-----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   6 - access("FACT_TYPE_CODE"='BS' AND ("STAGE_ID"=30 OR "STAGE_ID"=31) AND "STRCT_CODE"='898')
   8 - access("D"."CTRL_PERD"="CTRL_PERD" AND "D"."STRCT_CODE"='898')
  10 - access("S"."CUBE_ID"='SCE07' AND "S"."COL_NAME"='CUST_ID' AND "D"."DSEND_ID"="S"."NODE_ID" AND
              "S"."COL_NAME_2"='GEO_ID')
       filter("S"."COL_NAME_2"='GEO_ID' AND "D"."DSEND_ID"="S"."NODE_ID")

26 rows selected.

Elapsed: 00:00:02.17

执行计划变了,这个执行计划没有用到 HINT 的索引,而是用了 CUST_ASSOC_DNORM_PK,现在来看一下这些索引有多大

OWNER                          SEGMENT_NAME                   SEGMENT_TYPE                     Size(Gb)
------------------------------ ------------------------------ ------------------------------ ----------
IDWSU3                         CUST_ASSOC_DNORM_PK            INDEX PARTITION                14.9101563
IDWSU3                         CUST_ASSOC_DNORM_IDX2          INDEX PARTITION                 13.484375
IDWSU3                         SMRT_NODE_VAL_IDX2             INDEX PARTITION                .286987305

查看大表上有哪些索引,分别建立在什么列上

SQL> select index_name,column_position,descend,column_name from dba_ind_columns
  2  where table_owner=upper('IDWSU3') and table_name=upper('CUST_ASSOC_DNORM') order by 1,2;

Index Name                     Pos# Order    Column Name
------------------------------ ---- -------- ------------------------------
CUST_ASSOC_DNORM_IDX2             1 ASC      STRCT_CODE
                                  2 ASC      DSEND_ID
                                  3 ASC      CTRL_PERD
                                  4 ASC      NET_LVL
                                  5 ASC      DSEND_LVL

CUST_ASSOC_DNORM_IDX3             1 ASC      STRCT_CODE
                                  2 ASC      CTRL_PERD
                                  3 ASC      PARNT_ID
                                  4 ASC      NET_LVL

CUST_ASSOC_DNORM_PK               1 ASC      CTRL_PERD
                                  2 ASC      STRCT_CODE
                                  3 ASC      PARNT_ID
                                  4 ASC      DSEND_ID

根据以上信息,结合大表的过滤条件,索引CUST_ASSOC_DNORM_IDX2 和 CUST_ASSOC_DNORM_PK 都可以用来 检索数据
再仔细分析这个SQL

SELECT
   DISTINCT e.geo_id, d.ctrl_perd cust_ctrl_perd, d.parnt_id cust_id
FROM
( -- customer list
     SELECT s.node_id   cust_id
     ,      s.node_id_2 geo_id
     FROM   idwsu3.smrt_node_val s
     WHERE  s.cube_id = 'SCE07'
      AND   s.col_name_2 = 'GEO_ID'
      AND   s.col_name   = 'CUST_ID'
   ) e,
   idwsu3.cust_assoc_dnorm d
WHERE d.strct_code='898'
  AND d.ctrl_perd  IN ( SELECT  ctrl_perd
                              FROM   idwsu3.ref_ctrl
                              WHERE fact_type_code = 'BS'
                              AND   stage_id IN (30,31)
                              AND   strct_code = '898'
                            )
  AND d.dsend_id = e.cust_id;

看一下 in 会返回多少数据

SQL> SELECT  ctrl_perd
  2                                FROM   idwsu3.ref_ctrl
  3                                WHERE fact_type_code = 'BS'
  4                                AND   stage_id IN (30,31)
  5                                AND   strct_code = '898';

CTRL_PERD
------------------
25-APR-11
08-MAY-11

Elapsed: 00:00:00.93

只返回2行数据,再看看 FROM 后面的子查询(这个子查询就叫做内联视图,FROM后面的子查询就叫做内联视图)会返回多少数据,这里我用Explain plan 看一下

SQL> explain plan for  SELECT s.node_id   cust_id
  2       ,      s.node_id_2 geo_id
  3       FROM   idwsu3.smrt_node_val s
  4       WHERE  s.cube_id = 'SCE07'
  5        AND   s.col_name_2 = 'GEO_ID'
  6        AND   s.col_name   = 'CUST_ID';

Explained.

Elapsed: 00:00:05.21
SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------
Plan hash value: 1519841108

-------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name               | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                    | 14392 |   491K|    51   (2)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE|                    | 14392 |   491K|    51   (2)| 00:00:01 |    51 |    51 |
|*  2 |   INDEX RANGE SCAN     | SMRT_NODE_VAL_IDX2 | 14392 |   491K|    51   (2)| 00:00:01 |    51 |    51 |
-------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("S"."CUBE_ID"='SCE07' AND "S"."COL_NAME"='CUST_ID' AND "S"."COL_NAME_2"='GEO_ID')
       filter("S"."COL_NAME_2"='GEO_ID')

15 rows selected.

Elapsed: 00:00:01.25

这里和之前的执行计划返回的结果完全不一样,所以怀疑CBO 对这个子查询进行 View Merging 出错了,使用hint  /*+ NO_MERGE*/ 禁止View Merging

SQL> explain plan for SELECT
  2     DISTINCT e.geo_id, d.ctrl_perd cust_ctrl_perd, d.parnt_id cust_id
  3  FROM
  4  ( SELECT /*+ NO_MERGE*/ s.node_id   cust_id
  5       ,      s.node_id_2 geo_id
  6       FROM   idwsu3.smrt_node_val s
  7       WHERE  s.cube_id = 'SCE07'
  8        AND   s.col_name_2 = 'GEO_ID'
  9        AND   s.col_name   = 'CUST_ID'
 10     ) e,
 11     idwsu3.cust_assoc_dnorm d
 12  WHERE d.strct_code='898'
 13    AND d.ctrl_perd  IN ( SELECT  ctrl_perd
 14                                FROM   idwsu3.ref_ctrl
 15                                WHERE fact_type_code = 'BS'
 16                                AND   stage_id IN (30,31)
 17                                AND   strct_code = '898'
 18                              )
 19    AND d.dsend_id = e.cust_id;

Explained.

Elapsed: 00:00:14.71
SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2600639289

-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name                | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                     |     1 |    67 |    57   (4)| 00:00:01 |       |       |
|   1 |  HASH UNIQUE                    |                     |     1 |    67 |    57   (4)| 00:00:01 |       |       |
|*  2 |   HASH JOIN                     |                     |     1 |    67 |    56   (2)| 00:00:01 |       |       |
|   3 |    NESTED LOOPS                 |                     |     1 |    53 |     5   (0)| 00:00:01 |       |       |
|   4 |     INLIST ITERATOR             |                     |       |       |            |          |       |       |
|   5 |      TABLE ACCESS BY INDEX ROWID| REF_CTRL            |     1 |    19 |     3   (0)| 00:00:01 |       |       |
|*  6 |       INDEX UNIQUE SCAN         | REF_CTRL_PK         |     1 |       |     2   (0)| 00:00:01 |       |       |
|   7 |     PARTITION RANGE ITERATOR    |                     |     1 |    34 |     2   (0)| 00:00:01 |   KEY |   KEY |
|*  8 |      INDEX RANGE SCAN           | CUST_ASSOC_DNORM_PK |     1 |    34 |     2   (0)| 00:00:01 |   KEY |   KEY |
|   9 |    PARTITION RANGE SINGLE       |                     | 14392 |   196K|    51   (2)| 00:00:01 |    51 |    51 |
|  10 |     VIEW                        |                     | 14392 |   196K|    51   (2)| 00:00:01 |       |       |
|* 11 |      INDEX RANGE SCAN           | SMRT_NODE_VAL_IDX2  | 14392 |   491K|    51   (2)| 00:00:01 |    51 |    51 |
-----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("D"."DSEND_ID"="E"."CUST_ID")
   6 - access("FACT_TYPE_CODE"='BS' AND ("STAGE_ID"=30 OR "STAGE_ID"=31) AND "STRCT_CODE"='898')
   8 - access("D"."CTRL_PERD"="CTRL_PERD" AND "D"."STRCT_CODE"='898')
  11 - access("S"."CUBE_ID"='SCE07' AND "S"."COL_NAME"='CUST_ID' AND "S"."COL_NAME_2"='GEO_ID')
       filter("S"."COL_NAME_2"='GEO_ID')

27 rows selected.

Elapsed: 00:00:01.67

大家注意看执行计划,变成了HASH JOIN了,跑一下SQL

.....................省略..................................................
000                                           25-APR-11          2000379797
000                                           08-MAY-11          2000380033

151 rows selected.

Elapsed: 00:00:43.02

其实这个SQL没跑完,我ctrl+c 了,但是它能在10多秒以内出结果,而非以前6小时不动。

为了简便起见,我省略了其他信息,本SQL没有统计信息过期问题。


为什么我怀疑CBO是视图合并错了?-----谁能解释一下?

我们再来看一下前面的执行计划


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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3859030211

-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                       | Name                | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |                     |     1 |    88 |    56   (4)| 00:00:01 |       |       |
|   1 |  HASH UNIQUE                    |                     |     1 |    88 |    56   (4)| 00:00:01 |       |       |
|   2 |   NESTED LOOPS                  |                     |     1 |    88 |    55   (2)| 00:00:01 |       |       |
|   3 |    NESTED LOOPS                 |                     |     1 |    53 |     5   (0)| 00:00:01 |       |       |
|   4 |     INLIST ITERATOR             |                     |       |       |            |          |       |       |
|   5 |      TABLE ACCESS BY INDEX ROWID| REF_CTRL            |     1 |    19 |     3   (0)| 00:00:01 |       |       |
|*  6 |       INDEX UNIQUE SCAN         | REF_CTRL_PK         |     1 |       |     2   (0)| 00:00:01 |       |       |
|   7 |     PARTITION RANGE ITERATOR    |                     |     1 |    34 |     2   (0)| 00:00:01 |   KEY |   KEY |
|*  8 |      INDEX RANGE SCAN           | CUST_ASSOC_DNORM_PK |     1 |    34 |     2   (0)| 00:00:01 |   KEY |   KEY |
|   9 |    PARTITION RANGE SINGLE       |                     |     1 |    35 |    50   (2)| 00:00:01 |    51 |    51 |
|* 10 |     INDEX RANGE SCAN            | SMRT_NODE_VAL_IDX2  |     1 |    35 |    50   (2)| 00:00:01 |    51 |    51 |
-----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   6 - access("FACT_TYPE_CODE"='BS' AND ("STAGE_ID"=30 OR "STAGE_ID"=31) AND "STRCT_CODE"='898')
   8 - access("D"."CTRL_PERD"="CTRL_PERD" AND "D"."STRCT_CODE"='898')
  10 - access("S"."CUBE_ID"='SCE07' AND "S"."COL_NAME"='CUST_ID' AND "D"."DSEND_ID"="S"."NODE_ID" AND
              "S"."COL_NAME_2"='GEO_ID')
       filter("S"."COL_NAME_2"='GEO_ID' AND "D"."DSEND_ID"="S"."NODE_ID")


ID=6 这里是 PK 主键,对不对?

那么它返回1条记录对不对?

那么ID=5 返回1条记录对不对?

好,现在来看ID=8 ,CBO 认为它返回 1行数据,这个我们暂时不管。
为什么我不管呢?

因为ID=3是嵌套循环,驱动表返回1条记录,这个走 嵌套循环没错吧?

好,那么我们现在来看 ID=10 这条记录, CBO是不是认为它也返回1行数据?

为什么这里CBO会认为它只返回1行数据呢? 大家请看谓词过滤条件:

  10 - access("S"."CUBE_ID"='SCE07' AND "S"."COL_NAME"='CUST_ID' AND "D"."DSEND_ID"="S"."NODE_ID" AND
              "S"."COL_NAME_2"='GEO_ID')
       filter("S"."COL_NAME_2"='GEO_ID' AND "D"."DSEND_ID"="S"."NODE_ID")

这里有 一个关键的地方,就是 "D"."DSEND_ID"="S"."NODE_ID"

CBO一旦遇到这种情况,在计算返回基数的时候,它就会算错,怎么个算错发呢?
D 上面的操作返回多少行记录,那么这行就返回多少记录(这个我还没经过验证,是我实践总结的,你们可以暂时这么参考吧)

那么这里 D 之前返回的是1行数据(具体情况ID=8这里),因为D返回1行数据,那么它传递给了ID=11这步,导致ID=10这里,CBO也认为它返回1行。

大家回忆一下 视图合并原理,如果视图不合并,是不是视图里面的查询优先执行? 视图执行完毕之后,再与外面的表进行连接。

现在都懂了没?

 

你可能感兴趣的:(试图合并-分析)