【问题标题】:Indexing for dynamic query in OracleOracle中动态查询的索引
【发布时间】:2018-05-13 09:09:24
【问题描述】:

我有一个过程,取决于我构建动态条件的参数。如果任何参数为空,我们将忽略以在 where 条件下检查此列。谁能建议我对表进行索引以获得最佳性能的最佳方法是什么?

另一个问题是假设我有一个 12 列的表。我有两个查询,其中一个在 where 条件中有三列,另一个查询在 where 条件中有八列。在这种情况下,我应该创建两个不同的索引以获得更好的性能吗?

V_sql VARCHAR2(10000):='SELECT
    SV_ACC_REG.ACC_REG_ID            AS ACC_REG_ID           ,
    SV_ACC_REG.PRODUCT_ID            AS PRODUCT_ID           ,
    GEN_PRODUCT.FULL_NAME            AS PRODUCT_NAME         ,
    SV_ACC_REG.STATUS                AS STATUS               ,
    SV_ACC_REG.OPENING_DATE          AS OPENING_DATE         ,
    SV_ACC_REG.CURRENT_BALANCE       AS CURRENT_BALANCE      ,
    SV_ACC_REG.CLOSING_DATE          AS CLOSING_DATE         ,
    SV_ACC_REG.REG_NO                AS REG_NO               ,
    SV_ACC_REG.IS_WITHDRAW_BY_SINGLE AS IS_WITHDRAW_BY_SINGLE,
    SV_ACC_REG.IS_SINGLE             AS IS_SINGLE            ,
    SV_ACC_REG.IS_EXTENDABLE         AS IS_EXTENDABLE        ,
    SV_ACC_REG.REMARKS               AS REMARKS              ,
    SV_ACC_REG.PR_NO                 AS PR_NO                ,
    SV_ACC_REG.CREATED_ON            AS CREATED_ON           ,
    SV_ACC_REG.CREATED_BY            AS CREATED_BY           ,
    SV_ACC_REG.UPDATED_ON            AS UPDATED_ON           ,
    SV_ACC_REG.UPDATED_BY            AS UPDATED_BY           ,
    SV_ACC_REG.IS_DELETED            AS IS_DELETED           ,
    SV_ACC_REG.DELETED_ON            AS DELETED_ON           ,
    SV_ACC_REG.DELETED_BY            AS DELETED_BY           ,
    SV_ACC_REG.CLIENT_TYPE           AS CLIENT_TYPE          ,
    SV_ACC_REG.IS_TRANSFER           AS IS_TRANSFER          ,
    SV_ACC_REG.WITHDRAW_TYPE         AS WITHDRAW_TYPE        ,
    SV_ACC_REG.DEATH_DATE            AS DEATH_DATE           ,
    SV_ACC_REG.IS_MIGRATE            AS IS_MIGRATE           ,
    SV_ACC_REG.MIGRATE_COMMENTS      AS MIGRATE_COMMENTS     ,
    SV_ACC_REG.CHEQUE_HONOR_DATE     AS CHEQUE_HONOR_DATE    ,
    SV_ACC_REG.SO_NO                 AS SO_NO                ,
    SV_ACC_REG.IS_MINOR              AS IS_MINOR             ,
    SV_ACC_REG.NAME                  AS NAME                 ,
    SV_ACC_REG.IS_OLD                AS IS_OLD               ,
    SV_ACC_REG.IS_NO_PROFIT_CALC     AS IS_NO_PROFIT_CALC    ,
    SV_ACC_REG.IS_SIX_M_PROFIT_CALC  AS IS_SIX_M_PROFIT_CALC ,
    SV_ACC_REG.IS_SEND_DPMG                                  ,
    SV_CUSTOMER_INFO.CUSTOMER_NAME AS CUSTOMER_NAME
    FROM SV_ACC_REG
    LEFT JOIN GEN_PRODUCT ON SV_ACC_REG.PRODUCT_ID=GEN_PRODUCT.PRODUCT_NO
    LEFT JOIN SV_CUSTOMER_INFO ON SV_ACC_REG.ACC_REG_ID = SV_CUSTOMER_INFO.ACC_REG_ID';
