Issue with Oracle bind variables not using index properly

后端 未结 4 1420
执念已碎
执念已碎 2020-12-06 14:16

In my scenario, the following query runs fast (0.5 seconds on a table with 70 million rows):

select * from Purchases
where (purchase_id = 1700656396)
         


        
4条回答
  •  南方客
    南方客 (楼主)
    2020-12-06 14:28

    Strange as it may sound, in this specific case two combined cross joins can help.
    Look at the below example.

    Sample data table:

    select * from all_tables;
    drop table Purchases;
    create table Purchases as
    select zx.object_id + (lev-1) * 100000 purchase_id, 
              object_name purchase_name,
              round( dbms_random.value( 1, 200 )) purchase_price,
              zx.* 
    from all_objects zx
    cross join (select level lev from dual connect by level <= 170);
    
    create unique index purchases_id_ix on Purchases( Purchase_id );
    
    exec dbms_stats.gather_table_stats( user, 'Purchases' );
    
    select count(*) from Purchases;
    
      COUNT(*)
    ----------
      10316620
    



    The query:

    var Purchase_id varchar2( 4000 )
    var Purchase_name varchar2( 4000 )
    var Purchase_price varchar2( 4000 )
    
    begin
      :Purchase_id := '1139';
      :Purchase_name := NULL;
      :Purchase_price := NULL;
    end;
        /
    
    explain plan for
    select p.* 
    from Purchases p
    cross join (
      select 1 from dual d
      where :Purchase_id is not null
    ) part_1
    where Purchase_id = to_number( :Purchase_id )
      and ( :Purchase_name is null or Purchase_name = :Purchase_name )
      and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) )
    union all
    select p.* 
    from Purchases p
    cross join (
      select 1 from dual d
      where :Purchase_id is null
    ) part_2
    where 
      ( :Purchase_name is null or Purchase_name = :Purchase_name )
      and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) )
    ;
    



    The explain plan:

    Plan hash value: 460094106
    
    ------------------------------------------------------------------------------------------------------
    | Id  | Operation                       | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                |                    | 28259 |  5546K| 54093   (1)| 00:10:50 |
    |   1 |  NESTED LOOPS                   |                    | 28259 |  5546K| 54093   (1)| 00:10:50 |
    |   2 |   FAST DUAL                     |                    |     1 |       |     2   (0)| 00:00:01 |
    |   3 |   VIEW                          | VW_JF_SET$96C1679A | 28259 |  5546K| 54091   (1)| 00:10:50 |
    |   4 |    UNION-ALL                    |                    |       |       |            |          |
    |*  5 |     FILTER                      |                    |       |       |            |          |
    |*  6 |      TABLE ACCESS BY INDEX ROWID| PURCHASES          |     1 |   132 |     3   (0)| 00:00:01 |
    |*  7 |       INDEX UNIQUE SCAN         | PURCHASES_ID_IX    |     1 |       |     2   (0)| 00:00:01 |
    |*  8 |     FILTER                      |                    |       |       |            |          |
    |*  9 |      TABLE ACCESS FULL          | PURCHASES          | 28258 |  3642K| 54088   (1)| 00:10:50 |
    ------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       5 - filter(:PURCHASE_ID IS NOT NULL)
       6 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND
                  (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE)))
       7 - access("P"."PURCHASE_ID"=TO_NUMBER(:PURCHASE_ID))
       8 - filter(:PURCHASE_ID IS NULL)
       9 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND
                  (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE)))
    
    27 wierszy zosta│o wybranych.
    



    Test for :Purchase_id <> NULL

    SQL> set pagesize 0
    SQL> set linesize 200
    SQL> set timing on
    SQL> set autotrace traceonly
    SQL>
    SQL> begin
      2    :Purchase_id := '163027';
      3    :Purchase_name := NULL;
      4    :Purchase_price := NULL;
      5  end;
      6  /
    
    Procedura PL/SQL zosta│a zako˝czona pomyťlnie.
    
    Ca│kowity: 00:00:00.00
    SQL> select p.*
      2  from Purchases p
      3  cross join (
      4    select 1 from dual d
      5    where :Purchase_id is not null
      6  ) part_1
      7  where Purchase_id = to_number( :Purchase_id )
      8    and ( :Purchase_name is null or Purchase_name = :Purchase_name )
      9    and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) )
     10  union all
     11  select p.*
     12  from Purchases p
     13  cross join (
     14    select 1 from dual d
     15    where :Purchase_id is null
     16  ) part_2
     17  where
     18    ( :Purchase_name is null or Purchase_name = :Purchase_name )
     19    and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) )
     20  ;
    
    Ca│kowity: 00:00:00.09
    
    Plan wykonywania
    ----------------------------------------------------------
    Plan hash value: 460094106
    
    ------------------------------------------------------------------------------------------------------
    | Id  | Operation                       | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                |                    | 28259 |  5546K| 54093   (1)| 00:10:50 |
    |   1 |  NESTED LOOPS                   |                    | 28259 |  5546K| 54093   (1)| 00:10:50 |
    |   2 |   FAST DUAL                     |                    |     1 |       |     2   (0)| 00:00:01 |
    |   3 |   VIEW                          | VW_JF_SET$96C1679A | 28259 |  5546K| 54091   (1)| 00:10:50 |
    |   4 |    UNION-ALL                    |                    |       |       |            |          |
    |*  5 |     FILTER                      |                    |       |       |            |          |
    |*  6 |      TABLE ACCESS BY INDEX ROWID| PURCHASES          |     1 |   132 |     3   (0)| 00:00:01 |
    |*  7 |       INDEX UNIQUE SCAN         | PURCHASES_ID_IX    |     1 |       |     2   (0)| 00:00:01 |
    |*  8 |     FILTER                      |                    |       |       |            |          |
    |*  9 |      TABLE ACCESS FULL          | PURCHASES          | 28258 |  3642K| 54088   (1)| 00:10:50 |
    ------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       5 - filter(:PURCHASE_ID IS NOT NULL)
       6 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND
                  (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE)))
       7 - access("P"."PURCHASE_ID"=TO_NUMBER(:PURCHASE_ID))
       8 - filter(:PURCHASE_ID IS NULL)
       9 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND
                  (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE)))
    
    
    Statystyki
    ----------------------------------------------------------
              1  recursive calls
              0  db block gets
              4  consistent gets
              2  physical reads
              0  redo size
           1865  bytes sent via SQL*Net to client
            519  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
    



    Test for :Purchase_id = NULL

    SQL> begin
      2    :Purchase_id := NULL;
      3    :Purchase_name := 'DBMS_CUBE_UTIL';
      4    :Purchase_price := NULL;
      5  end;
      6  /
    
    Procedura PL/SQL zosta│a zako˝czona pomyťlnie.
    
    Ca│kowity: 00:00:00.00
    SQL> select p.*
      2  from Purchases p
      3  cross join (
      4    select 1 from dual d
      5    where :Purchase_id is not null
      6  ) part_1
      7  where Purchase_id = to_number( :Purchase_id )
      8    and ( :Purchase_name is null or Purchase_name = :Purchase_name )
      9    and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) )
     10  union all
     11  select p.*
     12  from Purchases p
     13  cross join (
     14    select 1 from dual d
     15    where :Purchase_id is null
     16  ) part_2
     17  where
     18    ( :Purchase_name is null or Purchase_name = :Purchase_name )
     19    and ( :Purchase_price is null or purchase_price = to_number( :Purchase_price ) )
     20  ;
    
    510 wierszy zosta│o wybranych.
    
    Ca│kowity: 00:00:11.90
    
    Plan wykonywania
    ----------------------------------------------------------
    Plan hash value: 460094106
    
    ------------------------------------------------------------------------------------------------------
    | Id  | Operation                       | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT                |                    | 28259 |  5546K| 54093   (1)| 00:10:50 |
    |   1 |  NESTED LOOPS                   |                    | 28259 |  5546K| 54093   (1)| 00:10:50 |
    |   2 |   FAST DUAL                     |                    |     1 |       |     2   (0)| 00:00:01 |
    |   3 |   VIEW                          | VW_JF_SET$96C1679A | 28259 |  5546K| 54091   (1)| 00:10:50 |
    |   4 |    UNION-ALL                    |                    |       |       |            |          |
    |*  5 |     FILTER                      |                    |       |       |            |          |
    |*  6 |      TABLE ACCESS BY INDEX ROWID| PURCHASES          |     1 |   132 |     3   (0)| 00:00:01 |
    |*  7 |       INDEX UNIQUE SCAN         | PURCHASES_ID_IX    |     1 |       |     2   (0)| 00:00:01 |
    |*  8 |     FILTER                      |                    |       |       |            |          |
    |*  9 |      TABLE ACCESS FULL          | PURCHASES          | 28258 |  3642K| 54088   (1)| 00:10:50 |
    ------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       5 - filter(:PURCHASE_ID IS NOT NULL)
       6 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND
                  (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE)))
       7 - access("P"."PURCHASE_ID"=TO_NUMBER(:PURCHASE_ID))
       8 - filter(:PURCHASE_ID IS NULL)
       9 - filter((:PURCHASE_NAME IS NULL OR "P"."PURCHASE_NAME"=:PURCHASE_NAME) AND
                  (:PURCHASE_PRICE IS NULL OR "P"."PURCHASE_PRICE"=TO_NUMBER(:PURCHASE_PRICE)))
    
    
    Statystyki
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
         197993  consistent gets
          82655  physical reads
              0  redo size
          16506  bytes sent via SQL*Net to client
            882  bytes received via SQL*Net from client
             35  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
            510  rows processed
    



    To know real execution times, do not look at plans, they tell lies, contain only estimates (how oracle thinks it would be). Look at rows with "Ca│kowity", it means "Total execution time" (I don't know how to change a codepage to english in sqlplus). Also look at "consistent gets", this is a number of logical consistent blocks that the query reads.

    The first query (purchase_id <> null )

    Ca│kowity: 00:00:00.09
              4  consistent gets
              2  physical reads
    


    obviously it uses the index, the time is 90 ms


    The second query (purchase_id = null )

    Ca│kowity: 00:00:11.90
         197993  consistent gets
          82655  physical reads
    


    this query does full table scan.

提交回复
热议问题