Postgres:在从 bash 脚本重新创建/重新填充之前清除整个数据库

Posted

技术标签:

【中文标题】Postgres:在从 bash 脚本重新创建/重新填充之前清除整个数据库【英文标题】:Postgres: clear entire database before re-creating / re-populating from bash script 【发布时间】:2011-01-04 15:21:15 【问题描述】:

我正在编写一个 shell 脚本(将成为一个 cronjob),它将:

1:转储我的生产数据库

2:将转储导入我的开发数据库

在第 1 步和第 2 步之间,我需要清除开发数据库(删除所有表?)。这如何最好地从 shell 脚本中完成?到目前为止,它看起来像这样:

#!/bin/bash
time=`date '+%Y'-'%m'-'%d'`
# 1. export(dump) the current production database
pg_dump -U production_db_name > /backup/dir/backup-$time.sql

# missing step: drop all tables from development database so it can be re-populated

# 2. load the backup into the development database
psql -U development_db_name < backup/dir/backup-$time.sql

【问题讨论】:

oneliner 为赶时间的人:dbname='db_name' &amp;&amp; dropdb $dbname &amp;&amp; createdb $dbname &amp;&amp; psql -d $dbname -f dump.sql 这个 oneliner 要求您拥有创建/删除数据库的权限。作者尝试的方法不需要特殊权限。 【参考方案1】:

虽然下面这行取自 Windows 批处理脚本,但命令应该非常相似:

psql -U username -h localhost -d postgres -c "DROP DATABASE \"$DATABASE\";"

此命令用于通过实际删除整个数据库来清除它。命令中的$DATABASE(在Windows 中应该是%DATABASE%)是一个windows 风格的环境变量,其计算结果为数据库名称。您需要将其替换为您的 development_db_name

【讨论】:

那么为什么不使用已经可用的dropdbcreatedb 命令呢?如果你可以运行 psql,你也可以运行它们。【参考方案2】:

我只是删除数据库然后重新创建它。在 UNIX 或 Linux 系统上,应该这样做:

$ dropdb development_db_name
$ createdb development_db_name

其实我就是这么干的。

【讨论】:

这也是我的做法。然后只需恢复到新创建的数据库。 是的。这更好,因为可能存在不属于您正在恢复的转储的对象。在这种情况下,他们肯定会被杀死。 一个节省我时间的技巧是 $ sudo -u postgres dropdb DATABASE_NAME 但是……数据库权限和所有权呢? @EmanuelePaolini createdb --owner=db_owner [--template=template0 --encoding=UTF8] db_name 我默认将最后两个添加到所有数据库中【参考方案3】:

如果您实际上不需要备份以纯文本 .sql 脚本文件格式转储到磁盘上的数据库,您可以通过管道将 pg_dumppg_restore 直接连接在一起。

要删除和重新创建表,您可以使用pg_dump--clean 命令行选项发出SQL 命令以在创建数据库对象之前清理(删除)数据库对象。 (这不会删除整个数据库,只会删除每个表/序列/索引/等,然后再重新创建它们。)

上面的两个看起来像这样:

pg_dump -U username --clean | pg_restore -U username

【讨论】:

我喜欢这个解决方案,因为我确实想要一个备份副本,所以我现在这样做:pg_dump -Ft -U production_db_name > /backup/dir/backup-$time.tar pg_restore - U development_db_name -d development_db_name -O --clean /backup/dir/backup-$time.tar 就像一个魅力,感谢您的帮助! 注意:--clean 选项只会删除那些在恢复文件中找到的关系。这意味着,如果您添加一个表进行测试,然后想要删除它(例如与生产数据库同步),它将不会被删除。 重要的是要记住 pg_dump 的 --clean 选项仅适用于纯文本备份。正如文档在postgresql.org/docs/9.4/static/app-pgdump.html 中明确指出的那样,您需要在 pg_restore 上使用 --clean 进行存档备份。 有没有办法在“--clean”选项中包含级联。因为它是这个选项看起来没用。我收到“错误:无法公开模式,因为其他对象依赖它”,就像 100% 的时间使用它一样。 关于删除所有表的问题。这只会删除在 pg_dump 正在转储的数据库中找到的表。【参考方案4】:

我用过:

pg_restore -c -d database_name filename.dump

【讨论】:

【参考方案5】:

转储:

pg_dump -Fc mydb > db.dump

恢复:

pg_restore --verbose --clean --no-acl --no-owner -h localhost -U myuser -d my_db db/latest.dump

【讨论】:

【参考方案6】:

注意:我的回答是关于真正删除表和其他数据库对象;对于deleting all data in the tables, i.e. truncating all tables,Endre Both 在一个月后提供了同样执行良好(直接执行)的语句。

