Попытка оптимизировать * случайный * запрос в Oracle SQL - PullRequest
0 голосов
/ 02 мая 2018

Мне нужно оптимизировать процедуру в Oracle SQL, в основном используя индексы. Это утверждение:

CREATE OR REPLACE PROCEDURE DEL_OBS(cuantos number) IS
begin
 FOR I IN (SELECT * FROM (SELECT * FROM observations ORDER BY DBMS_RANDOM.VALUE)WHERE ROWNUM<=cuantos)
LOOP
   DELETE FROM OBSERVATIONS WHERE nplate=i.nplate AND odatetime=i.odatetime; 
     END LOOP; 
end del_obs;

Мой план состоял в том, чтобы создать индекс, связанный с rownum , поскольку именно он, по-видимому, используется для удаления. Но я не знаю, будет ли это достойно. Проблема с этой процедурой заключается в том, что ее случайность вызывает много последовательных получений . Кто-нибудь может мне с этим помочь?? Спасибо:)

Примечание: я не могу изменить код, только после этого вносить улучшения

Ответы [ 5 ]

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

Используйте псевдостолбец ROWID для фильтрации столбцов:

CREATE OR REPLACE PROCEDURE DEL_OBS(
  cuantos number
)
IS
BEGIN
  DELETE FROM OBSERVATIONS
  WHERE ROWID IN (
    SELECT rid
    FROM   (
      SELECT ROWID AS rid
      FROM   observations
      ORDER BY DBMS_RANDOM.VALUE
    )
    WHERE ROWNUM < cuantos
  );
END del_obs;

Если у вас есть индекс в таблице, он может использовать индекс быстрого полного сканирования:

SQL Fiddle

Настройка схемы Oracle 11g R2 :

CREATE TABLE table_name ( id ) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 50000;

Запрос 1 : Без индекса:

DELETE FROM table_name
WHERE ROWID IN (
  SELECT rid
  FROM   (
    SELECT ROWID AS rid
    FROM   table_name
    ORDER BY DBMS_RANDOM.VALUE
  )
  WHERE ROWNUM <= 10000
)

План выполнения :

----------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Rows  | Bytes  | Cost | Time     |
----------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT               |            |     1 |     24 |  123 | 00:00:02 |
|   1 |   DELETE                       | TABLE_NAME |       |        |      |          |
|   2 |    NESTED LOOPS                |            |     1 |     24 |  123 | 00:00:02 |
|   3 |     VIEW                       | VW_NSO_1   | 10000 | 120000 |  121 | 00:00:02 |
|   4 |      SORT UNIQUE               |            |     1 | 120000 |      |          |
| * 5 |       COUNT STOPKEY            |            |       |        |      |          |
|   6 |        VIEW                    |            | 19974 | 239688 |  121 | 00:00:02 |
| * 7 |         SORT ORDER BY STOPKEY  |            | 19974 | 239688 |  121 | 00:00:02 |
|   8 |          TABLE ACCESS FULL     | TABLE_NAME | 19974 | 239688 |   25 | 00:00:01 |
|   9 |     TABLE ACCESS BY USER ROWID | TABLE_NAME |     1 |     12 |    1 | 00:00:01 |
----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 5 - filter(ROWNUM<=10000)
* 7 - filter(ROWNUM<=10000)

Запрос 2 Добавить индекс:

ALTER TABLE table_name ADD CONSTRAINT tn__id__pk PRIMARY KEY ( id )

Запрос 3 С индексом:

DELETE FROM table_name
WHERE ROWID IN (
  SELECT rid
  FROM   (
    SELECT ROWID AS rid
    FROM   table_name
    ORDER BY DBMS_RANDOM.VALUE
  )
  WHERE ROWNUM <= 10000
)

План выполнения :

---------------------------------------------------------------------------------------
| Id  | Operation                      | Name       | Rows | Bytes  | Cost | Time     |
---------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT               |            |    1 |     37 |   13 | 00:00:01 |
|   1 |   DELETE                       | TABLE_NAME |      |        |      |          |
|   2 |    NESTED LOOPS                |            |    1 |     37 |   13 | 00:00:01 |
|   3 |     VIEW                       | VW_NSO_1   | 9968 | 119616 |   11 | 00:00:01 |
|   4 |      SORT UNIQUE               |            |    1 | 119616 |      |          |
| * 5 |       COUNT STOPKEY            |            |      |        |      |          |
|   6 |        VIEW                    |            | 9968 | 119616 |   11 | 00:00:01 |
| * 7 |         SORT ORDER BY STOPKEY  |            | 9968 | 119616 |   11 | 00:00:01 |
|   8 |          INDEX FAST FULL SCAN  | TN__ID__PK | 9968 | 119616 |    9 | 00:00:01 |
|   9 |     TABLE ACCESS BY USER ROWID | TABLE_NAME |    1 |     25 |    1 | 00:00:01 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 5 - filter(ROWNUM<=10000)
* 7 - filter(ROWNUM<=10000)

