Why is DISTINCT on 3 identical values so slow? - oracle

I have a performance issue that I cannot understand in on of my queries. The query I am executing is the following:
SELECT UNIQUE GROUP_ID
FROM pos, TABLE (t_type(12984918, 12984919, 12984917))
WHERE pos.pos_id = COLUMN_VALUE AND GROUP_ID <> 0;
1 row returned in 99 ms
You might think that 83 ms is fine, but what really puzzles me is that removing UNIQUE makes the query a LOT faster even when there are only 3 duplicate values:
SELECT GROUP_ID
FROM pos, TABLE (t_type(12984918, 12984919, 12984917))
WHERE pos.pos_id = COLUMN_VALUE AND GROUP_ID <> 0;
3 rows returned in 0.048 ms
Is oracle really taking almost 100ms just to reduce a set of 3 elements to a single value? That seemed crazy to me, so I started doing some investigation and fetched the execution plan with TKPROF.
SELECT UNIQUE GROUP_ID
FROM pos, TABLE (t_type(12984918, 12984919, 12984917))
WHERE pos.pos_id = COLUMN_VALUE AND GROUP_ID <> 0
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 2 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 1 0.00 0.09 0 8 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 0.00 0.10 0 10 0 1
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 58
Number of plan statistics captured: 1
Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
1 1 1 HASH UNIQUE (cr=8 pr=0 pw=0 time=99258 us cost=31 size=3892 card=139)
3 3 3 NESTED LOOPS (cr=8 pr=0 pw=0 time=84 us)
3 3 3 NESTED LOOPS (cr=5 pr=0 pw=0 time=59 us cost=30 size=3892 card=139)
3 3 3 COLLECTION ITERATOR CONSTRUCTOR FETCH (cr=0 pr=0 pw=0 time=7 us cost=29 size=16336 card=8168)
3 3 3 INDEX UNIQUE SCAN IU_POS_POS_ID (cr=5 pr=0 pw=0 time=31 us cost=0 size=0 card=1)(object id 20684)
3 3 3 TABLE ACCESS BY INDEX ROWID POS (cr=3 pr=0 pw=0 time=12 us cost=0 size=26 card=1)
Well, it really looks like Oracle is taking 99 milliseconds to reduce 3 identical values into a single one, using the HASH UNIQUE operation. That actually STILL seems crazy. That should barely be noticeable. So I starter looking for an alternative. It turns out that Oracle can also use the SORT UNIQUE operation to implement DISTINCT. I also found out that I can force oracle to not use HASH UNIQUE by doing:
ALTER SESSION SET "_gby_hash_aggregation_enabled" = true;
An now, my query runs in 0.48ms! That's a 2000x improvement! WTF! Here's the execution plan that shows it's using SORT UNIQUE:
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 2 0.00 0.00 0 4 0 0
Execute 2 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.00 0 16 0 2
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 6 0.00 0.00 0 20 0 2
Misses in library cache during parse: 2
Optimizer mode: ALL_ROWS
Parsing user id: 58
Number of plan statistics captured: 2
Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
1 1 1 SORT UNIQUE (cr=8 pr=0 pw=0 time=48 us cost=30 size=28 card=1)
3 3 3 NESTED LOOPS (cr=8 pr=0 pw=0 time=35 us)
3 3 3 NESTED LOOPS (cr=5 pr=0 pw=0 time=26 us cost=29 size=28 card=1)
3 3 3 COLLECTION ITERATOR CONSTRUCTOR FETCH (cr=0 pr=0 pw=0 time=2 us cost=29 size=6 card=3)
3 3 3 INDEX UNIQUE SCAN IU_POS_POS_ID (cr=5 pr=0 pw=0 time=17 us cost=0 size=0 card=1)(object id 20684)
3 3 3 TABLE ACCESS BY INDEX ROWID POS (cr=3 pr=0 pw=0 time=7 us cost=0 size=26 card=1)
********************************************************************************
OK, so now I'm thinking how can I force oracle to use SORT UNIQUE without touching the session parameters. Turns out I can just add an ORDER BY clause, and speedup my query.
SELECT UNIQUE GROUP_ID
FROM pos, TABLE (t_type(12984918, 12984919, 12984917))
WHERE pos.pos_id = COLUMN_VALUE AND GROUP_ID <> 0
ORDER BY GROUP_ID;
So basically I've solved my problem (if you can call that kind of hack "solving a problem"), but I'm still puzzled.
So here are the question:
Why is Oracle choosing HASH UNIQUE over SORT UNIQUE ?
Why is HASH UNIQUE so slow when there are only 3 records to hash?
Is there a better way to hint oracle to use SORT UNIQUE over HASH UNIQUE?

