简介

Postges 是一个关系型数据库管理系统(DBMS),目前流行度、功能性、口碑均击败同定位的 MySQL。“关系型”指用表格形式存储数据,是很符合直觉的存储形式。另一条数据库赛道是非关系型数据库(NoSQL),代表如 Redis、MongoDB 等,在此不作展开
Postgres 提供了丰富的抽象层级,从上到下是:

  • Cluster,最顶层的概念,可包含多个 database。不同 cluster 在操作系统上使用独立的端口号、数据目录、配置目录、日志文件、角色管理,可以作为整体启停,备份恢复时整个 cluster 也常作为基本单元。cluster 对应实现了复杂功能、包含多个应用的复合体。一般来说,默认的 cluster main 已经足够使用
  • Database 是对外暴露的接口,作为一个整体提供数据。一般 database 对应一个应用
  • Schema 是 table、view 等的逻辑分组,默认的 schema 为 public。例如应用的订单模块、用户模块可使用不同的 schema
  • Table 直接存储数据,列称为 field,行称为 record。table 需要有 primary key 列,这一列必须每个 record 各不相同

此外,从 table 查找的数据可保存为 view,方便下次使用;index 可以加速特定 field 的查询
好了,你已经掌握大多数核心概念,开始上手吧!

安装

本文使用 Windows 的 Linux 子系统(WSL2),Linux 发行版为 Ubuntu,postgres 版本为 16
参考官方文档,首先更新APT存储库,以保证能找到较新的版本,然后安装

sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
sudo apt update
sudo apt install postgresql-16

这时,一个名为 postgres 的系统用户已经自动被创建了,是 postgres 的默认超级管理员。可 cat /etc/passwd 查看

postgres:x:108:118:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash

为什么不直接用 root 做 postgres 的超级管理员?新建一个用户,有授予最小权限、方便审计追踪、便于系统维护等多重优势
初始的数据库文件在 /var/lib/postgresql/16,配置文件在 /etc/postgresql/16,当前只有默认的 cluster main

使用

服务管理

Postgres 一旦安装,就会作为服务在后台启动。你可以通过多种方式连接该服务,例如使用默认的命令行工具 psql,使用可视化的工具如 navicat,直接通过链接接入应用,甚至通过编程接口访问数据库
首先,我们检查服务是否运行正常

sudo systemctl status postgresql*

你将看到类似这样的输出,Cluster 16-main 代表已启动的 main cluster,RDBMS 代表管理服务。至于为什么有个扎眼的 active (exited)这里有详细解释
image.png
如果运行不正常,可查看日志。日志文件名格式为 postgresql-16-<cluster>.log

sudo tail -n 50 /var/log/postgresql/postgresql-16-main.log

只有在 cluster 正常运行时,数据库才可以被连接。使用以下命令可以管理 postgres 服务的启动、重启、停止

sudo systemctl start postgresql
sudo systemctl restart postgresql
sudo systemctl stop postgresql

为了便于演示,我们创建一个名为 demo 的 postgres cluster

sudo -u postgres pg_createcluster 16 demo --start
pg_lsclusters

如前所述,你会看到 cluster demo 使用新端口 5433
image.png

Psql CMD

系统切换到 postgres 管理员用户,调用 psql 进入 postgres 命令行

[william@winL pg]$sudo -i -u postgres
postgres@winL:~$ psql -p 5433
postgres=# 

使用 \l 列出 database,\du 列出 roles。可以看到有名为 postgres, template0, template1 三个自带的数据库,一般情况下不要修改它们。还有一个名为 postgres 的 role,属性为超级用户。请注意区分操作系统层面的 system user 和数据库管理系统层面的 role

postgres=# \l
                                                   List of databases
   Name    |  Owner   | Encoding |
-----------+----------+----------+
 postgres  | postgres | UTF8     |
 template0 | postgres | UTF8     |
           |          |          |
 template1 | postgres | UTF8     |
           |          |          |
