2009-12-03 3 views
1

Редактировать: Пожалуйста, ответьте на один из двух ответов, которые я задаю. I знаю есть другие варианты, которые были бы лучше в другом случае. Эти другие потенциальные варианты (разбиение таблицы, выполнение одной крупной операции удаления без компрометации и т. Д.): NOT вариантов в моем случае из-за вещей, находящихся вне моего контроля.Вопрос оптимизации SQL (оракул)

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

table source 
    id --primary_key 
    import_source --used for choosing the ids to delete 

table t1 
    id --foreign key 
    --other fields 

table t2 
    id --foreign key 
    --different other fields 

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

declare 
my_counter integer := 0; 
begin 
for cur in (
select id from source where import_source = 'bad.txt' 
) loop 
    begin 
    delete from source where id = cur.id; 
    delete from t1 where id = cur.id; 
    delete from t2 where id = cur.id; 
    my_counter := my_counter + 1; 
    if my_counter > 500 then 
     my_counter := 0; 
     commit; 
    end if; 
    end; 
    end loop; 
    commit; 
end; 

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

declare 
type import_ids is table of integer index by pls_integer; 
my_count integer := 0; 
begin 
select id bulk collect into my_import_ids from source where import_source = 'bad.txt' 

for h in 1..my_import_ids.count 
    delete from t1 where id = my_import_ids(h); 
    --do commit check 
end loop; 
for h in 1..my_import_ids.count 
    delete from t2 where id = my_import_ids(h); 
    --do commit check 
end loop; 

--do commit check will be replaced with the same chunk to commit every 500 rows as the above query 

Так что мне нужно один из следующих ответил:

1) Какой из них лучше?

2) Как я могу узнать, что лучше для моего конкретного случая? (IE, если это зависит от того, сколько столов у меня есть, насколько они являются, и т.д.)

Edit:

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

EDIT:

ПРИМЕЧАНИЕ: Я требуется совершить в пакетах. Объем данных слишком велик, чтобы сделать это в одной партии. Таблицы отката будут разбивать нашу базу данных.

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

+1

Если вы считаете, что вам приходится прибегать к циклам из-за большого размера таблиц, вы (и/или DBA) посмотрели на использование функций разбиения на базы данных? Это один из «вариантов использования», с которым помогает разделение. Например, если у вас таблица размером 10 терабайт с миллиардом строк, проще удалить раздел (на основе идентификатора), чем перебрать миллионы строк. – JasDev

+1

Да, мы это рассмотрели. Мы разделили фрагменты нашей базы данных. Тем не менее, таблицы t1 и t2 (и т. Д.) Доступны несколькими способами (на основе полей, отличных от id). Таким образом, любое разбиение на них повредило бы повсюду. Я оставляю много деталей, которые не влияют на мой вопрос, но означает, что мы не можем разбивать t1, t2 и т. Д. –

+1

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

ответ

6

Давид, Если вы настаиваете на совершали, вы можете использовать следующий код:

declare 
    type import_ids is table of integer index by pls_integer; 
    my_import_ids import_ids; 
    cursor c is select id from source where import_source = 'bad.txt'; 
begin 
    open c; 
    loop 
    fetch c bulk collect into my_import_ids limit 500; 
    forall h in 1..my_import_ids.count 
     delete from t1 where id = my_import_ids(h); 
    forall h in 1..my_import_ids.count 
     delete from t2 where id = my_import_ids(h); 
    commit; 
    exit when c%notfound; 
    end loop; 
    close c; 
end; 

Эта программа извлекает идентификаторы кусками 500 строк, удаление и совершали каждую часть. Это должно быть намного быстрее, чем обработка строк за строкой, потому что bulk collect и forall работают как одна операция (в одном обратном направлении к базе данных и из базы данных), что сводит к минимуму количество переключателей контекста. См. Bulk Binds, Forall, Bulk Collect.

+0

С фиксацией внутри курсора интересно, лучше ли было бы ввести ORDER BY в курсорный выбор, чтобы убедиться, что все значения были прочитаны из исходной таблицы до того, как они были совершены, и мы надеемся, что также уменьшаем шансы на снимок старой ошибки. вы хотите проверить план выполнения, чтобы убедиться, что сортировка выполняется, конечно. –

+0

Дэвид Олдридж: Хм, нужно ли нам удалять строки из «исходной» таблицы? Я не обратил на это внимания. Но ЗАКАЗАТЬ, что? Мы хотели бы сортировать строки так, чтобы мы читали их блок за блоком, никогда не возвращаясь к предыдущему блоку снова, правильно? Я думаю, что FULL SCAN-доступ сделает это. –

7

Зачем вообще петля?