V_WHERE VARCHAR2(500):=' WHERE ';
BEGIN
BEGIN

  V_WHERE:=' WHERE ';
    IF p_ACC_REG_ID IS NOT NULL THEN
      V_WHERE := V_WHERE || ' SV_ACC_REG.ACC_REG_ID = '||p_ACC_REG_ID||' AND';
   END IF; 

    IF p_PRODUCT_ID IS NOT NULL THEN
      V_WHERE := V_WHERE || ' SV_ACC_REG.PRODUCT_ID = '||p_PRODUCT_ID||' AND';
    END IF; 

    IF p_STATUS IS NOT NULL THEN
        V_WHERE := V_WHERE || ' SV_ACC_REG.STATUS = '||p_STATUS||' AND';
    END IF; 

    IF p_IS_TRANSFER IS NOT NULL THEN
        V_WHERE := V_WHERE || ' SV_ACC_REG.IS_TRANSFER = '||p_IS_TRANSFER||' AND';
    END IF; 

    IF p_SO_NO IS NOT NULL THEN
        V_WHERE := V_WHERE || ' SV_ACC_REG.SO_NO = '||p_SO_NO||' AND';
    END IF; 

    IF p_IS_OLD IS NOT NULL THEN
        V_WHERE := V_WHERE || ' SV_ACC_REG.IS_OLD = '||p_IS_OLD||' AND';
    END IF; 

    IF p_IS_SEND_DPMG IS NOT NULL THEN
        V_WHERE := V_WHERE || ' SV_ACC_REG.IS_SEND_DPMG = '||p_IS_SEND_DPMG||' AND';
    END IF; 

    IF p_IS_SIX_M_PROFIT_CALC IS NOT NULL THEN
        V_WHERE := V_WHERE || ' IS_SIX_M_PROFIT_CALC= '||p_IS_SEND_DPMG||' AND';
    END IF;  

    IF  LENGTH(' WHERE ') =7 THEN
        V_sql :=V_sql ||'  ORDER BY SV_ACC_REG.ACC_REG_ID ASC';
    ELSE
         V_sql :=V_sql || SUBSTR(V_WHERE, 1, LENGTH(V_WHERE) - 3) ||'  ORDER BY SV_ACC_REG.ACC_REG_ID ASC';
    END IF; 
    --V_sql :=SUBSTR(V_sql, 1, LENGTH(V_sql) - 3);

   --OPEN cur_OUT FOR V_sql USING p_ACC_REG_ID, p_PRODUCT_ID,p_STATUS,p_IS_TRANSFER,p_SO_NO,p_IS_OLD,p_IS_SEND_DPMG,p_IS_SIX_M_PROFIT_CALC;
    OPEN cur_OUT FOR V_sql ;
END;
END;

【问题讨论】:

  • 这里的标准方法是确定将出现哪些列组合,然后为每个组合构建连接索引,并避免每列有 1 个索引。或者,我的第一个尝试是识别“标题”列(希望只是少数),其中一个将出现在任何给定的组合中并为它们编制索引,然后通过向这些索引添加辅助列来微调这些索引。跨度>
  • WHERE 子句中列的基数是多少?我假设列 IS_TRANSFERSTATUS 的值非常少,很可能只有 2 个。
  • 这张表有多少行?
  • @APC SV_ACC_REG 表中大约有 560 万个

标签: sql oracle indexing


【解决方案1】:

任何人都可以建议我将表格索引到的最佳方法是什么 获得最佳性能?

好吧,这个查询中有 10 个可选参数,可能的组合数(null/not null)是 2^10 = 1024,所以你可以得到这个查询的 1000 个变体,每个变体可能需要不同的一组索引。在这里给出一个明智的提示是相当不可能的。