对于不能只使用DROP SCHEMA public CASCADE;DROP OWNED BY current_user; 或其他东西的情况,这是我编写的独立 SQL 脚本,它是事务安全的(即您可以将它放在 BEGIN; 和 @ 987654328@ 只是测试它或COMMIT; 实际执行)并清理“所有”数据库对象......好吧,我们的应用程序使用的数据库中使用的所有对象,或者我可以明智地添加,即:

表上的触发器 对表格的约束(FK、PK、CHECKUNIQUE) 指标 VIEWs(正常或具体化) 表格 序列 例程(聚合函数、函数、过程) 所有非默认(即不是public或DB-internal)模式“我们”拥有:该脚本在以“非数据库超级用户”身份运行时很有用;超级用户可以删除 所有 模式(不过,真正重要的模式仍然被明确排除) 扩展(用户提供,但我通常故意将它们留在里面)

没有删除是(有些是故意的;有些只是因为我在我们的数据库中没有示例):

public 架构(例如,用于其中的扩展提供的东西) 排序规则和其他语言环境的东西 事件触发器 文本搜索内容,...(请参阅 here 了解我可能错过的其他内容) 角色或其他安全设置 复合类型 烤面包桌 FDW 和外部表

当您要恢复的转储与数据库具有不同的数据库架构版本(例如,使用 Debian dbconfig-common、Flyway 或 Liquibase/DB-Manul)时,这真的非常有用你想把它恢复到。

我还有一个版本,它删除了“除了两个表和属于它们的所有内容”(一个序列,手动测试,抱歉,我知道,无聊)以防有人感兴趣;差异很小。有兴趣请联系我或check this repo。

SQL

-- Copyright © 2019, 2020
--      mirabilos <t.glaser@tarent.de>
--
-- Provided that these terms and disclaimer and all copyright notices
-- are retained or reproduced in an accompanying document, permission
-- is granted to deal in this work without restriction, including un‐
-- limited rights to use, publicly perform, distribute, sell, modify,
-- merge, give away, or sublicence.
--
-- This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
-- the utmost extent permitted by applicable law, neither express nor
-- implied; without malicious intent or gross negligence. In no event
-- may a licensor, author or contributor be held liable for indirect,
-- direct, other damage, loss, or other issues arising in any way out
-- of dealing in the work, even if advised of the possibility of such
-- damage or existence of a defect, except proven that it results out
-- of said person’s immediate fault when using the work as intended.
-- -
-- Drop everything from the PostgreSQL database.

DO $$
DECLARE
        q TEXT;
        r RECORD;
