在Oracle 12c中进行子选择,性能不佳

时间:2016-10-18 07:16:05

标签: oracle oracle11g oracle12c

从Oracle 11g迁移到12c之后,我们遇到了一个巨大的性能问题,例如使用以下select语句。同样的声明在11g中运行良好。

表格和索引

-- Create table
create table PS_CS_ADRART_TB
(
  cs_adressmandant   VARCHAR2(5) not null,
  cs_person_id       VARCHAR2(20) not null,
  cs_beziehung_id    VARCHAR2(20) not null,
  seqnum             INTEGER not null,
  effdt              DATE,
  eff_status         VARCHAR2(1) not null,
  cs_adrart_cd       VARCHAR2(20) not null,
  cs_adress_id       VARCHAR2(20) not null,
  cs_kdnr_as400      VARCHAR2(8) not null,
  cs_plzgk           VARCHAR2(11) not null,
  cs_plz_pf          VARCHAR2(15) not null,
  cs_aendgr_cd       VARCHAR2(20) not null,
  cs_datasource_cd   VARCHAR2(20) not null,
  cs_betrag          NUMBER(14,4) not null,
  cs_belegdat        DATE,
  cs_adrtyp_xl       VARCHAR2(2) not null,
  cs_checked         VARCHAR2(1) not null,
  cs_journal_xl      VARCHAR2(4) not null,
  address2           VARCHAR2(55) not null,
  row_added_dttm     TIMESTAMP(6),
  row_added_oprid    VARCHAR2(30) not null,
  row_lastmant_dttm  TIMESTAMP(6),
  row_lastmant_oprid VARCHAR2(30) not null,
  cs_recstat_xl      VARCHAR2(4) not null,
  cs_update_count    NUMBER(10) not null
)
tablespace CS_APP
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 102416K
    next 1M
    minextents 1
    maxextents unlimited
  );
-- Create/Recreate indexes 
create unique index PSACS_ADRART_TB on PS_CS_ADRART_TB (CS_ADRESSMANDANT, CS_KDNR_AS400, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 20M
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PSBCS_ADRART_TB on PS_CS_ADRART_TB (CS_PERSON_ID)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 20M
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PSCCS_ADRART_TB on PS_CS_ADRART_TB (CS_BEZIEHUNG_ID)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 20M
    next 1M
    minextents 1
    maxextents unlimited
  );
