在之前的文章《由于版本升级引发的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
3、Group 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 by和index合力作用,需要让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 scan。Oracle认为:既然承认这个字段不能出现空值,那么所有的列值一定会出现在叶子节点上!所以“数数叶子节点,没问题!”。
但是后面,“数”的动作,Oracle还是选择了Hash Group By。没有出现Sort Group By。
至此,笔者感觉结论已经有了:在一般情况下,如果没有额外的声明“非空、主键”,即使已经给分组列加索引,Oracle路径也不会走。即使走到Oracle索引路径,sort group by也不会出现。
4、sort 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取值为1。在CBO的世界里,统计量大于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/