正确归档数据而不会丢失更新"

时间:2018-05-21 07:49:26

标签: sql oracle

如何在没有"丢失更新"?

的情况下正确归档数据

这是我想要做的:

INSERT INTO
SELECT FOR UPDATE

DELETE SELETED rows.

FOR UPDATE

不支持语法INSERT INTO ... SELECT...

可以在PL / SQL上使用没有Cursor的SQL来解决问题吗?

实施例

create table authority(id number, key varchar2(128));
create table authority_arch(id number, key varchar2(128));

insert into authority(1, 'random_key1');
insert into authority(1, 'random_key2');
insert into authority(1, 'random_key3');
insert into authority(2, 'random_key4');
insert into authority(2, 'random_key5');

commit;

1次会议

insert into authority_arch
select * from authority where id=2;

-- in this moment 2 session make insert! 'Lose rows' in next delete

delete from authority where id=2;

2次会议

insert into authority(2, 'random_key6', sysdate+1);
commit;

结果有:

select * from authority

id  |  key
-----------
1   |   random_key1
1   |   random_key2
1   |   random_key3

但我想删除仅选择的行

id  |  key
-----------
1   |   random_key1
1   |   random_key2
1   |   random_key3
2   |   random_key6

作为我使用的解决方案:

for rec in (select rowid as rid, a.* from authority a where id=2 FOR UPDATE nowait) loop
    insert into authority_arch values(rec.id, rec.key);
    delete from authority where rowid=rec.rid;
end loop;

3 个答案:

答案 0 :(得分:2)

在Oracle 12c及更高版本中,您可以使用 In-Database Archiving 来实现此目的。 在表上启用数据库内归档会导致添加系统生成的隐藏列ORA_ARCHIVE_STATE

它使用"标记为删除"的概念,因此数据仍然存在于表中,但对应用程序不可见。

首先,为您的表启用数据库内存档。

ALTER TABLE yourtable ROW ARCHIVAL;

因此,为表创建了一个名为ORA_ARCHIVE_STATE的隐藏列,您可以使用user_tab_cols

进行检查

现在,让你的行对其他应用程序/会话不可见,

UPDATE yourtable
SET    ORA_ARCHIVE_STATE = '1'
WHERE  id  BETWEEN 1 AND 10000;
COMMIT;

现在,您可以随时删除这些行。

参考In-Database Archiving in Oracle Database 12c Release 1 (12.1)

答案 1 :(得分:2)

for update条款对您没有帮助。这只会锁定您查询的行,阻止其他人更新/删除它们。添加的任何新行都没有锁定!因此删除将始终处理新行。

为了克服这个问题,你可以采取几种基本方法:

  • 插入&删除特定时间点存在的行
  • 保存要归档的所有行的列表。然后使用此列表插入+删除,而不是表本身。

您可以使用闪回查询执行第一个操作。使用dbms_flashback.get_system_change_number获取数据库的SCN。然后使用“as scn”来获取此时存在的行:

declare
  insert_time pls_integer;
begin
  insert_time := dbms_flashback.get_system_change_number;
  insert into authority_arch
    select * from authority as of scn insert_time
    where  id = 2;

  dbms_lock.sleep(10); -- wait to allow insert in session 2

  delete authority
  where  ( id, key ) in (
    select id, key from authority as of scn insert_time
    where  id = 2 
  );
end;
/

您可以将临时表用于第二种方法。或者您可以使用批量收集将它们提取到数组中。并插入+删除:

declare
  type auth_rec is table of authority%rowtype index by binary_integer;
  arch_recs auth_rec ;
begin

  select * 
  bulk collect into arch_recs
  from   authority
  where  id = 2;

  forall i in arch_recs.first .. arch_recs.last
    insert into authority_arch values arch_recs(i);

  dbms_lock.sleep(10); -- wait to allow insert in session 2

  forall i in arch_recs.first .. arch_recs.last
    delete authority
    where  id = arch_recs(i).id
    and    key = arch_recs(i).key;

end;
/

答案 2 :(得分:1)

您应该可以执行以下操作:

create table temp_keys as (select pk from yourtable where condition);
select pk from yourtable where pk in (select pk from temp_keys) for update;
insert into archivetable (columnlist)
    select columnlist from yourtable
        where pk in (select pk from temp_keys);
commit;
drop table temp_keys;

你也可以为temp_keys使用临时表,然后你不必每次都丢弃它。

编辑:使用您添加的新信息,您可以跳过选择更新。只需跟踪您为以下删除复制的ID。使用ids创建一个(可能是临时的)表,执行插入操作,执行删除操作。