在你的情况下我会怎么做:

  1. 将应用程序部署到生产环境
  2. 允许用户使用应用程序几天/一周
  3. 登录数据库并运行以下查询(您必须具有 DBA 授予的适当权限)
  4. 挑选最有问题的查询,调整它们,然后在几天/几周/几个月后一次又一次地重复。

此查询将提取有关哪些查询最常用以及哪些查询消耗最多资源的基本统计信息。那里有许多统计信息,例如 EXECUTIONS、总 ELAPSED_TIME、BUFFER_GETS 等,它们可以让您大致了解应用程序的操作、用户行为等,并允许选择最差的查询进行进一步分析。

您可以进一步查询v$sql_plan 以获得 rdbms 使用的执行计划(使用 sql_id 和 plan_hash_value 列)以便分析它们。

select 
        SQL_TEXT
        , SQL_FULLTEXT
        , SQL_ID
        , FETCHES
        , EXECUTIONS
        , FIRST_LOAD_TIME
        , PARSE_CALLS
        , DISK_READS
        , BUFFER_GETS
        , USER_IO_WAIT_TIME
        , ROWS_PROCESSED
        , OPTIMIZER_MODE
        , OPTIMIZER_COST
        , HASH_VALUE
        , PLAN_HASH_VALUE
        , CHILD_NUMBER
        , CPU_TIME
        , ELAPSED_TIME
        , IO_INTERCONNECT_BYTES
        , PHYSICAL_READ_REQUESTS
        , PHYSICAL_READ_BYTES
    from v$sql t
    where upper(sql_text) like upper('%FROM SV_ACC_REG%LEFT JOIN GEN_PRODUCT ON SV_ACC_REG.PRODUCT_ID=GEN_PRODUCT.PRODUCT_NO%')
    order by executions desc 

【讨论】:

  • 可能的组合数不是2^10吗?
【解决方案2】:

由于 SQL 注入的漏洞,我会提出这样的解决方案

V_sql VARCHAR2(10000):='SELECT
    SV_ACC_REG.ACC_REG_ID            AS ACC_REG_ID           ,
    SV_ACC_REG.PRODUCT_ID            AS PRODUCT_ID           ,
    GEN_PRODUCT.FULL_NAME            AS PRODUCT_NAME         ,
    ...
    SV_ACC_REG.IS_OLD                AS IS_OLD               ,
    SV_ACC_REG.IS_NO_PROFIT_CALC     AS IS_NO_PROFIT_CALC    ,
    SV_ACC_REG.IS_SIX_M_PROFIT_CALC  AS IS_SIX_M_PROFIT_CALC ,
    SV_ACC_REG.IS_SEND_DPMG                                  ,
    SV_CUSTOMER_INFO.CUSTOMER_NAME AS CUSTOMER_NAME
    FROM SV_ACC_REG
    LEFT JOIN GEN_PRODUCT ON SV_ACC_REG.PRODUCT_ID=GEN_PRODUCT.PRODUCT_NO
    LEFT JOIN SV_CUSTOMER_INFO ON SV_ACC_REG.ACC_REG_ID = SV_CUSTOMER_INFO.ACC_REG_ID';

V_WHERE VARCHAR2(500);

cur INTEGER := DBMS_SQL.OPEN_CURSOR;
curRef SYS_REFCURSOR;
ret INTEGER;