(3 rows)

postgres=# \du
                             List of roles
 Role name |                         Attributes                       
-----------+------------------------------------------------------------
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS

如无必要,不应使用超级管理员访问数据库。我们先创建新 role william,授予其创建 database 的权限,再中断连接、切回到 Linux 系统的同名普通用户

postgres=# CREATE ROLE william CREATEDB LOGIN PASSWORD '000000';
CREATE ROLE
postgres=# \q
postgres@winL:~$ exit
logout

使用 william 连接 demo cluster 下的 postgres database,新建名为 mydb 的 database,并创建名为 info 的 table

[william@winL pg]$psql -d postgres -p 5433
psql (16.4 (Ubuntu 16.4-1.pgdg22.04+1))
Type "help" for help.

postgres=> CREATE DATABASE mydb;
CREATE DATABASE
postgres=> \c mydb
You are now connected to database "mydb" as user "william".
mydb=> CREATE TABLE info();
CREATE TABLE

\dn 列出 shema,\dt 列出 table

mydb=> \dn
  List of schemas
  Name  |  Owner   
--------+----------
 public | postgres
(1 row)

mydb=> \dt
        List of relations
 Schema | Name | Type  |  Owner  
--------+------+-------+---------
 public | info | table | william
(1 row)

你可能注意到了,psql 的语法惯例是,带 \ 的命令小写,保留关键字如 CREATE 大写,database、role、table name 都全小写
如果你想在文件系统中定位 database,需要先确定 oid

mydb=> SELECT oid FROM pg_database WHERE datname = 'mydb';
  oid
-------
 16389

然后在 /var/lib/postgresql/16/demo/base/16389 即可找到。注意,虽然在 postgres 内部, mydb 由 role william 创建、归属权也是 role william,但在 Linux 操作系统层面,数据库文件的归属者仍是系统用户 postgres
如果你使用 postgres 系统用户,但希望通过其他用户连接 postgres,可能会遇到 Peer authentication 报错

postgres@winL:~$ psql -U william -p 5433
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: FATAL:  Peer authentication failed for user "william"

这是因为默认下,postgres 对本地(local)连接只允许 peer 认证,也就是 system user 与 postgres role 同名下的免密码登录
更改默认行为需要在 /etc/postgresql/16/demo/pg_hba.conf,将 METHOD 由 peer 改为 scram-sha-256,即密码验证

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     peer

但建议不要更改。这可以简化本地开发、减少密码泄露风险,而且不影响非本地的密码访问

打开 navicat,新建连接。Connection Name 随便写,本地的 host 是 localhost,cluster demo 的端口(port)是 5433。登录 postgres role 可以创建 role、新 database,在开发测试时比较方便
image.png
但你还记得吗,role postgres 之前都是 peer 登录,还没设置密码呢

[william@winL ~]$sudo -i -u postgres
postgres@winL:~$ psql -p 5433
postgres=# ALTER ROLE postgres WITH PASSWORD 'your_password';

设好后连接,就能可视化地查看、编辑 table
image.png
在特定 table 上右键,选择 design table,可以编辑 table 的 field;左下角的 + 可以新增 record
image.png
这时在 psql 中,也可以看到新增的记录

mydb=> SELECT * FROM info;
 name | gender
------+--------
 Lily | female
 Tom  | male
(2 rows)

接入应用

在这里,我们用 docker-compose 搭建两个应用,连接本地的 postgres 保存数据,看看真实场景下数据库与应用是如何协作的。本节假定读者能够使用 dockerdocker-compose
在 navicat 创建 role halo,为了演示密码就设为简单的 666666
image.png
在 navicat 创建 database halo,并将归属权分配给 role halo
image.png
同理创建 role miniflux 和 database miniflux
新建 docker-compose.yml,为简便起见使用 host 网络模式。指定数据库连接时,格式为 postgres://miniflux:666666@localhost:5433/miniflux?sslmode=disable,即 role:password@host:port/database?params

