加载一个大的table,被其他人引用,高效

Loading a big table, referenced by others, efficiently

我的用例如下:

我有大量 table users(约 2 亿行)用户使用 user_id 作为主键。 users 被其他几个使用外键 ON DELETE CASCADE 的 table 引用。

每天我都必须使用大量的 csv 文件来替换 users 的全部内容。 (请不要问我为什么要那样做,我只是不得不...)

我的想法是将主键和所有外键设置为 DEFERRED,然后在同一个事务中,删除整个 table 并使用 COPY 命令复制所有 csvs。预期的结果是所有检查和索引计算都将在交易结束时进行。 但实际上插入过程非常慢(4 小时,如果我插入并放置主键则需要 10 分钟)并且没有外键可以引用可延迟的主键。 由于外键,我无法在插入过程中删除主键。我也不想摆脱外键,因为我必须手动模拟 ON DELETE CASCADE 的行为。

所以基本上我正在寻找一种方法告诉 postgres 在事务结束之前不要关心主键索引或外键检查。

PS1:我编造了用户 table,我实际上正在处理非常不同类型的数据,但它与问题并不真正相关。

PS2:粗略估计,我每天在 200 多万条记录中删除 10 条记录,更新 100 万条,添加 100 万条。

全删+全插入会造成级联FK泛滥, 这将不得不由 DEFERRED 推迟, 这将在提交时对 DBMS 造成大量后果。

相反,不要{delete+create} 键,而是将它们保留在原处。 另外,不要碰不需要碰的记录。

        -- staging table
CREATE TABLE tmp_users AS SELECT * FROM big_users WHERE 1=0;

COPY TABLE tmp_users (...) FROM '...' WITH CSV;
-- ... and more copying ...
-- ... from more files ...

        -- If this fails, you have a problem!
ALTER TABLE tmp_users
        ADD PRIMARY KEY (id);

-- [EDIT]
-- I added this later, because the user_comments table
-- was not present in the original question.
DELETE FROM user_comments c
WHERE NOT EXISTS (
    SELECT * FROM tmp_users u WHERE u.id = c.user_id
    );
        -- These deletes are allowed to cascade
        -- [we assume that the mport of the CSV files was complete, here ...]
DELETE FROM big_users b
WHERE NOT EXISTS (
        SELECT *
        FROM tmp_users t
        WHERE t.id = b.id
        );

        -- Only update the records that actually **change**
        -- [ updates are expensive in terms of I/O, because they create row-versions
        -- , and the need to delete the old row-versions, afterwards ]
        -- Note that the key (id) does not change, so there will be no cascading.
        -- ------------------------------------------------------------
UPDATE big_users b
SET name_1 = t.name_1
        , name_2 = t.name_2
        , address = t.address
        -- , ... ALL THE COLUMNS here, except the key(s)
FROM tmp_users t
WHERE  t.id = b.id
AND (t.name_1, t.name_2, t.address, ...) -- ALL THE COLUMNS, except the key(s)
        IS DISTINCT FROM
        (b.name_1, b.name_2, b.address, ...)
        ;

        -- Maybe there were some new records in the CSV files. Add them.
INSERT INTO big_users (id,name_1,name_2,address, ...)
SELECT id,name_1,name_2,address, ...
FROM tmp_users t
WHERE NOT EXISTS (
        SELECT *
        FROM big_users x
        WHERE x.id = t.id
        );

我找到了一个 hacky 解决方案:

update pg_index set indisvalid = false, indisready=false where indexrelid= 'users_pkey'::regclass;
DELETE FROM users;
COPY TABLE users FROM 'file.csv';
REINDEX INDEX users_pkey;
DELETE FROM user_comments c WHERE NOT EXISTS (SELECT * FROM users u WHERE u.id = c.user_id )
commit;

神奇的脏 hack 是禁用 postgres 目录中的主键索引,并在最后强制重建索引(这将覆盖我们所做的更改)。我不能将外键与 ON DELETE CASCADE 一起使用,因为出于某种原因,它会立即执行约束...所以我的外键是 ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED,我必须自己删除。

这对我来说效果很好,因为只有少数用户在其他表中被引用。

不过我希望有一个更简洁的解决方案...