我想通过JDBC创建一个不存在的数据库。与MySQL不同,PostgreSQL不支持
create if not exists
语法。实现这一目标的最佳方法是什么?
应用程序不知道数据库是否存在。它应该进行检查,如果数据库存在,就应该使用它。因此,连接到所需的数据库是有意义的,如果由于不存在数据库而导致连接失败,它应该创建新的数据库(通过连接到默认的
postgres
数据库)。我检查了Postgres返回的错误代码,但我找不到任何种类相同的相关代码。
实现这一点的另一种方法是连接到
postgres
数据库,并检查所需的数据库是否存在,然后采取相应的操作。第二个问题有点单调乏味。
有没有办法在Postgres中实现这一功能?
我不得不使用一个略微扩展的@Erwin Brandstetter版本:
DO
DECLARE
_db TEXT := 'some_db';
_user TEXT := 'postgres_user';
_password TEXT := 'password';
BEGIN
CREATE EXTENSION IF NOT EXISTS dblink; -- enable extension
IF EXISTS (SELECT 1 FROM pg_database WHERE datname = _db) THEN
RAISE NOTICE 'Database already exists';
PERFORM dblink_connect('host=localhost user=' || _user || ' password=' || _password || ' dbname=' || current_database());
PERFORM dblink_exec('CREATE DATABASE ' || _db);
END IF;
$do$
我必须启用
dblink
扩展,另外我还必须提供dblink的凭据。适用于Postgres 9.4。
另一种选择是,以防你想要一个shell脚本,如果数据库不存在,它会创建数据库,否则就会保持它的原样:
psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'my_db'" | grep -q 1 || psql -U postgres -c "CREATE DATABASE my_db"
我发现这在devops配置脚本中很有帮助,您可能希望在同一实例上多次运行这些脚本。
对于那些想要解释的人:
-c = run command in database session, command is given in string
-t = skip header and footer
-q = silent mode for grep
|| = logical OR, if grep fails to find match run the subsequent command
升级到PostgreSQL 9.5或更高版本。在9.5版中引入了If (not) exists。
PostgreSQL不支持
IF NOT EXISTS
for
CREATE DATABASE
语句。仅在
CREATE SCHEMA
中支持。此外,
CREATE DATABASE
不能在事务中发出,因此它不能在带有异常捕获的
DO
块中。
当发出
CREATE SCHEMA IF NOT EXISTS
并且模式已经存在时,将引发具有重复对象信息的通知(而不是错误)。
要解决这些问题,您需要使用
dblink
扩展,它打开一个新的数据库服务器连接并执行查询,而无需进入事务。您可以通过提供空字符串来重用连接参数。
下面是
PL/pgSQL
代码,它完全模拟了
CREATE DATABASE IF NOT EXISTS
,具有与
CREATE SCHEMA IF NOT EXISTS
中相同的行为。它通过
dblink
调用
CREATE DATABASE
,捕捉
duplicate_database
异常(当数据库已经存在时发出),并通过传播
errcode
将其转换为通知。String message以与
CREATE SCHEMA IF NOT EXISTS
相同的方式附加了
, skipping
。
CREATE EXTENSION IF NOT EXISTS dblink;
DO $$
BEGIN
PERFORM dblink_exec('', 'CREATE DATABASE testdb');
EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
$$;
这个解决方案不像在其他答案中那样有任何竞争条件,在检查数据库是否存在和自己创建数据库之间,数据库可以由外部进程(或相同脚本的其他实例)创建。
此外,当
CREATE DATABASE
由于数据库已存在以外的其他错误而失败时,此错误将作为错误传播,而不是静默丢弃。只有catch for
duplicate_database
错误。所以它的行为就像
IF NOT EXISTS
应该做的那样。
您可以将此代码放入自己的函数中,直接调用或从事务中调用。仅回滚(恢复已删除的数据库)将不起作用。
测试输出(通过DO调用两次,然后直接调用):
$ sudo -u postgres psql
psql (9.6.12)
Type "help" for help.
postgres=# \set ON_ERROR_STOP on
postgres=# \set VERBOSITY verbose
postgres=#
postgres=# CREATE EXTENSION IF NOT EXISTS dblink;
CREATE EXTENSION
postgres=# DO $$
postgres$# BEGIN
postgres$# PERFORM dblink_exec('', 'CREATE DATABASE testdb');
postgres$# EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
postgres=#
postgres=# CREATE EXTENSION IF NOT EXISTS dblink;
NOTICE: 42710: extension "dblink" already exists, skipping
LOCATION: CreateExtension, extension.c:1539
CREATE EXTENSION
postgres=# DO $$
postgres$# BEGIN
postgres$# PERFORM dblink_exec('', 'CREATE DATABASE testdb');
postgres$# EXCEPTION WHEN duplicate_database THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
postgres$# END
postgres$# $$;
NOTICE: 42P04: database "testdb" already exists, skipping
LOCATION: exec_stmt_raise, pl_exec.c:3165
postgres=#
postgres=# CREATE DATABASE testdb;
ERROR: 42P04: database "testdb" already exists
LOCATION: createdb, dbcommands.c:467
只需使用
createdb
命令行工具创建数据库:
PGHOST="my.database.domain.com"
PGUSER="postgres"
PGDB="mydb"
createdb -h $PGHOST -p $PGPORT -U $PGUSER $PGDB
如果数据库存在,它将返回一个错误:
createdb: database creation failed: ERROR: database "mydb" already exists
如果您可以使用shell,请尝试
psql -U postgres -c 'select 1' -d $DB &>dev/null || psql -U postgres -tc 'create database $DB'
我认为
psql -U postgres -c "select 1" -d $DB
比
SELECT 1 FROM pg_database WHERE datname = 'my_db'
更容易,而且只需要一种类型的报价,更容易与
sh -c
结合。
我在我的ansible任务中使用它
- name: create service database
shell: docker exec postgres sh -c '{ psql -U postgres -tc "SELECT 1" -d {{service_name}} &> /dev/null && echo -n 1; } || { psql -U postgres -c "CREATE DATABASE {{service_name}}"}'
register: shell_result
changed_when: "shell_result.stdout != '1'"
在我看来,在阅读完所有这些复杂的解决方案后,我几乎忘记了有一种简单的方法可以在shell级别上处理它。尽管这可能不是一些人想要的,但我认为很多人想要简单,而不是创建过程和复杂的构造。
我正在使用docker,下面是我的bash脚本中的重要片段,它在devsetup中加载数据:
execute_psql_command_pipe () {
$DOCKER_COMMAND exec -it $POSTGRES_CONTAINER bash -c "echo \"$1\"| psql -h localhost -U postgres || echo psql command failed - object likely exists"
read -r -d '' CREATE_USER_COMMANDS << EOM
create user User1 WITH PASSWORD 'password';
create user User2 WITH PASSWORD 'password';