BEGIN
        -- triggers
        FOR r IN (SELECT pns.nspname, pc.relname, pt.tgname
                FROM pg_catalog.pg_trigger pt, pg_catalog.pg_class pc, pg_catalog.pg_namespace pns
                WHERE pns.oid=pc.relnamespace AND pc.oid=pt.tgrelid
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
                    AND pt.tgisinternal=false
            ) LOOP
                EXECUTE format('DROP TRIGGER %I ON %I.%I;',
                    r.tgname, r.nspname, r.relname);
        END LOOP;
        -- constraints #1: foreign key
        FOR r IN (SELECT pns.nspname, pc.relname, pcon.conname
                FROM pg_catalog.pg_constraint pcon, pg_catalog.pg_class pc, pg_catalog.pg_namespace pns
                WHERE pns.oid=pc.relnamespace AND pc.oid=pcon.conrelid
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
                    AND pcon.contype='f'
            ) LOOP
                EXECUTE format('ALTER TABLE ONLY %I.%I DROP CONSTRAINT %I;',
                    r.nspname, r.relname, r.conname);
        END LOOP;
        -- constraints #2: the rest
        FOR r IN (SELECT pns.nspname, pc.relname, pcon.conname
                FROM pg_catalog.pg_constraint pcon, pg_catalog.pg_class pc, pg_catalog.pg_namespace pns
                WHERE pns.oid=pc.relnamespace AND pc.oid=pcon.conrelid
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
                    AND pcon.contype<>'f'
            ) LOOP
                EXECUTE format('ALTER TABLE ONLY %I.%I DROP CONSTRAINT %I;',
                    r.nspname, r.relname, r.conname);
        END LOOP;
        -- indicēs
        FOR r IN (SELECT pns.nspname, pc.relname
                FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pns
                WHERE pns.oid=pc.relnamespace
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
                    AND pc.relkind='i'
            ) LOOP
                EXECUTE format('DROP INDEX %I.%I;',
                    r.nspname, r.relname);
        END LOOP;
        -- normal and materialised views
        FOR r IN (SELECT pns.nspname, pc.relname
                FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pns
                WHERE pns.oid=pc.relnamespace
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
                    AND pc.relkind IN ('v', 'm')
            ) LOOP
                EXECUTE format('DROP VIEW %I.%I;',
                    r.nspname, r.relname);
        END LOOP;
        -- tables
        FOR r IN (SELECT pns.nspname, pc.relname
                FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pns
                WHERE pns.oid=pc.relnamespace
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
                    AND pc.relkind='r'
            ) LOOP
                EXECUTE format('DROP TABLE %I.%I;',
                    r.nspname, r.relname);
        END LOOP;
        -- sequences
        FOR r IN (SELECT pns.nspname, pc.relname
                FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pns
                WHERE pns.oid=pc.relnamespace
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
                    AND pc.relkind='S'
            ) LOOP
                EXECUTE format('DROP SEQUENCE %I.%I;',
                    r.nspname, r.relname);
        END LOOP;
        -- extensions (only if necessary; keep them normally)
        FOR r IN (SELECT pns.nspname, pe.extname
                FROM pg_catalog.pg_extension pe, pg_catalog.pg_namespace pns
                WHERE pns.oid=pe.extnamespace
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
            ) LOOP
                EXECUTE format('DROP EXTENSION %I;', r.extname);
        END LOOP;
        -- aggregate functions first (because they depend on other functions)
        FOR r IN (SELECT pns.nspname, pp.proname, pp.oid
                FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pns, pg_catalog.pg_aggregate pagg
                WHERE pns.oid=pp.pronamespace
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast')
                    AND pagg.aggfnoid=pp.oid
            ) LOOP
                EXECUTE format('DROP AGGREGATE %I.%I(%s);',
                    r.nspname, r.proname,
                    pg_get_function_identity_arguments(r.oid));
        END LOOP;
        -- routines (functions, aggregate functions, procedures, window functions)
        IF EXISTS (SELECT * FROM pg_catalog.pg_attribute
                WHERE attrelid='pg_catalog.pg_proc'::regclass
                    AND attname='prokind' -- PostgreSQL 11+
            ) THEN
                q := 'CASE pp.prokind
                        WHEN ''p'' THEN ''PROCEDURE''
                        WHEN ''a'' THEN ''AGGREGATE''
                        ELSE ''FUNCTION''
                    END';
        ELSIF EXISTS (SELECT * FROM pg_catalog.pg_attribute
                WHERE attrelid='pg_catalog.pg_proc'::regclass
                    AND attname='proisagg' -- PostgreSQL ≤10
            ) THEN
                q := 'CASE pp.proisagg
                        WHEN true THEN ''AGGREGATE''
                        ELSE ''FUNCTION''
                    END';
        ELSE
                q := '''FUNCTION''';
        END IF;
        FOR r IN EXECUTE 'SELECT pns.nspname, pp.proname, pp.oid, ' || q || ' AS pt
                FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pns
                WHERE pns.oid=pp.pronamespace
                    AND pns.nspname NOT IN (''information_schema'', ''pg_catalog'', ''pg_toast'')
            ' LOOP
                EXECUTE format('DROP %s %I.%I(%s);', r.pt,
                    r.nspname, r.proname,
                    pg_get_function_identity_arguments(r.oid));
        END LOOP;
        -- nōn-default schemata we own; assume to be run by a not-superuser
        FOR r IN (SELECT pns.nspname
                FROM pg_catalog.pg_namespace pns, pg_catalog.pg_roles pr
                WHERE pr.oid=pns.nspowner
                    AND pns.nspname NOT IN ('information_schema', 'pg_catalog', 'pg_toast', 'public')
                    AND pr.rolname=current_user
            ) LOOP
                EXECUTE format('DROP SCHEMA %I;', r.nspname);
        END LOOP;
        -- voilà
        RAISE NOTICE 'Database cleared!';
END; $$;

在 PostgreSQL 9.6 (jessie-backports) 上经过测试,除了后来添加的内容(Clément Prévost 贡献的extensions)。在 9.6 和 12.2 上测试了骨料去除,也在 12.2 上测试了过程去除。欢迎进行错误修复和进一步改进!

【讨论】:

【参考方案7】:

如果要清理名为“example_db”的数据库:

1) 登录另一个数据库(例如'postgres'):

psql postgres

2) 删除您的数据库:

DROP DATABASE example_db;

3) 重新创建您的数据库:

CREATE DATABASE example_db;

【讨论】:

以上是关于Postgres:在从 bash 脚本重新创建/重新填充之前清除整个数据库的主要内容,如果未能解决你的问题,请参考以下文章

如何在从我产生的工作中获取一行标准输出时阻止 bash

如何在 Docker Postgres 的脚本中创建用户/数据库

为啥在从 C# 创建的进程中运行 bash 命令时我的 $PATH 不同?

通过bash将输出重定向到文件[重复]

将我的 bash 脚本变成守护进程的选项

Json String Parsing 在从 MSDOS 运行时有效,但在 Windows 上的 Ubuntu 上的 Bash 中无效 [重复]