create unique index PS_CS_ADRART_TB on PS_CS_ADRART_TB (CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 20M
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PSDCS_ADRART_TB on PS_CS_ADRART_TB (CS_PLZ_PF)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 40K
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PS0CS_ADRART_TB on PS_CS_ADRART_TB (CS_ADRESS_ID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 20M
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PS1CS_ADRART_TB on PS_CS_ADRART_TB (CS_KDNR_AS400, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 20M
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PS2CS_ADRART_TB on PS_CS_ADRART_TB (ROW_ADDED_DTTM, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 40K
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PS3CS_ADRART_TB on PS_CS_ADRART_TB (ROW_ADDED_OPRID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 40K
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PS4CS_ADRART_TB on PS_CS_ADRART_TB (ROW_LASTMANT_DTTM, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 40K
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PS5CS_ADRART_TB on PS_CS_ADRART_TB (ROW_LASTMANT_OPRID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 40K
    next 1M
    minextents 1
    maxextents unlimited
  );
create index PS6CS_ADRART_TB on PS_CS_ADRART_TB (CS_RECSTAT_XL, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)
  tablespace PSINDEX
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 40K
    next 1M
    minextents 1
    maxextents unlimited
  );

表格大小

select count(*) from ps_cs_adrart_tb a 
--> 41367270

选择声明

    SELECT A.CS_ADRESS_ID, A.SEQNUM, TO_CHAR(A.EFFDT, 'YYYY-MM-DD')
      from PS_CS_ADRART_TB A
    where A.CS_ADRESSMANDANT = '001'
       and a.cs_kdnr_as400 = '63916917'
       and a.effdt =
           (select max(b.effdt)
              from ps_cs_adrart_tb b
             where b.cs_adressmandant = a.cs_adressmandant 
               and b.cs_person_id = a.cs_person_id 
               and b.cs_beziehung_id = a.cs_beziehung_id
               and b.seqnum = a.seqnum
               and b.effdt  <= trunc(sysdate)
               )

解释计划Oracle 11g

    --------------------------------------------------------------------------------------------
    | Id  | Operation                       | Name            | Rows | Bytes | Cost | Time     |
    --------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                |                 |    1 |    89 |    2 | 00:00:01 |
    | * 1 |   FILTER                        |                 |      |       |      |          |
    |   2 |    TABLE ACCESS BY INDEX ROWID  | PS_CS_ADRART_TB |    1 |    89 |    1 | 00:00:01 |
    | * 3 |     INDEX RANGE SCAN            | PSACS_ADRART_TB |    1 |       |    1 | 00:00:01 |
    |   4 |    SORT AGGREGATE               |                 |    1 |    59 |      |          |
    |   5 |     FIRST ROW                   |                 |    1 |    59 |    1 | 00:00:01 |
    | * 6 |      INDEX RANGE SCAN (MIN/MAX) | PS_CS_ADRART_TB |    1 |    59 |    1 | 00:00:01 |
    --------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ------------------------------------------
    * 1 - filter("A"."EFFDT"= (SELECT MAX("B"."EFFDT") FROM "PS_CS_ADRART_TB" "B" WHERE "B"."EFFDT"<=TRUNC(SYSDATE@!) AND "B"."SEQNUM"=:B1 AND "B"."CS_BEZIEHUNG_ID"=:B2 AND "B"."CS_PERSON_ID"=:B3 AND
      "B"."CS_ADRESSMANDANT"=:B4))
    * 3 - access("A"."CS_ADRESSMANDANT"='001' AND "A"."CS_KDNR_AS400"='63916917')
    * 6 - access("B"."CS_ADRESSMANDANT"=:B1 AND "B"."CS_PERSON_ID"=:B2 AND "B"."CS_BEZIEHUNG_ID"=:B3 AND "B"."SEQNUM"=:B4 AND "B"."EFFDT"<=TRUNC(SYSDATE@!))    

解释计划Oracle 12c

    ------------------------------------------------------------------------------------------------------------
    | Id  | Operation                              | Name            | Rows     | Bytes     | Cost  | Time     |
    ------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                       |                 |        1 |       140 | 34366 | 00:00:02 |
    | * 1 |   HASH JOIN                            |                 |        1 |       140 | 34366 | 00:00:02 |
    |   2 |    TABLE ACCESS BY INDEX ROWID BATCHED | PS_CS_ADRART_TB |        1 |        89 |     1 | 00:00:01 |
    | * 3 |     INDEX RANGE SCAN                   | PS1CS_ADRART_TB |        1 |           |     1 | 00:00:01 |
    |   4 |    VIEW                                | VW_SQ_1         |    41889 |   2136339 | 34365 | 00:00:02 |
    | * 5 |     FILTER                             |                 |          |           |       |          |
    |   6 |      HASH GROUP BY                     |                 |    41889 |   2471451 | 34365 | 00:00:02 |
    | * 7 |       INDEX RANGE SCAN                 | PS_CS_ADRART_TB | 12746381 | 752036479 | 34365 | 00:00:02 |
    ------------------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ------------------------------------------
    * 1 - access("A"."EFFDT"="MAX(B.EFFDT)" AND "ITEM_1"="A"."CS_ADRESSMANDANT" AND "ITEM_2"="A"."CS_PERSON_ID" AND "ITEM_3"="A"."CS_BEZIEHUNG_ID" AND "ITEM_4"="A"."SEQNUM")
    * 3 - access("A"."CS_KDNR_AS400"='63916917' AND "A"."CS_ADRESSMANDANT"='001')
    * 5 - filter('001'='001')
    * 7 - access("B"."CS_ADRESSMANDANT"='001' AND "B"."EFFDT"<=TRUNC(SYSDATE@!))
    * 7 - filter("B"."EFFDT"<=TRUNC(SYSDATE@!))


    Note
    -----
    - dynamic sampling used for this statement                   

正如您所看到的,从索引PS_CS_ADRART_TB开始,只有CS_ADRESSMANDANTEFFDT用于过滤数据,这非常糟糕。

使用以下略有不同的select语句,Oracle 12c将按预期使用索引来确定subselect的数据。

更改外部选择的条件

  SELECT  A.CS_ADRESS_ID, A.SEQNUM, TO_CHAR(A.EFFDT, 'YYYY-MM-DD')
    from PS_CS_ADRART_TB A
  where a.cs_kdnr_as400 = '53916917' -- without CS_ADRESSMANDANT condition
     and a.effdt =
         (select  max(b.effdt)
            from ps_cs_adrart_tb b
           where b.cs_adressmandant = a.cs_adressmandant and 
                 b.cs_person_id = a.cs_person_id 
             and b.cs_beziehung_id = a.cs_beziehung_id 
             and b.seqnum = a.seqnum 
             and b.effdt <= trunc(sysdate)
             )

    --------------------------------------------------------------------------------------------------
    | Id  | Operation                             | Name            | Rows | Bytes | Cost | Time     |
    --------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                      |                 |    1 |    89 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID BATCHED | PS_CS_ADRART_TB |    1 |    89 |    1 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN                   | PS1CS_ADRART_TB |    1 |       |    1 | 00:00:01 |
    |   3 |     SORT AGGREGATE                    |                 |    1 |    59 |      |          |
    |   4 |      FIRST ROW                        |                 |    1 |    59 |    1 | 00:00:01 |
    | * 5 |       INDEX RANGE SCAN (MIN/MAX)      | PS_CS_ADRART_TB |    1 |    59 |    1 | 00:00:01 |
    --------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("A"."CS_KDNR_AS400"='53916917')
    * 2 - filter("A"."EFFDT"= (SELECT MAX("B"."EFFDT") FROM "PS_CS_ADRART_TB" "B" WHERE "B"."EFFDT"<=TRUNC(SYSDATE@!) AND "B"."SEQNUM"=:B1 AND "B"."CS_BEZIEHUNG_ID"=:B2 AND "B"."CS_PERSON_ID"=:B3 AND
      "B"."CS_ADRESSMANDANT"=:B4))
    * 5 - access("B"."CS_ADRESSMANDANT"=:B1 AND "B"."CS_PERSON_ID"=:B2 AND "B"."CS_BEZIEHUNG_ID"=:B3 AND "B"."SEQNUM"=:B4 AND "B"."EFFDT"<=TRUNC(SYSDATE@!))


    Note
    -----
    - dynamic sampling used for this statement

在子选择中使用min()代替max()

    SELECT A.CS_ADRESS_ID, A.SEQNUM, TO_CHAR(A.EFFDT, 'YYYY-MM-DD'), a.cs_person_id 
      from PS_CS_ADRART_TB A
    where a.cs_kdnr_as400 = '63916917' and a.cs_adressmandant = '001'
       and a.effdt =
           (select  min(b.effdt)
              from ps_cs_adrart_tb b
             where b.cs_adressmandant = a.cs_adressmandant
               and b.cs_person_id = a.cs_person_id
               and b.cs_beziehung_id = a.cs_beziehung_id
               and b.seqnum = a.seqnum
               and b.effdt  <= sysdate
               );

    ---------------------------------------------------------------------------------------------------
    | Id  | Operation                              | Name            | Rows | Bytes | Cost | Time     |
    ---------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                       |                 |    1 |   109 |    2 | 00:00:01 |
    |   1 |   NESTED LOOPS                         |                 |    1 |   109 |    2 | 00:00:01 |
    |   2 |    TABLE ACCESS BY INDEX ROWID BATCHED | PS_CS_ADRART_TB |    1 |    89 |    1 | 00:00:01 |
    | * 3 |     INDEX RANGE SCAN                   | PS1CS_ADRART_TB |    1 |       |    1 | 00:00:01 |
    | * 4 |    VIEW PUSHED PREDICATE               | VW_SQ_1         |    1 |    20 |    1 | 00:00:01 |
    |   5 |     SORT GROUP BY                      |                 |    1 |    59 |    1 | 00:00:01 |
    | * 6 |      INDEX RANGE SCAN                  | PS_CS_ADRART_TB |    1 |    59 |    1 | 00:00:01 |
    ---------------------------------------------------------------------------------------------------

    Predicate Information (identified by operation id):
    ------------------------------------------
    * 3 - access("A"."CS_KDNR_AS400"='63916917' AND "A"."CS_ADRESSMANDANT"='001')
    * 4 - filter("A"."EFFDT"="MIN(B.EFFDT)" AND "ITEM_1"="A"."CS_ADRESSMANDANT")
    * 6 - access("B"."CS_ADRESSMANDANT"='001' AND "B"."CS_PERSON_ID"="A"."CS_PERSON_ID" AND "B"."CS_BEZIEHUNG_ID"="A"."CS_BEZIEHUNG_ID" AND "B"."SEQNUM"="A"."SEQNUM" AND "B"."EFFDT"<=SYSDATE@!)


    Note
    -----
    - dynamic sampling used for this statement

重建索引(如上所述)并按

更新统计信息
EXEC DBMS_STATS.delete_table_stats('SYSADM', 'PS_CS_ADRART_TB');
-- EXEC DBMS_STATS.gather_table_stats('SYSADM', 'PS_CS_ADRART_TB');
EXEC DBMS_STATS.gather_table_stats(ownname => 'SYSADM', tabname =>  'PS_CS_ADRART_TB', cascade => true );

无济于事。

这里发生了什么?任何建议都非常感谢。提前谢谢。

2 个答案:

答案 0 :(得分:1)

我认为您应该将参数_unnest_subquery设置为FALSE

您可以尝试

alter session set "_unnest_subquery" = FALSE;

答案 1 :(得分:0)

谢。据我所知,你希望得到任何cs_person_id和cs_beziehung_id的最后一行,其中b.effdt低于或等于今天。首先,您可以收集有关选择性信息的统计信息。它有助于CBO做出选择。

BEGIN 
 dbms_stats.Gather_table_stats('SYSADM', 'PS_CS_ADRART_TB',
 method_opt => 'FOR COLUMNS SIZE 254 CS_KDNR_AS400 CS_ADRESSMANDANT'); 
END; 
/

Anothr认为我发现了什么。你有很多索引可能被创建为唯一的。它已包含唯一索引列。

create unique index PS1CS_ADRART_TB on PS_CS_ADRART_TB (CS_KDNR_AS400, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT)  ;
create unique index PS0CS_ADRART_TB on PS_CS_ADRART_TB (CS_ADRESS_ID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS1CS_ADRART_TB on PS_CS_ADRART_TB (CS_KDNR_AS400, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS2CS_ADRART_TB on PS_CS_ADRART_TB (ROW_ADDED_DTTM, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS3CS_ADRART_TB on PS_CS_ADRART_TB (ROW_ADDED_OPRID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS4CS_ADRART_TB on PS_CS_ADRART_TB (ROW_LASTMANT_DTTM, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS5CS_ADRART_TB on PS_CS_ADRART_TB (ROW_LASTMANT_OPRID, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);
create unique index PS6CS_ADRART_TB on PS_CS_ADRART_TB (CS_RECSTAT_XL, CS_ADRESSMANDANT, CS_PERSON_ID, CS_BEZIEHUNG_ID, SEQNUM, EFFDT);

最后我尝试重写查询以排除子查询

SELECT cs_adress_id,seqnum, effdt_chr 
FROM   (SELECT /* */ a.cs_adress_id 
          ,a.seqnum 
          ,to_char(a.effdt, 'YYYY-MM-DD') AS effdt_chr 
          ,dense_rank() over(PARTITION BY a.cs_adressmandant, a.cs_person_id
                                        , a.cs_beziehung_id, a.seqnum 
                                 ORDER BY a.effdt DESC) AS  rnk 
    FROM   ps_cs_adrart_tb a 
    WHERE  a.cs_adressmandant = '001' 
      AND  a.cs_kdnr_as400 = '63916917' 
      AND  a.effdt <= trunc(sysdate) 
) 
WHERE  rnk = 1;