Index Skip ScanHow Oracle can use a composite index without the leading column, when that helps, and when it does not
Index skip scan is one of those optimizer features that is often summarized too loosely. It does not make column order unimportant, and it does not mean every predicate on a nonleading column will suddenly benefit from a composite index. What it does mean is that Oracle has an extra access path in specific cases: when the leading column is absent, the later column is selective enough, and the leading column has relatively few distinct values.
Start with the right mental model: skip scan is still one composite index
Oracle documentation describes index skip scan as a way to use a composite index even when the initial column is not specified in the predicate. The optimizer does this by treating the index as a set of logical subindexes, one for each distinct leading-key value. That is why skip scan is closely tied to the number of distinct values in the leading column.
Skip scan is not a normal leading-prefix lookup. It is a fallback access path that becomes interesting when the leading column has relatively few distinct values and the later column is selective enough to justify probing those logical slices.
When the optimizer considers a skip scan
Oracle’s SQL Tuning Guide gives two central conditions. First, the leading column of the composite index is not specified in the query predicate. Second, the leading key has relatively few distinct values while the nonleading key has many. Those two conditions explain both the appeal and the limits of skip scan.
1. Leading key missing
2. Few leading-key values
3. Many later-key values
4. Cost still wins
| Condition | Why it matters |
|---|---|
| Leading column is absent from the predicate | Without that, Oracle would normally prefer a standard leading-prefix access path instead of skip scan. |
| Few distinct values in the leading column | This limits the number of logical slices the optimizer must probe. |
| Many distinct values in the nonleading key | This makes the later-column predicate more selective and therefore more likely to justify skip scan. |
| Favorable cost estimate | Skip scan is optional. Oracle may still choose a full table scan or another index path. |
A skip scan is usually easiest to picture on an index such as (gender, email), where gender has two values and email is much more selective.
How skip scan works and why distinctness changes everything
Oracle documentation says the number of logical subindexes is determined by the number of distinct values in the leading column. That is the performance hinge. Two logical slices can be manageable. Hundreds or thousands can make skip scan unattractive very quickly.
Index (cust_gender, cust_email) with two gender values and a highly selective email predicate.
Index (region_code, account_id) where the leading column has many distinct values, multiplying the slice probes.
When skip scan helps, and when it should make you rethink the index
Skip scan is useful when it saves you from discarding a composite index entirely for a later-column predicate. It is much less useful as an excuse to ignore column order. If a query pattern on the nonleading column is common and important, that may still point to a different index design rather than hoping skip scan always carries the workload.
| Workload pattern | How skip scan fits | Design instinct |
|---|---|---|
| Occasional nonleading-column lookup on an index whose leading column has very few values | Often reasonable | Skip scan may be enough if the pattern is occasional and selective. |
| Frequent business-critical lookup on the nonleading column | Possible, but not necessarily ideal | Consider whether a dedicated index or a different column order better matches the workload. |
| Leading column has many distinct values | Usually weak | Do not assume skip scan will rescue the design. |
| Broad result set where full table scan is cheap | Often unnecessary | Oracle may rightly prefer a table scan. |
Skip scan is a useful exception, not a replacement for workload-driven index design. A composite index should still be ordered primarily for the predicates you care about most.
Good use
Borderline use
Bad assumption
How to read and test skip scans safely
The safest way to reason about skip scan is to observe real plans and compare alternatives. Oracle gives you both plan-display tools and explicit hints for skip scans, including INDEX_SS, INDEX_SS_ASC, INDEX_SS_DESC, and NO_INDEX_SS. Use those hints for controlled testing, not as a substitute for understanding why the plan changed.
1. Gather stats
2. Run the real statement
3. Inspect DBMS_XPLAN
INDEX SKIP SCAN and compare actual row-source behavior.4. Compare responsibly
BEGIN DBMS_STATS.GATHER_TABLE_STATS( ownname => USER, tabname => 'CUSTOMER_DEMO', cascade => TRUE); END; / ALTER SESSION SET statistics_level = ALL; SELECT /*+ gather_plan_statistics */ customer_id, cust_email FROM customer_demo WHERE cust_email = 'user50000@example.com'; SELECT * FROM TABLE( DBMS_XPLAN.DISPLAY_CURSOR( NULL, NULL, 'ALLSTATS LAST +PREDICATE +BYTES +COST' ) );
-- Encourage a skip scan on the composite index. SELECT /*+ INDEX_SS(c customer_demo_gdr_email_ix) */ customer_id FROM customer_demo c WHERE cust_email = :email_value; -- Exclude skip scan for the same index and compare the chosen path. SELECT /*+ NO_INDEX_SS(c customer_demo_gdr_email_ix) */ customer_id FROM customer_demo c WHERE cust_email = :email_value;
INDEX_SS_ASC and INDEX_SS_DESC exist for skip-scan variants too, but the main design question is usually whether skip scan is appropriate at all.
Design guidance: let skip scan inform decisions, not replace them
A composite index that occasionally benefits from skip scan can still be a good design. But if skip-scan-driven queries are frequent enough to matter, you should examine whether column order, overlap with other indexes, or a dedicated index on the later column would better fit the workload.
Skip scan is one more reason not to oversimplify how Oracle uses composite indexes.
Skip scan means the leading column does not matter, so any column order will do.
CREATE INDEX customer_demo_email_ix_inv ON customer_demo(cust_email) INVISIBLE; ALTER SESSION SET optimizer_use_invisible_indexes = TRUE; -- Now compare plans carefully in a controlled session.
Invisible indexes are maintained by DML but ignored by the optimizer unless you enable them explicitly. That makes them useful for comparing a dedicated alternative against a skip-scan path without changing the rest of the workload immediately.
ALTER INDEX customer_demo_gdr_email_ix MONITORING USAGE; SELECT index_name, used, monitoring, start_monitoring, end_monitoring FROM user_object_usage WHERE index_name = 'CUSTOMER_DEMO_GDR_EMAIL_IX';
Common misconceptions about skip scan
| Misconception | Better reading |
|---|---|
| “A predicate on the second column can never use the composite index.” | Oracle may consider skip scan when the leading column is absent and the distinctness pattern is favorable. |
| “If skip scan exists, column order is no longer important.” | Column order is still fundamental. Skip scan is an exception, not the main design rule. |
| “If skip scan is possible, Oracle should always prefer it.” | Skip scan is cost-based. A full table scan or another access path may still be cheaper. |
| “Few leading-key values are enough by themselves.” | The later key also needs to be selective enough to justify the probes. |
| “Hints prove the right production design.” | Hints are useful for testing, but the durable decision comes from plan evidence and workload fit. |
Predicate nuance
Cost nuance
Design nuance
Hands-on lab: observe skip scan on a realistic composite index
This lab gives you a repeatable setup for testing when a later-column predicate may trigger skip scan and how that compares with a dedicated alternative.
Step 1: create a table with a low-distinctness leading column
DROP TABLE customer_demo PURGE; CREATE TABLE customer_demo AS SELECT level AS customer_id, CASE WHEN MOD(level, 2) = 0 THEN 'M' ELSE 'F' END AS cust_gender, 'user' || level || '@example.com' AS cust_email, MOD(level, 1000) AS dept_id FROM dual CONNECT BY level <= 100000; CREATE INDEX customer_demo_gdr_email_ix ON customer_demo(cust_gender, cust_email); BEGIN DBMS_STATS.GATHER_TABLE_STATS( ownname => USER, tabname => 'CUSTOMER_DEMO', cascade => TRUE); END; /
Step 2: query on the nonleading column
ALTER SESSION SET statistics_level = ALL; SELECT /*+ gather_plan_statistics */ customer_id, cust_email FROM customer_demo WHERE cust_email = 'user50000@example.com'; SELECT * FROM TABLE( DBMS_XPLAN.DISPLAY_CURSOR( NULL, NULL, 'ALLSTATS LAST +PREDICATE +BYTES +COST' ) );
Step 3: compare against a dedicated invisible alternative
CREATE INDEX customer_demo_email_ix_inv ON customer_demo(cust_email) INVISIBLE; ALTER SESSION SET optimizer_use_invisible_indexes = TRUE; SELECT /*+ gather_plan_statistics */ customer_id, cust_email FROM customer_demo WHERE cust_email = 'user50000@example.com';
What to inspect
INDEX SKIP SCAN and how that compares with the dedicated alternative.What not to assume
Quiz: do you have the right skip-scan instinct?
Answer from documented Oracle behavior and careful design reasoning
NO_INDEX_SS.Skip scan is useful when a composite index has a missing leading key, a low-distinctness prefix, and a later predicate selective enough to justify probing logical slices. It broadens Oracle’s options, but it does not remove the need to design indexes around real workload patterns and to validate plans carefully.
No comments:
Post a Comment