Why is Oracle choosing HASH UNIQUE over SORT UNIQUE?
Your first tkprof results show much higher (expected?) cardinality for the TABLE iterator. Oracle is optimizing for many more than three rows.
Why is HASH UNIQUE so slow when there are only 3 records to hash?
Hash unique will need to build a hash table no matter how many elements are being hashed. Presumably the size of the hash table will be smaller for fewer elements, but again the cardinality estimate is such that Oracle is going to build a bigger hash table.
Is there a better way to hint oracle to use SORT UNIQUE over HASH UNIQUE?
I would try hinting the cardinality on the table operator.

Is there a better way to hint oracle to use SORT UNIQUE over HASH
UNIQUE?
I´d check how are the table and index stats are gathered. They are used by oracle optimizer in order to build effective access plans.

Related

Weird results from oracle query with indexes and PreparedStatement

Recently, after upgrading our test env with a new version of application and introducing some new features I've stumbled upon some weird oracle behavior, which shows itself in query results that doesn't make sense and seem to be index dependant.
After doing some investigation I was able to create a minimal conditions to reproduce the issue.
I was experimenting on Oracle21 started in with docker:
docker run --restart always -d -p 1521:1521 -e ORACLE_PASSWORD=system --name oracle-21c-01 gvenzl/oracle-xe:21-slim
Then logging in as system user (system/system) I created another one with few permissions:
CREATE USER test PROFILE DEFAULT IDENTIFIED BY test ACCOUNT UNLOCK;
GRANT CONNECT TO test;
GRANT UNLIMITED TABLESPACE TO test;
GRANT CREATE TABLE TO test;
After that I logged in under this new user (test/test) and executed the following:
CREATE TABLE TEST_BUG
(
ID NUMBER(10) NOT NULL
CONSTRAINT PK_TEST_BUG PRIMARY KEY,
TENANT NUMBER(10) NOT NULL,
IDENTIFIER VARCHAR2(255) NOT NULL,
NAME VARCHAR2(255) NOT NULL
);
INSERT INTO TEST_BUG
VALUES (10, 1, 'IDENTIFIER', 'TESTBUG');
ALTER TABLE TEST_BUG
ADD CONSTRAINT UK_NAME_TENANT UNIQUE (NAME, TENANT);
ALTER TABLE TEST_BUG
ADD CONSTRAINT UK_IDENTIFIER_TENANT UNIQUE (IDENTIFIER, TENANT);
ALTER INDEX PK_TEST_BUG REBUILD;
As you can see here I'm just creating a very simple table with a couple of indexes, but the statements must be executed in this particular order (Also only with index rebuild on the last line I was able to reproduce the issue we're having on the actual environment, even though we do not do it anywhere in the update scripts).
With all that in mind, executing the following simle java code gives me weird results:
public class Main {
public static void main(String[] args) throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
try (Connection connection = DriverManager.getConnection("jdbc:oracle:thin:#localhost:1521:XE", "test", "test")) {
try (PreparedStatement preparedStatement = connection.prepareStatement(
"select id from test_bug where tenant = ? and name=? and id<>?"
)) {
preparedStatement.setInt(1, 1);
preparedStatement.setString(2, "TESTBUG");
preparedStatement.setInt(3, 10);
ResultSet resultSet = preparedStatement.executeQuery();
System.out.println(resultSet.next());
System.out.println(resultSet.getInt(1));
}
}
}
}
And results are:
true
10
Meaning that even though I've inserted a single record with id=10 and in the query asking specifically that id<>10, I still get that recoded as an output instead of getting nothing!
Couple more notes:
This only works with PreparedStatement, if I try to execute this query with sql developer - works just fine.
If I remove put id condition explicitly in query (like select id from test_bug where tenant = ? and name=? and id<>10) and remove preparedStatement.setInt(3, 10); - works also fine (no results)
If I disable PK index with ALTER INDEX PK_TEST_BUG UNUSABLE; - works also fine, no results, until I get index back with ALTER INDEX PK_TEST_BUG REBUILD;
For oracle driver I'm using here ojdbc11 21.9.0.0, but I've played with different versions - no difference.
Any ideas kindly appreciated!
This is an optimizer bug. If I run the java unchanged and do a SQL trace on it, you see the following the trace file
select id
from
test_bug where tenant = :1 and name=:2 and id != :3
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 1 0
Fetch 1 0.00 0.00 0 3 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 0.00 0.00 0 3 1 1
Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 109
Number of plan statistics captured: 1
Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
1 1 1 TABLE ACCESS BY INDEX ROWID BATCHED TEST_BUG (cr=3 pr=0 pw=0 time=32 us starts=1 cost=0 size=155 card=1)
1 1 1 BITMAP CONVERSION TO ROWIDS (cr=2 pr=0 pw=0 time=25 us starts=1)
1 1 1 BITMAP AND (cr=2 pr=0 pw=0 time=23 us starts=1)
1 1 1 BITMAP CONVERSION FROM ROWIDS (cr=1 pr=0 pw=0 time=9 us starts=1)
1 1 1 INDEX RANGE SCAN UK_NAME_TENANT (cr=1 pr=0 pw=0 time=8 us starts=1 cost=0 size=0 card=1)(object id 100690)
1 1 1 BITMAP CONVERSION FROM ROWIDS (cr=1 pr=0 pw=0 time=13 us starts=1)
1 1 1 SORT ORDER BY (cr=1 pr=0 pw=0 time=11 us starts=1)
1 1 1 INDEX RANGE SCAN PK_TEST_BUG (cr=1 pr=0 pw=0 time=3 us starts=1 cost=0 size=0 card=1)(object id 100689)
with the Fetch line Rows column of 1 meaning we found a row. If I disable that plan from use by changing the statement to:
con.prepareStatement("select /*+ full(t) */ id from test_bug t where tenant = ? and name=? and id != ?");
then with the altered plan, we get the result you expect
select /*+ full(t) */ id
from
test_bug t where tenant = :1 and name=:2 and id != :3
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 1 0
Fetch 1 0.00 0.00 0 7 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 0.00 0.00 0 7 1 0
Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 109
Number of plan statistics captured: 1
Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
0 0 0 TABLE ACCESS FULL TEST_BUG (cr=7 pr=0 pw=0 time=32 us starts=1 cost=3 size=155 card=1)
This is not to say that you should just put in a FULL hint and forget about it :-) but its worth logging an SR to see if that's an existing bug or if patches are available.
If you want a quick workaround, then:
select /*+ opt_param('_b_tree_bitmap_plans','false') */ id from test_bug where tenant = :b1 and name= :b2 and id<> :b3;
should take care of it.

