Oracle数据迁移【数据修改】——数据调优
Oracle Data Migration [data modification] - Data Tuning
我面临数据迁移,我的目标是在 8 小时内更新 250 万行,这是因为客户可以停用服务的时间有限 window。此外, table 在此执行期间无法锁定,因为被其他程序使用,我只能锁定记录。执行将通过批处理完成。
可能在这种情况下迁移不是正确的词,最好说 "altering data"...
系统:Oracle 11g
Table 信息
Table 名称: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)
索引是:
T_ID_PK [t_id] UNIQUE
T_IN_1 [t_fk2_id,t_fk1_id,t_start_date,t_del_flag] NONUNIQUE
T_IN_2 [t_last_update,t_fk2_id] NONUNIQUE
T_IN_3 [t_fk2_id,t_fk1_id] NONUNIQUE
目前我想到了一些可能的解决方案,其中大部分我已经测试过:
- 插入+删除:选择现有数据,插入需要修改的新记录,删除旧记录[此结果为最慢的方法~21h]
- 合并:使用合并命令更新现有数据[此结果为最快的方法~16h]
- 更新:更新现有数据[~18h]
通过上述解决方案,我遇到了一些问题,例如:如果使用 /*+ parallel(x) / 选项执行,table 被锁定,/ + RESULT_CACHE */ 似乎完全不影响选择时间。
我的最后一个想法是将 table 分区为一个新列并使用它来避免 table 锁定并继续解决方案 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)
孩子Table
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)
哇,5.2 亿行!但是,更新其中的250万只只是0.5%,应该是可以做到的。不知道你的数据,我的第一个假设是 MERGE 内部 Tab1 x Tab1 的自连接占用了大部分时间。可能还有许多加入迁移和 child_tables。索引T_IN_1、2、3也需要维护
正如您所说,要更新的行是固定的,我会尽量准备繁重的工作。这不会锁定 table 并且不计入停机时间:
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;
我省略了 old/new 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);
我不喜欢批量更新。由于您有时间估算,因此看起来您有一个测试系统。我建议试一试,一批试试。
方法2与方法1类似,但它使用ROWIDs而不是主键。理论上应该会快一点。
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);
您可以考虑基于 ROWID 块对 MERGE 进行批处理。那些在逻辑上往往是并置的,因此它应该更快一些。
如果方法一和方法二还是太慢,可以按你的分区思路来。比如引入一个列来区分需要迁移的行。因为 DEFAULT ... NOT NULL
这会非常快:
ALTER TABLE Tab1 ADD (todo NUMBER DEFAULT 0 NOT NULL);
现在将您的 table 分成两个部分,一个包含迁移数据,一个包含您不会接触的其余部分。在应用程序运行时引入分区我没有太多经验,但我认为它是可以解决的,例如在线重定义或
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
上工作并更新那里的数据。这应该比原来的 table 快 很多 ,因为分区的大小仅为整个 table 的 0.5%。不过不知道索引。
理论上,您可以创建一个具有与 PMIGRATION
相同结构和数据的 table,在 table 上工作,完成后,交换分区和工作 table 和 EXCHANGE PARTITION
。再次不知道索引。
我面临数据迁移,我的目标是在 8 小时内更新 250 万行,这是因为客户可以停用服务的时间有限 window。此外, table 在此执行期间无法锁定,因为被其他程序使用,我只能锁定记录。执行将通过批处理完成。 可能在这种情况下迁移不是正确的词,最好说 "altering data"...
系统:Oracle 11g
Table 信息
Table 名称: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)
索引是:
T_ID_PK [t_id] UNIQUE
T_IN_1 [t_fk2_id,t_fk1_id,t_start_date,t_del_flag] NONUNIQUE
T_IN_2 [t_last_update,t_fk2_id] NONUNIQUE
T_IN_3 [t_fk2_id,t_fk1_id] NONUNIQUE
目前我想到了一些可能的解决方案,其中大部分我已经测试过:
- 插入+删除:选择现有数据,插入需要修改的新记录,删除旧记录[此结果为最慢的方法~21h]
- 合并:使用合并命令更新现有数据[此结果为最快的方法~16h]
- 更新:更新现有数据[~18h]
通过上述解决方案,我遇到了一些问题,例如:如果使用 /*+ parallel(x) / 选项执行,table 被锁定,/ + RESULT_CACHE */ 似乎完全不影响选择时间。 我的最后一个想法是将 table 分区为一个新列并使用它来避免 table 锁定并继续解决方案 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)
孩子Table
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)
哇,5.2 亿行!但是,更新其中的250万只只是0.5%,应该是可以做到的。不知道你的数据,我的第一个假设是 MERGE 内部 Tab1 x Tab1 的自连接占用了大部分时间。可能还有许多加入迁移和 child_tables。索引T_IN_1、2、3也需要维护
正如您所说,要更新的行是固定的,我会尽量准备繁重的工作。这不会锁定 table 并且不计入停机时间:
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;
我省略了 old/new 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);
我不喜欢批量更新。由于您有时间估算,因此看起来您有一个测试系统。我建议试一试,一批试试。
方法2与方法1类似,但它使用ROWIDs而不是主键。理论上应该会快一点。
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);
您可以考虑基于 ROWID 块对 MERGE 进行批处理。那些在逻辑上往往是并置的,因此它应该更快一些。
如果方法一和方法二还是太慢,可以按你的分区思路来。比如引入一个列来区分需要迁移的行。因为 DEFAULT ... NOT NULL
这会非常快:
ALTER TABLE Tab1 ADD (todo NUMBER DEFAULT 0 NOT NULL);
现在将您的 table 分成两个部分,一个包含迁移数据,一个包含您不会接触的其余部分。在应用程序运行时引入分区我没有太多经验,但我认为它是可以解决的,例如在线重定义或
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
上工作并更新那里的数据。这应该比原来的 table 快 很多 ,因为分区的大小仅为整个 table 的 0.5%。不过不知道索引。
理论上,您可以创建一个具有与 PMIGRATION
相同结构和数据的 table,在 table 上工作,完成后,交换分区和工作 table 和 EXCHANGE PARTITION
。再次不知道索引。