在Postgres中将数据从一个表移动到另一个表的最快方法

时间:2015-04-15 00:48:00

标签: database postgresql concurrency etl bulk-load

背景资料

在我的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;

如果dest表(登台表)的行数多于{{1},那么想法是截断src表并使用src表中的数据加载它。表(实际生产表)。这实际上有效。

请注意,实际生产表(dest)具有约束和索引,而登台表(dest)配置了NO索引或约束,以加速ETL的加载过程。

问题

上面我的函数的问题是,由于src表上的索引和约束,数据副本可能非常昂贵。

问题

  1. 实现同一目标的更好方法是什么?
  2. 我正在考虑在数据复制步骤之前删除/禁用dest上的索引,然后将其添加回来。如何在SQL函数中执行此操作?
  3. 我也在考虑通过重命名来交换2个表,但是这需要将一个表上的索引复制到另一个表上。我怎样才能在函数内执行此操作?
  4. 编辑1

    Postgres版本:
    在x86_64-unknown-linux-gnu上的PostgreSQL 9.2.6,由gcc编译(Ubuntu / Linaro 4.6.3-1ubuntu5)4.6.3,64位

    表格约束:
    在表dest上,我在列dest上有(唯一的)主键,并在时间戳列上有索引。

    编辑2

    This questionthis question确实有帮助。对于上面的选项 3 ,我认为下面的代码接近我想要的。

    id

    编辑3

    另一个想法:在仅用于分析目的的表上可能不需要PK和FK。所以索引是唯一关注的问题。

    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; 
    

2 个答案:

答案 0 :(得分:0)

如果根据表(如视图和外键)没有其他对象,则删除现有表dest并重命名新表src是一种简单的操作。登记/> 您的列表中的项目 3。,只是没有这个考虑因素:

  

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

您不“复制”索引。只需在后台的新表src上创建新的相同索引即可。然后表就完全准备好了,切换器只需几毫秒。但是,如果您在表上同时加载该路由,则必须准备好在并发事务中获取这些错误消息:

ERROR:  could not open relation with OID 123456

相关答案以及dba.SE上的详细说明和示例代码:

答案 1 :(得分:0)

您可以构建复制系统,并且BI作业不会影响主数据库服务器,这是数据仓库和BI上非常常见的情况。 你保留了一个主服务器数据库,在另一个配置为slave的服务器上,你可以启动你的(通常是很难的)bi查询,并使用真实的新数据来启动。但是来自另一个孤立的源但具有完全相同的数据

http://www.rassoc.com/gregr/weblog/2013/02/16/zero-to-postgresql-streaming-replication-in-10-mins/