由于版本升级引发的SQL语句故障(续)

 

在之前的文章《由于版本升级引发的SQL语句故障》一文中,笔者介绍了由于版本从9i上升到10g,引起原先报表group by语句行为差异问题和解决方法。一个朋友提出另一种策略,笔者也在这里面讨论一下。

 

1、索引分组列

 

笔者文章中介绍的方法,其实大都是通过修改SQL语句或者隐含参数的策略。一个朋友提出建议将分组列加索引,从而实现执行计划恢复到sort group by

那位朋友提出的SQL语句案例是:select empno, count(*) from scott.emp group by empno;

笔者刚开始想到这样的策略有一些合理之处。当加入索引之后,对分组列的检索可以放在索引对象。索引本身叶子节点是有序的,这样时候Hash Group By的需求就不是那么大。也许会出现sort group by的动作。

如果这样的策略能够成功,我们也就不需要修改隐含参数这个大动作了。Oracle中,确定任何结论一定建立在特定版本下的实验基础上。

下面进行实验。

 

2、环境准备设置

 

我们选择Oracle 10g进行实验,创建更有代表性的数据表T

 

SQL> select * from v$version;

BANNER

------------------------

Oracle Database 10g Release 10.2.0.4.0 - 64bit Production

PL/SQL Release 10.2.0.4.0 - Production

CORE 10.2.0.4.0 Production

TNS for Linux: Version 10.2.0.4.0 - Production

NLSRTL Version 10.2.0.4.0 - Production

 

SQL> desc t;

Name           Type          Nullable Default Comments

-------------- ------------- -------- ------- --------

OWNER          VARCHAR2(30)  Y                        

OBJECT_NAME    VARCHAR2(128) Y                        

SUBOBJECT_NAME VARCHAR2(30)  Y                        

(篇幅原因,有省略……

 

 

SQL> select count(*) from t;

 

  COUNT(*)

----------

     52677

 

--收集统计量

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

3Group By实验

 

我们首先看一下Oracle的默认行为。

 

SQL> explain plan for select owner, count(*) from t group by owner;

Explained

 

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

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 47235625

---------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |    22 |   132 |   167   (5)| 00:00:03 |

|   1 |  HASH GROUP BY     |      |    22 |   132 |   167   (5)| 00:00:03 |

|   2 |   TABLE ACCESS FULL| T    | 52677 |   308K|   162   (2)| 00:00:02 |

---------------------------------------------------------------------------

 

9 rows selected

 

和我们之前的结论相同,Oracle依据成本因素,选择了hash group by动作,对全表数据进行内部小堆排序。

按照思路,我们给分组列添加索引。

 

--owner列添加索引

SQL> create index idx_t_owner on t(owner);

Index created

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

SQL> explain plan for select owner, count(*) from t group by owner;

Explained

 

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

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 47235625

---------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |    23 |   138 |   167   (5)| 00:00:03 |

|   1 |  HASH GROUP BY     |      |    23 |   138 |   167   (5)| 00:00:03 |

|   2 |   TABLE ACCESS FULL| T    | 52677 |   308K|   162   (2)| 00:00:02 |

---------------------------------------------------------------------------

 

9 rows selected

 

问题出现了,给owner列添加索引,也没有带来sort group by。索引没有走到。强制呢?

 

SQL> explain plan for select /*+index(t)*/owner, count(*) from t group by owner;

 

Explained

 

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

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 47235625

---------------------------------------------------------------------------

| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |

---------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |      |    23 |   138 |   167   (5)| 00:00:03 |

|   1 |  HASH GROUP BY     |      |    23 |   138 |   167   (5)| 00:00:03 |

|   2 |   TABLE ACCESS FULL| T    | 52677 |   308K|   162   (2)| 00:00:02 |

---------------------------------------------------------------------------

 

9 rows selected

 

 

使用index hint依然没有效果。

冷静下来,这样是有问题的。空值null就是一个重点!空值是不会进入单键值索引叶子节点的,但是group by是会group空值的。

如果要让group byindex合力作用,需要让Oracle得到“背书”,也就是not null描述!

 

 

SQL> alter table t modify owner not null;

Table altered

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

SQL> explain plan for select owner, count(*) from t group by owner;

Explained

 

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

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 3139300238

--------------------------------------------------------------------------------

| Id  | Operation             | Name        | Rows  | Bytes | Cost (%CPU)| Time

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT      |             |    23 |   138 |    34  (18)| 00:00

|   1 |  HASH GROUP BY        |             |    23 |   138 |    34  (18)| 00:00

|   2 |   INDEX FAST FULL SCAN| IDX_T_OWNER | 52677 |   308K|    29   (4)| 00:00

--------------------------------------------------------------------------------

 

9 rows selected

 

 

Oracle得到认可,说这个列上不能有空。形成的新执行计划出现索引,而且进行的是index fast full scanOracle认为:既然承认这个字段不能出现空值,那么所有的列值一定会出现在叶子节点上!所以“数数叶子节点,没问题!”。

但是后面,“数”的动作,Oracle还是选择了Hash Group By。没有出现Sort Group By

至此,笔者感觉结论已经有了:在一般情况下,如果没有额外的声明“非空、主键”,即使已经给分组列加索引,Oracle路径也不会走。即使走到Oracle索引路径,sort group by也不会出现。

 

4sort group by nosort

 

回到那个朋友的案例,先看看那个SQL

 

select empno, count(*) from scott.emp group by empno;

 

这个分组字段太特殊了,是主键(非空、唯一)。这个语句其实没有什么含义,完全可以被下面取代。

 

Select empno, 1 from scott.emp group by empno;

 

我们看看Oracle是怎么认为的。

 

 

SQL> explain plan for select empno, count(*) from scott.emp group by empno;

 

Explained

 

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

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 1749432681

-------------------------------------------------------------------------------

| Id  | Operation            | Name   | Rows  | Bytes | Cost (%CPU)| Time     |

-------------------------------------------------------------------------------

|   0 | SELECT STATEMENT     |        |    14 |    56 |     1   (0)| 00:00:01 |

|   1 |  SORT GROUP BY NOSORT|        |    14 |    56 |     1   (0)| 00:00:01 |

|   2 |   INDEX FULL SCAN    | PK_EMP |    14 |    56 |     1   (0)| 00:00:01 |

-------------------------------------------------------------------------------

 

9 rows selected

 

 

的确走了索引路径,而且我们也看到了sort group by动作。但是注意:这个sort group by后面还有一个nosort

在很多时候,Oracle CBO是非常聪明和狡猾的。有一种篮球中的用语叫“假动作Oracle在这里面好像是sort group by,但是实际上,它根本就没有排序,因为它知道:不需要排序。

Empno列为主键列:非空开启了索引路径,唯一开启了group by取值为1CBO的世界里,统计量大于RBO规则,比统计量成本更高的是约束和声明!主键、外键、非空这些,都是CBO工作的硬约束,比计算的cost值优先级高得多!

所以,这个SQL执行计划中虽然出现了Sort Group by,但是没有意义。

 

5、结论

 

CBO的世界是非常复杂的。过去RBO 15个规则确定执行计划已经不复存在了。近年来,统计量、索引、执行计划深入人心,一些朋友对执行计划也开始认识到位。但是,数据表定义、列约束是比这些更加重要的要素!Oracle CBO越智能,越是会向“捷径”找出路。

这就要求我们,特别是开发设计人员一定要谨慎细心,描述好每个字段和索引。才能形成最好的执行计划。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/17203031/viewspace-1063391/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/17203031/viewspace-1063391/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值