PostgreSQL从入门到不后悔

安装 PostgreSQL 10

下载 PostgreSQL 10,postgresql-10.1-3-linux-x64-binaries.tar.gz。下载地址:https://get.enterprisedb.com/postgresql/postgresql-10.1-3-linux-x64-binaries.tar.gz

(注:安装脚本如下(需要有 /opt/local 写权限),可使用如下命令创建 /opt/local 目录。)

1
2
sudo mkdir /opt/local
sudo chown -R $USER:$USER /opt/local

install_pg.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
OPT_BASE=/opt
PGVERSION=10.1
PGBASE=$OPT_BASE/local/pgsql
PGHOME=$OPT_BASE/local/pgsql/$PGVERSION
PGDATA=$OPT_BASE/var/pgsql/$PGVERSION
PG_SOFT_¥TAR="postgresql-10.1-3-linux-x64-binaries.tar.gz"
if [ -d $PGHOME ]; then
rm -rf $PGHOME
elif [ ! -d $PGBASE ]; then
mkdir -p $PGBASE
fi
if [ ! -d $PGDATA ]; then
mkdir -p $PGDATA
fi
echo "Install PostgreSQL"
tar zxf $PG_SOFT_TAR -C $PGBASE
mv $PGBASE/pgsql $PGHOME
cp pg-pwfile $PGHOME
echo "Init PostgreSQL"
pushd $PGHOME
./bin/initdb --pgdata="$PGDATA" --auth=ident --auth-host=md5 --encoding=UTF-8 --locale=zh_CN.UTF-8 --username=postgres --pwfile=pg-pwfile
rm -f pg-pwfile
popd
cp pg_hba.conf $PGDATA
cp postgresql.conf $PGDATA
chmod 600 $PGDATA/*.conf
echo "Start PostgreSQL"
$PGHOME/bin/pg_ctl -D $PGDATA -l logfile start
sleep 5
#cp .pgpass ~/
$PGHOME/bin/psql -h localhost -U postgres -d postgres -f pg_init.sql

install_pg.sh 脚本安装时依赖文件的完整版压缩包在此下载:https://yangbajing.me/files/postgresql10-scripts.tar.gz

  • pg-pwfile:在初始化数据库时设置默认管理员账户的密码
  • pg_hba.conf:默认只允许 127.0.0.1/8 访问数据库,这里改成允许所有网段可访问
  • postgresql.conf:修改数据库监听地址为 * ,监听所有本地网络地址
  • pg_init.sql:创建一个普通账户 yangbajing 和测试用数据库 yangbajing ,密码也设置为 yangbajing

安装后PG数据库管理管理员账号是 postgres,密码为 postgres。同时,还创建了一个普通账号:yangbajing 和同名数据库 yangbajing,密码也是 yangbajing

/opt/local/pgsql/10.1/bin 目录加入系统环境变量。

1
2
echo 'export PATH="/opt/local/pgsql/10.1/bin:$PATH" >> ~/.bashrc
. ~/.bashrc

使用如下命令来启动或停止PostgreSQL 10数据库

启动数据库

1
pg_ctl -D /opt/local/var/pgsql/10.1 -l logfile start

停止数据库

1
pg_ctl -D /opt/local/var/pgsql/10.1 -l logfile stop

体验 PG

输入以下命令访问PG数据库:

1
psql -h localhost -U yangbajing -d yangbajing -W

根据提示输入密码登录,进入 psql 的 REPL 界面。

1
2
3
4
5
Password for user yangbajing:
psql.bin (10.1)
Type "help" for help.
yangbajing=>

先建立一些测试表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE t_role (
id INT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ
);
CREATE TABLE t_user (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
roles INT [] NOT NULL,
data JSONB,
created_at TIMESTAMPTZ
);
INSERT INTO t_role (id, name, created_at) VALUES (1, '超级管理员', now()), (2, '管理员', now()), (3, '用户', now());
INSERT INTO t_user(name, roles, data, created_at) VALUES
('root', '{1}', '{"email":"root@yangbajing.me"}', now()),
('羊八井', '{2,3}', '{"email":"yangbajing"}', now()),
('哈哈', '{3}', '{"email":"haha@yangbajing.me"}', now());

先来执行两个简单的 SELECT 查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yangbajing=> select * from t_role;
id | name | created_at
----+------------+-------------------------------
1 | 超级管理员 | 2018-02-01 22:03:17.168906+08
2 | 管理员 | 2018-02-01 22:03:17.168906+08
3 | 用户 | 2018-02-01 22:03:17.168906+08
(3 rows)
yangbajing=> select * from t_user;
id | name | roles | data | created_at
----+--------+-------+---------------------------------------+-------------------------------
2 | root | {1} | {"email": "root@yangbajing.me"} | 2018-02-01 22:06:21.140465+08
3 | 哈哈 | {3} | {"email": "haha@yangbajing.me"} | 2018-02-01 22:06:21.140465+08
1 | 羊八井 | {2,3} | {"email": "yangbajing@yangbajing.me"} | 2018-02-01 22:04:41.580203+08
(3 rows)

接下来,尝试一些 PG 特色特性。

InsertOrUpdate

插入或更新,是一个很有用的特性,当在主键冲突时可以选择更新数据。在PG中,是使用 ON CONFLICT 来实现这个特性的。

1
2
3
4
INSERT INTO t_role (id, name, created_at)
VALUES (3, '普通用户', now())
ON CONFLICT (id)
DO UPDATE SET name = EXCLUDED.name;

在常用的 INSERT 语句后面用 ON CONFLICT (...) DO .... 语句来指定在某个/些字段出现冲突时需要执行的语句。在 on CONFLICT (...) 里的参数需要是主键或唯一索引(可以为复合字段)。当冲突发生时则会执行 DO .... 后面的语句,这里我们选择更新 name 字段的值。EXCLUDED 是用户引用在 VALUES .... 部分我们将插入的数据,EXCLUDED.name 在这里就是 '普通用户' 。除 DO UPDATE,我们还可以使用 DO NOTHING 来简单的忽略插入时的主键冲突。

SERIAL/BIGSERIAL

看看表 t_user 的结构:

1
2
3
4
5
6
7
8
9
10
11
yangbajing=> \d t_user
Table "public.t_user"
Column | Type | Collation | Nullable | Default
------------+--------------------------+-----------+----------+------------------------------------
id | bigint | | not null | nextval('t_user_id_seq'::regclass)
name | character varying(255) | | not null |
roles | integer[] | | not null |
data | jsonb | | |
created_at | timestamp with time zone | | |
Indexes:
"t_user_pkey" PRIMARY KEY, btree (id)

在建表时 id 字段的类型定义的是 BIGSERIAL ,但这里却是显示的 bigint 类型;另外,还多了一个默认值:nextval('t_user_id_seq'::regclass) 。这是 PG 中的 序列 ,PG中使用序列来实现 自增值 的特性。

序列:t_user_id_seq

1
2
3
4
5
6
yangbajing=> \d t_user_id_seq
Sequence "public.t_user_id_seq"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
--------+-------+---------+---------------------+-----------+---------+-------
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
Owned by: public.t_user.id

也可以先创建序列,再设置字段的默认值为该序列的下一个值。

1
CREATE SEQUENCE t_user_id2_seq INCREMENT BY 1 MINVALUE 1 START WITH 1;

这里创建一个序列,设置最小值为1,从1开始按1进行递增。

数组类型

在创建 t_user 表的 roles 字段时,使用了数组类型 INT [] 。数组类型对于我们的数据建模来说很有用,使用得好可以大大减少关系表的数量。

根据索引返回值

1
2
3
4
5
6
7
yangbajing=> SELECT id, name, roles[2], created_at FROM t_user;
id | name | roles | created_at
----+--------+-------+-------------------------------
2 | root | | 2018-02-01 22:06:21.140465+08
3 | 哈哈 | | 2018-02-01 22:06:21.140465+08
1 | 羊八井 | 1 | 2018-02-01 22:04:41.580203+08
(3 rows)

注意:PG 中,索引下标从0开始

以行的形式输出数组元素

1
2
3
4
5
6
7
8
yangbajing=> SELECT id, unnest(roles) AS role_id FROM t_user;
id | role_id
----+---------
2 | 1
3 | 3
1 | 2
1 | 1
(4 rows)

包含查找

1
2
3
4
5
yangbajing=> SELECT * FROM t_user WHERE roles @> ARRAY[1,2];
id | name | roles | data | created_at
----+--------+-------+---------------------------------------+-------------------------------
1 | 羊八井 | {2,1} | {"email": "yangbajing@yangbajing.me"} | 2018-02-01 22:04:41.580203+08
(1 row)

重叠查找

重叠查找和包含查找的不同之处在重叠查找只要匹配数组中的任意一个元素则为 true。

1
2
3
4
5
6
yangbajing=> SELECT * FROM t_user WHERE roles && ARRAY[1,2];
id | name | roles | data | created_at
----+--------+-------+---------------------------------------+-------------------------------
2 | root | {1} | {"email": "root@yangbajing.me"} | 2018-02-01 22:06:21.140465+08
1 | 羊八井 | {2,1} | {"email": "yangbajing@yangbajing.me"} | 2018-02-01 22:04:41.580203+08
(2 rows)

数组转换成字符串

array_to_string 函数的第二个参数指定转换成字符串后使用的分隔字符。

1
2
3
4
5
6
7
yangbajing=> SELECT id, name, array_to_string(roles, ',') AS role_ids FROM t_user;
id | name | role_ids
----+--------+----------
2 | root | 1
3 | 哈哈 | 3
1 | 羊八井 | 2,1
(3 rows)

JSON类型

TODO

Tooltip

随机获取一个用户

使用 random 函数来排序,并返回第一条记录。

1
2
3
4
5
6
7
8
9
10
11
yangbajing=> SELECT * FROM t_user ORDER BY random() LIMIT 1;
id | name | roles | data | created_at
----+------+-------+---------------------------------+-------------------------------
3 | 哈哈 | {3} | {"email": "haha@yangbajing.me"} | 2018-02-01 22:06:21.140465+08
(1 row)
yangbajing=> SELECT * FROM t_user ORDER BY random() LIMIT 1;
id | name | roles | data | created_at
----+--------+-------+---------------------------------------+-------------------------------
1 | 羊八井 | {2,1} | {"email": "yangbajing@yangbajing.me"} | 2018-02-01 22:04:41.580203+08
(1 row)

FDW

在之前创建的默认 PG 数据库之外,接下来将创建一个绑定到端口 5433 的另一个 PG 数据库。

(注:PostgreSQL中,创建和操作 xxx_fdw 扩展需要管理员权限)

使用 postgres_fdw 访问其它Postgres数据库

先创建第2个数据库,用于模拟远程访问。以下是创建第2个数据库的命令:

1
2
3
4
mkdir /opt/haishu/var/pgsql/10.1_2
echo "postgres" > pg-pwfile
/opt/haishu/local/pgsql/10.1/bin/initdb --pgdata=/opt/haishu/var/pgsql/10.1_2 --auth=ident --auth-host=md5 --encoding=UTF-8 --locale=zh_CN.UTF-8 --username=postgres --pwfile=pg-pwfile
rm pg-pwfile

数据库创建成功后会输入如下提示:

1
2
3
4
....
Success. You can now start the database server using:
/opt/haishu/local/pgsql/10.1/bin/pg_ctl -D /opt/haishu/var/pgsql/10.1_2 -l logfile start

这里我们需要修改第2个数据库 10.1_2 监听端口号,以免和已安装数据库冲突。编辑 /opt/haishu/var/pgsql/10.1_2/postgresql.conf 文件,修改内容如下:

1
port = 5433

再使用 /opt/haishu/local/pgsql/10.1/bin/pg_ctl -D /opt/haishu/var/pgsql/10.1_2 -l logfile start 命令启动第2个数据库。

1
2
3
/opt/haishu/local/pgsql/10.1/bin/pg_ctl -D /opt/haishu/var/pgsql/10.1_2 -l logfile start
waiting for server to start.... done
server started

现在,第2个PG数据库已建好,我们分别登录两个数据库。

使用账号:yangbajing 登录第1个PG

1
2
3
4
5
6
$ psql -h localhost -U yangbajing -d yangbajing
Password for user yangbajing:
psql.bin (10.1)
Type "help" for help.
yangbajing=>

使用账号:postgres 登录第2个PG,并创建测试用户 pg2 和测试数据库 pg2

1
2
3
4
5
6
7
8
9
10
11
12
]$ psql -h localhost -p 5433 -U postgres -d postgres
Password for user postgres:
psql.bin (10.1)
Type "help" for help.
postgres=# create user pg2 encrypted password 'pg2';
CREATE ROLE
postgres=# create database pg2 owner=pg2 template=template1;
CREATE DATABASE
postgres=# \c pg2
You are now connected to database "pg2" as user "postgres".
pg2=#

创建 postgres_fdw 扩展,以支持使用外部表的形式访问其它数据库。使用 postgres_fdw 主要步骤如下:

  1. 安装扩展 ,CREATE EXTENSION
  2. 创建外部服务对象:CREATE SERVER
  3. 创建用户映射:CREATE USER MAPPING
  4. 创建外部表:CREATE FOREIGN TABLEIMPORT FOREIGN SCHEMA

操作示例

安装扩展

1
2
pg2=# create extension postgres_fdw ;
CREATE EXTENSION

创建外部连接数据库

1
2
pg2=# CREATE SERVER foreign_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'localhost', port '5432', dbname 'yangbajing');
CREATE SERVER

创建用户映射

1
2
pg2=# CREATE USER MAPPING FOR pg2 SERVER foreign_server OPTIONS (user 'yangbajing', password 'yangbajing');
CREATE USER MAPPING

创建外部表

1
2
3
4
5
6
CREATE FOREIGN TABLE foreign_t_role (
id INT NOT NULL,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ
) SERVER foreign_server
OPTIONS (schema_name 'public', table_name 't_role');

在创建用户映射时,是将本地的 pg2 用户映射到远程服务器用户的,我们需要使用 pg2 账号登录来访问外部表。首先给 pg 赋于权限:

1
2
pg2=# grant ALL ON TABLE foreign_t_role to pg2 ;
GRANT

使用 pg2 账号登录访问外部表

1
2
3
4
5
6
7
8
psql -h localhost -U pg2 -d pg2
pg2=> select * from foreign_t_role ;
id | name | created_at
----+------------+-------------------------------
1 | 超级管理员 | 2018-02-01 22:03:17.168906+08
2 | 管理员 | 2018-02-01 22:03:17.168906+08
3 | 普通用户 | 2018-02-01 22:03:17.168906+08
(3 rows)

向外部表插入数据:

1
2
pg2=> INSERT INTO foreign_t_role(id, name, created_at) VALUES(4, '来宾', now());
INSERT 0 1

回到第1个数据库,我们可以看到由外部表插入进来的数据:

1
2
3
4
5
6
7
8
yangbajing=> select * from t_role ;
id | name | created_at
----+------------+-------------------------------
1 | 超级管理员 | 2018-02-01 22:03:17.168906+08
2 | 管理员 | 2018-02-01 22:03:17.168906+08
3 | 普通用户 | 2018-02-01 22:03:17.168906+08
4 | 来宾 | 2018-02-02 11:47:02.296937+08
(4 rows)

使用 mysql_fdw 访问MySQL数据库

mysql_fdw 由 EnterpriseDB 公司提供,我们需要从源码开始编译它。https://github.com/EnterpriseDB/mysql_fdw

安装 mysql_fdw 步骤如下:

1、下载源码包。

1
2
git clone https://github.com/EnterpriseDB/mysql_fdw
cd mysql_fdw

2、配置 pg_config 目录:export PATH=/opt/local/pgsql/10.1/bin:$PATH

3、配置 mysql_config 目录。这里使用官方的 YUM 源安装 MySQL 5.7,详细的安装使用说明请查阅官方文档:https://dev.mysql.com/doc/mysql-yum-repo-quick-guide/en/

1
2
3
4
sudo rpm -ivh https://dev.mysql.com/get/mysql57-community-release-fc27-10.noarch.rpm
sudo dnf erase mariadb-*
sudo dnf makecache
sudo dnf install mysql-community-server mysql-community-devel

4、编译并安装 mysql_fdw 扩展

1
2
make USE_PGXS=1 install
sudo sudo ldconfig // 重建系统动态链接库缓存

在 MySQL 中创建测试数据

(注:MySQL的使用非本文重点,请自行查阅相关文档)

登录 MySQL 并创建测试表及插入测试数据:

1
2
3
4
5
6
7
8
SET time_zone = '+08:00';
CREATE TABLE t_book(
isbn VARCHAR(255) PRIMARY KEY,
title VARCHAR(255),
created_at DATETIME);
INSERT INTO t_book(isbn, title, created_at) VALUES
('978-7-121-32529-8', 'Akka应用模式:分布式应用程序设计实践指南', '2017-10-01'),
('978-7-115-46938-0', 'Kafka技术内幕:图文详解Kafka源码设计与实现', '2017-11-01');

在 PG 中访问 MySQL

类似使用 postgres_fdw,使用 mysql_fdw 也需要 PG 数据库的管理员权限。

1、创建扩展:

1
CREATE EXTENSION mysql_fdw;

2、创建外部服务对象:

1
CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host '127.0.0.1', port '3306');

3、创建用户映射

1
CREATE USER MAPPING FOR yangbajing SERVER mysql_server OPTIONS(username 'yangbajing', password 'yang.Bajing2018');

4、创建外部表

1
2
3
4
5
6
CREATE FOREIGN TABLE foreign_t_book(
isbn VARCHAR(255),
title VARCHAR(255),
created_at TIMESTAMPTZ
) SERVER mysql_server OPTIONS(dbname 'yangbajing', table_name 't_book');
GRANT ALL ON TABLE foreign_t_book to yangbajing ;

现在,可以在 PG 中访问并使用在 MySQL 中创建的表和数据了,和 postgres_fdw 一样,也可以远程修改原表的内容。

接下来

本文简单介绍了 PostgreSQL 10 的安装、使用和一些特性,下一篇文章从应用开发的角度来谈谈怎样使用 PG。介绍怎样使用 JDBC 来访问 PostgreSQL 数据库,使用 Scala 编程语言作示例。