Лучший способ удалить очень большой набор записей в Oracle

dba.stackexchange https://dba.stackexchange.com/questions/710

  •  16-10-2019
  •  | 
  •  

Вопрос

Я управляю приложением, которое имеет очень большую (почти 1 ТБ данных с более чем 500 миллионами строк в одной таблице). База данных Oracle. База данных на самом деле ничего не делает (без Sprocs, без триггеров или чего -то еще), это просто хранилище данных.

Каждый месяц мы обязаны очищать записи из двух основных таблиц. Критерии для чистки варьируются и представляют собой комбинацию рядного возраста и нескольких полей статуса. Обычно мы заканчиваем чисткой от 10 до 50 миллионов строк в месяц (мы добавляем около 3-5 миллионов строк в неделю с помощью импорта).

В настоящее время мы должны сделать это удалить партиями около 50 000 строк (т.е. удалить 50000, comit, delete 50000, Commit, повтор). Попытка удалить всю партию за один раз делает базу данных без реагирования в течение примерно часа (в зависимости от # рядов). Удаление рядов такими партиями очень грубое в системе, и мы обычно должны делать это «по мере разрешения времени» в течение недели; Разрешение сценарию непрерывно может привести к деградации производительности, которая неприемлема для пользователя.

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

Вот сценарий, который один из наших людей ИТ использует для этой чистки:

BEGIN
LOOP

delete FROM tbl_raw 
  where dist_event_date < to_date('[date]','mm/dd/yyyy') and rownum < 50000;

  exit when SQL%rowcount < 49999;

  commit;

END LOOP;

commit;

END;

Эта база данных должен Будьте на 99,99999%, и у нас есть окно обслуживания только один раз в год.

Я ищу лучший метод для удаления этих записей, но я еще не нашел их. Какие-либо предложения?

Это было полезно?

Решение

Логика с «а» и «b» может быть «скрыта» за виртуальный колонка, на которой вы могли бы сделать разделение:

alter session set nls_date_format = 'yyyy-mm-dd';
drop   table tq84_partitioned_table;

create table tq84_partitioned_table (
  status varchar2(1)          not null check (status in ('A', 'B')),
  date_a          date        not null,
  date_b          date        not null,
  date_too_old    date as
                       (  case status
                                 when 'A' then add_months(date_a, -7*12)
                                 when 'B' then            date_b
                                 end
                        ) virtual,
  data            varchar2(100) 
)
partition   by range  (date_too_old) 
( 
  partition p_before_2000_10 values less than (date '2000-10-01'),
  partition p_before_2000_11 values less than (date '2000-11-01'),
  partition p_before_2000_12 values less than (date '2000-12-01'),
  --
  partition p_before_2001_01 values less than (date '2001-01-01'),
  partition p_before_2001_02 values less than (date '2001-02-01'),
  partition p_before_2001_03 values less than (date '2001-03-01'),
  partition p_before_2001_04 values less than (date '2001-04-01'),
  partition p_before_2001_05 values less than (date '2001-05-01'),
  partition p_before_2001_06 values less than (date '2001-06-01'),
  -- and so on and so forth..
  partition p_ values less than (maxvalue)
);

insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '2008-04-14', date '2000-05-17', 
 'B and 2000-05-17 is older than 10 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('B', date '1999-09-19', date '2004-02-12', 
 'B and 2004-02-12 is younger than 10 yrs, must be kept');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2000-06-16', date '2010-01-01', 
 'A and 2000-06-16 is older than 3 yrs, must be deleted');


insert into tq84_partitioned_table (status, date_a, date_b, data) values 
('A', date '2009-06-09', date '1999-08-28', 
 'A and 2009-06-09 is younger than 3 yrs, must be kept');

select * from tq84_partitioned_table order by date_too_old;

-- drop partitions older than 10 or 3 years, respectively:

alter table tq84_partitioned_table drop partition p_before_2000_10;
alter table tq84_partitioned_table drop partition p_before_2000_11;
alter table tq84_partitioned_table drop partition p2000_12;

select * from tq84_partitioned_table order by date_too_old;

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

