我有一个引用表B的表A.表B需要填充来自外部源的更新数据,为了提高效率,我使用TRUNCATE后跟COPY。即使应用程序处于运行状态,也可以完成此操作。
为了进一步提高效率as suggested in the documentation,我想删除然后重新创建外键。
但是我有些疑惑。
如果我删除FK,COPY然后在同一事务中重新创建FK ,我是否可以确保即使在事务期间插入表A中的数据也会保留约束?我问这个是因为理论上交易是原子的,但在文档中,关于FK的临时删除说:
在缺少约束的情况下,在数据加载速度和错误检查丢失之间进行权衡。
如果同时插入错误引用的可能性,当您尝试重新创建FK约束时会发生什么?
答案 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
表中的键:delete b_import
中的密钥:insert -- 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
;
A
和B
中的记录。B
表可能会遗漏一些仍被A
的FK引用的行。 A
个引用(这基本上是不可能的,没有额外的信息(这将是多余的,BTW))。