In-database archiving is a neat, new 12c-functionality that lets you logically “delete” rows by changing a flag in a column: A “soft-delete”. How does this functionality work in relation to foreign keys?
If your’e unfamiliar with in-database archiving, you might check out the 12.1 documentation and/or Tim Hall’s blog on the subject.
I thought that I had to check out how in-database archiving behaves when you have foreign keys enabled in the system. My expectation is that fk’s won’t be respected since the rows are still there, but I wondered if the bug if encountered around ANSI re-architecture might indicate something fancy rewrite-logic in the background.
So here’s my little test case that shows that in-database archiving does not respect foreign keys. I.e. you might end up with a dataset that’s not consistent even though you might have foreign keys declared as “on delete cascade”. I also found that the outer join bug extends to full outer joins and switching off ANSI re-architecture doesn’t work as a workaround.
First I create a couple of tables with a foreign key between them, and then enable row archival.
SQL> SELECT banner FROM V$VERSION; BANNER -------------------------------------------------------------------------------- Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production PL/SQL Release 12.2.0.1.0 - Production CORE 12.2.0.1.0 Production TNS for Linux: Version 12.2.0.1.0 - Production NLSRTL Version 12.2.0.1.0 - Production SQL> CREATE TABLE t1 (id PRIMARY KEY) 2 AS 3 SELECT rownum 4 FROM dual 5 CONNECT BY level <= 5; Table created. SQL> CREATE TABLE t2 2 ( id PRIMARY KEY, 3 fk_id) 4 AS 5 SELECT rownum, rownum 6 FROM dual 7 CONNECT BY level <= 3; Table created. SQL> ALTER TABLE t2 ADD CONSTRAINT t2_t1_fk 2 FOREIGN KEY (fk_id) REFERENCES t1 (id) 3 ENABLE VALIDATE; Table altered. SQL> SELECT * FROM t1; ID ---------- 1 2 3 4 5 SQL> SELECT * FROM t2; ID FK_ID ---------- ---------- 1 1 2 2 3 3 SQL> DELETE t1 WHERE id = 3; DELETE t1 WHERE id = 3 * ERROR at line 1: ORA-02292: integrity constraint (VEI.T2_T1_FK) violated - child record found
The foreign key prevents us from deleting a row in the master table when there’s a child-record present.
Next we enable row archival and see what happens:
SQL> ALTER TABLE t1 ROW ARCHIVAL; Table altered. SQL> ALTER TABLE t2 ROW ARCHIVAL; Table altered. SQL> UPDATE t1 SET ora_archive_state = 'Deleted' WHERE id = 3; 1 row updated. SQL> COMMIT; Commit complete.
No errors, so that means that we now have a dataset that is inconsistent in respect to what you would expect from the enabled foreign key, since we have a child record pointing to a master record that is “deleted”:
A full outer join makes no sense due to the mentioned bug, the query returns 6 rows instead of 5:
SQL> SELECT /*full*/ t1.id T1_id, t2.id T2_id, t2.fk_id 2 FROM t1 3 FULL OUTER JOIN t2 ON t1.id =t2.fk_id; T1_ID T2_ID FK_ID ---------- ---------- ---------- 1 1 1 2 2 2 3 4 5 3 3 6 rows selected.
And switching off ANSI re-architecture doesn’t work either:
ALTER SESSION SET "_optimizer_ansi_rearchitecture"=FALSE; Session altered. SQL> SELECT /*full*/ t1.id T1_id, t2.id T2_id, t2.fk_id 2 FROM t1 3 FULL OUTER JOIN t2 ON t1.id =t2.fk_id; T1_ID T2_ID FK_ID ---------- ---------- ---------- 1 1 1 2 2 2 3 4 5 3 3 6 rows selected.
Therefore I`ll just list the tables separately:
SQL> SELECT * FROM t1;
ID
----------
1
2
4
5
SQL> SELECT * FROM t2;
ID FK_ID
---------- ----------
1 1
2 2
3 3
SQL> INSERT INTO t2 (id, fk_id)
2 VALUES (4, 3);
1 row created.
SQL> INSERT INTO t2 (id, fk_id)
2 VALUES (6, 6);
INSERT INTO t2 (id, fk_id)
*
ERROR at line 1:
ORA-02291: integrity constraint (VEI.T2_T1_FK) violated - parent key not found
What happens when we create a new constraint – on what dataset is it validated? Turns out – it does not care about soft-deleted rows:
SQL> ALTER TABLE t2 DROP CONSTRAINT t2_t1_fk; Table altered. SQL> ALTER TABLE t2 ADD CONSTRAINT t2_t1_fk 2 FOREIGN KEY (fk_id) REFERENCES t1 (id) 3 ON DELETE CASCADE 4 ENABLE VALIDATE; Table altered.
With an on-delete-cascade fk – does it by any chance do something (delete/tag as deleted) about the child row?
SQL> ALTER TABLE t2 ADD CONSTRAINT t2_t1_fk 2 FOREIGN KEY (fk_id) REFERENCES t1 (id) 3 ON DELETE CASCADE 4 ENABLE VALIDATE; Table altered.
A regular delete also removed the child row:
SQL> DELETE t1 WHERE id = 2; 1 row deleted. SQL> COMMIT; Commit complete. SQL> SELECT * FROM t1; ID ---------- 1 4 5 SQL> SELECT * FROM t2; ID FK_ID ---------- ---------- 1 1 3 3 4 3 SQL> UPDATE t1 SET ora_archive_state = 'Deleted' WHERE id = 1; 1 row updated. SQL> COMMIT; Commit complete. SQL> SELECT * FROM t1; ID ---------- 4 5
As expected, the child row is still there:
SQL> SELECT * FROM t2;
ID FK_ID
---------- ----------
1 1
3 3
4 3
Turning on visibility of all rows, checking and cleaning up.
SQL> ALTER SESSION SET ROW ARCHIVAL VISIBILITY = ALL; Session altered. SQL> SELECT * FROM t1; ID ---------- 1 3 4 5 SQL> DROP TABLE t2; Table dropped. SQL> DROP TABLE t1; Table dropped. SQL> ALTER SESSION SET ROW ARCHIVAL VISIBILITY = ACTIVE; Session altered. SQL> ALTER SESSION SET "_optimizer_ansi_rearchitecture"=TRUE; Session altered.
So – as expected the soft-delete of in-database archiving does not respect foreign keys, so you will have to handle the related tables by yourself. But it is still a useful technique, as long as you’re aware of it’s limitations. And – beware of the outer/full outer join bug!