为什么在Postgres中“创建表”需要几秒钟?

时间:2017-08-30 18:54:31

标签: postgresql dynamic-sql postgresql-9.4

在我的项目中,我们有时必须将所有数据从一个模式复制到另一个模式。我通过简单的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;

由于目标架构是只读的,因此我在没有约束的情况下创建它以达到最大性能。我不认为NOT NULL约束是一个大问题,但我决定将所有内容保留在原处。

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

我决定调查一下。我的第一步是评论insert into select *声明,找出其他所有时间。它表明清理并重新创建所有表只需要半秒钟。我的线索是INSERT语句在程序上下文中以某种方式工作的时间更长。

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

ts := clock_timestamp();
execute 'insert into ...';
raise notice 'obj_name: %', clock_timestamp() - ts;

我还在psql中使用\timing执行了旧的静态脚本。但这表明我的假设是错误的。所有插入语句或多或少同时进行,在动态脚本中主要甚至更快(我认为这是由于psql中每个语句之后的自动提交和网络往返)。但是,动态脚本的总时间再次更长,而不是静态脚本的时间。

神秘?

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

raise notice '%: %', clock_timestamp()::timestamp(3), 'label';

我发现有时create table会立即执行,但有时需要几秒钟才能完成。好的,但是为什么所有表格的所有这些语句在我的第一个实验中花了几毫秒才完成?

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

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 insert之后的某个时间可能需要很长时间。这非常令人沮丧。尽管我解决了我的问题,但我不明白为什么会这样。有人有任何想法吗?

0 个答案:

没有答案