Индексирование для динамического запроса в Oracle - PullRequest
0 голосов
/ 13 мая 2018

У меня есть процедура, где в зависимости от параметров я строю динамическое, где условие.Если какой-либо параметр имеет значение null, мы игнорируем проверку этого столбца в условии условия.Может кто-нибудь предложить мне, как лучше индексировать таблицу, чтобы получить лучшую производительность?

Другой вопрос: предположим, у меня есть таблица с 12 столбцами.У меня есть два запроса, один из которых имеет три столбца в условии условия, а другой запрос имеет восемь столбцов в условии условия.В таком случае я должен создать два разных индекса для лучшей производительности?

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;

Ответы [ 4 ]

0 голосов
/ 15 мая 2018

Экспериментируя, я получаю лучшую производительность с помощью этой техники. пожалуйста, не обращайте внимания на проблему внедрения 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;  
0 голосов
/ 13 мая 2018

Это займет некоторую работу, но вам может не понадобиться ужасно сложная структура индекса.

В течение достаточно долгого времени Oracle поддерживала пропуски сканирования индекса - в дополнение к полному сканированию индекса и сканированию диапазона. Это может быть довольно мощным, но неясно, какие именно индексы понадобятся.

Здесь - хорошее сообщение в блоге, объясняющее, что такое сканирование с пропуском. Возможно, вы захотите узнать, работает ли он с вашими данными, поставив сначала несколько столбцов с низким количеством элементов.

0 голосов
/ 13 мая 2018

Из-за уязвимости 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.STATUS, SV_ACC_REG.IS_SEND_DPMG, SV_ACC_REG.IS_TRANSFER, SV_ACC_REG.IS_OLD, IS_SIX_M_PROFIT_CALC, кажется, имеют очень низкую мощность, я предполагаю, что они просто содержат значения Yes и No или аналогичные. Попробуйте использовать Bitmap-Indexes для этих столбцов. Битовые индексы на самом деле предназначены для объединения друг с другом, поэтому они работают наиболее эффективно.

Однако растровые индексы не подходят для приложений OLTP, т. Е. Их не следует использовать, когда часто изменяются данные таблицы (DELETE, INSERT, UPDATE). Становится еще хуже, если такие изменения выполняются несколькими сеансами одновременно.

Функция Контроль индекса должен помочь вам обнаружить бесполезные индексы.

0 голосов
/ 13 мая 2018

Может кто-нибудь предложить мне, каков наилучший способ индексации таблицы получить лучшую производительность?

Что ж, в этом запросе 10 необязательных параметров, количество возможных комбинаций (ноль / не ноль) равно 2 ^ 10 = 1024, поэтому вы можете получить 1 тысячу вариантов этого запроса, каждый из которых может требовать разные набор индексов. Здесь невозможно дать разумный намек.

Что бы я сделал в вашей ситуации:

  1. Развертывание приложения на производстве
  2. Разрешить пользователям использовать приложение в течение нескольких дней / недели
  3. Войдите в базу данных и выполните приведенный ниже запрос (у вас должны быть соответствующие разрешения, предоставленные вашим администратором базы данных)
  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 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...