Postgresql 为什么可以';创建表';在Postgres中需要几秒钟?

Postgresql 为什么可以';创建表';在Postgres中需要几秒钟?,postgresql,dynamic-sql,postgresql-9.4,Postgresql,Dynamic Sql,Postgresql 9.4,在我的项目中,有时我们必须将所有数据从一个模式复制到另一个模式。我通过简单的truncate/insert-into-select*脚本自动实现了这一点,但我很快意识到这种方式不能容忍源模式中的更改(添加/删除表需要修改脚本)。所以今天我决定将它改为PL/PGSQL脚本,它使用动态查询创建表并复制数据。我的第一个实现是这样的: do $$ declare source_schema text := 'source_schema'; dest_schema text := 'dest_sc

在我的项目中,有时我们必须将所有数据从一个模式复制到另一个模式。我通过简单的
truncate
/
insert-into-select*
脚本自动实现了这一点,但我很快意识到这种方式不能容忍源模式中的更改(添加/删除表需要修改脚本)。所以今天我决定将它改为PL/PGSQL脚本,它使用动态查询创建表并复制数据。我的第一个实现是这样的:

do
$$
declare
  source_schema text := 'source_schema';
  dest_schema text := 'dest_schema';
  obj_name text;
  source_table text;
  dest_table text;
  alter_columns text;
begin
    for dest_table in
        select table_schema || '.' || table_name
        from information_schema.tables
        where table_schema = dest_schema
        order by table_name
    loop
        execute 'drop table ' || dest_table;
    end loop;
    raise notice 'Data cleared';

    for obj_name in
        select table_name
        from information_schema.tables
        where table_schema = source_schema
        order by table_name
    loop
        source_table := source_schema || '.' || obj_name;
        dest_table := dest_schema || '.' || obj_name;
        execute 'create unlogged table ' || dest_table
            || ' (like ' || source_table || ' including comments)';

        alter_columns := (
            select string_agg('alter column ' || column_name || ' drop not null', ', ')
            from information_schema.columns
            where table_schema = dest_schema and table_name = obj_name
                and is_nullable = 'NO');
        if alter_columns is not null then
            execute 'alter table ' || dest_table || ' ' || alter_columns;
        end if;

        execute 'insert into ' || dest_table || '  select * from ' || source_table;
        raise notice '% done', obj_name;
    end loop;    
end;
$$
language plpgsql;
raise notice '%: %', clock_timestamp()::timestamp(3), 'label';
由于目标模式是只读的,所以我创建它时不使用约束以达到最大性能。我不认为NOTNULL约束有什么大不了的,但我决定把一切都保持原样

这个解决方案工作得很好,但我注意到,与静态脚本相比,复制数据需要更长的时间。不是很明显,但稳定地说,它比静态脚本长20-30秒

我决定调查一下。我的第一步是在select*语句中添加注释
insert,以了解其他所有操作所需的时间。它表明,清除和重新创建所有表只需半秒钟。我的线索是,INSERT语句在过程上下文中的工作时间更长

然后我添加了执行时间的度量:

ts := clock_timestamp();
execute 'insert into ...';
raise notice 'obj_name: %', clock_timestamp() - ts;
我还使用psql中的
\time
执行了旧的静态脚本。但这表明我的假设是错误的。所有insert语句都花费了差不多相同的时间,在动态脚本中主要是更快(我想这是由于psql中每个语句之后的自动提交和网络往返)。然而,动态脚本的总时间再次比静态脚本的总时间长

神秘主义

然后我添加了非常详细的日志记录,时间戳如下:

do
$$
declare
  source_schema text := 'source_schema';
  dest_schema text := 'dest_schema';
  obj_name text;
  source_table text;
  dest_table text;
  alter_columns text;
begin
    for dest_table in
        select table_schema || '.' || table_name
        from information_schema.tables
        where table_schema = dest_schema
        order by table_name
    loop
        execute 'drop table ' || dest_table;
    end loop;
    raise notice 'Data cleared';

    for obj_name in
        select table_name
        from information_schema.tables
        where table_schema = source_schema
        order by table_name
    loop
        source_table := source_schema || '.' || obj_name;
        dest_table := dest_schema || '.' || obj_name;
        execute 'create unlogged table ' || dest_table
            || ' (like ' || source_table || ' including comments)';

        alter_columns := (
            select string_agg('alter column ' || column_name || ' drop not null', ', ')
            from information_schema.columns
            where table_schema = dest_schema and table_name = obj_name
                and is_nullable = 'NO');
        if alter_columns is not null then
            execute 'alter table ' || dest_table || ' ' || alter_columns;
        end if;

        execute 'insert into ' || dest_table || '  select * from ' || source_table;
        raise notice '% done', obj_name;
    end loop;    
end;
$$
language plpgsql;
raise notice '%: %', clock_timestamp()::timestamp(3), 'label';
我发现有时
createtable
会立即执行,但有时需要几秒钟才能完成。好的,但是为什么在我的第一个实验中,所有表的所有这些语句只花了几毫秒就完成了呢

然后我基本上将一个循环分为两个:第一个循环创建所有表(我们现在知道它只需要毫秒),第二个循环只插入数据:

do
$$
declare
  source_schema text := 'onto_oper';
  dest_schema text := 'onto';
  obj_name text;
  source_table text;
  dest_table text;
  alter_columns text;
begin
    raise notice 'Clearing data...';    
    for dest_table in
        select table_schema || '.' || table_name
        from information_schema.tables
        where table_schema = dest_schema
        order by table_name
    loop
        execute 'drop table ' || dest_table;
    end loop;
    raise notice 'Data cleared';

    for obj_name in
        select table_name
        from information_schema.tables
        where table_schema = source_schema
        order by table_name
    loop
        source_table := source_schema || '.' || obj_name;
        dest_table := dest_schema || '.' || obj_name;
        execute 'create unlogged table ' || dest_table
            || ' (like ' || source_table || ' including comments)';

        alter_columns := (
            select string_agg('alter column ' || column_name || ' drop not null', ', ')
            from information_schema.columns
            where table_schema = dest_schema and table_name = obj_name
                and is_nullable = 'NO');
        if alter_columns is not null then
            execute 'alter table ' || dest_table || ' ' || alter_columns;
        end if;
    end loop;
    raise notice 'All tables created';

    for obj_name in
        select table_name
        from information_schema.tables
        where table_schema = source_schema
        order by table_name
    loop
        source_table := source_schema || '.' || obj_name;
        dest_table := dest_schema || '.' || obj_name;
        execute 'insert into ' || dest_table || '  select * from ' || source_table;
        raise notice '% done', obj_name;
    end loop;
end;
$$
language plpgsql;
令人惊讶的是,它修复了一切!这个版本的工作速度比旧的静态脚本快


我们得出了一个非常奇怪的结论:
create table
after
insert
s有时可能需要很长时间。这是非常令人沮丧的。尽管我解决了我的问题,但我不明白为什么会这样。有人有什么想法吗?

小题大做::使用
quote_ident()
,甚至
format()
来构造动态查询。他们将处理crippelled表名(带有空格、大写等)。这个问题很可能是由DDL期间锁定+解锁目录引起的。@wildplasser它如何解释两个脚本之间的差异?为什么在第一种情况下遇到目录锁的概率更高?