Классическое решение для этого - профила Ваши столы, например, к месяцу или к неделю. Если вы не сталкивались с ними раньше, разделенная таблица похожа на несколько идентично структурированных таблиц с неявным UNION При выборе, и Oracle автоматически хранит строку в соответствующем разделе при вставке его на основе критериев раздела. Вы упоминаете индексы - ну, каждый раздел также получает свои собственные разделенные индексы. Это очень дешевая операция в Oracle, чтобы отбросить раздел (она аналогична TRUNCATE С точки зрения нагрузки, потому что это то, что вы на самом деле делаете - усекаете или отбрасывая один из этих невидимых подтоков). Это будет значительное количество обработки для разделения «после факта», но нет смысла плакать из -за пролитого молока - преимущества для достижения до сих пор перевешивают расходы. Каждый месяц вы разделяете верхний раздел, чтобы создать новый раздел для данных следующего месяца (вы можете легко автоматизировать это с помощью DBMS_JOB).

А с перегородками вы также можете использовать Параллельный запрос а также Устранение раздела, что должно сделать ваших пользователей очень счастливыми ...

Одним из аспектов, который следует учитывать, является то, сколько производительности удаления является результатом индексов и сколько из таблицы RAW. Каждая запись, удаленная из таблицы, требует одинакового удаления строки из каждого индекса BTREE. Если у вас 30+ индексов BTREE, я подозреваю, что большая часть вашего времени тратится в техническом обслуживании индекса.

Это оказывает влияние на полезность разделения. Скажем, у вас есть указатель на имя. Стандартный индекс Btree, все в одном сегменте, может придется сделать четыре прыжка, чтобы добраться от корневого блока до блока листьев и пятого чтения, чтобы получить ряд. Если этот индекс разделен на 50 сегментов, и у вас нет ключа разделения как часть запроса, то необходимо будет проверить каждый из этих 50 сегментов. Каждый сегмент будет меньше, поэтому вам, возможно, придется делать только 2 прыжка, но вы все равно можете сделать 100 чтений, а не предыдущие 5.

Если они являются растровыми индексами, уравнения разные. Вы, вероятно, не используете индексы для выявления отдельных строк, а скорее их наборов. Таким образом, вместо запроса, использующего 5 iOS, чтобы вернуть одну запись, он использовал 10 000 iOS. Таким образом, дополнительные накладные расходы в дополнительных перегородках для индекса не будут иметь значения.

Удаление 50 миллионов записей в месяц партиями в размере 50 000 составляет всего 1000 итераций. Если вы делаете 1 удаление каждые 30 минут, это должно соответствовать вашим требованиям. Запланированная задача для запуска отправленного вами запроса, но удалить цикл, так что он выполняется только один раз, не должна вызывать заметную деградацию для пользователей. Мы делаем примерно такой же объем записей на нашем производственном заводе, который работает почти 24/7 и отвечает нашим потребностям. Мы на самом деле распределили его немного больше 10 000 записей каждые 10 минут, которые выполняются примерно за 1 или 2 секунды, работая на наших серверах Oracle Unix.

Если на дисковом пространстве нет премии, вы сможете создать «рабочую» копию таблицы, скажем, my_table_new, используя CTAS (создайте таблицу как Select) с критериями, которые будут пропустить записи, которые должны быть отброшены. Вы можете сделать оператор CREAT PARALLEL, а также с подсказкой Append, чтобы сделать его быстро, а затем создать все свои индексы. Затем, как только он закончится, (и протестировано), переименовать существующую таблицу на my_table_old и переименовать таблицу «Работа» на my_table. Анкет Как только вам все будет комфортно со всем drop my_table_old purge Чтобы избавиться от старого стола. Если есть куча иностранных ключевых ограничений, взгляните на dbms_redefinition PL/SQL Package. Анкет Он будет клонировать ваши индексы, противоречия и т. Д. При использовании соответствующих вариантов. Это суммирование предложения Тома Кайта из Аскатом слава. После первого запуска вы можете автоматизировать все, и таблица Create должна идти намного быстрее, и его можно сделать, пока система истекает, а время простоя применения будет ограничено менее чем минуту для переименования таблиц. Использование CTA будет намного быстрее, чем сделать несколько удаленных партий. Этот подход может быть особенно полезным, если у вас нет разделения лицензии.

Образец CTA, сохраняя строки с данными за последние 365 дней и flag_inactive = 'N':

create /*+ append */ table my_table_new 
   tablespace data as
   select /*+ parallel */ * from my_table 
       where some_date >= sysdate -365 
       and flag_inactive = 'N';

-- test out my_table_new. then if all is well:

alter table my_table rename to my_table_old;
alter table my_table_new rename to my_table;
-- test some more
drop table my_table_old purge;

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

«Обычно мы заканчиваем чисткой от 10 до 50 миллионов строк в месяц»

Я бы порекомендовал использовать пакет PL/SQL Delete, я думаю, несколько часов в порядке.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с dba.stackexchange
scroll top