delete from t1 where id IN (select id from source where import_source = 'bad.txt'; 
delete from t2 where id IN (select id from source where import_source = 'bad.txt'; 
delete from source where import_source = 'bad.txt' 

Это используется стандарт SQL. Я не знаю, что конкретно Oracle, но многие СУБД также используют многоэлементные DELETE на основе JOIN, которые позволят вам сделать все это в одном заявлении.

+0

+1 и кока-кола за избиение меня на пару секунд. –

+0

Невозможно. Из-за того, насколько велики таблицы, я * должен * делать это в цикле, совершая, когда я иду. –

+0

Я не человек Oracle, поэтому я не могу оспаривать утверждение вашего DBA, но мне трудно поверить, что Oracle не предоставляет какой-либо настройки для фиксации этих DELETE по мере выполнения команды. Это орехи, что вы не можете выпустить такую ​​простую команду SQL против основной базы данных SQL. –

1

Прежде всего, в цикле вы не должны commit - он не эффективен (генерирует много повторов), и если возникает некоторая ошибка, вы не можете откатить.

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

Что-то вроде этого:

CREATE TABLE new_table as select * from old_table where <filter only remaining rows>; 

index new_table 
grant on new table 
add constraints on new_table 
etc on new_table 

drop table old_table 
rename new_table to old_table; 

Смотрите также Ask Tom

+0

I * must * commit в цикле. Это заказ от нашего администратора баз данных - если этого не произойдет, информация о откате будет слишком большой и приведет к сбою нашей системы. Я удаляю небольшое количество записей, поэтому создание нового и падение невозможно. –

+1

Завершение цикла может дать вам ошибки ORA-01555, не говоря уже о более низкой производительности. Ваш администратор базы данных должен скорее увеличить размер сегмента отката. – Majkel

+0

Невозможно. Я ожидаю, что это удаление займет несколько часов. У нас не может быть таблиц в нашей производственной базе данных, заблокированных надолго. –

1

Ларри Люстиг правильно, что вам не нужен цикл. Тем не менее, может быть некоторая польза при удалении в меньших кусках. Здесь PL/SQL сыпучие связывает может улучшить скорость значительно:

declare 
type import_ids is table of integer index by pls_integer; 
my_count integer := 0; 
begin 
select id bulk collect into my_import_ids from source where import_source = 'bad.txt' 

forall h in 1..my_import_ids.count 
    delete from t1 where id = my_import_ids(h); 
forall h in 1..my_import_ids.count 
    delete from t2 where id = my_import_ids(h); 

Как я написал, что делает все это сразу, в этом случае да единственный SQL лучше. Но вы можете изменить условия цикла, чтобы разбить его на куски. Ключевыми моментами являются

  • не фиксировать на каждой строке. Во всяком случае, зафиксируйте только все N строк.
  • При использовании кусков N, не запускайте удаление в обычном цикле. Используйте forall для запуска удаления в виде массовой привязки, что намного быстрее.

Причина, за исключением накладных расходов, заключается в том, что каждый раз, когда вы выполняете инструкцию SQL внутри кода PL/SQL, она по существу выполняет контекстный переключатель. Массовые привязки избегают этого.

+0

Ваш первый момент: да, я беру 500 или около того строк. –

+0

2-й пункт: можете ли вы предоставить более подробную информацию о том, как работает все? Как ваш запрос может быть переписан для использования forall, но по-прежнему совершать в пакетах –

0

Вы можете попробовать разделить все равно, чтобы использовать параллельное выполнение, а не просто удалить один раздел. The Oracle documentation может оказаться полезным при настройке. В этом случае каждый раздел будет использовать собственный сегмент отката.

+0

Разделение таблиц t1, t2 и т. Д. Не является вариантом из-за соображений, не входящих в мой контроль. –

+1

Ну, тогда я отвечаю следующим образом: 1. Оба решения оптимальны для ваших обстоятельств. 2. Вы можете определить, что лучше, тестируя. Сделайте оба в моделируемой среде и посмотрите, что произойдет. –

0

Если вы делаете удаление из источника до удаления t1/t2, это говорит о том, что у вас нет ограничений ссылочной целостности (так как в противном случае вы получили бы ошибки, указывающие дочерние записи).

Я хотел бы создать ограничение с помощью DELETE CASCADE. Затем прост

DECLARE 
    v_cnt NUMBER := 1; 
BEGIN 
    WHILE v_cnt > 0 LOOP 
    DELETE FROM source WHERE import_source = 'bad.txt' and rownum < 5000; 
    v_cnt := SQL%ROWCOUNT; 
    COMMIT; 
    END LOOP; 
END; 

Детские записи удаляются автоматически.

Если вы не можете иметь ON DELETE CASCADE, я бы с глобальной временной таблицы с ON COMMIT DELETE ROWS

DECLARE 
    v_cnt NUMBER := 1; 
BEGIN 
    WHILE v_cnt > 0 LOOP 
    INSERT INTO temp (id) 
    SELECT id FROM source WHERE import_source = 'bad.txt' and rownum < 5000; 
    v_cnt := SQL%ROWCOUNT; 
    DELETE FROM t1 WHERE id IN (SELECT id FROM temp); 
    DELETE FROM t2 WHERE id IN (SELECT id FROM temp); 
    DELETE FROM source WHERE id IN (SELECT id FROM temp); 
    COMMIT; 
    END LOOP; 
END; 

Я бы также пойти на самый большой кусок ваш DBA позволит. Я ожидаю, что каждая транзакция будет продолжаться как минимум на минуту. Более частые коммиты будут пустой тратой.

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

Oracle не блокирует таблицы, только строки. Я предполагаю, что никто не будет блокировать строки, которые вы удаляете (или, по крайней мере, не надолго). Таким образом, блокировка не является проблемой.

Смежные вопросы