BEGIN

    IF p_ACC_REG_ID IS NOT NULL THEN
       V_WHERE := V_WHERE || ' AND SV_ACC_REG.ACC_REG_ID = :p_ACC_REG_ID';
    END IF; 
    IF p_PRODUCT_ID IS NOT NULL THEN
       V_WHERE := V_WHERE || ' AND SV_ACC_REG.PRODUCT_ID = :p_PRODUCT_ID';
    END IF; 
    IF p_STATUS IS NOT NULL THEN
       V_WHERE := V_WHERE || ' AND SV_ACC_REG.STATUS = :p_STATUS';
    END IF; 
    IF p_IS_TRANSFER IS NOT NULL THEN
       V_WHERE := V_WHERE || ' AND SV_ACC_REG.IS_TRANSFER = :p_IS_TRANSFER';
    END IF; 
    IF p_SO_NO IS NOT NULL THEN
       V_WHERE := V_WHERE || ' AND SV_ACC_REG.SO_NO = :p_SO_NO';
    END IF; 
    IF p_IS_OLD IS NOT NULL THEN
       V_WHERE := V_WHERE || ' AND SV_ACC_REG.IS_OLD = :p_IS_OLD';
    END IF; 
    IF p_IS_SEND_DPMG IS NOT NULL THEN
       V_WHERE := V_WHERE || ' AND SV_ACC_REG.IS_SEND_DPMG = :p_IS_SEND_DPMG';
    END IF; 
    IF p_IS_SIX_M_PROFIT_CALC IS NOT NULL THEN
       V_WHERE := V_WHERE || ' AND IS_SIX_M_PROFIT_CALC= :p_IS_SIX_M_PROFIT_CALC';
    END IF;  

    V_WHERE := REGEXP_REPLACE(V_WHERE, '^ AND', 'WHERE');
    V_sql := V_sql || V_WHERE ||' ORDER BY SV_ACC_REG.ACC_REG_ID ASC';
    DBMS_SQL.PARSE(cur, V_sql, DBMS_SQL.NATIVE);


    IF p_ACC_REG_ID IS NOT NULL THEN
       DBMS_SQL.BIND_VARIABLE(cur, ':p_ACC_REG_ID', p_ACC_REG_ID); 
    END IF; 
    IF p_PRODUCT_ID IS NOT NULL THEN
       DBMS_SQL.BIND_VARIABLE(cur, ':p_PRODUCT_ID', p_PRODUCT_ID); 
    END IF; 
    IF p_STATUS IS NOT NULL THEN
       DBMS_SQL.BIND_VARIABLE(cur, ':p_STATUS', p_STATUS); 
    END IF; 
    IF p_IS_TRANSFER IS NOT NULL THEN
       DBMS_SQL.BIND_VARIABLE(cur, ':p_IS_TRANSFER', p_IS_TRANSFER); 
    END IF; 
    IF p_SO_NO IS NOT NULL THEN
       DBMS_SQL.BIND_VARIABLE(cur, ':p_SO_NO', p_SO_NO); 
    END IF;     
    IF p_IS_OLD IS NOT NULL THEN
       DBMS_SQL.BIND_VARIABLE(cur, ':p_IS_OLD', p_IS_OLD); 
    END IF; 
    IF p_IS_SEND_DPMG IS NOT NULL THEN
       DBMS_SQL.BIND_VARIABLE(cur, ':IS_SEND_DPMG', IS_SEND_DPMG); 
    END IF; 
    IF p_IS_SIX_M_PROFIT_CALC IS NOT NULL THEN
       DBMS_SQL.BIND_VARIABLE(cur, ':p_IS_SIX_M_PROFIT_CALC', p_IS_SIX_M_PROFIT_CALC ); 
    END IF;  

    ret := DBMS_SQL.EXECUTE(cur);
    curRef := DBMS_SQL.TO_REFCURSOR(cur);

END;

