PostgreSQL

 

psql - PostgreSQL 交互式终端

PostgreSQL 交互式终端 psql 的使用,包括连接字符串,免密设置,元命令以及常见的 SQL 语句。

macOS 上安装 psql

brew install libpq

# libpq is keg-only, you can add its opt-path to the PATH
# and write to ~/.zshrc
export PATH="/usr/local/opt/libpq/bin:$PATH"

psql 命令摘要

psql [option...] [dbname [username]]

General options:
  -c, --command=COMMAND    run only single command (SQL or internal) and exit
  -d, --dbname=DBNAME      database name to connect to (default: "john")
  -f, --file=FILENAME      execute commands from file, then exit
  -l, --list               list available databases, then exit

连接数据库

要连接到数据库,需要目标数据库名称服务器主机名端口号用户名。可以通过命令行选项(分别为-d-h-p-U)告知psql这些参数。

psql -h HOSTNAME -U USERNAME -d DBNAME

# 或
psql -h HOSTNAME DBNAME USERNAME

通过设置环境变量 PGDATABASEPGHOSTPGPORTPGUSER可以避免每次输入这些信息。

export PGHOST='192.168.10.10'
export PGDATABASE='mydb'
export PGUSER='myname'

psql

通过设置 pgpass 文件可以避免每次输入密码,文件路径为:

  • Unix: ~/.pgpass

  • Windows: %APPDATA%\postgresql\pgpass.conf

也可以通过环境变量 PGPASSFILE 来指定文件路径。

pgpass 中每行的格式为:

hostname:port:database:username:password

前四个字段可以是具体的字符串或*,第一个匹配当前连接的密码会被使用。因此,应该把具体的条目放前面,把通配符多的条目放后面。在 Unix 系统上,还要注意文件的权限是0600

$ vim ~/.pgpass
# hostname:port:database:username:password
192.168.10.10:5432:*:postgres:mypassword

$ chmod 0600 ~/.pgpass

# 测试免密连接
$ psql -c '\conninfo'
You are connected to database "postgres" as user "postgres" on host "192.168.10.10" at port "5432".

$ psql mydb -c '\conninfo'
You are connected to database "mydb" as user "postgres" on host "192.168.10.10" at port "5432".

另一种连接数据库的方式是使用连接字符串(connection strings),有 Keyword/Value连接字符串连接URI(Connection URI) 两种格式。

# keyword=value
psql "host=localhost user=postgres dbname=mydb connect_timeout=10 sslmode=require"

# URI
psql postgresql://localhost:5433/mydb?sslmode=require

Keyword/Value 连接字符串

这种格式中,每个参数都以 keyword = value 的形式来设置,= 两边的空格是可选的。如果参数值为空,或参数值中含有空格,则用单引号把参数值括起来,例如:keyword='a value'。这里了解可识别的参数关键字

host=localhost port=5432 dbname=mydb connect_timeout=10

Connection URIs

一般形式

postgresql://[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&...]

URI scheme指示符可以是 postgresql://postgres://,每个URI部分都是可选的。以下示例说明了有效的URI语法用法:

postgresql://
postgresql://localhost
postgresql://localhost:5433
postgresql://localhost/mydb
postgresql://user@localhost
postgresql://user:secret@localhost
postgresql://other@localhost/otherdb?connect_timeout=10&application_name=myapp
postgresql://host1:123,host2:456/somedb?target_session_attrs=any&application_name=myapp

为了改善与JDBC connection URI的兼容性,将参数 ssl=true 转换为 sslmode=require

psql 常用命令

# 连接数据库,执行命令,然后退出
psql -c '\?'
psql -c '\x' -c 'select * from table1'
psql <<EOF
\x
select * from table1;
select * from table2;
EOF

在命令执行 SQL 文件

有时有太多 sql 语句,不方便在命令行执行,可以放到文件中来一起执行。

psql -h 192.168.1.3 -U user -d mydb -a -f run.sql

