Here’s an interesting OR-Expansion limit which reared its head a little bit after a 12cR1-to-19c Oracle upgrade. It happens during a Create Table As Select (CTAS) which was using a CONCATENATION operation under 12cR1 completing in a couple of seconds. However, under 19c version the same CTAS uses a dramatic NESTED LOOPS to the detriment of the more powerful OR-Expansion and took more than 6 hours.
Here’re below the 12cR1 and 19c execution plan respectively
------------------------------------------------------------------------ | Id | Operation | Name | Rows |Time | ------------------------------------------------------------------------ | 0 | CREATE TABLE STATEMENT | | | | | 1 | LOAD AS SELECT | TARGET_T | | | | 2 | OPTIMIZER STATISTICS GATHERING | | 71227 |00:00:05 | | 3 | HASH UNIQUE | | 71227 |00:00:05 | | 4 | CONCATENATION | | | | | 5 | HASH JOIN | | 38426 |00:00:03 | | 6 | TABLE ACCESS STORAGE FULL | SOURCE_T2 | 48067 |00:00:01 | | 7 | TABLE ACCESS STORAGE FULL | SOURCE_T1 | 16M|00:00:03 | | 8 | HASH JOIN | | 32801 |00:00:03 | | 9 | TABLE ACCESS STORAGE FULL | SOURCE_T2 | 48067 |00:00:01 | | 10 | TABLE ACCESS STORAGE FULL | SOURCE_T1 | 11M|00:00:03 | ------------------------------------------------------------------------- ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Time | ------------------------------------------------------------------------- | 0 | CREATE TABLE STATEMENT | | | | | 1 | LOAD AS SELECT | TARGET_T | | | | 2 | OPTIMIZER STATISTICS GATHERING | | 71227 | 08:53:39 | | 3 | HASH UNIQUE | | 71227 | 08:53:39 | | 4 | NESTED LOOPS | | 71227 | 08:53:39 | | 5 | TABLE ACCESS STORAGE FULL | SOURCE_T1 | 24M| 00:00:03 | | 6 | TABLE ACCESS STORAGE FULL | SOURCE_T2 | 1 | 00:00:01 | -------------------------------------------------------------------------
There is a simple root cause for this performance deterioration: OR-Expansion can’t be used by Oracle during a DDL operation.
You can locate the root cause of this transformation limit by using the or_expand hint and observing the generated Hint Report at the bottom of the corresponding execution plan:
create table t1 (n1 number, n2 number, c1 varchar2(10)); create index t1_idx1 on t1(n1, n2); create index t1_idx2 on t1(n2); create table t2 as select rownum id ,trunc((rownum-1/6)) n2 ,trunc((rownum-1/8)) n3 ,mod(rownum,5) n4 ,lpad('z', 4) vc from dual connect by level <=1e3; exec dbms_stats.gather_table_stats(user, 't1') exec dbms_stats.gather_table_stats(user, 't2');
SQL> explain plan for create table t1_bis as select /*+ or_expand */ t1.* from t1, t2 where t1.n1 = t2.id or t1.n1 = t2.n2; Explained. ----------------------------------------------------------- | Id | Operation | Name | Rows | ----------------------------------------------------------- | 0 | CREATE TABLE STATEMENT | | 1 | | 1 | LOAD AS SELECT | T1_BIS | | | 2 | OPTIMIZER STATISTICS GATHERING | | 1 | | 3 | NESTED LOOPS | | 1 | | 4 | TABLE ACCESS FULL | T1 | 1 | |* 5 | TABLE ACCESS FULL | T2 | 1 | ----------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 5 - filter("T1"."N1"="T2"."ID" OR "T1"."N1"="T2"."N2") Hint Report (identified by operation id / Query Block Name / Object Alias): Total hints for statement: 1 (U - Unused (1)) --------------------------------------------------------------------------- 1 - SEL$1 U - or_expand / Top query block of a DML -------> Note the reason for not using the hint
You can also get the reason for which Oracle by-passed the or-expansion for the CTAS case via the corresponding 10053
************************** Query transformations (QT) ************************** TE: Checking validity of table expansion for query block SEL$1 (#0) TE: Bypassed: Top query block of a DML. ORE: Checking validity of OR Expansion for query block SEL$1 (#0) ORE: Predicate chain before QB validity check - SEL$1 "T1"."N1"="T2"."ID" OR "T1"."N1"="T2"."N2" ORE: Predicate chain after QB validity check - SEL$1 "T1"."N1"="T2"."ID" OR "T1"."N1"="T2"."N2" ORE: bypassed - Top query block of a DML. ---> Note the by-pass reason
While the OR-expansion transformation is by passed during the CTAS it is, however, used for the select part in 19c:
explain plan for select /*+ or_expand */ t1.* from t1, t2 where t1.n1 = t2.id or t1.n1 = t2.n2; Explained. -------------------------------------------------------- | Id | Operation | Name | Rows | -------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | | 1 | VIEW | VW_ORE_F79C84EE | 2 | | 2 | UNION-ALL | | | |* 3 | HASH JOIN | | 1 | | 4 | TABLE ACCESS FULL| T1 | 1 | | 5 | TABLE ACCESS FULL| T2 | 1000 | |* 6 | HASH JOIN | | 1 | | 7 | TABLE ACCESS FULL| T1 | 1 | | 8 | TABLE ACCESS FULL| T2 | 1000 | -------------------------------------------------------- /*+ BEGIN_OUTLINE_DATA USE_HASH(@"SET$2A13AF86_2" "T2"@"SET$2A13AF86_2") LEADING(@"SET$2A13AF86_2" "T1"@"SET$2A13AF86_2" "T2"@"SET$2A13AF86_2") FULL(@"SET$2A13AF86_2" "T2"@"SET$2A13AF86_2") FULL(@"SET$2A13AF86_2" "T1"@"SET$2A13AF86_2") USE_HASH(@"SET$2A13AF86_1" "T2"@"SET$2A13AF86_1") LEADING(@"SET$2A13AF86_1" "T1"@"SET$2A13AF86_1" "T2"@"SET$2A13AF86_1") FULL(@"SET$2A13AF86_1" "T2"@"SET$2A13AF86_1") FULL(@"SET$2A13AF86_1" "T1"@"SET$2A13AF86_1") NO_ACCESS(@"SEL$9162BF3C" "VW_ORE_F79C84EE"@"SEL$F79C84EE") OUTLINE(@"SEL$1") OR_EXPAND(@"SEL$1" (1) (2)) OUTLINE_LEAF(@"SEL$9162BF3C") OUTLINE_LEAF(@"SET$2A13AF86") OUTLINE_LEAF(@"SET$2A13AF86_1") OUTLINE_LEAF(@"SET$2A13AF86_2") ALL_ROWS OPT_PARAM('_optimizer_use_feedback' 'false') DB_VERSION('19.1.0') OPTIMIZER_FEATURES_ENABLE('19.1.0') IGNORE_OPTIM_EMBEDDED_HINTS END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 3 - access("T1"."N1"="T2"."ID") 6 - access("T1"."N1"="T2"."N2") filter(LNNVL("T1"."N1"="T2"."ID"))
Workaround
To work around this limit, you can set back the optimizer features enable (OFE) to the previous version (12cR1 or less) where the OR-Expansion was not yet implemented so that your CTAS might be able to use the CONCATENATION operation. Or, you can opt for the following workaround provided by Mauro Pagano(@Mautro)
SQL> explain plan for create table t1_bis as select * from ( select /*+ no_merge or_expand */ t1.* from t1, t2 where t1.n1 = t2.id or t1.n1 = t2.n2 ); Explained. ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | ---------------------------------------------------------------------------- | 0 | CREATE TABLE STATEMENT | | 2 | 66 | | 1 | LOAD AS SELECT | T1_BIS | | | | 2 | OPTIMIZER STATISTICS GATHERING | | 2 | 66 | | 3 | VIEW | | 2 | 66 | | 4 | VIEW | VW_ORE_4C1C5B2C | 2 | 66 | | 5 | UNION-ALL | | | | |* 6 | HASH JOIN | | 1 | 37 | | 7 | TABLE ACCESS FULL | T1 | 1 | 33 | | 8 | TABLE ACCESS FULL | T2 | 1000 | 4000 | |* 9 | HASH JOIN | | 1 | 41 | | 10 | TABLE ACCESS FULL | T1 | 1 | 33 | | 11 | TABLE ACCESS FULL | T2 | 1000 | 8000 | ---------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 6 - access("T1"."N1"="T2"."ID") 9 - access("T1"."N1"="T2"."N2") filter(LNNVL("T1"."N1"="T2"."ID"))
It consists of wrapping the select part into an inline view and pre-empting this inline view from being merged with the DDL part of the CTAS. As such the SELECT part becomes a valid candidate for the OR-Expansion transformation.
Oddity
When I tried to put the or_expand hint at the create table level here’s what I got
SQL> select banner_full from v$version; BANNER_FULL -------------------------------------------------------------------------------- Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production Version 19.8.0.0.0 SQL> explain plan for create /*+ or_expand */ table t1_bis as select t1.* from t1, t2 where t1.n1 = t2.id or t1.n1 = t2.n2; 2 3 4 5 6 7 8 9 10 11 explain plan for * ERROR at line 1: ORA-03113: end-of-file on communication channel Process ID: 8658 Session ID: 198 Serial number: 5465
With the following error in the alert log
ORA-07445: exception encountered: core dump [qkshtQBStore()+219] [SIGSEGV] [ADDR:0x258] [PC:0x2628B8B] [Address not mapped to object] []