在事务中删除然后创建外键约束是否安全?

时间:2015-04-24 09:18:32

标签: sql postgresql transactions foreign-keys populate

我有一个引用表B的表A.表B需要填充来自外部源的更新数据,为了提高效率,我使用TRUNCATE后跟COPY。即使应用程序处于运行状态,也可以完成此操作。

为了进一步提高效率as suggested in the documentation,我想删除然后重新创建外键。

但是我有些疑惑。

如果我删除FK,COPY然后在同一事务中重新创建FK ,我是否可以确保即使在事务期间插入表A中的数据也会保留约束?我问这个是因为理论上交易是原子的,但在文档中,关于FK的临时删除说:

  

在缺少约束的情况下,在数据加载速度和错误检查丢失之间进行权衡。

如果同时插入错误引用的可能性,当您尝试重新创建FK约束时会发生什么?

3 个答案:

答案 0 :(得分:2)

除非您使用TRUNCATE,否则外键引用的任何表都不允许

TRUNCATE CASCADE,这也会截断引用表。约束的DEFERRABLE状态不会影响此情况。我认为没有办法解决这个问题;你需要放弃约束。

但是,这样做不存在违反完整性的风险。 ALTER TABLE ... ADD CONSTRAINT锁定有问题的表(与TRUNCATE一样),因此保证导入过程在其事务期间具有对表的独占访问权。任何并发插入的尝试都会挂起,直到导入已经提交,并且当它们被允许进行时,约束将会恢复到位。

答案 1 :(得分:1)

您可以设置外键约束deferrable(最初延迟)。这样,它将在交易结束时只检查一次。

ALTER TABLE
  xxx
ADD CONSTRAINT
  xxx_yyy_id_fk FOREIGN KEY (yyy_id)
REFERENCES
  yyy
DEFERRABLE INITIALLY DEFERRED;

在所有情况下,事务在PostgreSQL中完全是原子的(不仅在理论上),包括DDL语句(例如CREATE / DROP约束),所以即使你删除了一个外键,然后插入数据,然后创建外来的键并在一个事务中执行所有操作,然后您就安全了 - 如果重新创建外键约束失败,则插入的数据也将被解除。

但是,最好切换到延迟外键,而不是删除然后创建它们。

答案 2 :(得分:1)

分析答案:衡量新/相同/更新/删除记录的数量。 有四种情况:

  • b_import中没有B表中的键:delete
  • 旧版B中不存在b_import中的密钥:insert
  • 密钥出现在旧B和新B中,但内容相同:忽略
  • 键是相同的,但attribete值不同:Update
        -- some test data for `A`, `B` and `B_import`:
CREATE TABLE b
        ( id INTEGER NOT NULL PRIMARY KEY
        , payload varchar
        );
INSERT INTO b(id,payload) SELECT gs, 'bb_' || gs::varchar
FROM generate_series(1,20) gs;

CREATE TABLE b_import
        ( id INTEGER NOT NULL PRIMARY KEY
        , payload varchar
        );
INSERT INTO b_import(id,payload) SELECT gs, 'bb_' || gs::varchar
FROM generate_series(10,15) gs;
        -- In real life this table will be filled by a `COPY b_import FROM ...`
INSERT INTO b_import(id,payload) SELECT gs, 'b2_' || gs::varchar
FROM generate_series(16,25) gs;

CREATE TABLE a
        ( id SERIAL NOT NULL PRIMARY KEY
        , b_id INTEGER references b(id) ON DELETE SET NULL
        , aaaaa varchar
        );
INSERT INTO a(b_id,aaaaa)
SELECT gs,'aaaaa_' || gs::text FROM generate_series(1,20) gs;
CREATE INDEX ON a(b_id); -- index supporting the FK

        -- show it
SELECT a.id, a.aaaaa
        ,b.id, b.payload AS oldpayload
FROM a
FULL JOIN b ON a.b_id=b.id
ORDER BY a.id;

        -- Do the actual I/U/D and report the numbers of affected rows
-- EXPLAIN
WITH ins AS (   -- INSERTS
        INSERT INTO b(id, payload)
        SELECT b_import.id, b_import.payload
        FROM b_import
                WHERE NOT EXISTS (
                SELECT 1 FROM b
                WHERE b.id = b_import.id
                )
        RETURNING b.id
        )
, del AS (      -- DELETES
        DELETE FROM b
        WHERE NOT EXISTS (
                SELECT 2 FROM b_import
                WHERE b_import.id  = b.id
                )
        RETURNING b.id
        )
, upd AS (      -- UPDATES
        UPDATE b
        SET payload=b_import.payload
        FROM b_import
        WHERE b_import.id = b.id
        AND b_import.payload IS DISTINCT FROM b.payload -- exclude idempotent updates
        -- AND NOT EXISTS (     -- exclude deleted records
                -- SELECT 3 FROM del
                -- WHERE del.id = b_import.id
                -- )
        -- AND NOT EXISTS (     -- avoid touching freshly inserted rows
                -- SELECT 4 FROM ins
                -- WHERE ins.id = b_import.id
                -- )
        RETURNING b.id
        )
SELECT COUNT(*) AS orgb
        , (SELECT COUNT(*) FROM b_import) AS newb
        , (SELECT COUNT(*) FROM ins) AS ninserted
        , (SELECT COUNT(*) FROM del) AS ndeleted
        , (SELECT COUNT(*) FROM upd) AS nupdated
FROM b
        ;
  • 删除约束并在导入后重建它是很昂贵的:所有包括AB中的记录。
  • 暂时忽略约束危险:新的B表可能会遗漏一些仍被A的FK引用的行。
  • ergo:你可能最终得到一个残缺的模型,你必须重建A个引用(这基本上是不可能的,没有额外的信息(这将是多余的,BTW))。