
译者 林锦森 · 沃趣科技数据库技术专家
出品 沃趣科技
select /*+ qb_name(main) */
outer.*
from
emp outer
where
outer.sal > (
select /*+ qb_name(avg_subq) */
avg(inner.sal)
from
emp inner
where
inner.dept_no = outer.dept_no
)
;复制
语句中我将查询主体命名为“main”,相关联的子查询为“avg_subq”。如果显式声明名称的情况下,Oracle会生成sel$1,sel$2这种格式的名称,其他类型的会生成del$1,ins$1等。下面是这个查询的执行计划,使用dbms_xplan.display()并且格式选项选择‘+alias+outline’输出的结果,语句并没有任何其他hint。
----------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1000 | 98000 | 120 |
|* 1 | HASH JOIN | | 1000 | 98000 | 120 |
| 2 | VIEW | VW_SQ_1 | 6 | 156 | 84 |
| 3 | HASH GROUP BY | | 6 | 48 | 84 |
| 4 | TABLE ACCESS FULL| EMP | 20000 | 156K| 35 |
| 5 | TABLE ACCESS FULL | EMP | 20000 | 1406K| 35 |
----------------------------------------------------------------
QueryBlock Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1- SEL$C7CDAD1E
2- SEL$11FCF3E2 / VW_SQ_1@SEL$EF633D71
3- SEL$11FCF3E2
4- SEL$11FCF3E2 / INNER@AVG_SUBQ
5- SEL$C7CDAD1E / OUTER@MAIN
OutlineData
-------------
/*+
BEGIN_OUTLINE_DATA
USE_HASH_AGGREGATION(@"SEL$11FCF3E2")
FULL(@"SEL$11FCF3E2" "INNER"@"AVG_SUBQ")
USE_HASH(@"SEL$C7CDAD1E" "OUTER"@"MAIN")
LEADING(@"SEL$C7CDAD1E" "VW_SQ_1"@"SEL$EF633D71""OUTER"@"MAIN")
FULL(@"SEL$C7CDAD1E" "OUTER"@"MAIN")
NO_ACCESS(@"SEL$C7CDAD1E""VW_SQ_1"@"SEL$EF633D71")
OUTLINE(@"MAIN")
OUTLINE(@"SEL$EF633D71")
OUTLINE(@"AVG_SUBQ")
UNNEST(@"AVG_SUBQ")
OUTLINE_LEAF(@"SEL$C7CDAD1E")
OUTLINE_LEAF(@"SEL$11FCF3E2")
ALL_ROWS
OPT_PARAM('_optimizer_cost_model' 'io')
DB_VERSION('11.2.0.4')
OPTIMIZER_FEATURES_ENABLE('11.2.0.4')
IGNORE_OPTIM_EMBEDDED_HINTS
END_OUTLINE_DATA
*/复制
这个例子中我们可以发现以下几点:第1行告诉我们第2行和第5行之间使用hash join,但是第2行是VIEW操作,语句中并没有引用这个VIEW,事实上这个视图根本不存在数据库中。这是个在查询转换过程中Oracle生成内部视图之后使用非合并视图优化的例子。实际上优化器使用以下文本重写了这个查询:
select /*+ qb_name(main) */
outer.*
from
(
select /*+ qb_name(avg_subq) */
inner.dept_no, avg(inner.sal) avg_sal
from
emp inner
group by
inner.dept_no
) vw_sq_1,
emp outer
where
outer.sal > vw_sq_1.avg_sal
and outer.dept_no = vw_sq_1.dept_no
;复制
这个重写与原来的计划完全相同,只是有一点不同,即我显式给出的内联视图的名称没有出现在执行计划中。
优化器决定不将内联视图(无论是生成的还是显式声明的)合并到主查询中以生成单个连接,因此它分别对其进行了优化。除了VIEW操作之外我们还能通过两个地方找到线索—第一个是”Query Block Name/Object Alias”部分,它告诉我们第2行是查询块SEL$11FCF3E2的一个部分,之后我们看“Outline Data”部分,可以看到查询块SEL$11FCF3E2是一个“outline_lead”,换句话说,它是一个“最终”查询块,实际上已经被独立优化了。
select/*+ qb_name(avg_subq) merge */
复制
或者在查询主体中添加hint:
select/*+ qb_name(main) merge(@avg_subq)*/
复制
特别要注意,我前面使用“@”符号将hint指向特定的查询块。
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost |
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3333K| 254M| | 953K|
|* 1 | FILTER | | | | | |
| 2 | HASH GROUP BY | | 3333K| 254M| 6127M| 953K|
|* 3 | HASH JOIN | | 66M| 5086M| | 6749 |
| 4 | TABLE ACCESS FULL| EMP | 20000 | 156K| | 35 |
| 5 | TABLE ACCESS FULL| EMP | 20000 | 1406K| | 35 |
---------------------------------------------------------------------
QueryBlock Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1- SEL$A36D7A23
4- SEL$A36D7A23 / INNER@AVG_SUBQ
5- SEL$A36D7A23 / OUTER@MAIN
PredicateInformation (identified by operation id):
---------------------------------------------------
1-filter("OUTER"."SAL">AVG("INNER"."SAL"))
3- access("INNER"."DEPT_NO"="OUTER"."DEPT_NO")复制
值得注意的是,由于计划的变化,结果集的期望基数也发生了变化。这是不应该发生的,这也是优化器的一个缺陷,原理上来说,不同的路径通过优化器应该产生相同的基数估算(即使cost可能改变)。
select
/*+ qb_name(main) */
*
from
t1
join
t2
on t2.t2_n1 = t1.t1_n2
join
t3
on t3.t3_n1 = t2.t2_n2
join
t4
on t4.t4_n1 = t3.t3_n2
;
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0| SELECT STATEMENT | | 192K| 140M| 56 (15)| 00:00:01 |
|* 1| HASH JOIN | | 192K| 140M| 56 (15)| 00:00:01 |
| 2| TABLE ACCESS FULL | T4 | 3000 | 562K| 12 (0)| 00:00:01 |
|* 3| HASH JOIN | | 48000 | 26M| 39 (8)| 00:00:01 |
| 4| TABLE ACCESS FULL | T3 | 3000 | 562K| 12 (0)| 00:00:01 |
|* 5| HASH JOIN | | 12000 | 4488K| 25 (4)| 00:00:01 |
| 6| TABLE ACCESS FULL| T1 | 3000 | 562K| 12 (0)| 00:00:01 |
| 7| TABLE ACCESS FULL| T2 | 3000 | 559K| 12 (0)| 00:00:01 |
-----------------------------------------------------------------------------复制
执行计划中的步骤都在意料之中,四张表的连接都是合理的,但是”Query Block Name/Object Alias”部分存在异常。我们在原始文本中有一个查询块,所有4个表都在这个查询块中,检查一下语法,确认hint qb_name()的使用是正确的,但是Oracle是这样看的:
QueryBlock Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1- SEL$43767242
2- SEL$43767242 / T4@SEL$3
4- SEL$43767242 / T3@SEL$2
6- SEL$43767242 / T1@SEL$1
7- SEL$43767242 / T2@SEL$1复制
我们并没有一个名称为”main”的查询块,我们只有3个独立的查询块,缺省的名称为sel$1,sel$2和sel$3,而且我们并不是四张表一起连接,而是3次两张表之间的连接。事实上在”outline”部分可以看到,“main”查询块是在合并后被使用的。Oracle解析ANSI并将它转换为如下的查询:
select /*+ qb_name(main) */
*
from(
select /*+ qb_name(sel$3) */
*
from
(
select /*+ qb_name(sel$2) */
*
from (
select
/*+ qb_name(sel$1) */
*
from
t1,
t2
where t2.t2_n1 = t1.t1_n2
) v1,
t3
where t3.t3_n1 = v1.t2_n2
) v2,
t4
where t4.t4_n1 = v2.t3_n2
)
;复制
在复杂的情况下,这种类型的初步重写会使您在尝试强制执行某个查询的特定执行计划时,更加难以确定如何注入所需的hint。
| 译者简介
林锦森·沃趣科技数据库技术专家
沃趣科技数据库工程师,多年从事Oracle数据库,较丰富的故障处理、性能调优、数据迁移及备份恢复经验。
相关链接
组复制监控 | 全方位认识 MySQL 8.0 Group Replication
组复制安装部署 | 全方位认识 MySQL 8.0 Group Replication
组复制背景 | 全方位认识 MySQL 8.0 Group Replication
MySQL行级别并行复制能并行应用多少个binlog group?
MySQL高可用工具Orchestrator系列六:Orchestrator/raft一致性集群
MySQL高可用工具Orchestrator系列五:raft多节点模式安装
MySQL高可用工具Orchestrator系列四:拓扑恢复
MySQL高可用工具Orchestrator系列三:探测机制
Oracle RAC Cache Fusion系列十八:Oracle RAC Statisticsand Wait Events

更多干货,欢迎来撩~