-a--echo-all 会将执行的每一行 sql 语句同时打在命令行上。在 psql 的 shell 中,可以使用元命令 \i 读取文件执行 sql。

psql 元命令(Meta-Commands)

psql 中以反斜杠开头的命令是 psql 元命令,该命令由 psql 本身处理。以下是常用命令:

# 连接到 mydb 数据库
\c mydb

# 列出各类数据库对象及信息
# S = show system objects, + = additional detail
\d[S+]                 list tables, views, and sequences
\d[S+]  NAME           describe table, view, sequence, or index
\da[S]  [PATTERN]      list aggregates
\db[+]  [PATTERN]      list tablespaces
\ddp    [PATTERN]      list default privileges
\dg[S+] [PATTERN]      list roles
\di[S+] [PATTERN]      list indexes
\dm[S+] [PATTERN]      list materialized views
\dn[S+] [PATTERN]      list schemas
\dO[S+] [PATTERN]      list collations
\dp     [PATTERN]      list table, view, and sequence access privileges
\ds[S+] [PATTERN]      list sequences
\dt[S+] [PATTERN]      list tables
\dT[S+] [PATTERN]      list data types
\du[S+] [PATTERN]      list roles
\dv[S+] [PATTERN]      list views
\l[+]   [PATTERN]      list databases
\sf[+]  FUNCNAME       show a function's definition
\sv[+]  VIEWNAME       show a view's definition
\z      [PATTERN]      same as \dp

常用元命令

# 列出所有 schema
\dn *

# 列出 pg_catalog schema 下的所有表
\dt pg_catalog.*
\dt pg_*

# 查看 pg_catalog.pg_user 表定义
\d pg_catalog.pg_user

Schemas

Schema 是一个命名空间,可以包含表,视图,索引,数据类型,函数,操作符等数据库命名对象。可以将其类比文件系统中的目录,只是不能多层嵌套。

使用模式的原因有:

  • 允许多个用户使用一个数据库而不会互相干扰。
  • 将数据库对象组织到逻辑组中,使得它们更易于管理。
  • 第三方应用可以放到单独的 schema 中,不会与现有的对象产生命名冲突。

PostgreSQL 会为每个新建数据库创建一个名为 public 的 schema,你创建的任何对象,如果没有指定 schema,PostgreSQL会自动将它放入 public schema,所以下面两条语句作用相同。

CREATE TABLE mytable(...);
CREATE TABLE public.mytable(...);

The System Catalog Schema

除了 public 和用户创建的 schema,每个数据库还包含一个 pg_catalog schema,该 schema 包含系统表以及所有内置数据类型,函数和运算符,每个系统表都以 pg_ 前辍开头。

SELECT * FROM pg_catalog.pg_user;

创建 Schema

-- 为当前用户创建 myschema
CREATE SCHEMA myschema;

-- 创建一个与用户 dbuser 同名的 schema
CREATE SCHEMA AUTHORIZATION dbuser;

-- 为用户 dbuser 创建名为 test 的 schema, 如果该 schema 已经存在,则不做操作
CREATE SCHEMA IF NOT EXISTS test AUTHORIZATION dbuser;

删除 Schema

-- 删除一个空 Schema(不包含任何对象)
DROP SCHEMA myschema;

-- 删除 myschema 和其包含的所有对象
DROP SCHEMA myschema CASCADE;

SQL Commands - DDL

User

-- USER
-- 创建用户 dbuser,该用户有创建数据库和用户的权限
CREATE USER dbuser WITH CREATEDB CREATEROLE PASSWORD 'test';

-- 删除用户 dbuser
DROP USER dbuser;

-- 修改用户名
ALTER USER dbuser RENAME TO testuser;

-- 修改用户 dbuser 的密码
ALTER USER dbuser WITH PASSWORD '123';

-- 给用户添加 SUPERUSER 权限
ALTER USER dbuser WITH SUPERUSER;