Если вы не можете сделать это в одном операторе SQL, используя ROWID, тогда вы можете переписать существующую процедуру, чтобы использовать точно такие же запросы, но используйте оператор FORALL:

CREATE OR REPLACE PROCEDURE DEL_OBS(cuantos number)
IS
  TYPE obs_tab IS TABLE OF observations%ROWTYPE;
begin
  SELECT *
  BULK COLLECT INTO obs_tab
  FROM   (
    SELECT * FROM observations ORDER BY DBMS_RANDOM.VALUE
  )
  WHERE ROWNUM<=cuantos;

  FORALL i IN 1 .. obs_tab.COUNT
    DELETE FROM OBSERVATIONS
    WHERE  nplate    = obs_tab(i).nplate
    AND    odatetime = obs_tab(i).odatetime; 
END del_obs;
0 голосов
/ 02 мая 2018

Поскольку вы говорите, что nplate и odatetime являются первичными ключами observations, то я предполагаю, что проблема здесь:

SELECT * FROM (
  SELECT * 
  FROM observations 
  ORDER BY DBMS_RANDOM.VALUE)
  WHERE ROWNUM<=cuantos;

Нет способа предотвратить полное сканирование observations, плюс много сортировки, если это большая таблица.

Вам нужно изменить код, который работает. По far самый простой способ изменить код - это изменить исходный код и перекомпилировать его.

Однако, есть способы изменить код, который выполняется без изменения исходного кода. Вот два:

(1) Используйте DBMS_FGAC, чтобы добавить политику, которая определяет, участвуете ли вы в этой процедуре, и, если это так, добавьте предикат в таблицу observations, например:

AND rowid IN 
   ( SELECT obs_sample.rowid 
     FROM observations sample (0.05) obs_sample)

(2) Используйте DBMS_ADVANCED_REWRITE, чтобы переписать ваш запрос, изменив:

FROM observations

.. до ..

FROM observations SAMPLE (0.05)

Использование текста вашего запроса в политике перезаписи должно предотвратить его влияние на другие запросы к таблице observations.

Ничего из этого не легко (вообще), но стоит попробовать, если вы действительно застряли.

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

Вам определенно нужен индекс на OBSERVATIONS, чтобы разрешить DELETE с доступом к индексу.

 CREATE INDEX cuantos ON OBSERVATIONS(nplate, odatetime);

Выполнение процедуры приведет к одной FULL TABLE SCAN таблице OBSERVATIONS и одному INDEX ACCESS для каждой удаленной записи .

Для ограниченного числа удаленных записей это будет вести себя так же, как набор DELETE, предложенный в другом ответе; для большего количества удаленных записей прошедшее время будет линейно масштабироваться с количеством удалений.

Для нетривиального числа удаленных записей вы должны предположить, что индекс не полностью находится в пуле буферов и потребуется много доступа к диску. Таким образом, вы получите примерно 100 удаленных строк в секунду .

Другими словами , чтобы удалить 100К строк, потребуется ок. 1/4 часа .

Чтобы удалить 1M строк, вам нужно 2 3/4 часа .

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

Такое поведение также называется правилом: «Ряд за строкой медленнее за медленным» (т.е. обработка в цикле работает нормально, но только с ограниченным количеством записей).

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

Попробуй это. Тест на MSSQL надеется, так что он будет работать и на Oracle. пожалуйста, отметьте статус.

CREATE OR REPLACE PROCEDURE DEL_OBS(cuantos number) IS
begin

   DELETE OBSERVATIONS  FROM OBSERVATIONS 
   join (select * from  OBSERVATIONS ORDER BY VALUE ) as i on
            nplate=i.nplate AND 
            odatetime=i.odatetime  AND 
            i.ROWNUM<=cuantos;
End DEL_OBS;
0 голосов
/ 02 мая 2018

Вы можете сделать это, используя один оператор delete:

delete from observations o
    where (o.nplate, o.odatetime) in (select nplace, odatetime
                                      from (select o2.nplate, o2.odatetime
                                            from observations o2
                                            order by DBMS_RANDOM.VALUE
                                           ) o2
                                      where rownum <= v_cuantos
                                     );

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

...