Why there are current block reads in my TKPROF output on a simple select statement? Why there are two parses in one query?

In the query below, I used the sample SH schema in my query in Oracle. When I get the TKPROF output for that query, I see that, there are some current block reads. But as far as I know, current block reads happen when that block has a change on it. But I did not perform any update or something. I just executed that query.
So, why there are 28 current mode block reads?
Next question is, in the execution plan statistics, some starts values are 0. Does that mean this step is not performed? But when I check the autotrace output, it seems these steps are also executed. (For example, the index unique scan customers_pk has starts = 0). So is this step executed? If yes, why it writes 0 here?
Last question is, why there are both hard parse and soft parse (two parses) in one query?
Thanks in advance.
select s.prod_id, p.prod_name, s.cust_id, c.cust_first_name
from
sh.sales s, sh.products p, sh.customers c where s.prod_id = p.prod_id and
s.cust_id = c.cust_id and s.amount_sold > 1500
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.03 0.03 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 542 0.32 0.72 0 3607 28 8114
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 544 0.35 0.76 0 3607 28 8114
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: SYS
Number of plan statistics captured: 1
Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
8114 8114 8114 HASH JOIN (cr=3607 pr=0 pw=0 time=450527 us starts=1 cost=859 size=469504 card=8384)
8114 8114 8114 NESTED LOOPS (cr=1643 pr=0 pw=0 time=253284 us starts=1 cost=859 size=469504 card=8384)
8114 8114 8114 NESTED LOOPS (cr=1643 pr=0 pw=0 time=251761 us starts=1)
8114 8114 8114 STATISTICS COLLECTOR (cr=1643 pr=0 pw=0 time=250365 us starts=1)
8114 8114 8114 HASH JOIN (cr=1643 pr=0 pw=0 time=250044 us starts=1 cost=454 size=368896 card=8384)
72 72 72 NESTED LOOPS (cr=8 pr=0 pw=0 time=863 us starts=1 cost=454 size=368896 card=8384)
72 72 72 STATISTICS COLLECTOR (cr=8 pr=0 pw=0 time=790 us starts=1)
72 72 72 TABLE ACCESS FULL PRODUCTS (cr=8 pr=0 pw=0 time=691 us starts=1 cost=3 size=2160 card=72)
0 0 0 PARTITION RANGE ALL PARTITION: 1 28 (cr=0 pr=0 pw=0 time=0 us starts=0 cost=451 size=1624 card=116)
0 0 0 TABLE ACCESS BY LOCAL INDEX ROWID BATCHED SALES PARTITION: 1 28 (cr=0 pr=0 pw=0 time=0 us starts=0 cost=451 size=1624 card=116)
0 0 0 BITMAP CONVERSION TO ROWIDS (cr=0 pr=0 pw=0 time=0 us starts=0)
0 0 0 BITMAP INDEX SINGLE VALUE SALES_PROD_BIX PARTITION: 1 28 (cr=0 pr=0 pw=0 time=0 us starts=0)(object id 101439)
8114 8114 8114 PARTITION RANGE ALL PARTITION: 1 28 (cr=1635 pr=0 pw=0 time=248277 us starts=1 cost=451 size=117376 card=8384)
8114 8114 8114 TABLE ACCESS FULL SALES PARTITION: 1 28 (cr=1635 pr=0 pw=0 time=208294 us starts=28 cost=451 size=117376 card=8384)
0 0 0 INDEX UNIQUE SCAN CUSTOMERS_PK (cr=0 pr=0 pw=0 time=0 us starts=0)(object id 101249)
0 0 0 TABLE ACCESS BY INDEX ROWID CUSTOMERS (cr=0 pr=0 pw=0 time=0 us starts=0 cost=405 size=12 card=1)
55500 55500 55500 TABLE ACCESS FULL CUSTOMERS (cr=1964 pr=0 pw=0 time=120870 us starts=1 cost=405 size=666000 card=55500)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
PGA memory operation 35 0.00 0.00
Disk file operations I/O 1 0.00 0.00
SQL*Net message to client 542 0.00 0.00
SQL*Net message from client 542 41.10 57.47
********************************************************************************
We don't usually get a lot of tuning/optimization questions here, but I'll try to answer this.
Current block gets MIGHT indicate a change on the block (DML), but they don't always. They also appear whenever you have a full scan, because the segment headers have to be read in "current mode" in order to figure out how to scan the table.
Yes, a "starts" of 0 means that step wasn't performed. You'll note that those steps also have 0 rows. Those steps are still part of the plan because if any rows did match those conditions, those steps would need to be executed. But in this case they weren't used.
There aren't two parses - you're only running the statement once, so it's only being parsed once. That's what Parse count 1 means. It was a hard parse, because your first run of a query is always a hard parse. You can see that where it says "Misses in library cache during parse: 1". It checked to see whether this exact query had been run before and it hadn't; therefore it's a hard parse. If you ran this exact same query again, it would find it in the library cache and it would be a soft parse.

