Oracle Перенос данных [изменение данных] - Настройка данных - PullRequest
1 голос
/ 05 мая 2020

Я столкнулся с миграцией данных, моя цель - обновить 2,5 млн строк менее чем за 8 часов, потому что у клиента есть ограниченное время, в течение которого услугу можно отключить. Более того, таблица не может быть заблокирована во время этого выполнения, потому что она используется другими процедурами, я могу заблокировать только запись. Выполнение будет осуществляться в пакетном режиме. Вероятно, в этом случае миграция - неправильное слово, лучше сказать «изменение данных» ...

Система: Oracle 11g

Информация о таблице

Имя таблицы: Tab1 Всего строк: 520,000,000 Средняя длина строки: 57

DESC Tab1;
Name             Null?    Type
---------------- -------- -----------
t_id             NOT NULL NUMBER
t_fk1_id                  NUMBER
t_fk2_id                  NUMBER
t_start_date     NOT NULL DATE
t_end_date                DATE
t_del_flag       NOT NULL NUMBER(1)
t_flag1          NOT NULL NUMBER(1)
f_falg2          NOT NULL NUMBER(1)
t_creation_date           DATE
t_creation_user           NUMBER(10)
t_last_update             DATE
t_user_update             NUMBER(10)
t_flag3                   NUMBER(1)

Индексы:

  1. T_ID_PK [t_id] UNIQUE
  2. T_IN_1 [t_fk2_id,t_fk1_id,t_start_date,t_del_flag] NONUNIQUE
  3. T_IN_2 [t_last_update,t_fk2_id] NONUNIQUE
  4. T_IN_3 [t_fk2_id,t_fk1_id] NONUNIQUE

В настоящее время я подумал о некоторых возможных решениях, и большую часть из них я уже тестировал :

  1. Вставить + удалить: выбрать существующие данные, вставить новую запись с необходимыми изменениями и удалить старую [этот результат как самый медленный метод ~ 21 час]
  2. Слияние: используйте команда merge для обновления существующих данных [этот результат как самый быстрый метод ~ 16 часов]
  3. Обновление: обновить существующие данные [~ 18 часов]

С указанным выше решением я столкнулся некоторые проблемы, такие как: при выполнении с параметром / * + parallel (x) / таблица была заблокирована, / + RESULT_CACHE * /, похоже, не влияет на выбор вообще время. Моя последняя идея - разделить таблицу на новый столбец и использовать его, чтобы избежать блокировки таблицы и перейти к решению 1.


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

DECLARE
v_recordset NUMBER;
v_row_count NUMBER;
v_start_subset NUMBER;
v_tot_loops NUMBER;
BEGIN
--set the values manually for example purpose, I've use the same values
v_recordset := 10000;
v_tot_loops := 10000;
  BEGIN
    SELECT NVL(MIN(MOD(m_id,v_recordset)), 99999)
    INTO v_start_subset 
    FROM MIGRATION_TABLE
    WHERE m_status = 0; -- 0=not migrated , 1=migrated
  END;
  FOR v_n_subset IN v_start_subset..v_tot_loops
    LOOP
      BEGIN
        MERGE INTO Tab1 T1
        USING (
          SELECT m.m_new_id, c2.c_id, t.t_id
          FROM MIGRATION_TABLE m
            JOIN Tab1 t ON t.t_fk_id = m.m_old_id
            JOIN ChildTable c ON c.c_id = t.t_fk2_id
            JOIN ChildTable c2 ON c.c_name = c2.c_name --c_name is an UNIQUE index of ChildTable
          WHERE MOD(m.m_id,v_recordset) = v_n_subset 
            AND c.c_fk_id = old_product_id --value obtained from another subsystem
            AND c2.c_fk_id = new_product_id --value obtained from another subsystem
            AND t.t_del_flag = 0 --not deleted items
        ) T2
        ON (T1.t_id = T2.t_id)
        WHEN MATCHED THEN
          UPDATE T1.t_fk_id = T2.m_new_id, T1.t_fk2_id = T2.c_id, T1.t_last_update = trunc(sysdate)
        ;
        --Update the record as migrated and proceed
        COMMIT;
      EXCEPTION WHEN OTHERS THEN
        ROLLBACK;
      END;
  END LOOP;
END;

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

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


MIGRATION_TABLE

CREATE TABLE MIGRATION_TABLE(
 m_customer_from VARCHAR2(5 BYTE),
 m_customer_to VARCHAR2(5 BYTE),
 m_old_id NUMBER(10,0) NOT NULL,
 m_new_id NUMBER(10,0) NOT NULL,
 m_status VARCHAR2(100 BYTE),
 CONSTRAINT M_MIG_PK_1
 (
  m_old_id 
 )
 ENABLE
)
CREATE UNIQUE INDEX M_MIG_PK_1 ON MIGRATION_TABLE (m_old_id ASC)

ChildTable