-- 撤销用户SUPERUSER CREATEUSER的权限
ALTER USER dbuser WITH NOSUPERUSER NOCREATEDB;

Database

-- DATABASE
-- 创建数据库 mydb
CREATE DATABASE mydb OWNER dbuser;

-- 修改数据库名
ALTER DATABASE mydb RENAME TO testdb;

-- 将数据库拥有者改为 testuser
ALTER DATABASE mydb OWNER TO testuser;

-- DROP DATABASE
DROP DATABASE mydb;

Privileges

-- GRANT
-- 授权 dbuser 可以对 myschema 中的所有表执行 SELECT INSERT 和 UPDATE 操作
GRANT SELECT INSERT UPDATE ON myschema TO dbuser;

-- 授权所有用户对 catalog 表有 SELECT 权限
GRANT SELECT ON catalog TO PUBLIC;

GRANT ALL PRIVILEGES ON DATABASE mydb TO dbuser;

SQL Commands - DML

Create/Insert

-- CREATE TABLE
CREATE TABLE films (
    code        char(5) CONSTRAINT firstkey PRIMARY KEY,
    title       varchar(40) NOT NULL,
    did         integer NOT NULL,
    date_prod   date,
    kind        varchar(10),
    len         interval hour to minute
);

CREATE TABLE distributors (
     did    integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
     name   varchar(40) NOT NULL CHECK (name <> '')
);

-- Copy a table from an existing table including both table structure and data
CREATE TABLE copied AS TABLE existing_table;

-- Copy a table structure without data
CREATE TABLE copied AS TABLE existing_table WITH NO DATA;

-- Copy a table with partial data from an existing table
CREATE TABLE copied AS SELECT * FROM existing_table WHERE conditions;

-- Rename an existing column
ALTER TABLE distributors RENAME COLUMN address TO city;

-- change the types of two existing columns in one operation
ALTER TABLE distributors
  ALTER COLUMN email TYPE varchar(50),
  ALTER COLUMN city TYPE varchar(30);

-- DROP TABLE
DROP TABLE films, distributors;

-- INSERT
INSERT INTO films VALUES
    ('UA502', 'Bananas', 105, '1971-07-13', 'Comedy', '82 minutes');

-- 没有 len 字段,该字段会填入默认值
INSERT INTO films (code, title, did, date_prod, kind)
    VALUES ('T_601', 'Yojimbo', 106, '1961-06-16', 'Drama');

-- 插入多条记录,第二条记录的 date_prod 字段使用 DEFAULT 子句生成默认值
INSERT INTO films (code, title, did, date_prod, kind) VALUES
    ('B6717', 'Tampopo', 110, '1985-02-10', 'Comedy'),
    ('HG120', 'The Dinner Game', 140, DEFAULT, 'Comedy');

-- Insert data from another table
INSERT INTO mytable SELECT * FROM anthoer_tabel WHERE conditions;

Read

-- SELECT by order and display top 5 record
SELECT * FROM films ORDER BY date_prod DESC LIMIT 5;

-- 获取近10年出品的电影
SELECT * FROM films WHERE date_prod > (now() - interval '10 years');

查询 jsonb

SELECT * FROM json_table WHERE json_data::TEXT LIKE '%String%' ORDER BY created LIMIT 10;

IN 操作符

语法

value in (value1, value2, ...)

value in (SELECT columns FROM table_name)

例子

select id, name, age from table_name where id in ('001', '002', '003');

Update

UPDATE films SET kind = 'Dramatic' WHERE kind = 'Drama';

Delete

DELETE FROM films WHERE title='Yojimbo';

日期函数

-- 获取当前完整时间,下面两条命令等效
select now();
select current_timestamp;

-- 获取当前日期
select current_date;

-- 获取当前时间
select current_time;

-- 时间计算,years, months, weeks, days, hours, minutes, seconds
-- 两天后的时间
select now() + interval '2 days';