Update query taking long time in oracle 10g

I have a table which holds more then 2 million records, I am trying to update a table using following query
UPDATE toc T
SET RANK =
65535
- (SELECT COUNT (*)
FROM toc T2
WHERE S_KEY LIKE '00010001%'
AND A_ID IS NOT NULL
AND T2.TARGET = T.TARGET
AND T2.RANK > T.RANK)
WHERE S_KEY LIKE '00010001%' AND A_ID IS NOT NULL
Usually this query tooks 5 mins to update 50000 rows in our staging db which is a exact replica of production db but in our production db it is taking 6 hours to execute...
I tried Oracle advisory to select the correct execution plan but nothing is working...
Plan
UPDATE STATEMENT ALL_ROWSCost: 329,471
6 UPDATE TT.TOC
2 TABLE ACCESS BY INDEX ROWID TABLE TT.TOC Cost: 5 Bytes: 4,173,236 Cardinality: 54,911
1 INDEX SKIP SCAN INDEX TT.DATASTAT_SORTKEY_IDX Cost: 4 Cardinality: 1
5 SORT AGGREGATE Bytes: 76 Cardinality: 1
4 TABLE ACCESS BY INDEX ROWID TABLE TT.TOC Cost: 5 Bytes: 76 Cardinality: 1
3 INDEX SKIP SCAN INDEX TT.DATASTAT_SORTKEY_IDX Cost: 4 Cardinality: 1
I can see the following wait events
1,066 db file sequential read 10,267 0 3,993 0 6 39,933,580
1,066 db file scattered read 413 0 188 0 6 1,876,464
Any help will be greatly appreciated.
here is the current list of indexes
DSTAT_SKEY_IDX D_STATUS 1
DSTAT_SKEY_IDX S_KEY 2
IDX$$_165A0002 N_LABEL 1
S_KEY_IDX S_KEY 1
XAK1_TOC N_RELATIONSHIP 1
XAK2_TOC TARGET 1
XAK2_TOC N_LABEL 2
XAK2_TOC D_STATUS 3
XAK2_TOC A_ID 4
XIE1_TOC N_RELBASE 1
XIF4_TOC SOURCE_FILE_ID 1
XIF5_TOC A_ID 1
XPK_TOC N_ID 1
Atif
You're doing a skip scan where you supposedly want to do a range scan.
A range scan is only possible when the index columns are ordered by descending selectivity - in your case it seems that it should be S_KEY - TARGET - RANK
Update: rewriting the query in different order wouldn't make any difference. What matters is the sequence of the columns in the indexes of that table.
first show us the current index columns for that table:
select index_name, column_name, column_position from all_ind_columns where table_name = 'TOC'
then you could create a new index, e.g.
create index toc_i_s_key_target_rank on toc (s_key, target, rank) compress;

