How can we use use parallel (10) hint in Oracle Merge statement - oracle

How can we use parallel (10) hints in the oracle merge statement? , I am a bit new to hints and would like to know if this can be done for merge statements?

In very-very short:
First of all, parallel execution must be enabled on the server.
You need to check the following 3 parameters:
select name,value
from v$parameter
where name in (
'parallel_degree_policy'
,'parallel_servers_target'
,'parallel_max_servers'
);
PARALLEL_MAX_SERVERS specifies the maximum number of parallel execution processes, so it must be >0
PARALLEL_SERVERS_TARGET specifies the number of parallel server processes allowed to run parallel statements before statement queuing will be used. When the parameter PARALLEL_DEGREE_POLICY is set to AUTO, Oracle will queue SQL statements that require parallel execution, if the necessary parallel server processes are not available.
PARALLEL_DEGREE_POLICY specifies whether or not automatic degree of Parallelism, statement queuing, and in-memory parallel execution will be enabled.
Values:
MANUAL: Disables automatic degree of parallelism, statement queuing, and in-memory parallel execution. This reverts the behavior of parallel execution to what it was prior to Oracle Database 11g Release 2 (11.2). This is the default.
LIMITED: Enables automatic degree of parallelism for some statements but statement queuing and in-memory Parallel Execution are disabled. Automatic degree of parallelism is only applied to those statements that access tables or indexes decorated explicitly with the PARALLEL clause. Tables and indexes that have a degree of parallelism specified will use that degree of parallelism.
AUTO: Enables automatic degree of parallelism, statement queuing, and in-memory parallel execution.
Then you can check your session parameters:
select id,name,sql_feature,isdefault,value
from v$ses_optimizer_env e
where name like '%parallel%'
and sid=userenv('sid');
ID NAME SQL_FEATURE ISDEFAUL VALUE
---------- ---------------------------------------- ------------ -------- -------------------------
2 parallel_execution_enabled QKSFM_CBO YES true
13 parallel_threads_per_cpu QKSFM_CBO YES 2
35 parallel_query_mode QKSFM_ALL YES enabled
36 parallel_dml_mode QKSFM_ALL YES disabled
37 parallel_ddl_mode QKSFM_ALL YES enabled
245 parallel_degree_policy QKSFM_PQ YES manual
246 parallel_degree QKSFM_PQ YES 0
247 parallel_min_time_threshold QKSFM_PQ YES 10
256 parallel_query_default_dop QKSFM_PQ YES 0
272 parallel_degree_limit QKSFM_PQ YES 65535
273 parallel_force_local QKSFM_PQ YES false
274 parallel_max_degree QKSFM_PQ YES 16
289 parallel_autodop QKSFM_PQ YES 0
290 parallel_ddldml QKSFM_PQ YES 0
331 parallel_dblink QKSFM_PQ YES 0
Here you can notice three xxx_mode parameters: parallel_ddl_mode, parallel_dml_mode and parallel_query_mode, which allows related parallel operations on session level and can be enabled or disable using alter session enable/disable/force, for example alter session force parallel dml parallel 16 - enable parallel dml with DOP=16:
SQL> alter session force parallel dml parallel 16;
Session altered.
SQL> select id,name,sql_feature,isdefault,value
2 from v$ses_optimizer_env e
3 where name like 'parallel_dml%'
4 and sid=userenv('sid');
ID NAME SQL_FEATURE ISDEFAULT VALUE
---------- ------------------------- ----------- ---------- -------------------------
4 parallel_dml_forced_dop QKSFM_CBO NO 16
36 parallel_dml_mode QKSFM_ALL NO forced
Hint parallel has 2 forms:
statement level parallel(DoP)
and object level parallel(alias DoP)
In fact, this hint
enables parallel execution in case of parallel_degree_policy parameter=MANUAL
reduces a cost of parallel operations to make parallel plans more preferable (it doesn't force parallel execution - your plan still will be serial if its' cost cheaper then parallel one), and
specifies DoP (Degree of Parallelism).
Next, different plan steps can have different DOP, or even run serially: for example, different objects (indexes or tables or their partitions) can have different parallel options (alter table xxx parallel 8) or "read" operations (row-sources) can be parallelized, but "change" (like UPDATE or MERGE) operations are serial.
For example, with parallel_degree_policy=manual and disabled parallel dml:
SQL> explain plan for update/*+ parallel(4) */ t set b=a+1 where a >1;
Plan hash value: 1378397380
---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 9999 | 79992 | 2 (0)| 00:00:01 | | | |
| 1 | UPDATE | T | | | | | | | |
| 2 | PX COORDINATOR | | | | | | | | |
| 3 | PX SEND QC (RANDOM)| :TQ10000 | 9999 | 79992 | 2 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
| 4 | PX BLOCK ITERATOR | | 9999 | 79992 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
|* 5 | TABLE ACCESS FULL| T | 9999 | 79992 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
---------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - filter("A">1)
Note
-----
- Degree of Parallelism is 4 because of hint
You can see than FTS (Full table scan) is parallel, but UPDATE is serial. On newer actually supported Oracle versions and you can see one more note - PDML is disabled in current session.
And if you enable parallel dml using alter session enable parallel dml or alter session force parallel dml parallel N (on actual oracle version you can use also enable_parallel_dml hint, it was backported to 11.2.0.4, but documented only since 12.1, so I wouldn't suggest to use it until at least 12.1):
SQL> alter session enable parallel dml;
Session altered.
SQL> explain plan for update/*+ parallel(4) */ t set b=a+1 where a >1;
Plan hash value: 2037160838
---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 9999 | 79992 | 2 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10000 | 9999 | 79992 | 2 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
| 3 | UPDATE | T | | | | | Q1,00 | PCWP | |
| 4 | PX BLOCK ITERATOR | | 9999 | 79992 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
|* 5 | TABLE ACCESS FULL| T | 9999 | 79992 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
---------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - filter("A">1)
Note
-----
- Degree of Parallelism is 4 because of hint
As you can see, now UPDATE is parallel too.
So, it depends on your current database, table and sessions parameters and what exactly you want to get and: enable parallel operations in case of parallel_degree_policy=manual, or enable parallel dml, or limit/force required DOP, etc
A bit more details for start:
19.1.2.7 Hints for Parallel Execution
Using Parallel Execution
How Parallel Execution Works

How? Like this:
SQL> alter session enable parallel dml;
Session altered.
SQL> merge /*+ parallel(10) */ into emp e
2 using dept d
3 on (d.deptno = e.deptno)
4 when matched then update set
5 e.comm = e.comm * 1;
14 rows merged.
SQL>
The PARALLEL hint will enable read-parallelism, but to also enable write-parallelism you will need to either run the above ALTER SESSION command or use the hint /*+ ENABLE_PARALLEL_DML */.

Related

query rewrite not working properly on materialized view datasubset

I'm currently experimenting with Oracle materialized views, but I'm facing a problem:
I created a materialized view (a data subset that only contains data for January 2020) from base table DW_F_TIMESHEETLINE and matching bitmapindex:
create materialized view DW.MV_TSL_4
ENABLE QUERY REWRITE
as
SELECT "Fact_Timesheet_Line".*
FROM
DW."DW_F_TIMESHEETLINE" "Fact_Timesheet_Line"
WHERE
"Fact_Timesheet_Line".DDAT_WORK_SK between 20200101 and 20200131
;
CREATE BITMAP INDEX DW.IDX_BM_MV_TSL_4_DDAT_WORK_SK ON DW.MV_TSL_4 (DDAT_WORK_SK) NOLOGGING TABLESPACE DW_INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 STORAGE ( INITIAL 64K NEXT 1M MAXSIZE UNLIMITED MINEXTENTS 1 MAXEXTENTS UNLIMITED PCTINCREASE 0 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT ) NOPARALLEL;
When I run the following query with a filter on DDAT_WORK_SK, the query is being rewritten by the optimize to use the materialized view:
select sum(data) from DW.DW_F_TIMESHEETLINE where DDAT_WORK_SK between 20200101 and 20200131
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 14490 (1)| 00:00:02 |
| 1 | SORT AGGREGATE | | 1 | 4 | | |
| 2 | MAT_VIEW REWRITE ACCESS FULL| MV_TSL_4 | 1732K| 6769K| 14490 (1)| 00:00:02 |
------------------------------------------------------------------------------------------
Note
-----
- automatic DOP: Computed Degree of Parallelism is 1 because of parallel threshold
However, If I change the query so that the base table is joined with table DW_D_DATE on column DDAT_WORK_SK, the query is rewritten to get the data from the materialized view and also from the base table (with a UNION ALL):
select sum(data)
from DW.DW_F_TIMESHEETLINE , DW.DW_D_DATE
where DW_D_DATE.DDAT_SK = DW.DW_F_TIMESHEETLINE.DDAT_WORK_SK and
DW_D_DATE.DDAT_SK between 20200101 and 20200131;
---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 14500 (1)| 00:00:02 |
| 1 | SORT AGGREGATE | | 1 | 13 | | |
| 2 | VIEW | | 2 | 26 | 14500 (1)| 00:00:02 |
| 3 | UNION-ALL | | | | | |
| 4 | SORT AGGREGATE | | 1 | 16 | | |
|* 5 | HASH JOIN | | 131K| 2052K| 14500 (1)| 00:00:02 |
|* 6 | INDEX RANGE SCAN | DW_PK_DDAT_SK | 2 | 12 | 2 (0)| 00:00:01 |
|* 7 | MAT_VIEW REWRITE ACCESS FULL | MV_TSL_4 | 1732K| 16M| 14494 (1)| 00:00:02 |
| 8 | SORT AGGREGATE | | 1 | 10 | | |
|* 9 | FILTER | | | | | |
| 10 | TABLE ACCESS BY INDEX ROWID BATCHED| DW_F_TIMESHEETLINE | 2361K| 22M| 134K (1)| 00:00:11 |
| 11 | BITMAP CONVERSION TO ROWIDS | | | | | |
|* 12 | BITMAP INDEX RANGE SCAN | IDX_BM_FTSL_DDAT_WORK_SK | | | | |
---------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("DW_D_DATE"."DDAT_SK"="MV_TSL_4"."DDAT_WORK_SK")
6 - access("DW_D_DATE"."DDAT_SK">=20200101 AND "DW_D_DATE"."DDAT_SK"<=20200131)
7 - filter("MV_TSL_4"."DDAT_WORK_SK">=20200101 AND "MV_TSL_4"."DDAT_WORK_SK"<=20200131)
9 - filter(NULL IS NOT NULL)
12 - access("DW_F_TIMESHEETLINE"."DDAT_WORK_SK">=20200101 AND
"DW_F_TIMESHEETLINE"."DDAT_WORK_SK"<=20200131)
Note
-----
- dynamic statistics used: dynamic sampling (level=5)
- automatic DOP: Computed Degree of Parallelism is 1 because of parallel threshold
- this is an adaptive plan
I don't understand why the optimizer is still using the base table?
In the explain plan, I noticed line '9 - filter(NULL IS NOT NULL)'. After some googling this could be because of constraints that are not being validated.
I tried to validate the foreign key constraint DW_FK_DDAT_WORK_SK on DW_F_TIMESHEETLINE and create a few extra constraints on this column:
alter table DW.DW_F_TIMESHEETLINE modify constraint DW_FK_DDAT_WORK_SK validate;
alter table DW.DW_F_TIMESHEETLINE add constraint testconstr check (DDAT_WORK_SK is not null) validate;
alter table DW.MV_TSL_4 add constraint t1a_ck check (DDAT_WORK_SK is not null) validate;
alter table DW.MV_TSL_4 add constraint t1a_ck2 check (DDAT_WORK_SK between 20200101 and 20200131) validate;
But even with these constraints, the optimizer continues to rewrite the query using a UNION ALL between the materialized view and the base table.
Any idea what I am doing wrong?
Thank you for your feedback!
9 - filter(NULL IS NOT NULL) means that Oracle knows it can safely not execute this part.
You can use row source statistics to confirm which lines are executed in a plan (and how many times and how long they take).
alter session set statistics_level=all;
set serverout off
select sum(data)
from DW.DW_F_TIMESHEETLINE , DW.DW_D_DATE
where DW_D_DATE.DDAT_SK = DW.DW_F_TIMESHEETLINE.DDAT_WORK_SK and
DW_D_DATE.DDAT_SK between 20200101 and 20200131;
select * from dbms_xplan.display_cursor(sql_id=>null,format=>'typical allstats last');
(assuming the user you're running this as has the privileges to read plans, otherwise grab the sql_id of the execution and run the dbms_xplan part manually from a privileged session).
I imagine, Oracle has decided to expand this out as there is a foreign key which guarantees that the join is always successful - so if you were to use the base table, you don't need to do the join.
BTW it is a small red flag to have bitmap indexes on this base table, make sure you really need them as they do not make concurrency easy. Should also note that none of your shared queries would benefit from the bitmap index you created on the MView.

how to avoid TABLE ACCESS FULL when fetching rows in Oracle

I'm using Oracle 18c but I guess my question would not be bound to the specific version.
I want to fetch rows from a table but I found a complex, ugly solution.
I would like to know if there is better, simple query that can return the same result as following.
First of all, I have a simple table like this.
Note that col is going to store large text.
CREATE TABLE simpletable
(record_id NUMBER,
col CLOB,
PRIMARY KEY (record_id));
I want to retrieve single row from the above table and whichever row is acceptable.
First query came to my mind is as following.
SELECT * FROM (SELECT * FROM simpletable) WHERE rownum <= 1;
Another is as following.
SELECT * FROM (SELECT * FROM simpletableORDER BY record_id) WHERE rownum <= 1;
Unfortunately, neither of above two does not use primary-key index and uses TABLE ACCESS FULL which can take long time when the table grows enough large.
(I'm guessing that oracle preferred the simpler plan because my table is not enough large yet to use index scan.
Oracle might choose different plan if the table grows up further.)
My final solution that uses primary-key index to narrow down the table access is following.
SELECT simpletable.* FROM
(SELECT * FROM
(SELECT record_id, ROWID as id FROM simpletable ORDER BY record_id)
WHERE rownum<=1) a
JOIN simpletable ON a.id = simpletable.ROWID;
If you have a better solution, please let me know.
It would be very appreciated.
P.S.
The first two queries produced the following plan.
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2015 | 4 (25)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 1 | 2015 | 4 (25)| 00:00:01 |
|* 3 | SORT ORDER BY STOPKEY| | 1 | 2015 | 4 (25)| 00:00:01 |
| 4 | TABLE ACCESS FULL | SIMPLETABLE | 1 | 2015 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------------
the final one is:
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2039 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 2039 | 3 (0)| 00:00:01 |
| 2 | VIEW | | 1 | 25 | 2 (0)| 00:00:01 |
|* 3 | COUNT STOPKEY | | | | | |
| 4 | VIEW | | 1 | 25 | 2 (0)| 00:00:01 |
| 5 | INDEX FULL SCAN | SYS_C007561 | 1 | 25 | 2 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY USER ROWID| SIMPLETABLE | 1 | 2014 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
I think using OFFSET..FETCH method might helps you here -
SELECT *
FROM simpletable
ORDER BY record_id
OFFSET 0 ROWS
FETCH FIRST ROW ONLY;
If the Cost Based Optimizer code has access to reliable statistics, from its dictionary, regarding all the objects available for this query, then it will very likely produce an optimal execution plan. Of course, there are exceptions and you would argue with their support people as to whether or not choosing a suboptimal plan is a bug.
In this specific case, if you are querying a single table and the CBO could choose between a full table scan and some other scan and then chose a full table scan, then chances were good that the CBO determined that the number of blocks scanned (buffer gets) would have been smaller using a full table scan.
You can expose the truth of the matter by tracing the execution of multiple versions of the statement, each one using a different set of hints to force a particular execution plan. You should consider the execution with the fewest buffer gets to be the winner. Alternatively, if the execution plan is of a serial nature, then you can use response time as measure. If the winner is not automatically chosen by the CBO, then it's probably because the statistics it used were not accurate and you should make them accurate. If the statistics are indeed accurate then Oracle support will probably give you a very long homework assignment.
Similar to the Horror Vacui, some database developers suffer under the Horror FULL TABLE SCAN by simply assuming index access good, full scan bad.
But this is not true, FULL TABLE SCAN is a normal access method, that is preferred in some situation.
Let's illustrate it on a simple example with 10K rows in your table
insert into simpletable (record_id, col)
select rownum, rpad('x',3998,'y')
from dual connect by level <= 10000
To get one arbitrary row from the table you simple use the following query
select * from simpletable where rownum = 1;
Here is the output (edited for brevity) you get from SQL*Plus with setting set autotrace traceonly to see the execution plan and the statistics.
Execution Plan
----------------------------------------------------------
Plan hash value: 1007892724
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2015 | 2 (0)| 00:00:01
|* 1 | COUNT STOPKEY | | | | |
| 2 | TABLE ACCESS FULL| SIMPLETABLE | 10188 | 19M| 2 (0)| 00:00:01
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM=1)
Statistics
----------------------------------------------------------
5 consistent gets
2 physical reads
1 rows processed
The most important information is the statistics consistent gets - there were only 5 blocks accessed - the table is much larger.
What is the explanation? See the operation COUNT STOPKEY above the TABLE ACCESS FULL this ensures that the scan is terminated after the first row is found.
If you want to get a specific row, e.g. the one with the highest ID, the prefered approach is using the row_limiting_clause
SELECT *
FROM simpletable
ORDER BY record_id DESC
OFFSET 0 ROWS FETCH NEXT 1 ROW ONLY;
You will see the execution plan below, that performs first the INDEX FULL SCAN DESCENDING. The complete (full) index will be red in the descending order, but again due to STOPKEY you break after reading the highest key (which is the first entry due to the descending order).
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10188 | 19M| 613 (0)| 00:00:01 |
|* 1 | VIEW | | 10188 | 19M| 613 (0)| 00:00:01 |
|* 2 | WINDOW NOSORT STOPKEY | | 10188 | 19M| 613 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| SIMPLETABLE | 10188 | 19M| 613 (0)| 00:00:01 |
| 4 | INDEX FULL SCAN DESCENDING| SYS_C008793 | 10188 | | 29 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=CASE WHEN (0>=0) THEN
0 ELSE 0 END +1 AND "from$_subquery$_002"."rowlimit_$$_rownumber">0)
2 - filter(ROW_NUMBER() OVER ( ORDER BY
INTERNAL_FUNCTION("SIMPLETABLE"."RECORD_ID") DESC )<=CASE WHEN (0>=0) THEN 0 ELSE 0
END +1)
Note if the table is empty or contains very few rows, you will se even here a TABLE ACCESS FULL because the optimizer recognises that it is more effective that to first go to the index and that access the table.