-- 截取部分时间
select extract(year from now());
select date_part(day, now());

Copy

-- copies a table to the client using the vertical bar (|) as the field delimite
COPY country TO STDOUT (DELIMITER '|');

-- copy data from a file into the country table
COPY country FROM '/usr1/proj/bray/sql/country_data';

-- copy into a file just the countries whose names start with 'A'
COPY (SELECT * FROM country WHERE country_name LIKE 'A%') TO '/usr1/proj/bray/sql/a_list_countries.copy';

-- copy into a compressed file, you can pipe the output through an external compression program
COPY country TO PROGRAM 'gzip > /usr1/proj/bray/sql/country_data.gz';

-- export to a csv file with header
COPY country TO '/tmp/table.csv' (FORMAT csv, HEADER);

-- import the csv file into a table with header
COPY country (country, text, currency) FROM '/var/lib/pgsql/data.csv' (DELIMITER ',', FORMAT csv, header);

注意,输入文件路径可以是绝对路径或相对路径,输出文件路径必须是绝对路径

备份与恢复

pg_dumppg_restore

数据库导出/导入工具。

pg_dump dumps a database as a text file or to other formats.

Usage:
  pg_dump [OPTION]... [DBNAME]

General options:
  -f, --file=FILENAME          output file or directory name
  -F, --format=c|d|t|p         output file format (custom, directory, tar, plain text (default))
  -v, --verbose                verbose mode
  -V, --version                output version information, then exit

Options controlling the output content:
  -t, --table=PATTERN          dump the specified table(s) only
pg_restore restores a PostgreSQL database from an archive created by pg_dump.

Usage:
  pg_restore [OPTION]... [FILE]

General options:
  -d, --dbname=NAME        connect to database name

Options controlling the restore:
  -a, --data-only              restore only the data, no schema
  -c, --clean                  clean (drop) database objects before recreating
  -C, --create                 create the target database
  -n, --schema=NAME            restore only objects in this schema
  -O, --no-owner               skip restoration of object ownership
  -s, --schema-only            restore only the schema, no data
  -t, --table=NAME             restore named relation (table, view, etc.)

示例

将数据库导出为 SQL-script 文件

# 导出 mydb 数据库到 SQL-script 文件
pg_dump -h localhost -p 5432 -U postgres mydb > db.sql

# 将所有数据恢复到新建的 newdb 数据库
psql -U postgres -c 'CREATE DATABASE newdb;'
psql -U postgres -d newdb -f db.sql

将数据库导出为 custom-format 格式的归档文件

# 使用 custom-format 格式将数据库 mydb 导出到归档文件
pg_dump -U postgres -Fc mydb > db.dump

# 将所有数据恢复到新创建的 newdb 数据库
psql -U postgres -c 'CREATE DATABASE newdb;'
pg_restore -U postgres -d newdb db.dump

# 自动创建存档文件所使用的同名数据库,并将所有数据恢复到该数据库
# 使用 -C 选项时,-d 指定的数据库仅用于连接并执行DROP DATABASE和CREATE DATABASE命令。
pg_restore -U postgres -d postgres -C db.dump

导出单个数据库表到 SQL-script 文件

pg_dump -U postgres -t mytab mydb > db.sql

psql -U postgres -d mydb -f db.sql

pg_basebackup

数据库集群备份。pg_dump 只能备份单个数据库,而 pg_basebackup 可以备份整个 PostgreSQL 实例。

编辑主数据库的 pg_hba.conf 文件设置数据库复制权限

# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   replication     postgres                                peer  # 允许本机与数据库同名的 postgres 用户对数据库进行复制
host    replication     postgres        192.168.33.10/32        md5   # 允许从 IP 为 10.0.0.2 的主机使用 postgres 用户及密码登录数据库进行复制
host    replication     all             0.0.0.0/0               trust # 允许从任意主机使用任意数据库用户登录数据库进行复制

在本地备份为 tar.gz 包