Table not doing index scan

Hey guy so some reason my table is doing index fast full scan.
here is my query
SELECT bo.bid,cu.cid
FROM ass2master_booking bo, ass2master_customer cu
WHERE bo.cid = cu.cid
and rownum < 135000;
here is the trace file
SELECT bo.bid,cu.cid
FROM ass2master_booking bo, ass2master_customer cu
WHERE bo.cid = cu.cid
and rownum < 135000
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 2 0.00 0.00 0 0 0 0
Fetch 9001 0.19 0.41 387 2131 0 134999
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 9004 0.19 0.41 387 2131 0 134999
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 5594
Rows Row Source Operation
------- ---------------------------------------------------
134999 COUNT STOPKEY (cr=2131 pr=387 pw=189 time=411804 us)
134999 HASH JOIN (cr=2131 pr=387 pw=189 time=276737 us)
150000 INDEX FAST FULL SCAN CUSTOMER_CID_IDX (cr=320 pr=315 pw=0 time=263 us)(object id 332052)
7412 TABLE ACCESS FULL ASS2MASTER_BOOKING (cr=1811 pr=44 pw=0 time=7566 us)
basically i was told to add structure to unstructred data i was given 2 tables with 150,000 rows and determ which of the following structures were the best.
non clustered index
clustered index
hash clustered
unstructured data.
And the query i chose to do the testing with is given above.
You are selecting a large part of both tables, so index access doesn't makes sense. (at least oracle thinks that)
It uses Index Fast Full Scan instead of Table Access Full because because it finds everything it needs (cu.cid) in the index and doesn't need the table.
I have no idea what you mean with
non clustered index clustered index hash clustered unstructured data.
Update:
I think a rule of thumb I use:
If you need more then 20% of a table I expect a full table scan.
If you need less then 5% of a table I expect some kind of index access.
One of the first things I do when tuning SQL statement is to look in the execution plan and check the number of rows oracle expects to get back from each step. If those numbers are completely wrong, the execution plan is probably pretty bad.

Why is Oracle using a skip scan for this query?

Here's the tkprof output for a query that's running extremely slowly (WARNING: it's long :-) ):
SELECT mbr_comment_idn, mbr_crt_dt, mbr_data_source, mbr_dol_bl_rmo_ind, mbr_dxcg_ctl_member, mbr_employment_start_dt, mbr_employment_term_dt, mbr_entity_active, mbr_ethnicity_idn, mbr_general_health_status_code, mbr_hand_dominant_code, mbr_hgt_feet, mbr_hgt_inches, mbr_highest_edu_level, mbr_insd_addr_idn, mbr_insd_alt_id, mbr_insd_name, mbr_insd_ssn_tin, mbr_is_smoker, mbr_is_vip, mbr_lmbr_first_name, mbr_lmbr_last_name, mbr_marital_status_cd, mbr_mbr_birth_dt, mbr_mbr_death_dt, mbr_mbr_expired, mbr_mbr_first_name, mbr_mbr_gender_cd, mbr_mbr_idn, mbr_mbr_ins_type, mbr_mbr_isreadonly, mbr_mbr_last_name, mbr_mbr_middle_name, mbr_mbr_name, mbr_mbr_status_idn, mbr_mpi_id, mbr_preferred_am_pm, mbr_preferred_time, mbr_prv_innetwork, mbr_rep_addr_idn, mbr_rep_name, mbr_rp_mbr_id, mbr_same_mbr_ins, mbr_special_needs_cd, mbr_timezone, mbr_upd_dt, mbr_user_idn, mbr_wgt, mbr_work_status_idn
FROM (SELECT /*+ FIRST_ROWS(1) */ mbr_comment_idn, mbr_crt_dt, mbr_data_source, mbr_dol_bl_rmo_ind, mbr_dxcg_ctl_member, mbr_employment_start_dt, mbr_employment_term_dt, mbr_entity_active, mbr_ethnicity_idn, mbr_general_health_status_code, mbr_hand_dominant_code, mbr_hgt_feet, mbr_hgt_inches, mbr_highest_edu_level, mbr_insd_addr_idn, mbr_insd_alt_id, mbr_insd_name, mbr_insd_ssn_tin, mbr_is_smoker, mbr_is_vip, mbr_lmbr_first_name, mbr_lmbr_last_name, mbr_marital_status_cd, mbr_mbr_birth_dt, mbr_mbr_death_dt, mbr_mbr_expired, mbr_mbr_first_name, mbr_mbr_gender_cd, mbr_mbr_idn, mbr_mbr_ins_type, mbr_mbr_isreadonly, mbr_mbr_last_name, mbr_mbr_middle_name, mbr_mbr_name, mbr_mbr_status_idn, mbr_mpi_id, mbr_preferred_am_pm, mbr_preferred_time, mbr_prv_innetwork, mbr_rep_addr_idn, mbr_rep_name, mbr_rp_mbr_id, mbr_same_mbr_ins, mbr_special_needs_cd, mbr_timezone, mbr_upd_dt, mbr_user_idn, mbr_wgt, mbr_work_status_idn, ROWNUM AS ora_rn
FROM (SELECT mbr.comment_idn AS mbr_comment_idn, mbr.crt_dt AS mbr_crt_dt, mbr.data_source AS mbr_data_source, mbr.dol_bl_rmo_ind AS mbr_dol_bl_rmo_ind, mbr.dxcg_ctl_member AS mbr_dxcg_ctl_member, mbr.employment_start_dt AS mbr_employment_start_dt, mbr.employment_term_dt AS mbr_employment_term_dt, mbr.entity_active AS mbr_entity_active, mbr.ethnicity_idn AS mbr_ethnicity_idn, mbr.general_health_status_code AS mbr_general_health_status_code, mbr.hand_dominant_code AS mbr_hand_dominant_code, mbr.hgt_feet AS mbr_hgt_feet, mbr.hgt_inches AS mbr_hgt_inches, mbr.highest_edu_level AS mbr_highest_edu_level, mbr.insd_addr_idn AS mbr_insd_addr_idn, mbr.insd_alt_id AS mbr_insd_alt_id, mbr.insd_name AS mbr_insd_name, mbr.insd_ssn_tin AS mbr_insd_ssn_tin, mbr.is_smoker AS mbr_is_smoker, mbr.is_vip AS mbr_is_vip, mbr.lmbr_first_name AS mbr_lmbr_first_name, mbr.lmbr_last_name AS mbr_lmbr_last_name, mbr.marital_status_cd AS mbr_marital_status_cd, mbr.mbr_birth_dt AS mbr_mbr_birth_dt, mbr.mbr_death_dt AS mbr_mbr_death_dt, mbr.mbr_expired AS mbr_mbr_expired, mbr.mbr_first_name AS mbr_mbr_first_name, mbr.mbr_gender_cd AS mbr_mbr_gender_cd, mbr.mbr_idn AS mbr_mbr_idn, mbr.mbr_ins_type AS mbr_mbr_ins_type, mbr.mbr_isreadonly AS mbr_mbr_isreadonly, mbr.mbr_last_name AS mbr_mbr_last_name, mbr.mbr_middle_name AS mbr_mbr_middle_name, mbr.mbr_name AS mbr_mbr_name, mbr.mbr_status_idn AS mbr_mbr_status_idn, mbr.mpi_id AS mbr_mpi_id, mbr.preferred_am_pm AS mbr_preferred_am_pm, mbr.preferred_time AS mbr_preferred_time, mbr.prv_innetwork AS mbr_prv_innetwork, mbr.rep_addr_idn AS mbr_rep_addr_idn, mbr.rep_name AS mbr_rep_name, mbr.rp_mbr_id AS mbr_rp_mbr_id, mbr.same_mbr_ins AS mbr_same_mbr_ins, mbr.special_needs_cd AS mbr_special_needs_cd, mbr.timezone AS mbr_timezone, mbr.upd_dt AS mbr_upd_dt, mbr.user_idn AS mbr_user_idn, mbr.wgt AS mbr_wgt, mbr.work_status_idn AS mbr_work_status_idn
FROM mbr JOIN mbr_identfn ON mbr.mbr_idn = mbr_identfn.mbr_idn
WHERE mbr_identfn.mbr_idn = mbr.mbr_idn AND mbr_identfn.identfd_type = :identfd_type_1 AND mbr_identfn.identfd_number = :identfd_number_1 AND mbr_identfn.entity_active = :entity_active_1)
WHERE ROWNUM <= :ROWNUM_1)
WHERE ora_rn > :ora_rn_1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 9936 0.46 0.49 0 0 0 0
Execute 9936 0.60 0.59 0 0 0 0
Fetch 9936 329.87 404.00 0 136966922 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 29808 330.94 405.09 0 136966922 0 0
Misses in library cache during parse: 0
Optimizer mode: FIRST_ROWS
Parsing user id: 36 (JIVA_DEV)
Rows Row Source Operation
------- ---------------------------------------------------
0 VIEW (cr=102 pr=0 pw=0 time=2180 us)
0 COUNT STOPKEY (cr=102 pr=0 pw=0 time=2163 us)
0 NESTED LOOPS (cr=102 pr=0 pw=0 time=2152 us)
0 INDEX SKIP SCAN IDX_MBR_IDENTFN (cr=102 pr=0 pw=0 time=2140 us)(object id 341053)
0 TABLE ACCESS BY INDEX ROWID MBR (cr=0 pr=0 pw=0 time=0 us)
0 INDEX UNIQUE SCAN PK_CLAIMANT (cr=0 pr=0 pw=0 time=0 us)(object id 334044)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: HINT: FIRST_ROWS
0 VIEW
0 COUNT (STOPKEY)
0 NESTED LOOPS
0 INDEX MODE: ANALYZED (SKIP SCAN) OF 'IDX_MBR_IDENTFN'
(INDEX (UNIQUE))
0 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'MBR'
(TABLE)
0 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_CLAIMANT'
(INDEX (UNIQUE))
********************************************************************************
Based on my reading of Oracle's documentation of skip scans, a skip scan is most useful when the first column of an index has a low number of unique values. The thing is that the first index of this column has a high number of uniques. So am I correct in assuming that a skip scan is the wrong thing to do here? Also, what kind of scan should it be doing? Should I do some more hinting for this query?
EDIT: I should also point out that the query's where clause uses the columns in IDX_MBR_IDENTFN and no columns other than what's in that index. So as far as I can tell, I'm not skipping any columns.
EDIT 2: I've done a few things to speed this query up. First of all, I removed the paging. As it turns out, this query only returns one row anyway. Secondly, I added a LEADING hint to make sure tables were being queried in the right order. Thirdly, I removed the duplicate mbr_idn predicate. Lastly, I made IDX_MBR_IDENTFN unique. Altogether, this makes a drastic performance improvement (although it's still the most expensive query I'm running):
SELECT /*+ LEADING (mbr_identfn, mbr) */ mbr.comment_idn AS mbr_comment_idn, mbr.crt_dt AS mbr_crt_dt, mbr.data_source AS mbr_data_source, mbr.dol_bl_rmo_ind AS mbr_dol_bl_rmo_ind, mbr.dxcg_ctl_member AS mbr_dxcg_ctl_member, mbr.employment_start_dt AS mbr_employment_start_dt, mbr.employment_term_dt AS mbr_employment_term_dt, mbr.entity_active AS mbr_entity_active, mbr.ethnicity_idn AS mbr_ethnicity_idn, mbr.general_health_status_code AS mbr_general_health_status_code, mbr.hand_dominant_code AS mbr_hand_dominant_code, mbr.hgt_feet AS mbr_hgt_feet, mbr.hgt_inches AS mbr_hgt_inches, mbr.highest_edu_level AS mbr_highest_edu_level, mbr.insd_addr_idn AS mbr_insd_addr_idn, mbr.insd_alt_id AS mbr_insd_alt_id, mbr.insd_name AS mbr_insd_name, mbr.insd_ssn_tin AS mbr_insd_ssn_tin, mbr.is_smoker AS mbr_is_smoker, mbr.is_vip AS mbr_is_vip, mbr.lmbr_first_name AS mbr_lmbr_first_name, mbr.lmbr_last_name AS mbr_lmbr_last_name, mbr.marital_status_cd AS mbr_marital_status_cd, mbr.mbr_birth_dt AS mbr_mbr_birth_dt, mbr.mbr_death_dt AS mbr_mbr_death_dt, mbr.mbr_expired AS mbr_mbr_expired, mbr.mbr_first_name AS mbr_mbr_first_name, mbr.mbr_gender_cd AS mbr_mbr_gender_cd, mbr.mbr_idn AS mbr_mbr_idn, mbr.mbr_ins_type AS mbr_mbr_ins_type, mbr.mbr_isreadonly AS mbr_mbr_isreadonly, mbr.mbr_last_name AS mbr_mbr_last_name, mbr.mbr_middle_name AS mbr_mbr_middle_name, mbr.mbr_name AS mbr_mbr_name, mbr.mbr_status_idn AS mbr_mbr_status_idn, mbr.mpi_id AS mbr_mpi_id, mbr.preferred_am_pm AS mbr_preferred_am_pm, mbr.preferred_time AS mbr_preferred_time, mbr.prv_innetwork AS mbr_prv_innetwork, mbr.rep_addr_idn AS mbr_rep_addr_idn, mbr.rep_name AS mbr_rep_name, mbr.rp_mbr_id AS mbr_rp_mbr_id, mbr.same_mbr_ins AS mbr_same_mbr_ins, mbr.special_needs_cd AS mbr_special_needs_cd, mbr.timezone AS mbr_timezone, mbr.upd_dt AS mbr_upd_dt, mbr.user_idn AS mbr_user_idn, mbr.wgt AS mbr_wgt, mbr.work_status_idn AS mbr_work_status_idn
FROM mbr JOIN mbr_identfn ON mbr.mbr_idn = mbr_identfn.mbr_idn
WHERE mbr_identfn.identfd_type = :identfd_type_1 AND mbr_identfn.identfd_number = :identfd_number_1 AND mbr_identfn.entity_active = :entity_active_1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 10102 0.45 0.42 0 0 0 0
Execute 10102 0.44 0.52 0 0 0 0
Fetch 10102 1.60 1.81 0 218121 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 30306 2.50 2.75 0 218121 0 0
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 36 (JIVA_DEV)
Rows Row Source Operation
------- ---------------------------------------------------
0 NESTED LOOPS (cr=3 pr=0 pw=0 time=96 us)
0 TABLE ACCESS BY INDEX ROWID MBR_IDENTFN (cr=3 pr=0 pw=0 time=88 us)
0 INDEX UNIQUE SCAN UK_CLM_IDFN (cr=3 pr=0 pw=0 time=77 us)(object id 334118)
0 TABLE ACCESS BY INDEX ROWID MBR (cr=0 pr=0 pw=0 time=0 us)
0 INDEX UNIQUE SCAN PK_CLAIMANT (cr=0 pr=0 pw=0 time=0 us)(object id 334044)
Rows Execution Plan
------- ---------------------------------------------------
0 SELECT STATEMENT MODE: ALL_ROWS
0 NESTED LOOPS
0 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF
'MBR_IDENTFN' (TABLE)
0 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'UK_CLM_IDFN' (INDEX
(UNIQUE))
0 TABLE ACCESS MODE: ANALYZED (BY INDEX ROWID) OF 'MBR' (TABLE)
0 INDEX MODE: ANALYZED (UNIQUE SCAN) OF 'PK_CLAIMANT' (INDEX
(UNIQUE))
Index skip scan means, that the first column of the index is ignored. This costs performance since Oracle has read every item of the first column, and check if the second (or third, ...) column is what you searched for. This usually is faster than a full-table scan (depends on your query), but slower than a index range scan.
Try to create a separate index on the column that is part of IDX_MBR_IDENTFN and used in your query.
For example, if your_table looks like this:
id status
1 0
2 0
3 0
4 1
and you have a compound index on (id, status), the query Select * From your_table Where status = 1 is likely to use the index, but in order to find the correct rows, it has to read every row of the index (id 1 to 4) and check the status.
Update: The following index could improve performance a little further, but you will have to try if it really helps:
mbr_identfn( identfd_type, identfd_number, entity_active, mbr_idn )
This could even help to avoid the hint.
I would shift my focus away from the skip scan.
The tkprof snippet shows you that your first priority should be to reduce the number of times you issue this statement. Currently you are executing this statement 9936 times. And each execution takes only 405/9936 seconds. Reasonably fast. But not if you execute it 9936 times.
So this statement is almost sure inside a loop construct. In each iteration you provide a different input parameter set(:identfd_type_1, :identfd_number_1, :entity_active_1, :ROWNUM_1, :ora_rn_1). Rewrite this loop construct to have this statement execute once for the entire set, and your performance problem will probably a thing from the past. If not, please post the new tkprof output.
Regards,
Rob.
It would help if you identified what the columns in the indexes (PK_CLAIMANT and IDX_MBR_IDENTFN) are, and in what order.
I suspect it is a datetype issue. If, for example, mbr_identfn.identfd_type is the leading column of the index and is numeric, but your :identfd_type_1 is character variable (or vice versa) it becomes unusable. However if there are few types, then the index can be used with a skip scan.
You also specify the predicate "mbr.mbr_idn = mbr_identfn.mbr_idn" in both the where clause and the join clause.
To explain the skip scan ... this appears to be the relevant predicate portion of the query:
WHERE mbr_identfn.mbr_idn = mbr.mbr_idn
AND mbr_identfn.identfd_type = :identfd_type_1
AND mbr_identfn.identfd_number = :identfd_number_1
AND mbr_identfn.entity_active = :entity_active_1
If the execution start with MBR_IDENTFN, then we don't yet have a value for MBR_IDN to look up in the index; this means we can't do a unique or range scan. But we have values given (as bind variables) for the other three columns of the index, so we can do a skip scan. Oracle is choosing to do this in order to avoid accessing the base table at all, which seems sensible.
What is the primary key of MBR_IDENTFN? Is it MBR_IDN alone?
I am thinking you should have a separate index on MBR_IDENTFN with some or all of IDENTFD_TYPE, IDENTFD_NUMBER, and ENTITY_ACTIVE as the leading columns. This would allow a range or unique scan to be done instead of a skip scan.

Resources