How to imporve performace of an insert when a table has so many indexes in Oracle?

We have a table having about 100 indexes on it. So when I try to insert a high number of rows into this table, it takes too much time to perform the insert. I tried the PARALLEL and APPEND hints but did not help much.
Is there any other ways to improve the insert performance in such situations? (I don't want to disable and then enable the triggers)
Use the explain plan to ensure that you are using the append and parallel hints correctly - there are many ways for those hints can go wrong.
Here's an example of a good explain plan for large data warehouse statements:
create table test1(a number);
explain plan for
insert /*+ append parallel enable_parallel_dml */ into test1
select 1 from test1;
select * from table(dbms_xplan.display);
Plan hash value: 1209398148
--------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 2 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10000 | 1 | 2 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
| 3 | LOAD AS SELECT (HYBRID TSM/HWMB)| TEST1 | | | | Q1,00 | PCWP | |
| 4 | OPTIMIZER STATISTICS GATHERING | | 1 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
| 5 | PX BLOCK ITERATOR | | 1 | 2 (0)| 00:00:01 | Q1,00 | PCWC | |
| 6 | TABLE ACCESS FULL | TEST1 | 1 | 2 (0)| 00:00:01 | Q1,00 | PCWP | |
--------------------------------------------------------------------------------------------------------------------
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------
0 - STATEMENT
U - parallel
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- automatic DOP: Computed Degree of Parallelism is 2
For good parallel direct-path performance, these are the main things to look for in the explain plan:
Ensure direct path writes are used. "LOAD AS SELECT" means the append hint worked, "INSERT CONVENTIONAL" means the hint was not used. There are many possible reasons why direct path cannot be used, the most common of which is that parallel DML is not enabled, which is what the third hint does. (Before 12c, you had to run alter session enable parallel dml.)
Ensure parallelism is used for both reads and writes. There should be a "PX" operation both before and after the "LOAD AS SELECT". If there's not an operation before, then the writing is not done in parallel.
Ensure the degree of parallelism is correct. The explain plan will tell you the requested degree of parallelism. The DOP is hard to get right and is affected by many factors. If your DOP seems wrong, use this checklist to look for possible problems.
If you're still having problems, use a SQL monitoring report to find information about the actual execution plan, rows, times, and wait events. Generating the report is usually as easy as select dbms_sqltune.report_sql_monitor('your SQL_ID') from dual. If you post the results here someone can probably find a way to improve the performance.

Why is Oracle's query planner adding a filter predicate that replicates a constraint?

I have a simple Oracle query with a plan that doesn't make sense.
SELECT
u.custid AS "custid",
l.listid AS "listid"
FROM
users u
INNER JOIN lists l ON u.custid = l.custid
And here’s what the autotrace explain tells me it has for a plan
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1468K| 29M| | 11548 (1)| 00:00:01 |
|* 1 | HASH JOIN | | 1468K| 29M| 7104K| 11548 (1)| 00:00:01 |
| 2 | INDEX FAST FULL SCAN| USERS_PK | 404K| 2367K| | 266 (2)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | LISTS | 1416K| 20M| | 9110 (1)| 00:00:01 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("U"."CUSTID"="L"."CUSTID")
3 - filter(TRUNC("SORT_TYPE")>=1 AND TRUNC("SORT_TYPE")<=16)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- this is an adaptive plan
- 1 Sql Plan Directive used for this statement
What concerns me is predicate 3. sort_type does not appear in the query, and is not indexed at all. It seems to me that sort_type should not be involved in this query at all.
There is a constraint on lists.sort_type: (Yes, I know we could have sort_type be an INTEGER not a NUMBER)
sort_type NUMBER DEFAULT 2 NOT NULL,
CONSTRAINT lists_sort_type CHECK ( sort_type BETWEEN 1 AND 16 AND TRUNC(sort_type) = sort_type )
It looks to me that that filter is on sort_type is basically a tautology. Every row in lists must pass that filter because of that constraint.
If I drop the constraint, the filter no longer shows up in the plan, and the estimated cost goes down a little bit. If I add the constraint back, the plan uses the filter again. There's no significant difference in execution speed one way or the other.
I'm concerned because I discovered this filter in a much larger, more complex query that I was trying to optimize down from a couple of minutes of runtime.
Why is Oracle adding that filter, and is it a problem and/or pointing to another problem?
EDIT: If I change the constraint on sort_type to not have the TRUNC part, the filter disappears. If I split the constraint into two different constraints, the filter comes back.
Generally speaking, Oracle generates predicates based on your CHECK constraints whenever doing so will give the optimizer more information to generate a good plan. It is not always smart enough to recognize when those are redundant. Here is a short example in Oracle 12c using your table names:
-- Create the CUSTS table
CREATE TABLE custs ( custid number not null );
CREATE INDEX custs_u1 on custs (custid);
-- Create the LISTS table
CREATE TABLE lists
( listid number not null,
sort_type number not null,
custid number,
constraint lists_c1 check ( sort_type between 1 and 16 and
trunc(sort_type) = sort_Type )
);
-- Explain a join
EXPLAIN PLAN FOR
SELECT /*+ USE_HASH(u) */
u.custid AS "custid",
l.listid AS "listid"
FROM custs u
INNER JOIN lists l ON u.custid = l.custid;
-- Show the plan
select * from table(dbms_xplan.display);
Plan hash value: 2443150416
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 39 | 3 (0)| 00:00:01 |
|* 1 | HASH JOIN | | 1 | 39 | 3 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | CUSTS_U1 | 1 | 13 | 1 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| LISTS | 1 | 26 | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("U"."CUSTID"="L"."CUSTID")
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
So far, nothing weird. No questionable predicates added.
Now, let's tell the Oracle optimizer that the distribution of data on TRUNC(sort_type) might matter...
declare
x varchar2(30);
begin
x := dbms_stats.create_extended_stats ( user, 'LISTS', '(TRUNC(SORT_TYPE))');
dbms_output.put_line('Extension name = ' || x);
end;
... and, now, let's explain that same query again...
-- Re-explain the same join as before
EXPLAIN PLAN FOR
SELECT /*+ USE_HASH(u) */
u.custid AS "custid",
l.listid AS "listid"
FROM custs u
INNER JOIN lists l ON u.custid = l.custid;
-- Show the new plan
select * from table(dbms_xplan.display);
Plan hash value: 2443150416
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 52 | 3 (0)| 00:00:01 |
|* 1 | HASH JOIN | | 1 | 52 | 3 (0)| 00:00:01 |
| 2 | INDEX FULL SCAN | CUSTS_U1 | 1 | 13 | 1 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| LISTS | 1 | 39 | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("U"."CUSTID"="L"."CUSTID")
3 - filter(TRUNC("SORT_TYPE")>=1 AND TRUNC("SORT_TYPE")<=16)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
Now, Oracle has added the predicate, because the CBO might benefit from it. Does it really benefit from it? No, but Oracle is only smart enough to know that it might and that it doesn't(*) hurt anything.
(*) there have been numerous bugs in previous versions where this _has_ hurt things by messing up the selectivity estimated by the CBO.
The presence of extended statistics is only one example reason of why Oracle might think it could benefit from this predicate. To find out if that is the reason in your case, you can look for extended statistics in your database like this:
SELECT * FROM dba_stat_extensions where table_name = 'LISTS';
Keep in mind, the Oracle CBO can create stat extensions on its own. So there could be extended stats that you didn't realize were there.

Oracle slow query performance with PARALLEL optimization plan

I have very simple query
SELECT
A
FROM table
where B = 'X'
explain plan for it looks like
|
0 | SELECT STATEMENT | | 2 | 16 | 4 (0)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10000 | 2 | 16 | 4 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
| 3 | PX BLOCK ITERATOR | | 2 | 16 | 4 (0)| 00:00:01 | Q1,00 | PCWC | |
|* 4 | INDEX FAST FULL SCAN| TABLE_UNIQUE_ROLES_KEY1 | 2 | 16 | 4 (0)| 00:00:01 | Q1,00 | PCWP | |
It appears to me that Oracle tries to run PARALLEL execution plan.
But I do not have any understanding why it would do it. It significantly slows down query
and if I do
SELECT /*+ NO_PARALLEL */
A
FROM table
where B = 'X'
it works fast, and plan is:
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 16 | 4 (0)| 00:00:01 |
|* 1 | INDEX FAST FULL SCAN| TABLE_UNIQUE_ROLES_KEY1 | 2 | 16 | 4 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------
What causes parallelism in first scenario?
The degree on the table is set to 1 but the degree on the TABLE_UNIQUE_ROLES_KEY1 (and the other indexes on the table) are all set to 4. I don't have privileges to query v$parameter so I can't see how parallelism is configured for the database.
TABLE_UNIQUE_ROLES_KEY1 is a covering index for the query-- it is defined on the columns (a, b, c, d) where a is the column I'm selecting, b is the column that I'm filtering on and c and d are not involved in the query.
The immediate cause is that someone has told Oracle that it should use parallel query (the degree for your indexes has all been set to 4). That tends to make the optimizer think that full scanning the index in parallel will be relatively cheap which is why the optimizer is picking that plan.
You can change the parallel setting on your index
ALTER INDEX TABLE_UNIQUE_ROLES_KEY1 NOPARALLEL
which should stop the optimizer from choosing this plan (you may have to set other indexes to noparallel as well to prevent the optimizer from picking a different index to full scan in parallel). But I'd hesitate to do that until I understood what person or process set the degree on your indexes to 4-- if you don't understand the root cause, it's likely that you'll end up either breaking something else or in an endless battle where that person/ process sets your indexes to use parallelism and you set them back.
The two most likely candidates for what caused the indexes to have a degree of 4 are that someone (either a developer or a DBA) was trying to get parallel query to kick in for some other query or that the DBA is running an (almost certainly unnecessary) script that periodically rebuilds indexes that does so in parallel without realizing that this changes the degree setting on the index and makes it likely that parallel query kicks in. So you probably need to have a chat with the other developers and/or the other DBAs to figure out whether setting the index to noparallel will negatively affect them and whether there are other processes that will be changing the setting on you.

Resources