version: "3.4"
services:
  halo:
    image: halohub/halo:2.13
    network_mode: host
    command:
      - --spring.r2dbc.url=r2dbc:pool:postgresql://halo:666666@localhost:5433/halo
      - --spring.sql.init.platform=postgresql
      - --server.port=9000

  miniflux:
    image: miniflux/miniflux:latest
    network_mode: host
    environment:
      - DATABASE_URL=postgres://miniflux:666666@localhost:5433/miniflux?sslmode=disable
      - PORT=9100
      - RUN_MIGRATIONS=1
      - CREATE_ADMIN=1
      - ADMIN_USERNAME=rss
      - ADMIN_PASSWORD=123456

拉取镜像、创建容器,等待搭建完成

sudo docker-compose up

halo 是个博客平台,浏览器访问 http://localhost:9000,就可以开始写作博客了
image.png
miniflux 是个 RSS 阅读器,浏览器访问 http://localhost:9100,使用账号 rss 和密码 123456 登录,就可以在菜单 Feeds (+) 处订阅 RSS 源。例如 https://feeds.feedburner.com/ruanyifeng,订阅阮一峰的《科技爱好者周刊》
image.png
这时,我们回过头看两个应用的数据库架构
halo 的数据库中只有一张 table extension,表中每条 record 都是一个 blob 对象,内部保存的应该是 json,相当于把关系型数据库当非关系型使用,非常简单粗暴。
image.png
miniflux 的数据库显然经过更多的设计,有多个不同功能的 table,其中 entries 存储了所有的阅读条目
image.png
可以看到,虽然有很多所谓数据库设计的“最佳实践”,但在实际生产中还是有很多的自由度

备份恢复

准备

我们对备份最朴素的理解是,第一次创建全量备份,之后每次创建增量备份,即只存储上一次备份以来新增的数据。需要恢复时,将全量备份和历次增量备份结合,整合最新备份用于恢复
在 postgres 中,除了以上基本逻辑外,Write-Ahead Logging(WAL)日志文件记录了数据库的每一次操作,为我们提供了恢复到任意时间点(Point-in-Time Recovery, PITR)的可能
既然有了 WAL,增量备份不就没必要了吗?实际两者互为补充。增量备份粒度不够,但节省空间、恢复速度快
pgBackRest 是专为 postgres 设计的第三方备份软件,相比于 postgre 自带的 pg_dump,pg_basebackup 命令,在压缩、并行、远程备份、自动化等方面做了大量优化
安装 pgbackrest,创建日志、配置目录、备份目录,将权限都赋予系统用户 postgres

sudo apt install pgbackrest
sudo mkdir -m 755 /var/log/pgbackrest
sudo mkdir /etc/pgbackrest /var/lib/pgbackrest
sudo chown postgres:postgres /var/log/pgbackrest /etc/pgbackrest /var/lib/pgbackrest

WAL 文件默认在 /var/lib/postgresql/16/demo/pg_wal 目录,既然目的是备份,我们应默认所有 postgres 相关目录都可能被清空,需要额外的安全位置保存 WAL
可在 /etc/postgresql/16/demo/postgresql.conf 配置保存 WAL 的命令,重启 postgres 应用配置

archive_command = 'pgbackrest --stanza=demo archive-push %p'
archive_mode = on
max_wal_senders = 3
wal_level = replica

其中,%p 代表 postgres 归档的 WAL 文件, pgbackrest archive-push 命令将 WAL压缩复制到 pgbackres 的相应目录。--stanza=demo 的含义下文详解

配置

切换到 postgres 用户

sudo su postgres

/etc/pgbackrest/pgbackrest.conf 中写入配置,在备份和恢复时,这些配置都会被读取

[demo]
pg1-path=/var/lib/postgresql/16/demo
pg1-port=5433

[global]
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPvTGirhPygu4jOKOXf9LO4vjfO
repo1-cipher-type=aes-256-cbc
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2