关于性能,我建议在 WHERE 条件下可能拥有的每一列上创建单独的索引,即每个索引一列。 Oracle 能够组合索引(参见示例https://jonathanlewis.wordpress.com/2010/11/26/index-join-2/),但是除非您通过INDEX_JOIN 提示强制它,否则这种情况很少见。通常,Oracle 只会采用最具选择性的索引。例如,如果SV_ACC_REG.PRODUCT_ID = 12345 的结果仅返回几行,则其他条件/索引在性能方面不再重要。

对于经常非常使用的组合,您可以考虑使用专门的综合指数。

SV_ACC_REG.STATUSSV_ACC_REG.IS_SEND_DPMGSV_ACC_REG.IS_TRANSFERSV_ACC_REG.IS_OLDIS_SIX_M_PROFIT_CALC 列的基数似乎很低,我假设它们只包含 YesNo 值或类似值。考虑对这些列使用Bitmap-Indexes。位图索引实际上旨在相互组合,这就是它们最有效的工作方式。

但是,位图索引不适用于 OLTP 应用程序,即当表数据经常更改(删除、插入、更新)时,您不应该使用它们。如果多个会话同时进行此类更改,情况会变得更糟。

功能 Index Monitoring 应该可以帮助您检测无用的索引。

【讨论】:

    【解决方案3】:

    这需要一些工作,但您可能不需要非常复杂的索引结构。

    很长一段时间以来,Oracle 一直支持索引的跳过扫描——除了全索引扫描和范围扫描。这可能非常强大,但不清楚需要哪些确切的索引。

    Here 是一篇很好的博文,它解释了什么是跳过扫描。您可能想通过先放置一些低基数列来查看它是否适用于您的数据。

    【讨论】:

      【解决方案4】:

      通过实验,我通过这种技术获得了更好的性能。请忽略 SQL 注入问题。我稍后会修复它。如果有人有更好的想法,请添加。

      IF p_REG_NO IS NOT NULL THEN
        V_WHERE := V_WHERE || ' SV_ACC_REG.REG_NO ='''||p_REG_NO||''' AND';
      END IF; 
      
      
      IF p_PRODUCT_ID IS  NULL THEN
        V_WHERE := V_WHERE || ' SV_ACC_REG.PRODUCT_ID IN(1,2,3,4,5,6) AND';
      ELSE
          V_WHERE := V_WHERE || ' SV_ACC_REG.PRODUCT_ID ='||p_PRODUCT_ID||' AND';
      END IF; 
      
      IF p_STATUS IS NULL THEN
          V_WHERE := V_WHERE || ' SV_ACC_REG.STATUS IN (0,1) AND';
      ELSE    
          V_WHERE := V_WHERE || ' SV_ACC_REG.STATUS ='||p_STATUS||' AND';
      END IF; 
      
      IF p_IS_TRANSFER IS NULL THEN
          V_WHERE := V_WHERE || ' SV_ACC_REG.IS_TRANSFER IN(0,1) AND';
      ELSE 
          V_WHERE := V_WHERE || ' SV_ACC_REG.IS_TRANSFER ='||p_IS_TRANSFER||' AND';    
      END IF; 
      
      IF p_SO_NO IS NULL THEN
          BEGIN
              --Select listagg(SO_NO,', ')  within group(order by SO_NO) INTO V_List from GEN_SO;
              V_WHERE := V_WHERE || ' SV_ACC_REG.SO_NO> 0 AND';
          END;
      ELSE 
          V_WHERE := V_WHERE || ' SV_ACC_REG.SO_NO ='||p_SO_NO||' AND';   
      END IF; 
      
      IF p_IS_OLD IS NULL THEN
          V_WHERE := V_WHERE || ' SV_ACC_REG.IS_OLD IN (0,1) AND';
      ELSE 
          V_WHERE := V_WHERE || ' SV_ACC_REG.IS_OLD ='||p_IS_OLD||' AND';
      END IF; 
      
      IF p_IS_SEND_DPMG IS NULL THEN
          V_WHERE := V_WHERE || ' SV_ACC_REG.IS_SEND_DPMG IN(0,1) AND';
      ELSE
          V_WHERE := V_WHERE || ' SV_ACC_REG.IS_SEND_DPMG ='||p_IS_SEND_DPMG||' AND';
      END IF; 
      
      IF p_IS_SIX_M_PROFIT_CALC IS NULL THEN
          V_WHERE := V_WHERE || ' SV_ACC_REG.IS_SIX_M_PROFIT_CALC IN(0,1) ';
      ELSE 
          V_WHERE := V_WHERE || ' SV_ACC_REG.IS_SIX_M_PROFIT_CALC='||p_IS_SIX_M_PROFIT_CALC||' ';
      END IF;  
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多