This time around, I am going to take a look at how 19c has changed that.
Firstly, I am going to recreate my initial table: or_expand_setup.sql:
SQL> @or_expand_setup SQL> drop table t1; Table dropped. SQL> SQL> create table t1 ( 2 part_key varchar2(8), 3 status varchar2(12), 4 pad1 char(500) 5 ) 6 partition by list(part_key) ( 7 partition values ('P1'), 8 partition values (default) 9 ) 10 ; Table created. SQL> SQL> SQL> insert into t1 2 select 'P1', status, 'X' 3 from (select 90 pct, 'PROCESSED' status from dual union all 4 select 1, 'UNPROCESSED' from dual union all 5 select 9, 'PENDING' from dual) params, 6 lateral( 7 select level 8 from dual 9 connect by level <= params.pct * 1000 10 ) duplicator; 100000 rows created. SQL> SQL> commit; Commit complete. SQL> SQL> create index t1_status_i on t1(status) local; Index created. SQL> SQL> select status, 2 count(*), 3 round(ratio_to_report(count(*)) over () * 100, 2) pct 4 from t1 5 group by status 6 order by 1; STATUS COUNT(*) PCT ------------ ---------- ---------- PENDING 9000 9 PROCESSED 90000 90 UNPROCESSED 1000 1Secondly, I am running the or_expand_test_vcol.sql script which was previously performed OR Expansion in both 12.2 and 18c:
SQL> @or_expand_test_vcol SQL> set def on lin 124 pages 100 SQL> SQL> alter table t1 add lower_status varchar2(128) generated always as (lower(status)) virtual; Table altered. SQL> SQL> exec dbms_stats.gather_table_stats( '', 't1', method_opt => 'for all columns size 254') PL/SQL procedure successfully completed. SQL> SQL> col tfi old_v tfi nopri SQL> SQL> select to_char(sysdate, 'yyyymmdd_hh24miss') tfi from dual; SQL> SQL> alter session set tracefile_identifier='&tfi.'; old 1: alter session set tracefile_identifier='&tfi.' new 1: alter session set tracefile_identifier='20200608_180223' Session altered. SQL> SQL> alter session set events 'trace[sql_optimizer.*]'; Session altered. SQL> SQL> SQL> var part_key varchar2(10)='P1' SQL> var param varchar2(12)='WAITING' SQL> SQL> select --+ gather_plan_statistics or_expand(@sel$1 (1) (2)) 2 count(pad1) 3 from t1 4 where part_key = :part_key 5 and (:param = 'WAITING' and status = 'UNPROCESSED' 6 or 7 :param = 'ALL' and status <> 'PENDING'); COUNT(PAD1) ----------- 1000 SQL> SQL> select * 2 from dbms_xplan.display_cursor(format=> 'allstats last outline'); PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------------------------------- SQL_ID fq8b3jumk131t, child number 0 ------------------------------------- select --+ gather_plan_statistics or_expand(@sel$1 (1) (2)) count(pad1) from t1 where part_key = :part_key and (:param = 'WAITING' and status = 'UNPROCESSED' or :param = 'ALL' and status <> 'PENDING') Plan hash value: 1293629841 ----------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | ----------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.03 | 7826 | | 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.03 | 7826 | | 2 | PARTITION LIST SINGLE| | 1 | 920 | 1000 |00:00:00.03 | 7826 | |* 3 | TABLE ACCESS FULL | T1 | 1 | 920 | 1000 |00:00:00.03 | 7826 | ----------------------------------------------------------------------------------------- Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('19.1.0') DB_VERSION('19.1.0') ALL_ROWS OUTLINE_LEAF(@"SEL$1") FULL(@"SEL$1" "T1"@"SEL$1") END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 3 - filter((((:PARAM='ALL' AND "STATUS"<>'PENDING') OR ("STATUS"='UNPROCESSED' AND :PARAM='WAITING')) AND "PART_KEY"=:PART_KEY)) 38 rows selected. SQL> SQL> alter session set events 'trace[sql_optimizer.*] off'; Session altered. SQL> SQL> col value for a80 SQL> SQL> select value 2 from v$diag_info 3 where name='Default Trace File'; VALUE -------------------------------------------------------------------------------- /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_7253_20200608_180223.trcAs the output above demonstrates, there is no OR Expansion anymore. The trace file orcl_ora_7253_20200608_180223.trc shows the following:
*********************************** Cost-Based OR Expansion *********************************** ORE: Trying CBQT OR expansion before unnesting ORE: Checking validity of OR Expansion for query block SEL$1 (#1) ORE: Predicate chain before QB validity check - SEL$1 "T1"."PART_KEY"=:B1 AND (:B2='WAITING' AND "T1"."STATUS"='UNPROCESSED' OR :B3='ALL' AND "T1"."STATUS"<>'PENDING') ORE: Predicate chain after QB validity check - SEL$1 "T1"."PART_KEY"=:B1 AND (:B2='WAITING' AND "T1"."STATUS"='UNPROCESSED' OR :B3='ALL' AND "T1"."STATUS"<>'PENDING') OR Expansion on query block SEL$1 (#1) ORE: Checking validity of OR Expansion for query block SEL$1 (#1) ORE: Predicate chain before QB validity check - SEL$1 "T1"."PART_KEY"=:B1 AND (:B2='WAITING' AND "T1"."STATUS"='UNPROCESSED' OR :B3='ALL' AND "T1"."STATUS"<>'PENDING') ORE: Predicate chain after QB validity check - SEL$1 "T1"."PART_KEY"=:B1 AND (:B2='WAITING' AND "T1"."STATUS"='UNPROCESSED' OR :B3='ALL' AND "T1"."STATUS"<>'PENDING') ORE: Using search type: linear ORE: Checking validity of OR Expansion for query block SEL$1 (#1) ORE: Predicate chain before QB validity check - SEL$1 "T1"."PART_KEY"=:B1 AND (:B2='WAITING' AND "T1"."STATUS"='UNPROCESSED' OR :B3='ALL' AND "T1"."STATUS"<>'PENDING') ORE: Predicate chain after QB validity check - SEL$1 "T1"."PART_KEY"=:B1 AND (:B2='WAITING' AND "T1"."STATUS"='UNPROCESSED' OR :B3='ALL' AND "T1"."STATUS"<>'PENDING') ORE: Checking validity of disjunct chain ORE: Bypassed for disjunct chain: No Index or Partition driver found. ORE: # conjunction chain - 1 ORE: No state generated.It is worth noting that even a full outline does not lead to the desired plan. That is how I discovered that issue initially. The query in question did have an SQL Plan Baseline which stopped being reproducible following a 19c upgrade.
A straightforward way to get the desired execution plan is to rewrite the initial query as follows:
SQL> @or_expand_test_rewrite ..skip.. SQL> var part_key varchar2(10)='P1' SQL> var param varchar2(12)='WAITING' SQL> SQL> select --+ gather_plan_statistics or_expand(@sel$1 (1) (2)) 2 count(pad1) 3 from t1 4 where 1 = 1 5 and (part_key = :part_key and :param = 'WAITING' and status = 'UNPROCESSED' 6 or 7 part_key = :part_key||'' and :param = 'ALL' and status <> 'PENDING'); COUNT(PAD1) ----------- 1000 SQL> SQL> select * 2 from dbms_xplan.display_cursor(format=> 'allstats last outline'); PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------------------------------- SQL_ID 99zvump911bc7, child number 0 ------------------------------------- select --+ gather_plan_statistics or_expand(@sel$1 (1) (2)) count(pad1) from t1 where 1 = 1 and (part_key = :part_key and :param = 'WAITING' and status = 'UNPROCESSED' or part_key = :part_key||'' and :param = 'ALL' and status <> 'PENDING') Plan hash value: 3973059565 ---------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | ---------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 83 | | 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 83 | | 2 | VIEW | VW_ORE_BA8ECEFB | 1 | 91982 | 1000 |00:00:00.01 | 83 | | 3 | UNION-ALL | | 1 | | 1000 |00:00:00.01 | 83 | |* 4 | FILTER | | 1 | | 1000 |00:00:00.01 | 83 | | 5 | PARTITION LIST SINGLE | | 1 | 999 | 1000 |00:00:00.01 | 83 | |* 6 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| T1 | 1 | 999 | 1000 |00:00:00.01 | 83 | |* 7 | INDEX RANGE SCAN | T1_STATUS_I | 1 | 1000 | 1000 |00:00:00.01 | 6 | |* 8 | FILTER | | 1 | | 0 |00:00:00.01 | 0 | | 9 | PARTITION LIST SINGLE | | 0 | 90983 | 0 |00:00:00.01 | 0 | |* 10 | TABLE ACCESS FULL | T1 | 0 | 90983 | 0 |00:00:00.01 | 0 | ---------------------------------------------------------------------------------------------------------------------------- Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('19.1.0') DB_VERSION('19.1.0') ALL_ROWS OUTLINE_LEAF(@"SET$2A13AF86_2") OUTLINE_LEAF(@"SET$2A13AF86_1") OUTLINE_LEAF(@"SET$2A13AF86") OUTLINE_LEAF(@"SEL$9162BF3C") OR_EXPAND(@"SEL$1" (1) (2)) OUTLINE(@"SEL$1") NO_ACCESS(@"SEL$9162BF3C" "VW_ORE_BA8ECEFB"@"SEL$BA8ECEFB") INDEX_RS_ASC(@"SET$2A13AF86_1" "T1"@"SET$2A13AF86_1" ("T1"."STATUS")) BATCH_TABLE_ACCESS_BY_ROWID(@"SET$2A13AF86_1" "T1"@"SET$2A13AF86_1") FULL(@"SET$2A13AF86_2" "T1"@"SET$2A13AF86_2") END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 4 - filter(:PARAM='WAITING') 6 - filter("PART_KEY"=:PART_KEY) 7 - access("STATUS"='UNPROCESSED') 8 - filter(:PARAM='ALL') 10 - filter(("STATUS"<>'PENDING' AND "PART_KEY"=:PART_KEY||'' AND (LNNVL("PART_KEY"=:PART_KEY) OR LNNVL(:PARAM='WAITING') OR LNNVL("STATUS"='UNPROCESSED')))) 57 rows selected. SQL> SQL> alter session set events 'trace[sql_optimizer.*] off'; Session altered. SQL> SQL> col value for a80 SQL> SQL> select value 2 from v$diag_info 3 where name='Default Trace File'; VALUE -------------------------------------------------------------------------------- /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_7253_20200608_180825.trcThat is what is done in or_expand_test_rewrite.sql; the corresponding trace file is: orcl_ora_7253_20200608_180825.trc.
Evidently, this rewrite can be applied in a limited number of cases.
Thankfully, the old OR Expansion transformation, aka CONCATENATION, works just fine for this example which is demonstrated in the listing below: or_expand_test_use_concat.sql
SQL> @or_expand_test_use_concat ..skip.. SQL> var part_key varchar2(10)='P1' SQL> var param varchar2(12)='WAITING' SQL> SQL> select --+ gather_plan_statistics use_concat(or_predicates(2)) 2 count(pad1) 3 from t1 4 where part_key = :part_key 5 and (:param = 'WAITING' and status = 'UNPROCESSED' 6 or 7 :param = 'ALL' and status <> 'PENDING'); COUNT(PAD1) ----------- 1000 SQL> SQL> select * 2 from dbms_xplan.display_cursor(format=> 'allstats last outline'); PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------------------------------- SQL_ID cygpq5hyy2uu2, child number 0 ------------------------------------- select --+ gather_plan_statistics use_concat(or_predicates(2)) count(pad1) from t1 where part_key = :part_key and (:param = 'WAITING' and status = 'UNPROCESSED' or :param = 'ALL' and status <> 'PENDING') Plan hash value: 496678280 ----------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | ----------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 83 | | 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 83 | | 2 | CONCATENATION | | 1 | | 1000 |00:00:00.01 | 83 | |* 3 | FILTER | | 1 | | 0 |00:00:00.01 | 0 | | 4 | PARTITION LIST SINGLE | | 0 | 90992 | 0 |00:00:00.01 | 0 | |* 5 | TABLE ACCESS FULL | T1 | 0 | 90992 | 0 |00:00:00.01 | 0 | |* 6 | FILTER | | 1 | | 1000 |00:00:00.01 | 83 | | 7 | PARTITION LIST SINGLE | | 1 | 990 | 1000 |00:00:00.01 | 83 | |* 8 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| T1 | 1 | 990 | 1000 |00:00:00.01 | 83 | |* 9 | INDEX RANGE SCAN | T1_STATUS_I | 1 | 990 | 1000 |00:00:00.01 | 6 | ----------------------------------------------------------------------------------------------------------------------- Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('19.1.0') DB_VERSION('19.1.0') ALL_ROWS OUTLINE_LEAF(@"SEL$1") OUTLINE_LEAF(@"SEL$1_1") USE_CONCAT(@"SEL$1" OR_PREDICATES(2)) OUTLINE_LEAF(@"SEL$1_2") FULL(@"SEL$1_1" "T1"@"SEL$1") INDEX_RS_ASC(@"SEL$1_2" "T1"@"SEL$1_2" ("T1"."STATUS")) BATCH_TABLE_ACCESS_BY_ROWID(@"SEL$1_2" "T1"@"SEL$1_2") END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 3 - filter(:PARAM='ALL') 5 - filter(("STATUS"<>'PENDING' AND "PART_KEY"=:PART_KEY)) 6 - filter(:PARAM='WAITING') 8 - filter("PART_KEY"=:PART_KEY) 9 - access("STATUS"='UNPROCESSED') filter((LNNVL(:PARAM='ALL') OR LNNVL("STATUS"<>'PENDING'))) 53 rows selected. SQL> SQL> alter session set events 'trace[sql_optimizer.*] off'; Session altered. SQL> SQL> col value for a80 SQL> SQL> select value 2 from v$diag_info 3 where name='Default Trace File'; VALUE -------------------------------------------------------------------------------- /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_7253_20200608_181301.trcThe corresponding trace file is: orcl_ora_7253_20200608_181301.trc.
This blog post demonstrates a specific example in which OR Expansion fails to produce the desired execution plan, leading to a suboptimal execution.
By way of contrast, CONCATENATION results in the execution plan I was seeking, performing as effectively as OR Expansion did before 19c.
It is my hope that Oracle will fix this issue eventually, which I am trying to get done in SR 3-22449907431 : OR-Expansion does not work following 19.5 upgrade.
Комментариев нет:
Отправить комментарий
Примечание. Отправлять комментарии могут только участники этого блога.