[global:archive-push]
compress-level=3

每个 postgres cluster 都可单独或组合配置,这在 pgbackrest 中称为 stanza。 [demo] 开启了一个 stanza,我们希望它对应 postgres 的 cluster demopg1-path,pg1-port 指定数据路径、端口
[global] 部分的内容对所有 stanza 生效

  • repo1-cipher-pass 是个密码,指定后会加密备份文件。实际生产中,可通过将配置文件设为仅 postgres 用户可读、从环境变量读取等提高安全性
  • repo1-path 指定了备份的保存路径
  • repo1-retention-full 指定全量备份最多保留的个数,旧的全量备份会被自动删除

然后运行初始化命令

pgbackrest stanza-create --stanza demo

如果遇到问题可调出日志

pgbackrest --log-level-console=debug check

执行

开始备份,首次执行全量备份,之后默认增量备份

pgbackrest --stanza=demo --log-level-console=info backup

调用 pgbackrest info,即可看到备份信息

stanza: demo
status: ok
cipher: aes-256-cbc

db (current)
    wal archive min/max (16): 000000010000000000000002/000000010000000000000004

    full backup: 20240822-121032F
        timestamp start/stop: 2024-08-22 12:10:32+08 / 2024-08-22 12:10:37+08
        wal start/stop: 000000010000000000000004 / 000000010000000000000004
        database size: 45.2MB, database backup size: 45.2MB
        repo1: backup set size: 6MB, backup size: 6MB

在新主机从头恢复,需要 stanza-create 建好 stanza,并将备份文件放置在对应的路径。恢复之前要停掉 postgres 服务。

pgbackrest --stanza=demo --log-level-console=detail restore

在同一台主机上差异恢复,使用 --delta 选项。如果两个备份间没有任何事务,恢复会失败

pgbackrest --stanza=demo --log-level-console=detail --delta restore

还可利用 WAL 文件,恢复到特定的时间点

pgbackrest --stanza=demo --delta \
	--type=time --target="2024-08-22 12:40:00+08" \
	--target-action=promote \
	--log-level-console=detail restore

如果你需要恢复到历史时间,确保对 timeline 有所了解。简单来说,恢复会创建新的 timeline,可在 WAL 的前 8 个数字确认目前的 timeline。如果恢复时 timeline 没有对应,将导致 cluster 无法启动
恢复到最新数据时,postgres 永远会切换到最新的 timeline,不需多加考虑;但恢复到历史时间后,将会存在多条 timeline 的重合。可在 /etc/postgresql/16/demo/postgresql.conf 指定恢复的 timeline

云备份

Pgbackrest 支持通过 S3 协议备份,这意味着你可以备份到亚马逊、腾讯、阿里、华为、七牛等任意云厂商,甚至自建的 S3 服务器
实现方式也很简单,修改配置即可

[demo]
pg1-path=/var/lib/postgresql/16/demo
pg1-port=5433

[global]
repo1-type=s3
repo1-s3-key=your_accessKey
repo1-s3-key-secret=your_secretKey
repo1-s3-endpoint=s3.cn-south-1.qiniucs.com
repo1-s3-region=cn-south-1
repo1-s3-bucket=will-dbs
repo1-path=/demo
repo1-retention-full=3
start-fast=y
delta=y
compress-type=zst
compress-level=6
log-level-console=info
log-level-file=debug

当然,你也能本地、S3 同时备份,续写 repo2 就行

周期备份

只需使用 postgres 用户, crontab -e 打开计划表,添加计划(例如每周备份)

@weekly pgbackrest --stanza=demo backup

保存即可

结语

以上,我们探索了数据库如何配置、使用、备份恢复,现在你应该可以管理好自用的数据库,至少不担心丢数据了
如果你需要一个生产环境下极其强大的数据库及配套基础设施,可以考虑 Pigsty
image.png|578
最后,如果你想删除用于演示的 cluster demo

sudo pg_dropcluster 16 demo