pg_basebackup -D backup -Ft -Xs -z -P

在远程机器进行复制并启用

service posgresql-9.4 stop

mv /var/lib/pgsql/9.4/data /var/lib/pgsql/9.4/data.bak

pg_basebackup -h 10.0.0.1 -U postgres -D /var/lib/pgsql/9.4/data -Xs -P

service postgresql-9.4 start

安装 PostgreSQL 数据库

  • 安装系统 AppStream Repo 中的 postgresql module
yum module list 'postgresql*'

yum module install postgresql:9.6

注意,安装官网 Repo 的 postgresql-server 安装包,需要先禁用系统 AppStream Repo 中的 postgresql module stream,否则将无法看到该包。

# Install the repository RPM:
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm

# Disable the built-in PostgreSQL module:
sudo dnf -qy module disable postgresql

# Install PostgreSQL:
sudo dnf install -y postgresql96-server

# Optionally initialize the database and enable automatic start:
sudo /usr/pgsql-9.6/bin/postgresql96-setup initdb
sudo systemctl enable postgresql-9.6
sudo systemctl start postgresql-9.6
  • 安装不再支持的 PostgreSQL 数据库版本,参考官方文档
# Create special repo config file
cat << EOF > /etc/yum.repos.d/pgdg-94.repo
[pgdg94]
name=PostgreSQL 9.4 RPMs for RHEL/CentOS 8
baseurl=https://yum-archive.postgresql.org/9.4/redhat/rhel-8-x86_64
enabled=1
gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-PGDG
EOF

# Disable the built-in PostgreSQL module:
sudo dnf -qy module disable postgresql

# Install PostgreSQL:
sudo dnf install -y postgresql94-server

# Optionally initialize the database and enable automatic start:
sudo /usr/pgsql-9.4/bin/postgresql94-setup initdb
sudo systemctl enable postgresql-9.4
sudo systemctl start postgresql-9.4

升级 PostgreSQL 数据库

9.4 → 9.6

如果已经安装过9.6版本,需要再次将9.4数据库升级到9.6,可以将9.6的data目录备份,然后使用 /usr/pgsql-9.6/initdb 新建data目录。

mv /var/lib/pgsql/9.6/data{,.bak}

/usr/pgsql-9.6/bin/initdb -D /var/lib/pgsql/9.6/data

兼容性检查

/usr/pgsql-9.6/bin/pg_upgrade --old-bindir=/usr/pgsql-9.4/bin/ --new-bindir=/usr/pgsql-9.6/bin/ --old-datadir=/var/lib/pgsql/9.4/data/ --new-datadir=/var/lib/pgsql/9.6/data/ --check
Performing Consistency Checks
-----------------------------
Checking cluster versions                                   ok
Checking database user is the install user                  ok
Checking database connection settings                       ok
Checking for prepared transactions                          ok
Checking for system-defined composite types in user tables  ok
Checking for reg* system OID user data types                ok
Checking for contrib/isn with bigint-passing mismatch       ok
Checking for roles starting with 'pg_'                      ok
Checking for presence of required libraries                 ok
Checking database user is the install user                  ok
Checking for prepared transactions                          ok
Checking for new cluster tablespace directories             ok

*Clusters are compatible*

停掉 9.4 的数据库

service postgresql-9.4 stop

开始升级

/usr/pgsql-9.6/bin/pg_upgrade --old-bindir=/usr/pgsql-9.4/bin/ --new-bindir=/usr/pgsql-9.6/bin/ --old-datadir=/var/lib/pgsql/9.4/data/ --new-datadir=/var/lib/pgsql/9.6/data/

启动 9.6 数据库

service postgresql-9.6 start

使用 psql 登录数据库检查数据库版本

select version();

流复制

部分参考 postgresql-9.6 主从复制(流复制)

前提

主/备数据库信息

  IP Address
Master DB 192.168.33.10
Slave DB 192.168.33.20

