Попытка оптимизировать * случайный * запрос в Oracle SQL

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

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

Вопрос в том, что вы пытаетесь сделать? Почему вы хотите удалить случайное подмножество своих записей?

APC 02.05.2018 12:13

@APC это не мой код, я просто хочу улучшить его выполнение

Marta Lobo 02.05.2018 12:18

Оптимизация означает более эффективное получение того же результата. IP трудно сделать это, когда мы не понимаем, что должен делать исходный код.

APC 02.05.2018 12:25

@APC У меня есть таблица, и я удаляю случайные N строк. Вот что делает код. Как сделать удаление более эффективным?

Marta Lobo 02.05.2018 12:43

Если вы не знаете, чего пытаетесь достичь, мы не сможем прокомментировать, как сделать реализацию более эффективной.

symcbean 02.05.2018 13:10

Код по своей сути медленный. Если вы не можете его переписать, нет никакого способа сделать это быстрее (учитывая, что путь доступа является первичным ключом). Итак, вам нужно вернуться к человеку, который поставил вам эту задачу, и объяснить, что невозможно улучшить производительность кода, не переписав его. Базы данных - это двигатели, больше похожие на механические устройства, а не на мешок с волшебными зернами. Вот что делает их красивыми (а иногда и разочаровывающими).

APC 02.05.2018 13:10

@APC спасибо за совет

Marta Lobo 02.05.2018 13:22

@symcbean Я знаю, что мне нужно удалить из таблицы N разных строк. И, учитывая приведенный выше код, попробуйте улучшить его производительность с помощью внешних инструментов.

Marta Lobo 02.05.2018 13:25

Насколько мне известно, этот запрос ничем не поможет, если вы не можете изменить код. При выполнении ORDER BY DBMS_RANDOM.VALUE выполняется сканирование таблицы, и никакой индекс не может улучшить производительность. Я бы отказался от этого задания, это невозможно. Это что-то вроде «переделать мою кухню, но вы не можете ничего убрать или добавить, или отключить воду, или сделать пыль». :-)

Bill Karwin 02.05.2018 17:23

Единственные другие варианты: (1) УДАЛИТЬ сначала большую часть данных, чтобы оставшийся набор данных был меньше и случайная сортировка будет быстрее, (2) добавить намного больше ОЗУ на сервер и увеличить пул буферов, чтобы удерживать всю таблицу, независимо от ее размера, (3) перейти на более мощный сервер с большей производительностью ЦП и ввода-вывода.

Bill Karwin 02.05.2018 17:25
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
10
102
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вы можете сделать это с помощью одного оператора 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
                                     );

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

Спасибо за подсказку, но я не могу изменить запрос, это заданный код, который нам не разрешено трогать. Мы можем только добавить внешние улучшения, с индексами или изменить PCTFREE и PCTUSED ...

Marta Lobo 02.05.2018 12:17

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

 CREATE INDEX cuantos ON OBSERVATIONS(nplate, odatetime);

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

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

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

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

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

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

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

Я попробую, но сейчас я не понимаю, как этот индекс поможет, если этот индекс создается СУБД (nplate и odatetime являются PK НАБЛЮДЕНИЙ, извините, я этого не сказал)

Marta Lobo 02.05.2018 12:47

Чем индекс уже существует. Вы можете легко убедиться в этом, показав сценарий DDL таблицы.

Marmite Bomber 02.05.2018 17:03

Попробуй это. test на 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;

Спасибо за помощь, но я не могу изменить код, только потом внесу улучшения: S

Marta Lobo 02.05.2018 12:42

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

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

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

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

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

(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.

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

Ответ принят как подходящий

Используйте псевдоколонку 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;

Другие вопросы по теме