繁体   English   中英

读取 Oracle 中的执行计划

[英]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/FKCOMAR连接。

样本数据

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM