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.
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.
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.