Database 在Postgres中将数据从一个表移动到另一个表的最快方式 背景信息

Database 在Postgres中将数据从一个表移动到另一个表的最快方式 背景信息,database,postgresql,concurrency,etl,bulk-load,Database,Postgresql,Concurrency,Etl,Bulk Load,在我的Postgres服务器上,我有几个表,商业智能应用程序经常访问这些表,因此理想情况下,它们应该在大部分时间都保持可用。我们的ETL管道每天晚上都会刷新和重新加载这些表,我知道。。。由于一些旧的设置,我不能在这里使用增量更新。装载过程需要相当长的时间,而且还不是防弹稳定的 为了提高表的可用性,我决定首先使用暂存表从ETL管道加载下游数据,如果成功加载,则将数据复制到实际的生产表 下面是我为此创建的一个复制函数: CREATE OR REPLACE FUNCTION guarded_copy(

在我的Postgres服务器上,我有几个表,商业智能应用程序经常访问这些表,因此理想情况下,它们应该在大部分时间都保持可用。我们的ETL管道每天晚上都会刷新和重新加载这些表,我知道。。。由于一些旧的设置,我不能在这里使用增量更新。装载过程需要相当长的时间,而且还不是防弹稳定的

为了提高表的可用性,我决定首先使用暂存表从ETL管道加载下游数据,如果成功加载,则将数据复制到实际的生产表

下面是我为此创建的一个复制函数:

CREATE OR REPLACE FUNCTION guarded_copy(src text, dest text) RETURNS void AS
    $$
      DECLARE
        c1 INTEGER;
        c2 INTEGER;
      BEGIN
        EXECUTE FORMAT('SELECT COUNT(*) FROM %I', src) INTO c1;
        EXECUTE FORMAT('SELECT COUNT(*) FROM %I', dest) INTO c2;
        IF c1>=c2 THEN
          EXECUTE FORMAT('TRUNCATE TABLE %I CASCADE;', dest);
          EXECUTE FORMAT('INSERT INTO %I SELECT * FROM %I;', dest, src);
        END IF;
      END
    $$
LANGUAGE plpgsql VOLATILE;
如果src TABLETAGING表的行数比dest table实际生产表的行数多,则可以截断dest表并从src表加载数据。这确实有效

请注意,实际的生产tabledest具有约束和索引,而staging tablesrc配置为没有索引或约束,以加快从ETL加载过程

问题 上面我的函数的问题是,由于dest表上的索引和约束,数据拷贝可能非常昂贵

问题 实现相同目标的更好方法是什么? 我正在考虑在数据复制步骤之前删除/禁用dest上的索引,然后立即将其添加回dest。如何在SQL函数中实现这一点? 我还考虑通过重命名这两个表来交换它们,但这需要将一个表上的索引复制到另一个表上。如何在函数中执行此操作? 编辑1 博士后版本: x86_64-unknown-linux-gnu上的PostgreSQL 9.2.6,由gcc Ubuntu/Linaro 4.6.3-1ubuntu5 4.6.3编译,64位

表约束: 在表dest上,我在列id上有唯一的主键,在时间戳列上有索引

编辑2 真的很有帮助。对于上面的选项3,我认为下面的代码接近我想要的

CREATE OR REPLACE FUNCTION guarded_swap(src text, dest text) RETURNS void AS
    $$
      DECLARE
        c1 INTEGER;
        c2 INTEGER;
        _query TEXT;
      BEGIN
        EXECUTE FORMAT('SELECT COUNT(*) FROM %I', src) INTO c1;
        EXECUTE FORMAT('SELECT COUNT(*) FROM %I', dest) INTO c2;
        IF c1>=c2 THEN

          -- create indexes in src table
          FOR _query IN
              SELECT FORMAT('%s;', REPLACE(pg_get_indexdef(ix.indexrelid), dest, src))
              FROM pg_class t, pg_class i, pg_index ix
              WHERE t.oid = ix.indrelid
                    AND i.oid = ix.indexrelid
                    AND t.relkind = 'r' and i.relkind = 'i'
                    AND t.oid= dest::regclass
              ORDER BY
                  t.relname, i.relname
          LOOP
              EXECUTE _query;
          END LOOP;

          -- drop indexes in dest table
          FOR _query IN
              SELECT FORMAT('DROP INDEX %s;', i.relname)
              FROM pg_class t, pg_class i, pg_index ix
              WHERE t.oid = ix.indrelid
                    AND i.oid = ix.indexrelid
                    AND t.relkind = 'r' and i.relkind = 'i'
                    AND t.oid= dest::regclass
              ORDER BY
                  t.relname, i.relname
          LOOP
              EXECUTE _query;
          END LOOP;


        -- create constraints in src table
          FOR _query IN
              SELECT
                  FORMAT ('ALTER TABLE %s ADD CONSTRAINT %s %s;', src,
                      REPLACE(conname, dest, src),
                      pg_get_constraintdef(oid))
              FROM pg_constraint
              WHERE contype = 'p' AND conrelid = dest::regclass
          LOOP
              EXECUTE _query;
          END LOOP;

          -- drop all constraints in dest table
          FOR _query IN
              SELECT
                  FORMAT ('ALTER TABLE %s DROP CONSTRAINT IF EXISTS %s;', dest, conname)
              FROM pg_constraint
              WHERE conrelid = dest::regclass
          LOOP
              EXECUTE _query;
          END LOOP;

          -- swap the table names
          EXECUTE FORMAT('ALTER TABLE %I RENAME TO %I;', dest, CONCAT(dest, '_old'));
          EXECUTE FORMAT('ALTER TABLE %I RENAME TO %I;', src, dest);
          EXECUTE FORMAT('ALTER TABLE %I RENAME TO %I;', CONCAT(dest, '_old'), src);


        END IF;
      END
    $$
LANGUAGE plpgsql VOLATILE; 
编辑3 另一个想法是:对于仅用于分析目的的表,PKs和FKs可能是不必要的。因此,索引是这里唯一关心的问题

CREATE OR REPLACE FUNCTION guarded_swap(src text, dest text) RETURNS void AS
    $$
      DECLARE
        c1 INTEGER;
        c2 INTEGER;
        _idx_name TEXT;
        _query TEXT;
        _qs TEXT[];
      BEGIN
        EXECUTE FORMAT('SELECT COUNT(*) FROM %I', src) INTO c1;
        EXECUTE FORMAT('SELECT COUNT(*) FROM %I', dest) INTO c2;
        IF c1>=c2 THEN

          -- drop indexes in dest table
          FOR _idx_name, _query IN
              SELECT i.relname, FORMAT('%s;', pg_get_indexdef(ix.indexrelid))
              FROM pg_class t, pg_class i, pg_index ix
              WHERE t.oid = ix.indrelid
                    AND i.oid = ix.indexrelid
                    AND t.relkind = 'r' and i.relkind = 'i'
                    AND t.oid= dest::regclass
              ORDER BY
                  t.relname, i.relname
          LOOP
              _qs := array_append(_qs, _query);
              EXECUTE FORMAT('DROP INDEX IF EXISTS %s;', _idx_name);
          END LOOP;

          -- swap the table names
          EXECUTE FORMAT('ALTER TABLE %I RENAME TO %I;', dest, CONCAT(dest, '_old'));
          EXECUTE FORMAT('ALTER TABLE %I RENAME TO %I;', src, dest);
          EXECUTE FORMAT('ALTER TABLE %I RENAME TO %I;', CONCAT(dest, '_old'), src);

          -- build indexes in dest table
          FOREACH _query IN ARRAY _qs
          LOOP
              EXECUTE _query;
          END LOOP;

        END IF;
      END
    $$
LANGUAGE plpgsql VOLATILE; 

如果没有其他依赖于表的对象(如视图和外键),只需删除现有表dest并重命名新表src即可。 这是第三项。在您的列表中,不考虑以下因素:

但这需要将一个表上的索引复制到另一个表上

你不能复制索引。只需在后台的新表src上创建新的相同索引。然后表格就完全准备好了,switcheroo只需几毫秒。但是,如果在表上同时加载时执行该路径,则必须准备在并发事务中获取以下一些错误消息:

在dba.SE上有详细解释和示例代码的相关答案:


您可以构建一个复制系统,BI作业不会影响主db服务器,这是数据仓库和BI上非常常见的场景。 您保留一个主服务器数据库,在另一台配置为从服务器的服务器上,您可以启动通常是硬的bi查询和ETL,并使用真实的新数据。但来自另一个孤立的来源,但数据完全相同


tabledest有约束和索引:什么约束?显然你需要提供你的Postgres版本。@ErwinBrandstetter,谢谢你的回复!版本9.2.6。在表dest上,我在列id上有唯一的主键,在timestamp列上有索引-刚才也编辑了这个问题。谢谢你的回答!我假设第3项是最快的,但还没有测试出来。如果我想使这个函数成为泛型函数,那么如何在新表src上轻松复制相同的索引呢?我的意思是,有没有一种简单的方法可以从dest中选择索引创建语句并对src运行它?这可以在sql函数中完成吗?谢谢
ERROR:  could not open relation with OID 123456