[英]Read execution plan in Oracle
我的问题是为什么选项中有完全访问权限但是我为 LIGPOR 和 DATRAI 表创建了两个索引? 如果有任何人可以帮助我清楚地阅读 sql oracle 中的长执行计划
我有这个查询:
select *
FROM DATRAI D,
LIGPOR L
WHERE D.COMAR= L.COMAR
and TO_CHAR(l.daprm,'yyyy') ='2017';
这是执行计划
SQL_ID 0v5mmx6sg8scu, child number 0
-------------------------------------
select * FROM LIGPOR L,DATRAI D WHERE D.COMAR= L.COMAR and
TO_CHAR(l.daprm,:"SYS_B_0") =:"SYS_B_1"
Plan hash value: 3308708566
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 9 (100)| |
| 1 | NESTED LOOPS | | 1 | 2177 | 9 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 2177 | 9 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | LIGPOR | 1 | 2138 | 8 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | DATRAI1 | 1 | | 0 (0)| |
| 5 | TABLE ACCESS BY INDEX ROWID| DATRAI | 1 | 39 | 1 (0)| 00:00:01
表创作
CREATE TABLE "user"."LIGPOR"
(
"COINT" VARCHAR2(11 BYTE) NOT NULL ENABLE,
"COMAR" VARCHAR2(5 BYTE) NOT NULL ENABLE,
"DAPRM" DATE
) SEGMENT CREATION IMMEDIATE
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
CREATE TABLE "user"."DATRAI"
( "COMAR" VARCHAR2(5 BYTE) NOT NULL ENABLE,
"DATRA" DATE NOT NULL ENABLE
) SEGMENT CREATION IMMEDIATE
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
索引创建
CREATE INDEX "user"."LIGPOR5" ON "user"."LIGPOR" ("COMAR")
PCTFREE 10 INITRANS 2 MAXTRANS 255 NOLOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
CREATE UNIQUE INDEX "user"."DATRAI1" ON "user"."DATRAI" ("COMAR")
PCTFREE 10 INITRANS 2 MAXTRANS 255 NOLOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1
BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "UBIX_TABLES" ;
thix:)
你用的SQL很简单,方案也很简单。 数据库正在做的是从一个表开始,这里它选择了 LIGPO,使用全扫描读取数据,因为没有谓词允许它使用索引。 然后,对于每一行,它使用第二个表上的索引来查看是否有任何匹配的行(因为第二个表在 COMAR 列上有一个索引)。 如果找到一个,它会使用索引给出的 rowid 从表中读取剩余的数据。 根据您的表的大小,它可能会选择相反的方式,即从使用全扫描的 DATRAI 开始,使用索引检查 LIGPO 中的匹配行。
优化器所做的取决于对象的统计信息,对于像您这样的查询,它很可能会选择对两个表进行全扫描的 hash 连接。
最后一句话:不要写TO_CHAR(l.daprm,'yyyy') ='2017'
这样的谓词,因为即使你在 DAPRM 上有索引,数据库也不会使用它。 最好做类似ldaprm >= date '2017-01-01' and ldaprm < date '2018-01-01'
。
首先,您必须意识到一般规则索引快 - 全表扫描慢几乎没有例外。
最明显的是
表很小,它只包含几个块(一个简单的多块读取比在索引和表之间重复跳转更好)
您想访问大部分表数据(再次在索引和表之间切换时死掉)
我想第一点是你的情况。
让我们尝试模拟它。 基本上你有一个父DATRAI
-子LIGPOR
表,由PK/FK
列COMAR
连接。
样本数据
create table DATRAI as
select rownum COMAR,
date'2017-01-01' + rownum DATRA
from dual connect by level <= 2;
CREATE UNIQUE INDEX "DATRAI1" ON "DATRAI" ("COMAR");
create table LIGPOR as
select rownum COINT,
mod(rownum,1000)+1 COMAR,
date'2017-01-01' + rownum DAPRM
from dual connect by level <= 20;
CREATE INDEX "LIGPOR5" ON "LIGPOR" ("COMAR");
请注意,我在 18g 上,其中object 统计信息在线收集在创建表和索引中,在以前的版本中,您需要添加对dbms_stats.gather_table_stats
的调用
OLTP 示例
让我们从典型示例开始(您可能已经想到) - 您使用主键( COMAR = 10
)约束父级并使用外键访问所有对应的子行。
EXPLAIN PLAN SET STATEMENT_ID = 'EXPL_1' into plan_table FOR
SELECT *
FROM DATRAI D
JOIN LIGPOR L ON D.COMAR= L.COMAR
WHERE
D.COMAR = 10 AND
TO_CHAR(l.daprm,'yyyy') ='2017';
SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'EXPL_1','ALL'));
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 28 | 13 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 28 | 13 (0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID | DATRAI | 1 | 12 | 2 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | DATRAI1 | 1 | | 1 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| LIGPOR | 1 | 16 | 11 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | LIGPOR5 | 10 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("D"."COMAR"=10)
4 - filter(TO_CHAR(INTERNAL_FUNCTION("L"."DAPRM"),'yyyy')='2017')
5 - access("L"."COMAR"=10)
简短说明如何阅读执行计划
您从具有最高意图的第一行开始,即Id = 3
的行。
Id
用星号标记,因此您检查访问/过滤条件的Predicate Information
- 这里是access("D"."COMAR"=10)
这意味着您使用 select 的索引(索引名称在列Name
中),因为Rows
在一行中的列中通知您。
接下来( Id = 2
)您从索引中获取rowid
来访问表
接下来( Id = 1
),您处于NESTED LOOPS
中,即对于每一行,您使用Id 5 and 4
执行该行,即再次使用access("L"."COMAR"=10)
进行索引访问,然后是rowid 表访问。
请注意, BATCHED
是最近版本的优化 - 我不能发表太多评论。
所以我猜 - 这是你会喜欢的执行计划。
其他示例
让 go 回到您的示例,这基本上是相同的查询 - 只是没有 PK predicate 。
EXPLAIN PLAN SET STATEMENT_ID = 'EXPL_2' into plan_table FOR
SELECT *
FROM DATRAI D
JOIN LIGPOR L ON D.COMAR= L.COMAR
WHERE
TO_CHAR(l.daprm,'yyyy') ='2017';
SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'EXPL_2','ALL'));
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 25 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 25 | 4 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 25 | 4 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | LIGPOR | 1 | 14 | 3 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | DATRAI1 | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| DATRAI | 1 | 11 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter(TO_CHAR(INTERNAL_FUNCTION("L"."DAPRM"),'yyyy')='2017')
4 - access("D"."COMAR"="L"."COMAR")
同样,起始行是Id = 3
,但这里 Oracle 选择从子表LIGPOR
开始。 为什么? Id = 3
至少有一个过滤谓词,这使得 Oracle Cost Based Optimizer更便宜。
另一种方法是对完全没有过滤器的表DATRAI
进行全面扫描。
*请注意访问谓词 - 主动索引选择 - 和过滤谓词之间的关键区别 - 您必须处理所有行并丢弃其中一些行。
rest 与之前的嵌套循环基本相同,只是在技术上执行的首次索引访问是表访问的两倍。
为了不让答案太长,我做了总结
不要害怕过早地table access full
不要使用select *
如果您可以限制结果列 - 这可以使 Oracle 得到更好的优化
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.