【Oracle】从雇员表的年龄查询看CBO、索引和Hint的作用
本例适用的Oracle版本:Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
不保证在其它版本的Oracle上也是同样效果。
有一张雇员表含三个字段:
create table emp( id int, name nvarchar2(20), age int, primary key(id));
给它填充百万数据:
insert into emp select rownum, dbms_random.string('*',dbms_random.value(6,20)), dbms_random.value(18,65) from dual connect by level<1000001;
然后我们要看看select name from emp where age=42这句SQL的cost是多少,注意现在没加索引(id上的索引除外,此索引与下面的测试无关)
分别执行下面两句:
explain plan for select name from emp where age=42;
select * from table(dbms_xplan.display);
不加索引的解释计划 SQL> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 3956160932 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 23462 | 801K| 1523 (2)| 00:00:19 | |* 1 | TABLE ACCESS FULL| EMP | 23462 | 801K| 1523 (2)| 00:00:19 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- 1 - filter("AGE"=42) Note ----- - dynamic sampling used for this statement (level=2) 已选择17行。
解释计划告诉我们Cost是1523,走的是全表查询。
之后加age,name的组合索引
create index idx_age_name on emp(age,name);
再看看情况:
SQL> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 2015333714 -------------------------------------------------------------------------------- - | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------- - PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 23462 | 801K| 125 (0)| 00:00:02 | |* 1 | INDEX RANGE SCAN| IDX_AGE_NAME | 23462 | 801K| 125 (0)| 00:00:02 | -------------------------------------------------------------------------------- - Predicate Information (identified by operation id): PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- --------------------------------------------------- 1 - access("AGE"=42) Note ----- - dynamic sampling used for this statement (level=2) 已选择17行。
这回较好,索引起作用了,Cost降低到原来的十分之一,走的是索引范围扫描。
之所以这个索引见效显著,是因为name和age都编到了索引中,在索引里找到了age,不用回表就找到了name。
所以说,在查询条件、排序方式、显示列中出现的字段,如果确实需要,都可以编入到索引中。
如果我们只对age加索引,看cbo是会选择回表还是全表查询。
删除组合索引,只给age加单列索引 SQL> drop index idx_age_name; 索引已删除。 SQL> create index idx_age on emp(age); 索引已创建。 SQL> explain plan for select name from emp where age=42; 已解释。 SQL> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 3956160932 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 23462 | 801K| 1523 (2)| 00:00:19 | |* 1 | TABLE ACCESS FULL| EMP | 23462 | 801K| 1523 (2)| 00:00:19 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- 1 - filter("AGE"=42) Note ----- - dynamic sampling used for this statement (level=2) 已选择17行。
Cost又恢复了原值1523,查询方式还是走了全表查询,看来CBO认为全表查询比回表更省。
我们可以试试用hint强制引导oracle使用idx_age这个索引:
explain plan for select /*+ index(emp,idx_age)*/ name from emp where age=42;
执行效果:
SQL> explain plan for select /*+ index(emp,idx_age)*/ name from emp where age=42; 已解释。 SQL> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 977828616 -------------------------------------------------------------------------------- ------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Tim e | -------------------------------------------------------------------------------- ------- PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 23462 | 801K| 5471 (1)| 00: 01:06 | | 1 | TABLE ACCESS BY INDEX ROWID| EMP | 23462 | 801K| 5471 (1)| 00: 01:06 | |* 2 | INDEX RANGE SCAN | IDX_AGE | 23462 | | 44 (0)| 00: 00:01 | -------------------------------------------------------------------------------- ------- PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("AGE"=42) Note ----- - dynamic sampling used for this statement (level=2) 已选择18行。
上面已经出现了使用单列索引的迹象,但Cost不减反增。
看来Cbo坚持全表查询的路子是对的。
这个例子告诉我们:
1.合理的索引能避免回表的发生;
2.如果索引不合理,Cbo自会选择它认为的最合理的方式;
3.hint只有合理才能提高效率。
-END-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
2017-09-17 【Canvas与诗词】苔.袁枚
2017-09-17 【Canvas与色彩】彩虹七色
2017-09-17 【Canvas与艺术】带阴影的六角大楼
2017-09-17 【Canvas与诗词】闻志愿军收复汉城 萨镇冰
2017-09-17 【高中数学/对数/指数】已知函数f(x)=e^x-ln(x+m),求证m<=2时,证明f(x)>0