主库需要设置配置文件

  • postgresql.conf
  • pg_hba.conf

备库需要设置配置文件

  • recovery.conf
  • .pg_pass

如果需要做主从切换,可以在主备数据库上都配置好这四个文件。

注:recovery.conf 文件在备库上是 recovery.conf,在主库上配置为 recovery.done

主库设置

创建流复制用户

CREATE USER replica WITH REPLICATION LOGIN PASSWORD 'P@ssw0rd!';

数据库配置查询命令

show config_file;
show hba_file;

配置 postgresql.conf

listen_addresses = '*'
port             = 5432
max_connections  = 1024

# replication
max_wal_senders   = 2
wal_level         = hot_standby
wal_keep_segments = 32
hot_standby       = on

配置 pg_hba.conf

TYPE DATABASE USER ADDRESS AUTH-METHOD
local all all   peer
host all all 127.0.0.1/32 trust
host all all 192.168.33.0/24 md5
host replication replica 192.168.33.20/32 md5

配置修改后,可以使用 /usr/pgsql-9.4/bin/pg_ctl reload 命令使配置生效而不重启数据库。

备库设置

在备库启动流复制

主库配置好后,在备库使用 pg_basebackup 命令做一次基准备份,该命令会同步主库的 data 目录到备库,因此对于备库来讲,

  • 如果是新的数据库安装,不需要使用 initdb 命令初始化数据库
  • 如果已经创建了 data 目录,需要先将原来的 data 目录删除或备份到其它地方
service posgresql-9.4 stop

rm -rf /var/lib/pgsql/9.4/data

# -R 表示会在备份结束后自动生成recovery.conf文件,这样就避免了手动创建。
pg_basebackup -h 192.168.33.10 -U replica -D /var/lib/pgsql/9.4/data -Xs -P -R

service posgresql-9.4 start

配置 recovery.conf

recovery_target_timeline = 'latest'
standby_mode = 'on'
# 连接到主库的IP和端口,主库上的用户 replica 和其密码
primary_conninfo = 'host=192.168.33.10 port=5432 user=replica password=P@ssw0rd!'

配置 pgpass

创建 .pgpass 文件,设置主库IP、端口、用户、密码,以便免密连接主库。

cat << EOF > ~/.pgpass
192.168.33.10:5432:*:replica:P@ssw0rd!
EOF

chmod 0600 ~/.pgpass

查看数据库状态

  1. 主/备库状态
# version 9.4
/usr/pgsql-9.4/bin/pg_controldata ${PGDATA}
/usr/pgsql-9.4/bin/pg_controldata ${PGDATA} | grep state

# version 12
/usr/pgsql-12/bin/pg_controldata -D ${PGDATA}

显示结果

# 主库
Database cluster state:               in production

# 备库
Database cluster state:               in archive recovery
  1. 检查异步流复制状态,查询主库 pg_stat_replication
select pid,state,client_addr,sync_priority,sync_state from pg_stat_replication;
  1. 检查当前数据库是否处于备库状态,是的话返回 (t)rue,否的话返回 (f)alse
select pg_is_in_recovery();

主从切换

  1. 停止主库服务
sudo service postgresql-9.4 stop
  1. 提升备库为主库
sudo -i -u postgres /usr/pgsql-9.4/bin/pg_ctl promote -D /var/lib/pgsql/9.4/data
  1. 设置原主库 recovery.conf

  2. 启动原主库作为备库

sudo service postgresql-9.4 start

临时命令

show archive_modes

-- Create restore point
select pg_create_restore_point('first_point');

常见问题

系统开启 SELinux 的话可能会阻止 web 应用访问数据库 (比如通过 php 访问数据库),如果在不关闭 SELinux 的情况下允许对数据库访问,可以设置 httpd_can_network_connect 参数为 on

httpd_can_network_connect (HTTPD Service):: Allow HTTPD scripts and modules to connect to the network.

sudo setsebool -P httpd_can_network_connect_db on

参考文档