CREATE TABLE ChildTable(
 c_id NUMBER(10, 0) NOTE NULL,
 c_fk_id NUMBER(10, 0),
 c_name VARCHAR2(100 BYTE),
 c_date DATE,
 c_note VARCHAR2(100 BYTE),
 CONSTRAINT C_CT_PK_1
 (
  c_id
 )
 ENABLE
)
CREATE UNIQUE INDEX C_CT_PK_1 ON ChildTable (c_id ASC)
CREATE UNIQUE INDEX C_CT_PK_2 ON ChildTable (c_name ASC, c_fk_id ASC)

Ответы [ 3 ]

1 голос
/ 06 мая 2020

Метод 2 аналогичен методу 1, но в нем используются ROWID вместо первичного ключа. Теоретически это должно быть поэтому немного быстрее.

CREATE TABLE migration_temp NOLOGGING AS
SELECT t.t_id, 
       t.rowid    AS rid,
       m.m_new_id AS new_fk1_id, 
       c2.c_id    AS new_fk2_id 
  FROM MIGRATION_TABLE m
  JOIN Tab1 t        ON t.t_fk1_id = m.m_old_id
  JOIN ChildTable c1 ON c1.c_id = t.t_fk2_id
  JOIN ChildTable c2 ON c1.c_name = c2.c_name
 WHERE t.t_del_flag = 0
 ORDER BY t.rowid;
EXEC DBMS_STATS.GATHER_TABLE_STATS(null,'migration_temp');

MERGE INTO Tab1 t USING migration_temp m ON (t.rowid = m.rid)
 WHEN MATCHED THEN UPDATE SET 
      t.t_fk1_id = m.new_fk1_id,
      t.t_fk2_id = m.new_fk2_id,
      t.t_last_update = trunc(sysdate);

Вы можете подумать о пакетном MERGE на основе блоков ROWID. Они, как правило, логически размещены, поэтому это должно быть немного быстрее.

0 голосов
/ 07 мая 2020

Если методы 1 и 2 все еще слишком медленные, вы можете следовать своей идее разбиения. Например, введите столбец, чтобы различать guish строки, которые необходимо перенести. Из-за DEFAULT ... NOT NULL это будет очень быстро:

ALTER TABLE Tab1 ADD (todo NUMBER DEFAULT 0 NOT NULL);

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

ALTER TABLE Tab1 MODIFY 
  PARTITION BY LIST (todo) (
  PARTITION pdonttouch VALUES (0),
  PARTITION pmigration VALUES (1) 
) ONLINE UPDATE INDEXES (
  T_ID_PK GLOBAL, T_IN_1  GLOBAL,
  T_IN_2  GLOBAL, T_IN_3  GLOBAL
);

Теперь вы можете определить строки, которые нужно переместить. Это можно делать построчно, это не влияет на другие процессы и не учитывается при простоях. Строки миграции переместятся из раздела pdonttouch в раздел pmigration, поэтому вам необходимо включить перемещение строк.

ALTER TABLE Tab1 ENABLE ROW MOVEMENT;
UPDATE Tab1 SET todo=1 WHERE .... JOIN ...;

Теперь вы можете работать с разделом PMIGRATION и обновлять данные там. Это должно быть намного быстрее, чем в исходной таблице, так как размер раздела составляет всего 0,5% от всей таблицы. Однако не знаю об индексах.

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

0 голосов
/ 06 мая 2020

Ух ты, 520 миллионов строк! Однако обновление 2,5 миллиона из них составляет лишь 0,5%, что должно быть выполнимо. Не зная ваших данных, мое первое предположение состоит в том, что большую часть времени занимает самостоятельное соединение Tab1 x Tab1 внутри MERGE. Возможно, также многие присоединяются к миграции- и child_tables. И индексы T_IN_1, 2 и 3 также нуждаются в обслуживании.

Как вы говорите, строки, которые нужно обновить, исправлены, я бы попытался подготовить тяжелую работу. Это не блокирует таблицу и не учитывается при простое:

CREATE TABLE migration_temp NOLOGGING AS
SELECT t.t_id, 
       m.m_new_id AS new_fk1_id, 
       c2.c_id    AS new_fk2_id 
  FROM MIGRATION_TABLE m
  JOIN Tab1 t        ON t.t_fk1_id = m.m_old_id
  JOIN ChildTable c1 ON c1.c_id = t.t_fk2_id
  JOIN ChildTable c2 ON c1.c_name = c2.c_name
 WHERE t.t_del_flag = 0;

Я пропустил бит со старым / новым product_ids, потому что я не полностью понимал, как он должен работать, но это Надеюсь, это не проблема.

Метод 1 - это соединение через первичные ключи:

ALTER TABLE migration_temp ADD CONSTRAINT pk_migration_temp PRIMARY KEY(t_id);
EXEC DBMS_STATS.GATHER_TABLE_STATS(null,'migration_temp');

MERGE INTO Tab1 t USING migration_temp m ON (t.t_id = m.t_id)
 WHEN MATCHED THEN UPDATE SET 
      t.t_fk1_id = m.new_fk1_id,
      t.t_fk2_id = m.new_fk2_id,
      t.t_last_update = trunc(sysdate);

Я не поклонник пакетных обновлений. По вашим оценкам времени, похоже, у вас есть тестовая система. Я бы посоветовал поставить ему go и попробовать за один раз.

...