PostgreSQL 基础学习
一、PostgreSQL 介绍
1、简介
PostgreSQL 是一个功能强大的开源数据库系统。经过长达15年以上的积极开发和不断改进,PostgreSQL已在可靠性、稳定性、数据一致性等获得了业内极高的声誉。目前PostgreSQL可以运行在所有主流操作系统上,包括Linux、Unix和Windows。

PostgreSQL 是完全的事务安全性数据库,支持丰富的数据类型(如JSON和JSONB类型、数组类型)和自定义类型。PostgreSQL数据库提供了丰富的接口,可以很方便地扩展它的功能,如可以在GiST框架下实现自己的索引类型,支持使用C语言写自定义函数、触发器,也支持使用流行的编程语言写自定义函数。PL/Perl提供了使用Perl语言写自定义函数的功能,当然还有PL/Python、PL/Java、PL/Tcl等。
作为一种企业级数据库,PostgreSQL以它所具有的各种高级功能而自豪,像多版本并发控制( MVCC )、按时间点恢复(PITR)、表空间、异步复制、嵌套事务、在线热备、复杂查询的规划和优化以及为容错而进行的预写日志等。它支持国际字符集、多字节编码并支持使用当地语言进行排序、大小写处理和格式化等操作。它也在所能管理的大数据量和所允许的大用户量并发访问时间具有完全的高伸缩性。
2、PostgreSQL 的优势
PostgreSQL 数据库是目前功能最强大的开源数据库,它是最接近工业标准SQL92的查询语言,至少实现了SQL:2011标准中要求的179项主要功能中的160项。
| 优势 | 说明 |
|---|---|
| 稳定可靠 | 支持事务、WAL、MVCC、PITR、流复制,适合承载核心业务数据 |
| SQL 能力强 | 支持复杂查询、窗口函数、CTE、多种 Join、子查询、聚合分析 |
| 数据类型丰富 | 支持 JSONB、数组、范围类型、UUID、网络地址、几何类型等 |
| 扩展能力强 | 支持插件扩展,例如 pg_stat_statements、PostGIS、pg_trgm 等 |
| 运维观测能力好 | 内置大量系统视图,如 pg_stat_activity、pg_locks、pg_stat_database |
| 开源免费 | 使用类 BSD 协议,适合企业自建和二次集成 |
| 生态成熟 | 支持主流语言和框架,例如 Java、Go、Python、Node.js、PHP 等 |
- 稳定可靠:PostgreSQL是唯一能做到数据零丢失的开源数据库。目前有报道称国内外有部分银行使用PostgreSQL数据库。
- 开源省钱:PostgreSQL数据库是开源的、免费的,而且使用的是类BSD协议,在使用和二次开发上基本没有限制。
- 支持广泛:PostgreSQL 数据库支持大量的主流开发语言,包括C、C++、Perl、Python、Java、Tcl以及PHP等。
- PostgreSQL社区活跃:PostgreSQL基本上每3个月推出一个补丁版本,这意味着已知的Bug很快会被修复,有应用场景的需求也会及时得到响应。
3、PostgreSQL 对比 MySQL
Postgresql和Mysql都是开源数据库。
- 功能强大:支持所有主流的多表连接查询的方式,如“Hash JOIN”“Sort Merge JOIN”等;字段类型还支持数组类型,甚至有一些业务功能都不再需要写代码来实现了,直接使用数据库的功能即可解决问题。
- 性能优化工具与度量信息丰富:PostgreSQL数据库中有大量的性能视图,可以方便地定位问题(比如可以看到正在执行的SQL,可以通过锁视图看到谁在等待,以及哪条记录被锁定等)。PostgreSQL中设计了专门架构和进程用于收集性能数据,既有物理I/O方面的统计,也有表扫描及索引扫描方面的性能数据。
- 在线操作功能好:PostgreSQL增加空值列时,本质上只是在系统表上把列定义上,无须对物理结构做更新,这就让PostgreSQL在加列时可以做到瞬间完成。PostgreSQL还支持在线建索引的功能,在创建索引的过程可以不锁更新操作。
- 从PostgreSQL9.1开始,支持同步复制(synchronous replication)功能,通过Master和Slave之间的复制可以实现零数据丢失的高可用方案。可以方便地写插件来扩展PostgreSQL数据库的功能:支持移动互联网的新功能,如空间索引。如果应用的数据访问很简单,那么后端使用MySQL也是很合适的。但是如果应用复杂,而且不想消耗太多的开发资源,那么PostgreSQL是一个很明智的选择。
PostgreSQL 和 MySQL 都是常见的开源数据库,但两者定位略有不同。
| 对比项 | PostgreSQL | MySQL |
|---|---|---|
| SQL 能力 | 更完整,复杂查询能力强 | 常规 OLTP 场景简单高效 |
| 数据类型 | 类型丰富,支持 JSONB、数组、范围类型等 | 类型相对简单,JSON 能力也可用 |
| 扩展能力 | 插件体系强,适合复杂业务 | 插件生态有,但整体扩展性弱一些 |
| 事务一致性 | 一致性和标准兼容性较强 | 依赖具体存储引擎,InnoDB 为主 |
| 运维复杂度 | 参数、权限、连接、Vacuum 等需要理解 | 相对容易上手 |
| 适用场景 | 复杂业务、强一致、报表、GIS、JSONB、企业系统 | 简单业务、高并发读写、互联网常规业务 |
如果业务只是简单的增删改查,MySQL 使用成本较低;如果业务中存在复杂查询、强一致性、JSONB、报表分析、权限管理、GIS、数据建模等需求,PostgreSQL 更合适。
4、不适用 PostgreSQL
在不安装任何扩展包的情况下,PG需要占用100MB以上的磁盘空间,可以看出它的个头是比较大的,因此在一些存储空间极为有限的小型设备上使用PG是不合适的。因此在一些存储空间极为有限的小型设备上使用PG是不合适的,把PG当成简单的缓存区来用也是不合适的,此时应选用一些更轻量级的数据库。
因为作为一款企业级数据库产品,PG对其安全也是极其重视的,因此,如果你在开发一个把安全管理放到应用层去做的轻量级应用,那么PG完善的安全机制反倒会成为负担,因为它的角色和权限管理非常复杂,会带来不必要的管理复杂度和性能损耗。
鉴于上面的种种,PG数据库一般是会和别的数据库搭配使用,使他们各展所长。一种常见的组合是把Redis当成PG的查询缓存来用,另一种的组合是用PG做主数据库。
5、核心概念
| 概念 | 说明 |
|---|---|
| Cluster | PostgreSQL 实例级概念,Ubuntu 下通常是 版本/集群名,例如 16/main |
| Database | 数据库,一个 PostgreSQL Cluster 内可以有多个 Database |
| Role/User | PostgreSQL 中用户和角色统一为 Role,带 LOGIN 权限的角色就是用户 |
| Schema | 数据库内的命名空间,默认常见为 public |
| Data Directory | 数据目录,存放真实数据库文件 |
| WAL | 预写日志,用于事务恢复、复制和备份 |
| postgresql.conf | 主配置文件,控制监听地址、端口、日志、内存、WAL 等参数 |
| pg_hba.conf | 客户端认证配置文件,控制哪些 IP、用户、数据库可以连接 |
| pg_lsclusters | Ubuntu/Debian 下查看 PostgreSQL 集群状态的工具 |
| pg_ctlcluster | Ubuntu/Debian 下启动、停止、重启指定 PostgreSQL 集群的工具 |
二、PostgreSQL 安装部署
PostgreSQL 官方在 Ubuntu 上主要推荐通过系统自带 APT 源或 PostgreSQL 官方 PGDG APT 仓库安装;Ubuntu 默认源包含 PostgreSQL,但版本会随 Ubuntu 版本固定,而 PGDG 仓库可以提供当前受支持的 PostgreSQL 主版本。
PostgreSQL 在 Ubuntu 环境下常见安装方式有三种:
| 安装方式 | 适用场景 | 特点 | 推荐程度 |
|---|---|---|---|
| Ubuntu 默认 APT 源 | 测试环境、版本无特殊要求 | 安装简单,但版本受 Ubuntu 系统版本限制 | 一般 |
| PostgreSQL PGDG APT 源 | 生产环境、需要指定 PostgreSQL 主版本 | 可安装指定主版本,适合标准化部署 | 推荐 |
| 脚本安装 | 多台机器批量部署、标准化环境 | 自动完成目录、参数、权限、密码、服务配置 | 推荐,但脚本要维护 |
1、Ubuntu apt 源安装
PostgreSQL 官方下载页明确区分了 ready-to-use packages / installers 与 source code archive,源码编译应单独归类为“源码安装”。
本次 PostgreSQL 安装采用 阿里云 PostgreSQL PGDG 源,通过安装脚本完成 PostgreSQL 的安装、初始化、目录规划、参数配置和服务启动。
默认安装版本为 PostgreSQL 16,默认目录规划如下:
| 类型 | 路径 |
|---|---|
| 软件目录 | /usr/lib/postgresql/16 |
| 软件软链接 | /usr/local/pgsql |
| 配置目录 | /etc/postgresql/16/main |
| 数据目录 | /data/postgresql/16/main |
| 日志目录 | /data/postgresql/log |
| 备份目录 | /data/postgresql/backup |
| 凭据文件 | /data/postgresql/backup/.postgresql.auth |
| 服务名称 | postgresql@16-main.service |
| 默认端口 | 5432 |
1.1 环境准备
安装依赖包:
apt update
apt install -y ca-certificates curl gnupg lsb-release postgresql-common1.2 获取apt源
install -d /usr/share/postgresql-common/pgdg
curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \
--fail https://mirrors.aliyun.com/postgresql/repos/apt/ACCC4CF8.asc
cat > /etc/apt/sources.list.d/pgdg.sources <<EOF
Types: deb
URIs: https://mirrors.aliyun.com/postgresql/repos/apt
Suites: $(lsb_release -cs)-pgdg
Architectures: $(dpkg --print-architecture)
Components: main
Signed-By: /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc
EOF1.3 安装PostgreSQL
apt update
apt install -y postgresql-16 postgresql-client-16 postgresql-contrib检查安装结果:
systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
Active: active (exited) since Fri 2026-05-15 10:20:51 CST; 5min ago
Main PID: 43730 (code=exited, status=0/SUCCESS)
CPU: 930us
May 15 10:20:51 instance-wc5p3ngj systemd[1]: Starting PostgreSQL RDBMS...
May 15 10:20:51 instance-wc5p3ngj systemd[1]: Finished PostgreSQL RDBMS.
pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
16 main 5432 online postgres /var/lib/postgresql/16/main /var/log/postgresql/postgresql-16-main.log1.4 登陆验证
切换为postgres用户进行登陆:
su - postgres
psql -U postgres
psql (16.14 (Ubuntu 16.14-1.pgdg22.04+1))
Type "help" for help.
postgres-# \l
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype | ICU Locale | ICU Rules | Access privileges
-----------+----------+----------+-----------------+---------+---------+------------+-----------+-----------------------
postgres | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | |
template0 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
(3 rows)
postgres-# \du
List of roles
Role name | Attributes
-----------+------------------------------------------------------------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS
postgres-# \q| 命令 | 作用 |
|---|---|
su - postgres |
切换到 PostgreSQL 默认系统用户 |
psql |
进入 PostgreSQL 命令行 |
\l |
查看数据库列表 |
\du |
查看数据库角色 / 用户 |
\q |
退出 psql |
exit |
退出 postgres 系统用户 |
1.5 备份默认配置文件
cp -a /etc/postgresql/16/main/postgresql.conf /etc/postgresql/16/main/postgresql.conf.bak.$(date +%F_%H%M%S)
cp -a /etc/postgresql/16/main/pg_hba.conf /etc/postgresql/16/main/pg_hba.conf.bak.$(date +%F_%H%M%S)| 命令 | 作用 |
|---|---|
备份 postgresql.conf |
防止配置改错后无法恢复 |
备份 pg_hba.conf |
防止认证规则改错导致无法登录 |
1.6 修改基础配置
修改 pg_hba.conf:
PG_CONF="/etc/postgresql/16/main/postgresql.conf"
sed -ri "s|^[#[:space:]]*listen_addresses[[:space:]]*=.*|listen_addresses = '*'|" "$PG_CONF"
sed -ri "s|^[#[:space:]]*port[[:space:]]*=.*|port = 5432|" "$PG_CONF"
sed -ri "s|^[#[:space:]]*password_encryption[[:space:]]*=.*|password_encryption = 'scram-sha-256'|" "$PG_CONF"
sed -ri "s|^[#[:space:]]*timezone[[:space:]]*=.*|timezone = 'Asia/Shanghai'|" "$PG_CONF"
sed -ri "s|^[#[:space:]]*log_timezone[[:space:]]*=.*|log_timezone = 'Asia/Shanghai'|" "$PG_CONF"
grep -q "^log_min_duration_statement" "$PG_CONF" \
&& sed -ri "s|^[#[:space:]]*log_min_duration_statement[[:space:]]*=.*|log_min_duration_statement = 1000|" "$PG_CONF" \
|| echo "log_min_duration_statement = 1000" >> "$PG_CONF"
grep -q "^log_lock_waits" "$PG_CONF" \
&& sed -ri "s|^[#[:space:]]*log_lock_waits[[:space:]]*=.*|log_lock_waits = on|" "$PG_CONF" \
|| echo "log_lock_waits = on" >> "$PG_CONF"
grep -q "^deadlock_timeout" "$PG_CONF" \
&& sed -ri "s|^[#[:space:]]*deadlock_timeout[[:space:]]*=.*|deadlock_timeout = '1s'|" "$PG_CONF" \
|| echo "deadlock_timeout = '1s'" >> "$PG_CONF"修改配置说明:
| 配置 | 推荐值 | 作用 |
|---|---|---|
listen_addresses |
'*' |
监听所有网卡,允许远程连接 |
port |
5432 |
PostgreSQL 默认端口 |
password_encryption |
'scram-sha-256' |
设置新密码使用 SCRAM 加密方式 |
timezone |
'Asia/Shanghai' |
数据库时区 |
log_timezone |
'Asia/Shanghai' |
日志时间使用中国时区 |
log_min_duration_statement |
1000 |
记录执行超过 1000ms 的 SQL |
log_lock_waits |
on |
记录锁等待事件 |
deadlock_timeout |
'1s' |
锁等待超过 1 秒后触发锁等待日志 |
最终修改的配置:
listen_addresses = '*'
port = 5432
password_encryption = 'scram-sha-256'
timezone = 'Asia/Shanghai'
log_timezone = 'Asia/Shanghai'
log_min_duration_statement = 1000
log_lock_waits = on
deadlock_timeout = '1s'验证配置:
root@instance-wc5p3ngj:/data# grep -E 'listen_addresses|port|password_encryption|timezone|log_timezone|log_min_duration_statement|log_lock_waits|deadlock_timeout' /etc/postgresql/16/main/postgresql.conf
listen_addresses = '*'
port = 5432
password_encryption = 'scram-sha-256'
log_timezone = 'Asia/Shanghai'
timezone = 'Asia/Shanghai'
log_min_duration_statement = 1000
log_lock_waits = on
deadlock_timeout = '1s'重启服务生效配置:
# 重启PostgreSQL 16服务
sudo systemctl restart postgresql@16-main
# 验证服务是否正常运行
sudo systemctl status postgresql@16-main动态验证服务生效:
root@instance-wc5p3ngj:/data# sudo -u postgres psql
psql (16.14 (Ubuntu 16.14-1.pgdg22.04+1))
Type "help" for help.
postgres=# SHOW listen_addresses;
listen_addresses
------------------
*
(1 row)
postgres=# SHOW port;
port
------
5432
(1 row)
postgres=# SHOW password_encryption;
password_encryption
---------------------
scram-sha-256
(1 row)
postgres=# SHOW timezone;
TimeZone
---------------
Asia/Shanghai
(1 row)
postgres=# SHOW log_min_duration_statement;
log_min_duration_statement
----------------------------
1s
(1 row)
postgres=# SHOW log_lock_waits;
log_lock_waits
----------------
on
(1 row)
postgres=# SHOW deadlock_timeout;
deadlock_timeout
------------------
1s
(1 row)
postgres=# 1.7 设置 postgres 密码
使用postgres用户进行登陆:
su - postgres
psql执行SQL语句:
ALTER USER postgres WITH PASSWORD '你的强密码';
\q退出数据库后退出postgres用户,使用root用户测试登陆。
修改了 postgres 管理员密码,直接在服务器上敲 psql 是无法验证密码的(PostgreSQL 默认本地连接免密),必须用密码认证模式连接,才能验证新密码是否生效。
psql -U postgres -h localhost -d postgres
Password for user postgres:
psql (16.14 (Ubuntu 16.14-1.pgdg22.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)
Type "help" for help.
postgres=# 2、脚本安装
2.1 脚本内容
#!/usr/bin/env bash
# PostgreSQL apt 源安装脚本
# Ubuntu/Debian
# 默认所有数据、日志、备份和凭据均放在 /data/postgresql 下
# 使用方式:sudo bash postgresql_pgdg_install.sh install
# 卸载方式:sudo bash postgresql_pgdg_install.sh uninstall
# 查看状态:sudo bash postgresql_pgdg_install.sh status
# 重置密码:sudo bash postgresql_pgdg_install.sh reset-password
set -Eeuo pipefail
umask 027
export DEBIAN_FRONTEND=noninteractive
# PostgreSQL 主版本;PGDG APT 包按主版本安装,例如 postgresql-16
POSTGRES_VERSION="${POSTGRES_VERSION:-16}"
# 官方仓库默认会安装16.13
# PostgreSQL 集群名;Ubuntu/Debian 多版本多实例体系中常用 main
CLUSTER_NAME="${CLUSTER_NAME:-main}"
# PostgreSQL 运行用户和组;PGDG/Ubuntu 包默认使用 postgres
POSTGRES_USER="${POSTGRES_USER:-postgres}"
POSTGRES_GROUP="${POSTGRES_GROUP:-postgres}"
# 所有持久化目录默认都在 /data/postgresql 下
PG_BASE_DIR="${PG_BASE_DIR:-/data/postgresql}"
DATA_DIR="${DATA_DIR:-}"
LOG_DIR="${LOG_DIR:-}"
BACKUP_DIR="${BACKUP_DIR:-}"
AUTH_FILE="${AUTH_FILE:-}"
# PostgreSQL 软件真实安装目录由官方 APT 包固定为 /usr/lib/postgresql/<主版本>
# 这里提供软链接,便于统一运维路径
INSTALL_PREFIX="${INSTALL_PREFIX:-}"
INSTALL_LINK="${INSTALL_LINK:-/usr/local/pgsql}"
# 配置目录;pg_createcluster 默认生成到 /etc/postgresql/<版本>/<集群名>
CONFIG_BASE_DIR="${CONFIG_BASE_DIR:-/etc/postgresql}"
CONFIG_DIR="${CONFIG_DIR:-}"
CONF_FILE="${CONF_FILE:-}"
HBA_FILE="${HBA_FILE:-}"
IDENT_FILE="${IDENT_FILE:-}"
# 运行时目录仍使用 /run/postgresql,这是 PostgreSQL-common/systemd 约定目录,不属于持久化数据
RUN_DIR="${RUN_DIR:-/run/postgresql}"
# PGDG 仓库。默认使用阿里云国内镜像;如需官方源,设为 https://apt.postgresql.org/pub/repos/apt
PGDG_REPO_URL="${PGDG_REPO_URL:-https://mirrors.aliyun.com/postgresql/repos/apt}"
PGDG_KEY="${PGDG_KEY:-/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc}"
PGDG_SOURCE="${PGDG_SOURCE:-/etc/apt/sources.list.d/pgdg.sources}"
DISABLE_AUTO_MAIN_CLUSTER="${DISABLE_AUTO_MAIN_CLUSTER:-true}"
# 监听配置
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
LISTEN_ADDRESSES="${LISTEN_ADDRESSES:-0.0.0.0}"
ALLOW_REMOTE_CIDR="${ALLOW_REMOTE_CIDR:-0.0.0.0/0}"
PASSWORD_ENCRYPTION="${PASSWORD_ENCRYPTION:-scram-sha-256}"
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-passwd}"
# 是否保留关闭防火墙逻辑;如由安全组/iptables/nftables 管控,可设为 false
CLOSE_FIREWALL="${CLOSE_FIREWALL:-true}"
# 基础生产配置
MAX_CONNECTIONS="${MAX_CONNECTIONS:-1000}"
SHARED_BUFFERS="${SHARED_BUFFERS:-auto}"
SHARED_BUFFERS_PERCENT="${SHARED_BUFFERS_PERCENT:-25}"
EFFECTIVE_CACHE_SIZE="${EFFECTIVE_CACHE_SIZE:-auto}"
EFFECTIVE_CACHE_SIZE_PERCENT="${EFFECTIVE_CACHE_SIZE_PERCENT:-75}"
MAINTENANCE_WORK_MEM="${MAINTENANCE_WORK_MEM:-auto}"
WORK_MEM="${WORK_MEM:-auto}"
HUGE_PAGES="${HUGE_PAGES:-try}"
TIMEZONE="${TIMEZONE:-Asia/Shanghai}"
DEFAULT_TEXT_SEARCH_CONFIG="${DEFAULT_TEXT_SEARCH_CONFIG:-pg_catalog.simple}"
# WAL/checkpoint/复制相关配置
WAL_LEVEL="${WAL_LEVEL:-replica}"
WAL_COMPRESSION="${WAL_COMPRESSION:-on}"
MAX_WAL_SIZE="${MAX_WAL_SIZE:-8GB}"
MIN_WAL_SIZE="${MIN_WAL_SIZE:-1GB}"
CHECKPOINT_COMPLETION_TARGET="${CHECKPOINT_COMPLETION_TARGET:-0.9}"
MAX_WAL_SENDERS="${MAX_WAL_SENDERS:-10}"
MAX_REPLICATION_SLOTS="${MAX_REPLICATION_SLOTS:-10}"
# IO/查询优化配置;默认偏 SSD/NVMe。机械盘建议调高 RANDOM_PAGE_COST 并降低 EFFECTIVE_IO_CONCURRENCY
RANDOM_PAGE_COST="${RANDOM_PAGE_COST:-1.1}"
EFFECTIVE_IO_CONCURRENCY="${EFFECTIVE_IO_CONCURRENCY:-200}"
DEFAULT_STATISTICS_TARGET="${DEFAULT_STATISTICS_TARGET:-100}"
JIT="${JIT:-off}"
# 日志配置
LOG_MIN_DURATION_STATEMENT="${LOG_MIN_DURATION_STATEMENT:-1000}"
LOG_LINE_PREFIX="${LOG_LINE_PREFIX:-%m [%p] %u@%d %r }"
LOG_FILENAME="${LOG_FILENAME:-}"
LOG_ROTATION_AGE="${LOG_ROTATION_AGE:-1d}"
LOG_ROTATION_SIZE="${LOG_ROTATION_SIZE:-100MB}"
LOG_TRUNCATE_ON_ROTATION="${LOG_TRUNCATE_ON_ROTATION:-on}"
LOG_LOCK_WAITS="${LOG_LOCK_WAITS:-on}"
DEADLOCK_TIMEOUT="${DEADLOCK_TIMEOUT:-1s}"
# autovacuum 配置
AUTOVACUUM="${AUTOVACUUM:-on}"
AUTOVACUUM_MAX_WORKERS="${AUTOVACUUM_MAX_WORKERS:-5}"
AUTOVACUUM_NAPTIME="${AUTOVACUUM_NAPTIME:-30s}"
AUTOVACUUM_VACUUM_COST_LIMIT="${AUTOVACUUM_VACUUM_COST_LIMIT:-2000}"
AUTOVACUUM_VACUUM_SCALE_FACTOR="${AUTOVACUUM_VACUUM_SCALE_FACTOR:-0.05}"
AUTOVACUUM_ANALYZE_SCALE_FACTOR="${AUTOVACUUM_ANALYZE_SCALE_FACTOR:-0.02}"
# systemd、limits、sysctl
SERVICE_NAME="${SERVICE_NAME:-}"
LIMIT_NOFILE="${LIMIT_NOFILE:-65535}"
MANAGE_LIMITS="${MANAGE_LIMITS:-true}"
MANAGE_SYSCTL="${MANAGE_SYSCTL:-true}"
SWAPPINESS="${SWAPPINESS:-1}"
SOMAXCONN="${SOMAXCONN:-1024}"
FS_FILE_MAX="${FS_FILE_MAX:-1000000}"
VM_OVERCOMMIT_MEMORY="${VM_OVERCOMMIT_MEMORY:-0}"
# 是否把 PostgreSQL 常用命令软链接到系统 PATH 中
LINK_BINARIES="${LINK_BINARIES:-true}"
BIN_LINK_DIR="${BIN_LINK_DIR:-/usr/local/bin}"
WRITE_PROFILE="${WRITE_PROFILE:-true}"
# 是否创建业务库和业务用户
CREATE_APP_USER="${CREATE_APP_USER:-false}"
APP_DB_NAME="${APP_DB_NAME:-appdb}"
APP_DB_USER="${APP_DB_USER:-appuser}"
APP_DB_PASSWORD="${APP_DB_PASSWORD:-}"
APP_HOST_CIDR="${APP_HOST_CIDR:-0.0.0.0/0}"
APP_PRIVILEGES="${APP_PRIVILEGES:-ALL PRIVILEGES}"
# 卸载行为
PURGE_PACKAGES="${PURGE_PACKAGES:-true}"
REMOVE_POSTGRES_USER="${REMOVE_POSTGRES_USER:-false}"
REMOVE_LOG_FILES="${REMOVE_LOG_FILES:-true}"
OS_ID=""
OS_VERSION_CODENAME=""
PG_MAJOR=""
NEW_CLUSTER_CREATED="false"
log() {
echo "[$(date '+%F %T')] [INFO] $*"
}
warn() {
echo "[$(date '+%F %T')] [WARN] $*" >&2
}
err() {
echo "[$(date '+%F %T')] [ERROR] $*" >&2
}
die() {
err "$*"
exit 1
}
usage() {
cat <<EOF_USAGE
用法:
sudo bash $0 install
sudo bash $0 uninstall
sudo bash $0 status
sudo bash $0 reset-password
不传参数时默认执行 install。
默认目录:
数据目录:/data/postgresql/<主版本>/<集群名>
日志目录:/data/postgresql/log
备份目录:/data/postgresql/backup
凭据文件:/data/postgresql/backup/.postgresql.auth
常用环境变量示例:
POSTGRES_VERSION=17 POSTGRES_PASSWORD='StrongPassword' sudo -E bash $0 install
POSTGRES_VERSION=17 DATA_DIR=/data/postgresql/17/main LOG_DIR=/data/postgresql/log sudo -E bash $0 install
ALLOW_REMOTE_CIDR='10.0.0.0/8' CLOSE_FIREWALL=false sudo -E bash $0 install
SHARED_BUFFERS=8GB EFFECTIVE_CACHE_SIZE=24GB MAX_CONNECTIONS=500 sudo -E bash $0 install
CREATE_APP_USER=true APP_DB_NAME=appdb APP_DB_USER=appuser APP_DB_PASSWORD='AppStrongPassword' sudo -E bash $0 install
PGDG_REPO_URL='https://apt.postgresql.org/pub/repos/apt' sudo -E bash $0 install
EOF_USAGE
}
is_true() {
[[ "${1,,}" == "true" || "${1,,}" == "yes" || "${1}" == "1" ]]
}
command_exists() {
command -v "$1" >/dev/null 2>&1
}
require_root() {
[[ "${EUID}" -eq 0 ]] || die "请使用 root 用户或 sudo 执行。"
}
confirm_yes() {
local prompt="$1"
local answer=""
read -r -p "${prompt} 输入 yes 继续:" answer || return 1
[[ "${answer}" == "yes" ]]
}
sql_escape() {
printf "%s" "$1" | sed "s/'/''/g"
}
conf_quote() {
local escaped
escaped="$(printf "%s" "$1" | sed "s/'/''/g")"
printf "'%s'" "${escaped}"
}
generate_password() {
LC_ALL=C tr -dc 'A-Za-z0-9_@#%+=-' </dev/urandom | head -c 24
echo
}
backup_file() {
local file="$1"
[[ -f "${file}" ]] || return 0
install -d -m 0750 -o root -g root "${BACKUP_DIR}"
cp -a "${file}" "${BACKUP_DIR}/$(basename "${file}").$(date '+%Y%m%d%H%M%S').bak"
}
detect_os() {
[[ -r /etc/os-release ]] || die "无法识别系统:/etc/os-release 不存在。"
. /etc/os-release
OS_ID="${ID,,}"
OS_VERSION_CODENAME="${VERSION_CODENAME:-}"
case "${OS_ID}" in
ubuntu|debian)
;;
*)
die "当前脚本仅支持 Ubuntu/Debian,当前系统:${OS_ID}。"
;;
esac
[[ -n "${OS_VERSION_CODENAME}" ]] || die "无法读取 VERSION_CODENAME。"
log "系统识别完成:${PRETTY_NAME:-${OS_ID}},codename=${OS_VERSION_CODENAME}。"
}
normalize_version_and_paths() {
if [[ "${POSTGRES_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then
warn "PGDG APT 包按主版本安装,已将 POSTGRES_VERSION=${POSTGRES_VERSION} 规范化为 ${POSTGRES_VERSION%%.*}。"
POSTGRES_VERSION="${POSTGRES_VERSION%%.*}"
fi
[[ "${POSTGRES_VERSION}" =~ ^[0-9]+$ ]] || die "POSTGRES_VERSION 必须是主版本号,例如 16、17、18。"
PG_MAJOR="${POSTGRES_VERSION}"
INSTALL_PREFIX="${INSTALL_PREFIX:-/usr/lib/postgresql/${PG_MAJOR}}"
CONFIG_DIR="${CONFIG_DIR:-${CONFIG_BASE_DIR}/${PG_MAJOR}/${CLUSTER_NAME}}"
CONF_FILE="${CONF_FILE:-${CONFIG_DIR}/postgresql.conf}"
HBA_FILE="${HBA_FILE:-${CONFIG_DIR}/pg_hba.conf}"
IDENT_FILE="${IDENT_FILE:-${CONFIG_DIR}/pg_ident.conf}"
DATA_DIR="${DATA_DIR:-${PG_BASE_DIR}/${PG_MAJOR}/${CLUSTER_NAME}}"
LOG_DIR="${LOG_DIR:-${PG_BASE_DIR}/log}"
BACKUP_DIR="${BACKUP_DIR:-${PG_BASE_DIR}/backup}"
AUTH_FILE="${AUTH_FILE:-${BACKUP_DIR}/.postgresql.auth}"
LOG_FILENAME="${LOG_FILENAME:-postgresql-${PG_MAJOR}-${CLUSTER_NAME}-%Y-%m-%d.log}"
SERVICE_NAME="${SERVICE_NAME:-postgresql@${PG_MAJOR}-${CLUSTER_NAME}.service}"
PGDG_REPO_URL="${PGDG_REPO_URL%/}"
}
validate_inputs() {
[[ "${CLUSTER_NAME}" =~ ^[A-Za-z0-9_]+$ ]] || die "CLUSTER_NAME 只能包含字母、数字、下划线。"
[[ "${POSTGRES_PORT}" =~ ^[0-9]+$ ]] || die "POSTGRES_PORT 必须是数字。"
(( POSTGRES_PORT >= 1 && POSTGRES_PORT <= 65535 )) || die "POSTGRES_PORT 必须在 1 到 65535 之间。"
[[ "${MAX_CONNECTIONS}" =~ ^[0-9]+$ ]] || die "MAX_CONNECTIONS 必须是数字。"
[[ "${SHARED_BUFFERS_PERCENT}" =~ ^[0-9]+$ ]] || die "SHARED_BUFFERS_PERCENT 必须是数字。"
[[ "${EFFECTIVE_CACHE_SIZE_PERCENT}" =~ ^[0-9]+$ ]] || die "EFFECTIVE_CACHE_SIZE_PERCENT 必须是数字。"
case "${PASSWORD_ENCRYPTION}" in
scram-sha-256|md5) ;;
*) die "PASSWORD_ENCRYPTION 只能是 scram-sha-256 或 md5。" ;;
esac
case "${HUGE_PAGES}" in
on|off|try) ;;
*) die "HUGE_PAGES 只能是 on、off 或 try。" ;;
esac
if is_true "${CREATE_APP_USER}"; then
[[ "${APP_DB_NAME}" =~ ^[A-Za-z0-9_]+$ ]] || die "APP_DB_NAME 只能包含字母、数字、下划线。"
[[ "${APP_DB_USER}" =~ ^[A-Za-z0-9_]+$ ]] || die "APP_DB_USER 只能包含字母、数字、下划线。"
fi
}
install_base_dependencies() {
log "安装基础依赖。"
apt-get update -y
apt-get install -y ca-certificates curl gnupg lsb-release procps sysstat acl postgresql-common
}
disable_auto_cluster_creation() {
is_true "${DISABLE_AUTO_MAIN_CLUSTER}" || return 0
local file="/etc/postgresql-common/createcluster.conf"
[[ -f "${file}" ]] || return 0
backup_file "${file}"
if grep -Eq '^[#[:space:]]*create_main_cluster[[:space:]]*=' "${file}"; then
sed -ri 's/^[#[:space:]]*create_main_cluster[[:space:]]*=.*/create_main_cluster = false/' "${file}"
else
echo 'create_main_cluster = false' >> "${file}"
fi
log "已设置 ${file}: create_main_cluster = false,避免安装包自动创建默认数据目录。"
}
setup_pgdg_repo() {
log "配置 PostgreSQL PGDG APT 仓库:${PGDG_REPO_URL}"
install -d -m 0755 "$(dirname "${PGDG_KEY}")"
if curl -fsSL -o "${PGDG_KEY}" "${PGDG_REPO_URL}/ACCC4CF8.asc"; then
log "已从 ${PGDG_REPO_URL} 下载 PostgreSQL 仓库签名密钥。"
else
warn "从 ${PGDG_REPO_URL}/ACCC4CF8.asc 下载密钥失败,尝试官方源。"
curl -fsSL -o "${PGDG_KEY}" https://www.postgresql.org/media/keys/ACCC4CF8.asc
fi
chmod 0644 "${PGDG_KEY}"
cat > "${PGDG_SOURCE}" <<EOF_PGDG
Types: deb
URIs: ${PGDG_REPO_URL}
Suites: ${OS_VERSION_CODENAME}-pgdg
Architectures: $(dpkg --print-architecture)
Components: main ${PG_MAJOR}
Signed-By: ${PGDG_KEY}
EOF_PGDG
rm -f /var/lib/apt/lists/*apt.postgresql.org* 2>/dev/null || true
rm -f /var/lib/apt/lists/*mirrors.aliyun.com*postgresql* 2>/dev/null || true
rm -f /var/lib/apt/lists/*mirrors.cloud.aliyuncs.com*postgresql* 2>/dev/null || true
apt-get update -y
if ! apt-cache show "postgresql-${PG_MAJOR}" >/dev/null 2>&1; then
echo
warn "当前可见的 PostgreSQL 主版本包如下:"
apt-cache search '^postgresql-[0-9]+$' || true
echo
die "未找到 postgresql-${PG_MAJOR} 包。请检查 PGDG_REPO_URL=${PGDG_REPO_URL} 是否同步完整,或更换 POSTGRES_VERSION。"
fi
log "已找到 postgresql-${PG_MAJOR}:$(apt-cache policy "postgresql-${PG_MAJOR}" | awk '/Candidate:/ {print $2; exit}')"
}
install_postgresql_packages() {
local packages=()
packages+=("postgresql-${PG_MAJOR}")
packages+=("postgresql-client-${PG_MAJOR}")
if apt-cache show "postgresql-contrib-${PG_MAJOR}" >/dev/null 2>&1; then
packages+=("postgresql-contrib-${PG_MAJOR}")
elif apt-cache show "postgresql-contrib" >/dev/null 2>&1; then
packages+=("postgresql-contrib")
else
warn "未找到 postgresql-contrib 包,跳过 contrib 扩展包安装。"
fi
log "安装 PostgreSQL ${PG_MAJOR} 官方二进制包:${packages[*]}。"
apt-get install -y "${packages[@]}"
[[ -x "/usr/lib/postgresql/${PG_MAJOR}/bin/postgres" ]] || die "PostgreSQL 二进制文件不存在:/usr/lib/postgresql/${PG_MAJOR}/bin/postgres。"
ln -sfn "/usr/lib/postgresql/${PG_MAJOR}" "${INSTALL_LINK}"
if [[ -d "${INSTALL_LINK}/lib" ]]; then
echo "${INSTALL_LINK}/lib" > "/etc/ld.so.conf.d/postgresql-${PG_MAJOR}.conf"
ldconfig || warn "ldconfig 执行失败,请手动检查。"
fi
}
cluster_exists() {
pg_lsclusters --no-header 2>/dev/null | awk -v ver="${PG_MAJOR}" -v name="${CLUSTER_NAME}" '
$1 == ver && $2 == name { found=1 }
END { exit found ? 0 : 1 }
'
}
cluster_status() {
pg_lsclusters --no-header 2>/dev/null | awk -v ver="${PG_MAJOR}" -v name="${CLUSTER_NAME}" '
$1 == ver && $2 == name { print $4; exit }
'
}
cluster_data_dir() {
pg_lsclusters --no-header 2>/dev/null | awk -v ver="${PG_MAJOR}" -v name="${CLUSTER_NAME}" '
$1 == ver && $2 == name { print $6; exit }
'
}
cluster_port() {
pg_lsclusters --no-header 2>/dev/null | awk -v ver="${PG_MAJOR}" -v name="${CLUSTER_NAME}" '
$1 == ver && $2 == name { print $3; exit }
'
}
check_port_conflict() {
local conflict
conflict="$(pg_lsclusters --no-header 2>/dev/null | awk -v ver="${PG_MAJOR}" -v name="${CLUSTER_NAME}" -v port="${POSTGRES_PORT}" '
$3 == port && !($1 == ver && $2 == name) { print $1 "/" $2 " 使用端口 " $3 }
' || true)"
[[ -z "${conflict}" ]] || die "端口 ${POSTGRES_PORT} 已被其他 PostgreSQL 集群占用:${conflict}。"
}
prepare_dirs() {
log "创建 PostgreSQL 目录。"
install -d -m 0750 -o "${POSTGRES_USER}" -g "${POSTGRES_GROUP}" "${DATA_DIR}"
install -d -m 0750 -o "${POSTGRES_USER}" -g "${POSTGRES_GROUP}" "${LOG_DIR}"
install -d -m 0755 -o "${POSTGRES_USER}" -g "${POSTGRES_GROUP}" "${RUN_DIR}" || true
install -d -m 0750 -o root -g root "${BACKUP_DIR}"
}
ensure_data_dir_safe() {
if [[ -f "${DATA_DIR}/PG_VERSION" ]]; then
return 0
fi
if find "${DATA_DIR}" -mindepth 1 -maxdepth 1 ! -name lost+found | grep -q .; then
die "数据目录 ${DATA_DIR} 非空但未检测到 PostgreSQL 初始化标记 PG_VERSION。为避免误删数据,请清空目录或更换 DATA_DIR。"
fi
}
create_or_validate_cluster() {
check_port_conflict
prepare_dirs
ensure_data_dir_safe
if cluster_exists; then
local existing_dir real_existing real_expected existing_port
existing_dir="$(cluster_data_dir)"
existing_port="$(cluster_port)"
real_existing="$(readlink -f "${existing_dir}")"
real_expected="$(readlink -f "${DATA_DIR}")"
[[ "${real_existing}" == "${real_expected}" ]] || die "已存在集群 ${PG_MAJOR}/${CLUSTER_NAME},但数据目录为 ${existing_dir},不是当前配置的 ${DATA_DIR}。"
[[ "${existing_port}" == "${POSTGRES_PORT}" ]] || warn "已存在集群端口为 ${existing_port},当前配置 POSTGRES_PORT=${POSTGRES_PORT};稍后会写入配置并重启。"
log "PostgreSQL 集群已存在:${PG_MAJOR}/${CLUSTER_NAME}。"
NEW_CLUSTER_CREATED="false"
else
log "创建 PostgreSQL 集群:${PG_MAJOR}/${CLUSTER_NAME},数据目录:${DATA_DIR}。"
pg_createcluster "${PG_MAJOR}" "${CLUSTER_NAME}" \
--datadir="${DATA_DIR}" \
--port="${POSTGRES_PORT}" \
--start
NEW_CLUSTER_CREATED="true"
fi
[[ -f "${CONF_FILE}" ]] || die "配置文件不存在:${CONF_FILE}。"
[[ -f "${HBA_FILE}" ]] || die "认证文件不存在:${HBA_FILE}。"
}
calculate_percent_mb() {
local percent="$1"
local min_mb="$2"
local max_mb="$3"
local mem_kb mem_mb result
mem_kb="$(awk '/MemTotal/ {print $2}' /proc/meminfo)"
mem_mb=$(( mem_kb / 1024 ))
result=$(( mem_mb * percent / 100 ))
(( result >= min_mb )) || result="${min_mb}"
if [[ "${max_mb}" -gt 0 && "${result}" -gt "${max_mb}" ]]; then
result="${max_mb}"
fi
echo "${result}MB"
}
calculate_work_mem() {
if [[ "${WORK_MEM}" != "auto" ]]; then
echo "${WORK_MEM}"
return 0
fi
local mem_kb mem_mb result
mem_kb="$(awk '/MemTotal/ {print $2}' /proc/meminfo)"
mem_mb=$(( mem_kb / 1024 ))
result=$(( mem_mb / MAX_CONNECTIONS / 4 ))
(( result >= 4 )) || result=4
(( result <= 64 )) || result=64
echo "${result}MB"
}
postgresql_conf_set() {
local key="$1"
local value="$2"
local file="${CONF_FILE}"
if grep -Eq "^[#[:space:]]*${key}[[:space:]]*=" "${file}"; then
sed -ri "s|^[#[:space:]]*${key}[[:space:]]*=.*|${key} = ${value}|" "${file}"
else
echo "${key} = ${value}" >> "${file}"
fi
}
write_postgresql_conf() {
backup_file "${CONF_FILE}"
local effective_shared_buffers effective_cache effective_maintenance_mem effective_work_mem
if [[ "${SHARED_BUFFERS}" == "auto" ]]; then
effective_shared_buffers="$(calculate_percent_mb "${SHARED_BUFFERS_PERCENT}" 128 0)"
else
effective_shared_buffers="${SHARED_BUFFERS}"
fi
if [[ "${EFFECTIVE_CACHE_SIZE}" == "auto" ]]; then
effective_cache="$(calculate_percent_mb "${EFFECTIVE_CACHE_SIZE_PERCENT}" 256 0)"
else
effective_cache="${EFFECTIVE_CACHE_SIZE}"
fi
if [[ "${MAINTENANCE_WORK_MEM}" == "auto" ]]; then
effective_maintenance_mem="$(calculate_percent_mb 5 64 2048)"
else
effective_maintenance_mem="${MAINTENANCE_WORK_MEM}"
fi
effective_work_mem="$(calculate_work_mem)"
log "写入 PostgreSQL 生产配置:${CONF_FILE}。"
postgresql_conf_set "data_directory" "$(conf_quote "${DATA_DIR}")"
postgresql_conf_set "hba_file" "$(conf_quote "${HBA_FILE}")"
postgresql_conf_set "ident_file" "$(conf_quote "${IDENT_FILE}")"
postgresql_conf_set "external_pid_file" "$(conf_quote "${RUN_DIR}/${PG_MAJOR}-${CLUSTER_NAME}.pid")"
postgresql_conf_set "listen_addresses" "$(conf_quote "${LISTEN_ADDRESSES}")"
postgresql_conf_set "port" "${POSTGRES_PORT}"
postgresql_conf_set "unix_socket_directories" "$(conf_quote "${RUN_DIR}")"
postgresql_conf_set "password_encryption" "$(conf_quote "${PASSWORD_ENCRYPTION}")"
postgresql_conf_set "max_connections" "${MAX_CONNECTIONS}"
postgresql_conf_set "shared_buffers" "${effective_shared_buffers}"
postgresql_conf_set "effective_cache_size" "${effective_cache}"
postgresql_conf_set "maintenance_work_mem" "${effective_maintenance_mem}"
postgresql_conf_set "work_mem" "${effective_work_mem}"
postgresql_conf_set "huge_pages" "${HUGE_PAGES}"
postgresql_conf_set "dynamic_shared_memory_type" "posix"
postgresql_conf_set "timezone" "$(conf_quote "${TIMEZONE}")"
postgresql_conf_set "log_timezone" "$(conf_quote "${TIMEZONE}")"
postgresql_conf_set "default_text_search_config" "$(conf_quote "${DEFAULT_TEXT_SEARCH_CONFIG}")"
postgresql_conf_set "wal_level" "${WAL_LEVEL}"
postgresql_conf_set "wal_compression" "${WAL_COMPRESSION}"
postgresql_conf_set "max_wal_size" "${MAX_WAL_SIZE}"
postgresql_conf_set "min_wal_size" "${MIN_WAL_SIZE}"
postgresql_conf_set "checkpoint_completion_target" "${CHECKPOINT_COMPLETION_TARGET}"
postgresql_conf_set "max_wal_senders" "${MAX_WAL_SENDERS}"
postgresql_conf_set "max_replication_slots" "${MAX_REPLICATION_SLOTS}"
postgresql_conf_set "random_page_cost" "${RANDOM_PAGE_COST}"
postgresql_conf_set "effective_io_concurrency" "${EFFECTIVE_IO_CONCURRENCY}"
postgresql_conf_set "default_statistics_target" "${DEFAULT_STATISTICS_TARGET}"
postgresql_conf_set "jit" "${JIT}"
postgresql_conf_set "logging_collector" "on"
postgresql_conf_set "log_directory" "$(conf_quote "${LOG_DIR}")"
postgresql_conf_set "log_filename" "$(conf_quote "${LOG_FILENAME}")"
postgresql_conf_set "log_rotation_age" "${LOG_ROTATION_AGE}"
postgresql_conf_set "log_rotation_size" "${LOG_ROTATION_SIZE}"
postgresql_conf_set "log_truncate_on_rotation" "${LOG_TRUNCATE_ON_ROTATION}"
postgresql_conf_set "log_min_duration_statement" "${LOG_MIN_DURATION_STATEMENT}"
postgresql_conf_set "log_line_prefix" "$(conf_quote "${LOG_LINE_PREFIX}")"
postgresql_conf_set "log_lock_waits" "${LOG_LOCK_WAITS}"
postgresql_conf_set "deadlock_timeout" "${DEADLOCK_TIMEOUT}"
postgresql_conf_set "log_checkpoints" "on"
postgresql_conf_set "log_autovacuum_min_duration" "1000"
postgresql_conf_set "autovacuum" "${AUTOVACUUM}"
postgresql_conf_set "autovacuum_max_workers" "${AUTOVACUUM_MAX_WORKERS}"
postgresql_conf_set "autovacuum_naptime" "${AUTOVACUUM_NAPTIME}"
postgresql_conf_set "autovacuum_vacuum_cost_limit" "${AUTOVACUUM_VACUUM_COST_LIMIT}"
postgresql_conf_set "autovacuum_vacuum_scale_factor" "${AUTOVACUUM_VACUUM_SCALE_FACTOR}"
postgresql_conf_set "autovacuum_analyze_scale_factor" "${AUTOVACUUM_ANALYZE_SCALE_FACTOR}"
chown "${POSTGRES_USER}:${POSTGRES_GROUP}" "${CONF_FILE}"
chmod 0640 "${CONF_FILE}"
}
write_pg_hba() {
backup_file "${HBA_FILE}"
log "写入 PostgreSQL 认证配置:${HBA_FILE}。"
cat > "${HBA_FILE}" <<EOF_HBA
# 由 postgresql_pgdg_install.sh 生成
# local postgres 使用 peer,便于本机 root/runuser 运维
# 业务访问默认使用 ${PASSWORD_ENCRYPTION}
local all postgres peer
local all all ${PASSWORD_ENCRYPTION}
host all all 127.0.0.1/32 ${PASSWORD_ENCRYPTION}
host all all ::1/128 ${PASSWORD_ENCRYPTION}
host all all ${ALLOW_REMOTE_CIDR} ${PASSWORD_ENCRYPTION}
EOF_HBA
chown "${POSTGRES_USER}:${POSTGRES_GROUP}" "${HBA_FILE}"
chmod 0640 "${HBA_FILE}"
}
configure_limits() {
is_true "${MANAGE_LIMITS}" || return 0
log "配置 PostgreSQL 文件句柄限制。"
cat > "/etc/security/limits.d/99-${POSTGRES_USER}.conf" <<EOF_LIMITS
${POSTGRES_USER} soft nofile ${LIMIT_NOFILE}
${POSTGRES_USER} hard nofile ${LIMIT_NOFILE}
${POSTGRES_USER} soft nproc ${LIMIT_NOFILE}
${POSTGRES_USER} hard nproc ${LIMIT_NOFILE}
EOF_LIMITS
}
configure_sysctl() {
is_true "${MANAGE_SYSCTL}" || return 0
log "写入 PostgreSQL sysctl 参数。"
cat > /etc/sysctl.d/99-postgresql.conf <<EOF_SYSCTL
vm.swappiness = ${SWAPPINESS}
net.core.somaxconn = ${SOMAXCONN}
fs.file-max = ${FS_FILE_MAX}
vm.overcommit_memory = ${VM_OVERCOMMIT_MEMORY}
EOF_SYSCTL
sysctl -p /etc/sysctl.d/99-postgresql.conf >/dev/null || warn "加载 sysctl 参数失败,请手动检查 /etc/sysctl.d/99-postgresql.conf。"
}
write_systemd_override() {
local override_dir="/etc/systemd/system/${SERVICE_NAME}.d"
install -d -m 0755 "${override_dir}"
log "写入 systemd override:${override_dir}/override.conf。"
cat > "${override_dir}/override.conf" <<EOF_SYSTEMD
[Service]
LimitNOFILE=${LIMIT_NOFILE}
OOMScoreAdjust=-500
EOF_SYSTEMD
systemctl daemon-reload
}
link_binaries() {
is_true "${LINK_BINARIES}" || return 0
log "创建 PostgreSQL 常用命令软链接到 ${BIN_LINK_DIR}。"
install -d -m 0755 "${BIN_LINK_DIR}"
local bin
for bin in psql pg_dump pg_restore pg_basebackup pg_isready pg_ctl initdb postgres createdb createuser dropdb dropuser vacuumdb reindexdb clusterdb; do
if [[ -x "${INSTALL_LINK}/bin/${bin}" ]]; then
ln -sfn "${INSTALL_LINK}/bin/${bin}" "${BIN_LINK_DIR}/${bin}"
fi
done
}
write_profile() {
is_true "${WRITE_PROFILE}" || return 0
log "写入 PostgreSQL PATH 配置。"
cat > /etc/profile.d/postgresql.sh <<EOF_PROFILE
# 由 postgresql_pgdg_install.sh 生成
export PGHOME=${INSTALL_LINK}
export PGDATA=${DATA_DIR}
export PGPORT=${POSTGRES_PORT}
export PATH=${INSTALL_LINK}/bin:\$PATH
EOF_PROFILE
chmod 0644 /etc/profile.d/postgresql.sh
}
close_firewall() {
is_true "${CLOSE_FIREWALL}" || return 0
if command_exists ufw && ufw status 2>/dev/null | grep -q "active"; then
log "关闭 ufw 防火墙。"
ufw disable >/dev/null || warn "ufw disable 执行失败。"
fi
if command_exists firewall-cmd && systemctl is-active --quiet firewalld; then
log "关闭 firewalld 防火墙。"
systemctl stop firewalld || true
systemctl disable firewalld >/dev/null 2>&1 || true
fi
}
start_or_restart_cluster() {
log "启动/重启 PostgreSQL 集群:${PG_MAJOR}/${CLUSTER_NAME}。"
install -d -m 2775 -o "${POSTGRES_USER}" -g "${POSTGRES_GROUP}" "${RUN_DIR}"
if [[ "$(cluster_status || true)" == "online" ]]; then
pg_ctlcluster "${PG_MAJOR}" "${CLUSTER_NAME}" restart
else
pg_ctlcluster "${PG_MAJOR}" "${CLUSTER_NAME}" start
fi
systemctl enable postgresql >/dev/null 2>&1 || true
local i
for i in $(seq 1 60); do
if pg_isready -h 127.0.0.1 -p "${POSTGRES_PORT}" -U postgres >/dev/null 2>&1; then
log "PostgreSQL 健康检查通过。"
return 0
fi
sleep 1
done
echo
warn "PostgreSQL 健康检查失败,输出最近日志:"
pg_lsclusters || true
journalctl -u "postgresql@${PG_MAJOR}-${CLUSTER_NAME}.service" --no-pager -n 120 >&2 || true
find "${LOG_DIR}" -maxdepth 1 -type f -name "postgresql-${PG_MAJOR}-${CLUSTER_NAME}-*.log" -print -exec tail -n 120 {} \; 2>/dev/null || true
die "PostgreSQL 健康检查失败。"
}
set_postgres_password() {
if [[ -z "${POSTGRES_PASSWORD}" ]]; then
POSTGRES_PASSWORD="$(generate_password)"
log "已自动生成 postgres 密码。"
fi
local escaped
escaped="$(sql_escape "${POSTGRES_PASSWORD}")"
log "设置 postgres 用户密码。"
runuser -u "${POSTGRES_USER}" -- "${INSTALL_LINK}/bin/psql" -X -v ON_ERROR_STOP=1 --dbname=postgres <<EOF_SQL
ALTER USER postgres WITH PASSWORD '${escaped}';
EOF_SQL
install -d -m 0750 -o root -g root "$(dirname "${AUTH_FILE}")"
cat > "${AUTH_FILE}" <<EOF_AUTH
PGHOST=127.0.0.1
PGPORT=${POSTGRES_PORT}
PGUSER=postgres
PGPASSWORD=${POSTGRES_PASSWORD}
PGDATABASE=postgres
PGDATA=${DATA_DIR}
PGSERVICE=${SERVICE_NAME}
EOF_AUTH
chmod 0600 "${AUTH_FILE}"
}
run_postgres_sql() {
PGPASSWORD="${POSTGRES_PASSWORD}" "${INSTALL_LINK}/bin/psql" \
-h 127.0.0.1 \
-p "${POSTGRES_PORT}" \
-U postgres \
-d postgres \
-X \
-v ON_ERROR_STOP=1
}
create_app_database() {
is_true "${CREATE_APP_USER}" || return 0
if [[ -z "${APP_DB_PASSWORD}" ]]; then
APP_DB_PASSWORD="$(generate_password)"
log "已自动生成业务用户密码。"
fi
local escaped_app_password
escaped_app_password="$(sql_escape "${APP_DB_PASSWORD}")"
log "创建业务库和业务用户:${APP_DB_NAME} / ${APP_DB_USER}。"
run_postgres_sql <<EOF_SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${APP_DB_USER}') THEN
CREATE ROLE ${APP_DB_USER} LOGIN PASSWORD '${escaped_app_password}';
ELSE
ALTER ROLE ${APP_DB_USER} WITH LOGIN PASSWORD '${escaped_app_password}';
END IF;
END
\$\$;
SELECT 'CREATE DATABASE ${APP_DB_NAME} OWNER ${APP_DB_USER}'
WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = '${APP_DB_NAME}')\gexec
GRANT ${APP_PRIVILEGES} ON DATABASE ${APP_DB_NAME} TO ${APP_DB_USER};
EOF_SQL
if ! grep -q '^APP_DB_NAME=' "${AUTH_FILE}" 2>/dev/null; then
cat >> "${AUTH_FILE}" <<EOF_APP_AUTH
APP_DB_NAME=${APP_DB_NAME}
APP_DB_USER=${APP_DB_USER}
APP_DB_PASSWORD=${APP_DB_PASSWORD}
APP_HOST_CIDR=${APP_HOST_CIDR}
EOF_APP_AUTH
chmod 0600 "${AUTH_FILE}"
fi
if ! grep -Eq "^host[[:space:]]+${APP_DB_NAME}[[:space:]]+${APP_DB_USER}[[:space:]]+${APP_HOST_CIDR}[[:space:]]+" "${HBA_FILE}"; then
cat >> "${HBA_FILE}" <<EOF_APP_HBA
host ${APP_DB_NAME} ${APP_DB_USER} ${APP_HOST_CIDR} ${PASSWORD_ENCRYPTION}
EOF_APP_HBA
pg_ctlcluster "${PG_MAJOR}" "${CLUSTER_NAME}" reload
fi
}
print_result() {
local effective_shared effective_cache effective_work_mem
effective_shared="$(awk -F= '/^[[:space:]]*shared_buffers[[:space:]]*=/{gsub(/[[:space:]]/,"",$2); print $2}' "${CONF_FILE}" | tail -n1)"
effective_cache="$(awk -F= '/^[[:space:]]*effective_cache_size[[:space:]]*=/{gsub(/[[:space:]]/,"",$2); print $2}' "${CONF_FILE}" | tail -n1)"
effective_work_mem="$(awk -F= '/^[[:space:]]*work_mem[[:space:]]*=/{gsub(/[[:space:]]/,"",$2); print $2}' "${CONF_FILE}" | tail -n1)"
cat <<EOF_RESULT
PostgreSQL 安装完成。
版本:${PG_MAJOR}
服务:${SERVICE_NAME}
软件目录:/usr/lib/postgresql/${PG_MAJOR}
软链接:${INSTALL_LINK}
命令目录:${INSTALL_LINK}/bin
配置目录:${CONFIG_DIR}
配置文件:${CONF_FILE}
认证文件:${HBA_FILE}
数据目录:${DATA_DIR}
日志目录:${LOG_DIR}
备份目录:${BACKUP_DIR}
运行目录:${RUN_DIR}
端口:${POSTGRES_PORT}
监听:${LISTEN_ADDRESSES}
允许访问:${ALLOW_REMOTE_CIDR}
shared_buffers:${effective_shared}
effective_cache_size:${effective_cache}
work_mem:${effective_work_mem}
凭据文件:${AUTH_FILE}
常用命令:
systemctl status ${SERVICE_NAME}
systemctl restart ${SERVICE_NAME}
journalctl -u ${SERVICE_NAME} -f
psql -h 127.0.0.1 -p ${POSTGRES_PORT} -U postgres -d postgres
pg_lsclusters
EOF_RESULT
if is_true "${CREATE_APP_USER}"; then
cat <<EOF_APP_RESULT
业务库:
数据库:${APP_DB_NAME}
用户:${APP_DB_USER}
密码:已写入 ${AUTH_FILE}
EOF_APP_RESULT
fi
}
install_postgresql() {
require_root
normalize_version_and_paths
validate_inputs
detect_os
install_base_dependencies
disable_auto_cluster_creation
setup_pgdg_repo
install_postgresql_packages
configure_limits
configure_sysctl
link_binaries
write_profile
close_firewall
create_or_validate_cluster
write_postgresql_conf
write_pg_hba
write_systemd_override
start_or_restart_cluster
set_postgres_password
start_or_restart_cluster
create_app_database
print_result
}
reset_password() {
require_root
normalize_version_and_paths
validate_inputs
command_exists pg_lsclusters || die "未找到 pg_lsclusters,请先安装 PostgreSQL。"
cluster_exists || die "未找到 PostgreSQL 集群 ${PG_MAJOR}/${CLUSTER_NAME}。"
if [[ "$(cluster_status || true)" != "online" ]]; then
pg_ctlcluster "${PG_MAJOR}" "${CLUSTER_NAME}" start
fi
set_postgres_password
log "postgres 密码重置完成,凭据已写入 ${AUTH_FILE}。"
}
status_postgresql() {
require_root
normalize_version_and_paths
detect_os
echo
pg_lsclusters || true
echo
if command_exists systemctl; then
systemctl --no-pager --full status "${SERVICE_NAME}" || true
fi
}
uninstall_postgresql() {
require_root
normalize_version_and_paths
detect_os
echo
warn "即将卸载 PostgreSQL ${PG_MAJOR}/${CLUSTER_NAME}。"
warn "服务:${SERVICE_NAME}"
warn "配置目录:${CONFIG_DIR}"
warn "数据目录:${DATA_DIR}"
warn "日志目录:${LOG_DIR}"
warn "备份目录:${BACKUP_DIR}"
echo
if ! confirm_yes "第一次确认:是否真的卸载 PostgreSQL?"; then
log "用户取消卸载。"
exit 0
fi
if command_exists pg_lsclusters && cluster_exists; then
log "删除 PostgreSQL 集群:${PG_MAJOR}/${CLUSTER_NAME}。"
pg_dropcluster --stop "${PG_MAJOR}" "${CLUSTER_NAME}"
else
warn "未检测到集群 ${PG_MAJOR}/${CLUSTER_NAME},跳过 pg_dropcluster。"
systemctl stop "${SERVICE_NAME}" 2>/dev/null || true
systemctl disable "${SERVICE_NAME}" 2>/dev/null || true
fi
log "清理 systemd override。"
rm -rf "/etc/systemd/system/${SERVICE_NAME}.d"
systemctl daemon-reload || true
systemctl reset-failed || true
echo
warn "第二次确认将删除 PostgreSQL 相关文件。"
warn "将删除:${INSTALL_LINK}、${CONFIG_DIR}、${DATA_DIR}、${AUTH_FILE}、profile、limits、sysctl、ldconfig、命令软链接。"
if is_true "${REMOVE_LOG_FILES}"; then
warn "还将删除匹配 ${LOG_DIR}/postgresql-${PG_MAJOR}-${CLUSTER_NAME}-*.log 的日志文件。"
fi
echo
if ! confirm_yes "第二次确认:是否删除配置、数据和脚本生成的文件?"; then
log "用户选择保留文件。卸载已完成:集群已停止并移除,文件未删除。"
exit 0
fi
log "清理 PostgreSQL 命令软链接。"
local bin
for bin in psql pg_dump pg_restore pg_basebackup pg_isready pg_ctl initdb postgres createdb createuser dropdb dropuser vacuumdb reindexdb clusterdb; do
if [[ -L "${BIN_LINK_DIR}/${bin}" ]] && readlink "${BIN_LINK_DIR}/${bin}" | grep -q "${INSTALL_LINK}/bin/"; then
rm -f "${BIN_LINK_DIR}/${bin}"
fi
done
log "清理 PostgreSQL 文件。"
rm -f "${INSTALL_LINK}"
rm -rf "${CONFIG_DIR}"
rm -rf "${DATA_DIR}"
rm -f "${AUTH_FILE}"
rm -f /etc/profile.d/postgresql.sh
rm -f "/etc/security/limits.d/99-${POSTGRES_USER}.conf"
rm -f /etc/sysctl.d/99-postgresql.conf
rm -f "/etc/ld.so.conf.d/postgresql-${PG_MAJOR}.conf"
ldconfig || true
if is_true "${REMOVE_LOG_FILES}"; then
find "${LOG_DIR}" -maxdepth 1 -type f -name "postgresql-${PG_MAJOR}-${CLUSTER_NAME}-*.log" -delete 2>/dev/null || true
fi
if is_true "${PURGE_PACKAGES}"; then
log "卸载 PostgreSQL ${PG_MAJOR} 软件包。"
apt-get purge -y "postgresql-${PG_MAJOR}" "postgresql-client-${PG_MAJOR}" || true
apt-get autoremove -y || true
fi
if is_true "${REMOVE_POSTGRES_USER}"; then
warn "删除 postgres 用户可能影响其他 PostgreSQL 版本或集群。"
userdel "${POSTGRES_USER}" 2>/dev/null || true
groupdel "${POSTGRES_GROUP}" 2>/dev/null || true
fi
log "PostgreSQL 卸载完成。"
}
main() {
local action="${1:-install}"
case "${action}" in
install)
install_postgresql
;;
uninstall|remove|purge)
uninstall_postgresql
;;
status)
status_postgresql
;;
reset-password|resetpwd)
reset_password
;;
-h|--help|help)
usage
;;
*)
usage
die "不支持的参数:${action}。"
;;
esac
}
main "$@"
2.2 手动配置脚本参数
脚本中的参数都集中在开头部分,正常情况下只需要关注少量核心参数,不需要修改全部配置。
| 参数 | 默认值 | 作用 |
|---|---|---|
POSTGRES_VERSION |
16 |
PostgreSQL 主版本 |
CLUSTER_NAME |
main |
PostgreSQL 集群名称 |
PG_BASE_DIR |
/data/postgresql |
PostgreSQL 数据、日志、备份的根目录 |
POSTGRES_PORT |
5432 |
PostgreSQL 监听端口 |
LISTEN_ADDRESSES |
0.0.0.0 |
PostgreSQL 监听地址 |
ALLOW_REMOTE_CIDR |
0.0.0.0/0 |
允许访问 PostgreSQL 的远程网段 |
POSTGRES_PASSWORD |
passwd |
postgres 用户密码 |
PASSWORD_ENCRYPTION |
scram-sha-256 |
密码认证方式 |
PGDG_REPO_URL |
https://mirrors.aliyun.com/postgresql/repos/apt |
PostgreSQL APT 源地址 |
CREATE_APP_USER |
false |
是否创建业务库和业务用户 |
APP_DB_NAME |
appdb |
业务数据库名称 |
APP_DB_USER |
appuser |
业务数据库用户 |
APP_DB_PASSWORD |
空 | 业务数据库用户密码 |
如果只做基础安装,一般只需要修改以下几个参数:
POSTGRES_VERSION="${POSTGRES_VERSION:-16}"
POSTGRES_PORT="${POSTGRES_PORT:-5432}"
LISTEN_ADDRESSES="${LISTEN_ADDRESSES:-0.0.0.0}"
ALLOW_REMOTE_CIDR="${ALLOW_REMOTE_CIDR:-0.0.0.0/0}"
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-passwd}"2.3 脚本执行流程
脚本执行安装时,整体流程如下:
| 步骤 | 说明 |
|---|---|
| 1 | 检查是否使用 root 或 sudo 执行 |
| 2 | 识别当前系统版本,只支持 Ubuntu / Debian |
| 3 | 安装基础依赖包 |
| 4 | 配置 PostgreSQL PGDG APT 源 |
| 5 | 安装 PostgreSQL 指定版本的软件包 |
| 6 | 创建 PostgreSQL 数据目录、日志目录、备份目录 |
| 7 | 创建或检查 PostgreSQL 集群 |
| 8 | 写入 postgresql.conf 主配置 |
| 9 | 写入 pg_hba.conf 访问控制配置 |
| 10 | 配置 systemd、limits、sysctl 等基础系统参数 |
| 11 | 启动或重启 PostgreSQL 服务 |
| 12 | 设置 postgres 用户密码 |
| 13 | 根据参数决定是否创建业务库和业务用户 |
| 14 | 输出安装结果和常用命令 |
脚本默认执行的是安装流程:
sudo bash postgresql_pgdg_install.sh install不传参数时,也会默认执行安装:
sudo bash postgresql_pgdg_install.sh查看状态:
sudo bash postgresql_pgdg_install.sh status重置 postgres 用户密码:
POSTGRES_PASSWORD='NewStrongPassword' sudo -E bash postgresql_pgdg_install.sh reset-password卸载 PostgreSQL:
sudo bash postgresql_pgdg_install.sh uninstall注意:卸载操作会涉及停止集群、删除配置、删除数据目录等动作,生产环境不要随意执行。
2.4 执行脚本测试
./psql_install.sh install
[2026-05-15 10:56:25] [INFO] 系统识别完成:Ubuntu 22.04.5 LTS,codename=jammy。
[2026-05-15 10:56:25] [INFO] 安装基础依赖。
Hit:1 http://mirrors.tencentyun.com/ubuntu jammy InRelease
Hit:2 http://mirrors.tencentyun.com/ubuntu jammy-updates InRelease
Hit:3 http://mirrors.tencentyun.com/ubuntu jammy-security InRelease
...... 自动安装过程省略......
[2026-05-15 10:57:07] [INFO] 写入 PostgreSQL 生产配置:/etc/postgresql/16/main/postgresql.conf。
[2026-05-15 10:57:07] [INFO] 写入 PostgreSQL 认证配置:/etc/postgresql/16/main/pg_hba.conf。
[2026-05-15 10:57:07] [INFO] 写入 systemd override:/etc/systemd/system/postgresql@16-main.service.d/override.conf。
[2026-05-15 10:57:07] [INFO] 启动/重启 PostgreSQL 集群:16/main。
[2026-05-15 10:57:11] [INFO] PostgreSQL 健康检查通过。
[2026-05-15 10:57:11] [INFO] 设置 postgres 用户密码。
ALTER ROLE
[2026-05-15 10:57:11] [INFO] 启动/重启 PostgreSQL 集群:16/main。
[2026-05-15 10:57:14] [INFO] PostgreSQL 健康检查通过。
PostgreSQL 安装完成。
版本:16
服务:postgresql@16-main.service
软件目录:/usr/lib/postgresql/16
软链接:/usr/local/pgsql
命令目录:/usr/local/pgsql/bin
配置目录:/etc/postgresql/16/main
配置文件:/etc/postgresql/16/main/postgresql.conf
认证文件:/etc/postgresql/16/main/pg_hba.conf
数据目录:/data/postgresql/16/main
日志目录:/data/postgresql/log
备份目录:/data/postgresql/backup
运行目录:/run/postgresql
端口:5432
监听:0.0.0.0
允许访问:0.0.0.0/0
shared_buffers:1904MB
effective_cache_size:5714MB
work_mem:4MB
凭据文件:/data/postgresql/backup/.postgresql.auth
常用命令:
systemctl status postgresql@16-main.service
systemctl restart postgresql@16-main.service
journalctl -u postgresql@16-main.service -f
psql -h 127.0.0.1 -p 5432 -U postgres -d postgres
pg_lsclusters
安装完成。
如果无法登陆,检查以下内容:
| 检查项 | 命令 |
|---|---|
| 服务是否运行 | pg_lsclusters |
| 端口是否监听 | `ss -lnpt |
| 访问网段是否放行 | cat /data/postgresql/backup/.postgresql.auth 和 cat /etc/postgresql/16/main/pg_hba.conf |
| 防火墙是否放行 | ufw status 或 nft list ruleset |
| 云安全组是否放行 | 检查云厂商安全组 5432 端口 |
| 密码是否正确 | 使用 reset-password 重置密码 |
3、Docker 安装
Docker 安装 PostgreSQL 比较简单,核心就是:
- 指定镜像版本;
- 设置初始化用户、密码、数据库;
- 挂载数据目录,保证容器删除后数据不丢;
- 映射
5432端口;
启动后测试连接。
这里以 PostgreSQL 16 为例。
3.1 准备数据目录
mkdir -p /data/postgresql/docker/data
mkdir -p /data/postgresql/docker/backup
chmod 700 /data/postgresql/docker/data目录说明:
| 目录 | 作用 |
|---|---|
/data/postgresql/docker/data |
PostgreSQL 数据目录 |
/data/postgresql/docker/backup |
后续备份目录 |
3.2 Docker 直接启动
使用 docker run 启动 PostgreSQL:
docker run -d \
--name postgres16 \
--restart always \
-p 5432:5432 \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD='你的强密码' \
-e POSTGRES_DB=postgres \
-e TZ=Asia/Shanghai \
-v /data/postgresql/docker/data:/var/lib/postgresql/data \
postgres:16参数说明:
| 参数 | 作用 |
|---|---|
--name postgres16 |
容器名称 |
--restart always |
容器异常退出或服务器重启后自动拉起 |
-p 5432:5432 |
将宿主机 5432 映射到容器 5432 |
POSTGRES_USER |
初始化数据库用户 |
POSTGRES_PASSWORD |
初始化数据库用户密码 |
POSTGRES_DB |
初始化默认数据库 |
TZ=Asia/Shanghai |
设置容器时区 |
-v /data/postgresql/docker/data:/var/lib/postgresql/data |
持久化数据库数据 |
postgres:16 |
使用 PostgreSQL 16 镜像 |
查看容器状态:
docker ps | grep postgres16查看日志:
docker logs -f postgres16进入容器:
docker exec -it postgres16 bash进入数据库:
docker exec -it postgres16 psql -U postgres -d postgres3.3 Docker Compose 启动
如果后续需要长期维护,推荐使用 docker-compose.yml 管理。
创建目录:
mkdir -p /data/postgresql/docker-compose
cd /data/postgresql/docker-compose编写 docker-compose.yml:
version: "3.8"
services:
postgres:
image: postgres:16
container_name: postgres16
restart: always
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: "你的强密码"
POSTGRES_DB: postgres
TZ: Asia/Shanghai
volumes:
- /data/postgresql/docker/data:/var/lib/postgresql/data
- /data/postgresql/docker/backup:/backup
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 10s
timeout: 5s
retries: 5启动:
docker compose up -d如果服务器使用的是老版本 docker-compose,则执行:
docker-compose up -d查看服务:
docker compose ps查看日志:
docker compose logs -f postgres停止服务:
docker compose down重启服务:
docker compose restart postgres3.4 登录验证
本机连接测试:
psql -h 127.0.0.1 -p 5432 -U postgres -d postgres如果宿主机没有安装 psql 客户端,也可以直接进入容器测试:
docker exec -it postgres16 psql -U postgres -d postgres进入数据库后执行:
\l
\du
SELECT version();
\q3.5 创建业务库和业务用户
进入数据库:
docker exec -it postgres16 psql -U postgres -d postgres创建业务用户和数据库:
CREATE USER appuser WITH PASSWORD '业务用户强密码';
CREATE DATABASE appdb OWNER appuser;
GRANT ALL PRIVILEGES ON DATABASE appdb TO appuser;
\c appdb
GRANT USAGE, CREATE ON SCHEMA public TO appuser;
ALTER SCHEMA public OWNER TO appuser;
\q业务连接方式:
psql -h PostgreSQL服务器IP -p 5432 -U appuser -d appdb3.6 常用 Docker 管理命令
# 查看容器
docker ps | grep postgres16
# 查看日志
docker logs -f postgres16
# 重启容器
docker restart postgres16
# 停止容器
docker stop postgres16
# 启动容器
docker start postgres16
# 进入容器
docker exec -it postgres16 bash
# 进入数据库
docker exec -it postgres16 psql -U postgres -d postgres3.7 数据备份和恢复
备份数据库:
docker exec -t postgres16 pg_dump -U postgres -d appdb > /data/postgresql/docker/backup/appdb.sql恢复数据库:
cat /data/postgresql/docker/backup/appdb.sql | docker exec -i postgres16 psql -U postgres -d appdb备份为自定义格式:
docker exec -t postgres16 pg_dump -U postgres -d appdb -F c > /data/postgresql/docker/backup/appdb.dump恢复自定义格式:
cat /data/postgresql/docker/backup/appdb.dump | docker exec -i postgres16 pg_restore -U postgres -d appdb3.8 注意事项
- 不建议使用
postgres:latest,生产环境应固定版本,例如postgres:16。 - 一定要挂载数据目录,否则容器删除后数据会丢失。
- 不建议将
5432直接暴露到公网。 - 生产环境建议使用独立业务用户,不要让业务直接使用
postgres超级用户。 POSTGRES_PASSWORD只在第一次初始化数据目录时生效,如果数据目录已经存在,修改环境变量不会自动修改数据库密码。- 如果需要修改密码,应进入数据库执行:
ALTER USER postgres WITH PASSWORD '新密码';3.9 Docker 安装方式总结
| 安装方式 | 适用场景 | 特点 |
|---|---|---|
docker run |
临时测试、快速验证 | 命令简单,但后续维护不方便 |
docker compose |
长期运行、标准化部署 | 配置清晰,便于维护和迁移 |
如果只是测试 PostgreSQL,使用 docker run 即可。
如果是长期部署,建议使用 docker compose。
三、PostgreSQL 常用命令与 SQL 语句
1、数据库运维命令
1.1 服务管理命令
# 查看 PostgreSQL 总服务状态
systemctl status postgresql
# 查看 PostgreSQL 16/main 实例状态
systemctl status postgresql@16-main.service
# 启动 PostgreSQL 16/main
systemctl start postgresql@16-main.service
# 停止 PostgreSQL 16/main
systemctl stop postgresql@16-main.service
# 重启 PostgreSQL 16/main
systemctl restart postgresql@16-main.service
# 设置开机自启
systemctl enable postgresql
# 查看 PostgreSQL 服务日志
journalctl -u postgresql@16-main.service -f| 命令 | 作用 |
|---|---|
systemctl status postgresql |
查看 PostgreSQL 总管理服务状态 |
systemctl status postgresql@16-main.service |
查看具体数据库实例状态 |
systemctl start |
启动 PostgreSQL |
systemctl stop |
停止 PostgreSQL |
systemctl restart |
重启 PostgreSQL |
systemctl enable |
设置开机自启 |
journalctl -u |
查看 systemd 日志 |
1.2 集群管理命令
# 查看当前 PostgreSQL 集群
pg_lsclusters
# 启动指定集群
pg_ctlcluster 16 main start
# 停止指定集群
pg_ctlcluster 16 main stop
# 重启指定集群
pg_ctlcluster 16 main restart
# 重载配置,不中断数据库连接
pg_ctlcluster 16 main reload| 命令 | 作用 |
|---|---|
pg_lsclusters |
查看当前机器上的 PostgreSQL 集群 |
pg_ctlcluster 16 main start |
启动 PostgreSQL 16/main 集群 |
pg_ctlcluster 16 main stop |
停止 PostgreSQL 16/main 集群 |
pg_ctlcluster 16 main restart |
重启 PostgreSQL 16/main 集群 |
pg_ctlcluster 16 main reload |
重载配置文件,常用于修改 pg_hba.conf 后生效 |
1.3 登陆数据库
# 切换到 postgres 系统用户
su - postgres
# 登录 PostgreSQL
psql
# 指定主机、端口、用户、数据库登录
psql -h 127.0.0.1 -p 5432 -U postgres -d postgres
# 退出 psql
\q| 命令 | 作用 |
|---|---|
su - postgres |
切换到 PostgreSQL 默认系统用户 |
psql |
进入 PostgreSQL 命令行 |
-h |
指定数据库地址 |
-p |
指定端口 |
-U |
指定数据库用户 |
-d |
指定数据库名称 |
\q |
退出 PostgreSQL 命令行 |
1.4 psql 常用内部命令
进入psql库后,可以使用以下命令:
-- 查看数据库列表
\l
-- 查看用户 / 角色
\du
-- 查看当前连接信息
\conninfo
-- 查看当前数据库下的表
\dt
-- 查看表结构
\d 表名
-- 查看所有 schema
\dn
-- 切换数据库
\c 数据库名
-- 查看 psql 帮助
\?
-- 查看 SQL 帮助
\h
-- 退出
\q| 命令 | 作用 |
|---|---|
\l |
查看数据库列表 |
\du |
查看用户和角色 |
\conninfo |
查看当前连接信息 |
\dt |
查看当前数据库中的表 |
\d 表名 |
查看指定表结构 |
\dn |
查看 schema |
\c 数据库名 |
切换数据库 |
\? |
查看 psql 命令帮助 |
\h |
查看 SQL 语法帮助 |
\q |
退出 psql |
1.5 数据库和用户管理
-- 创建数据库
CREATE DATABASE testdb;
-- 删除数据库
DROP DATABASE testdb;
-- 创建用户
CREATE USER testuser WITH PASSWORD 'StrongPassword';
-- 修改用户密码
ALTER USER testuser WITH PASSWORD 'NewStrongPassword';
-- 给用户授权数据库
GRANT ALL PRIVILEGES ON DATABASE testdb TO testuser;
-- 删除用户
DROP USER testuser;| SQL | 作用 |
|---|---|
CREATE DATABASE |
创建数据库 |
DROP DATABASE |
删除数据库 |
CREATE USER |
创建数据库用户 |
ALTER USER ... PASSWORD |
修改用户密码 |
GRANT ALL PRIVILEGES |
授权用户访问数据库 |
DROP USER |
删除用户 |
2、数据库增删改查SQL语句
2.1 创建测试表
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
age INT,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);| 字段 | 说明 |
|---|---|
id |
自增主键 |
username |
用户名,不能为空 |
age |
年龄 |
email |
邮箱 |
created_at |
创建时间,默认当前时间 |
2.2 插入数据
INSERT INTO users (username, age, email)
VALUES ('zhangsan', 25, 'zhangsan@example.com');
INSERT INTO users (username, age, email)
VALUES
('lisi', 28, 'lisi@example.com'),
('wangwu', 30, 'wangwu@example.com');| SQL | 作用 |
|---|---|
INSERT INTO |
插入数据 |
VALUES |
指定插入的值 |
多行 VALUES |
一次插入多条数据 |
2.3 查询数据
-- 查询所有数据
SELECT * FROM users;
id | username | age | email | created_at
----+----------+-----+----------------------+----------------------------
1 | zhangsan | 25 | zhangsan@example.com | 2026-05-15 11:10:37.106878
2 | lisi | 28 | lisi@example.com | 2026-05-15 11:10:37.108108
3 | wangwu | 30 | wangwu@example.com | 2026-05-15 11:10:37.108108
(3 rows)
-- 查询指定字段
SELECT id, username, email FROM users;
id | username | email
----+----------+----------------------
1 | zhangsan | zhangsan@example.com
2 | lisi | lisi@example.com
3 | wangwu | wangwu@example.com
(3 rows)
-- 条件查询
SELECT * FROM users WHERE age > 25;
id | username | age | email | created_at
----+----------+-----+--------------------+----------------------------
2 | lisi | 28 | lisi@example.com | 2026-05-15 11:10:37.108108
3 | wangwu | 30 | wangwu@example.com | 2026-05-15 11:10:37.108108
(2 rows)
-- 模糊查询
SELECT * FROM users WHERE username LIKE '%zhang%';
id | username | age | email | created_at
----+----------+-----+----------------------+----------------------------
1 | zhangsan | 25 | zhangsan@example.com | 2026-05-15 11:10:37.106878
(1 row)
-- 排序查询
SELECT * FROM users ORDER BY id DESC;
id | username | age | email | created_at
----+----------+-----+----------------------+----------------------------
3 | wangwu | 30 | wangwu@example.com | 2026-05-15 11:10:37.108108
2 | lisi | 28 | lisi@example.com | 2026-05-15 11:10:37.108108
1 | zhangsan | 25 | zhangsan@example.com | 2026-05-15 11:10:37.106878
(3 rows)
-- 限制返回条数
SELECT * FROM users LIMIT 10;
-- 分页查询
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20;| SQL | 作用 |
|---|---|
SELECT * |
查询所有字段 |
WHERE |
条件过滤 |
LIKE |
模糊匹配 |
ORDER BY |
排序 |
LIMIT |
限制返回条数 |
OFFSET |
跳过指定条数,常用于分页 |
2.4 修改数据
-- 修改单个字段
UPDATE users
SET age = 26
WHERE username = 'zhangsan';
-- 验证 25->26
SELECT * FROM users WHERE username LIKE '%zhang%';
id | username | age | email | created_at
----+----------+-----+----------------------+----------------------------
1 | zhangsan | 26 | zhangsan@example.com | 2026-05-15 11:10:37.106878
(1 row)
-- 修改多个字段
UPDATE users
SET age = 29,
email = 'new_lisi@example.com'
WHERE username = 'lisi';
-- 验证 多条数据改变
SELECT * FROM users WHERE username LIKE '%lisi%';
id | username | age | email | created_at
----+----------+-----+----------------------+----------------------------
2 | lisi | 29 | new_lisi@example.com | 2026-05-15 11:10:37.108108
(1 row)| SQL | 作用 |
|---|---|
UPDATE |
修改数据 |
SET |
设置新的字段值 |
WHERE |
指定修改条件 |
执行 UPDATE 时一定要带 WHERE 条件,否则会更新整张表。
2.5 删除数据
-- 删除指定用户
DELETE FROM users
WHERE username = 'wangwu';
-- 验证
SELECT * FROM users;
id | username | age | email | created_at
----+----------+-----+----------------------+----------------------------
1 | zhangsan | 26 | zhangsan@example.com | 2026-05-15 11:10:37.106878
2 | lisi | 29 | new_lisi@example.com | 2026-05-15 11:10:37.108108
(2 rows)
-- 删除年龄小于 18 的用户
DELETE FROM users
WHERE age < 18;| SQL | 作用 |
|---|---|
DELETE FROM |
删除数据 |
WHERE |
指定删除条件 |
注意:
执行 DELETE 时一定要确认 WHERE 条件,否则会删除整张表数据。
2.6 修改表结构
-- 增加字段
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- 验证
\d users
Table "public.users"
Column | Type | Collation | Nullable | Default
------------+-----------------------------+-----------+----------+-----------------------------------
id | integer | | not null | nextval('users_id_seq'::regclass)
username | character varying(50) | | not null |
age | integer | | |
email | character varying(100) | | |
created_at | timestamp without time zone | | | CURRENT_TIMESTAMP
phone | character varying(20) | | |
-- 修改字段类型
ALTER TABLE users ALTER COLUMN phone TYPE VARCHAR(50);
-- 删除字段
ALTER TABLE users DROP COLUMN phone;
-- 重命名字段
ALTER TABLE users RENAME COLUMN username TO name;
-- 重命名表
ALTER TABLE users RENAME TO user_info;| SQL | 作用 |
|---|---|
ALTER TABLE ... ADD COLUMN |
增加字段 |
ALTER TABLE ... ALTER COLUMN |
修改字段类型 |
ALTER TABLE ... DROP COLUMN |
删除字段 |
RENAME COLUMN |
重命名字段 |
RENAME TO |
重命名表 |
2.7 创建和删除索引
-- 创建普通索引
CREATE INDEX idx_users_username ON users(username);
-- 创建唯一索引
CREATE UNIQUE INDEX idx_users_email ON users(email);
-- 删除索引
DROP INDEX idx_users_username;| SQL | 作用 |
|---|---|
CREATE INDEX |
创建普通索引 |
CREATE UNIQUE INDEX |
创建唯一索引 |
DROP INDEX |
删除索引 |
索引可以提高查询速度,但也会增加写入成本。常见适合建索引的字段包括:
WHERE 条件字段
JOIN 关联字段
ORDER BY 排序字段
高频查询字段2.8 事务操作
-- 开启事务
BEGIN;
-- 执行修改
UPDATE users SET age = 31 WHERE username = 'zhangsan';
-- 提交事务
COMMIT;如果发现操作有问题,可以回滚:
BEGIN;
DELETE FROM users WHERE username = 'lisi';
ROLLBACK;| SQL | 作用 |
|---|---|
BEGIN |
开启事务 |
COMMIT |
提交事务 |
ROLLBACK |
回滚事务 |
3、数据库内部状态查询SQL语句
3.1 查看当前数据库版本
SELECT version();
PostgreSQL 16.14 (Ubuntu 16.14-1.pgdg22.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0, 64-bit
(1 row)作用:查看当前 PostgreSQL 版本信息。
3.2 查看当前数据库和用户
SELECT current_database();
SELECT current_user;
SELECT inet_client_addr();
SELECT inet_server_addr();| SQL | 作用 |
|---|---|
current_database() |
查看当前数据库 |
current_user |
查看当前登录用户 |
inet_client_addr() |
查看客户端 IP |
inet_server_addr() |
查看服务端 IP |
3.3 查看当前连接
SELECT
pid,
usename,
datname,
client_addr,
state,
backend_start,
query_start,
query
FROM pg_stat_activity
ORDER BY query_start DESC;字段说明:
| 字段 | 说明 |
|---|---|
pid |
PostgreSQL 后端进程 ID |
usename |
数据库用户 |
datname |
数据库名称 |
client_addr |
客户端 IP |
state |
当前连接状态 |
backend_start |
连接创建时间 |
query_start |
SQL 开始执行时间 |
query |
当前 SQL |
3.4 查看正在执行的 SQL
SELECT
pid,
usename,
datname,
client_addr,
now() - query_start AS running_time,
query
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY running_time DESC;作用:查看当前正在执行的 SQL,并按执行时间排序。
3.5 查看空闲连接
SELECT
pid,
usename,
datname,
client_addr,
state,
now() - state_change AS idle_time
FROM pg_stat_activity
WHERE state = 'idle'
ORDER BY idle_time DESC;作用:查看空闲连接,排查连接池是否释放连接异常。
3.6 查看连接数统计
SELECT
datname,
COUNT(*) AS connection_count
FROM pg_stat_activity
GROUP BY datname
ORDER BY connection_count DESC;作用:按数据库统计当前连接数。
查看最大连接数配置:
SHOW max_connections;3.7 查看锁等待
SELECT
pid,
usename,
datname,
wait_event_type,
wait_event,
state,
query
FROM pg_stat_activity
WHERE wait_event IS NOT NULL;作用:查看当前是否存在等待事件,例如锁等待、IO 等待。
3.8 查看阻塞关系
SELECT
blocked.pid AS blocked_pid,
blocked.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking.query AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_locks blocked_locks
ON blocked.pid = blocked_locks.pid
JOIN pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_stat_activity blocking
ON blocking.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;作用:查看谁被阻塞、谁在阻塞别人。
3.9 查看数据库大小
SELECT
datname,
pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
ORDER BY pg_database_size(datname) DESC;作用:查看每个数据库占用空间。
3.10 查看表大小
SELECT
schemaname,
relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
pg_size_pretty(pg_relation_size(relid)) AS table_size,
pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) AS index_size
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;字段说明:
| 字段 | 说明 |
|---|---|
total_size |
表总大小,包括索引 |
table_size |
表数据大小 |
index_size |
索引大小 |
3.11 查看表行数估算
SELECT
schemaname,
relname AS table_name,
n_live_tup AS estimated_rows
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;作用:快速查看业务表的大概数据量。
注意:
这里是估算值,不是精确 COUNT(*)。大表不要频繁执行全表 COUNT(*)。
3.12 查看数据库命中率
SELECT
datname,
blks_hit,
blks_read,
ROUND(
blks_hit * 100.0 / NULLIF(blks_hit + blks_read, 0),
2
) AS cache_hit_ratio
FROM pg_stat_database
WHERE blks_hit + blks_read > 0
ORDER BY cache_hit_ratio ASC;作用:查看数据库缓存命中率。
一般来说,缓存命中率长期偏低,可能说明:
- 内存不足
- SQL 扫描数据过多
- 索引设计不合理
- 业务访问数据范围过大
3.13 查看事务提交和回滚情况
SELECT
datname,
xact_commit,
xact_rollback,
ROUND(
xact_rollback * 100.0 / NULLIF(xact_commit + xact_rollback, 0),
2
) AS rollback_ratio
FROM pg_stat_database
ORDER BY rollback_ratio DESC;作用:查看事务提交和回滚比例。
如果回滚比例过高,可能说明:
- 业务异常较多
- SQL 执行失败较多
- 事务控制不合理
- 应用连接数据库存在错误
3.14 查看慢 SQL 配置
SHOW log_min_duration_statement;作用:查看当前慢 SQL 记录阈值。
例如:
1000表示记录执行时间超过 1000ms 的 SQL。
3.15 查看常用配置参数
SHOW listen_addresses;
SHOW port;
SHOW max_connections;
SHOW shared_buffers;
SHOW work_mem;
SHOW timezone;
SHOW log_timezone;作用:查看当前 PostgreSQL 主要配置是否生效。
3.16 取消正在执行的 SQL
SELECT pg_cancel_backend(pid);示例:
SELECT pg_cancel_backend(12345);作用:取消指定 PID 正在执行的 SQL。
如果取消无效,可以终止连接:
SELECT pg_terminate_backend(12345);区别:
| 命令 | 作用 |
|---|---|
pg_cancel_backend(pid) |
只取消当前 SQL,连接还在 |
pg_terminate_backend(pid) |
直接断开该数据库连接 |
PostgreSQL 运维调优
一、监控部署
PostgreSQL 监控一般通过 postgres_exporter 采集数据库内部指标,再由 Prometheus 抓取,最后在 Nightingale / Grafana 中展示和配置告警。
本次设计不使用Prometheus作为存储,选择使用:
PostgreSQL
↓
postgres_exporter
↓
Categraf
↓
victoriametrics
↓
Nightingale1、创建 PostgreSQL 监控用户
监控用户不建议使用 postgres 超级用户,应该单独创建一个只读监控用户。
进入 PostgreSQL:
su - postgres
psql执行 SQL:
CREATE USER postgres_exporter WITH PASSWORD '<监控用户密码>';
GRANT pg_monitor TO postgres_exporter;
GRANT CONNECT ON DATABASE postgres TO postgres_exporter;退出:
\q参数说明:
| SQL | 作用 |
|---|---|
CREATE USER postgres_exporter |
创建 PostgreSQL 监控用户 |
WITH PASSWORD |
设置监控用户密码 |
GRANT pg_monitor |
授予 PostgreSQL 内置监控权限 |
GRANT CONNECT ON DATABASE postgres |
允许监控用户连接 postgres 数据库 |
其中 pg_monitor 是 PostgreSQL 内置监控角色,适合 exporter 采集数据库状态、连接数、事务、锁、表统计等信息。
2、验证监控用户连接
在 PostgreSQL 服务器本机执行:
psql -h 127.0.0.1 -p 5432 -U postgres_exporter -d postgres输入密码后,如果可以正常进入,说明监控用户创建成功。
进入后可以执行:
SELECT current_user;
SELECT current_database();
\q正常结果应显示当前用户为:
postgres_exporter3、创建 postgres_exporter 配置文件
创建环境变量配置文件:
cat > /etc/default/postgres_exporter <<'EOF'
DATA_SOURCE_URI=127.0.0.1:5432/postgres?sslmode=disable
DATA_SOURCE_USER=postgres_exporter
DATA_SOURCE_PASS=<监控用户密码>
EOF修改权限:
chmod 600 /etc/default/postgres_exporter配置说明:
| 配置项 | 说明 |
|---|---|
DATA_SOURCE_URI |
PostgreSQL 连接地址 |
127.0.0.1:5432 |
连接本机 PostgreSQL 5432 端口 |
/postgres |
连接的数据库名称 |
sslmode=disable |
本机连接不启用 SSL |
DATA_SOURCE_USER |
exporter 使用的数据库用户 |
DATA_SOURCE_PASS |
exporter 使用的数据库密码 |
注意:
文档中不要直接写真实密码,建议使用 <监控用户密码> 占位。
4、创建 postgres_exporter systemd 服务
假设 postgres_exporter 程序目录为:
/data/postgres_exporter二进制文件路径为:
/data/postgres_exporter/postgres_exporter创建 systemd 服务文件:
cat > /etc/systemd/system/postgres_exporter.service <<'EOF'
[Unit]
Description=PostgreSQL Exporter
After=network.target postgresql.service
[Service]
Type=simple
WorkingDirectory=/data/postgres_exporter
EnvironmentFile=/etc/default/postgres_exporter
ExecStart=/data/postgres_exporter/postgres_exporter --web.listen-address=:9187
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF配置说明:
| 配置项 | 作用 |
|---|---|
Description |
服务描述 |
After=network.target postgresql.service |
网络和 PostgreSQL 服务启动后再启动 exporter |
WorkingDirectory |
exporter 工作目录 |
EnvironmentFile |
引用 exporter 数据库连接配置 |
ExecStart |
exporter 启动命令 |
--web.listen-address=:9187 |
exporter 监听 9187 端口 |
Restart=always |
异常退出后自动重启 |
RestartSec=5 |
退出 5 秒后重启 |
WantedBy=multi-user.target |
设置为系统多用户模式下启动 |
5、启动 postgres_exporter
重新加载 systemd:
systemctl daemon-reload设置开机自启并立即启动:
systemctl enable --now postgres_exporter查看服务状态:
systemctl status postgres_exporter查看端口监听:
ss -lnpt | grep 9187正常情况下可以看到:
LISTEN 0 4096 0.0.0.0:91876、验证 exporter 指标
本机访问 exporter 指标接口:
curl http://127.0.0.1:9187/metrics如果正常,会看到类似指标:
pg_up 1
pg_database_size_bytes
pg_stat_database_xact_commit
pg_stat_database_xact_rollback
pg_stat_activity_count7、接入Categraf
编辑 Categraf 的 Prometheus input 配置文件。
常见路径为:
vim /etc/categraf/conf/input.prometheus/prometheus.toml添加 PostgreSQL exporter 采集配置:
# psql
[[instances]]
urls = ["http://127.0.0.1:9187/metrics"]
url_label_key = "instance"
url_label_value = "{{.Host}}"
interval_times = 2
labels = { job = "psql_exporter", service = "psql", cluster = "psql-prod", env = "prod" }配置说明:
| 配置项 | 说明 |
|---|---|
urls |
postgres_exporter 指标地址 |
url_label_key |
采集后附加的实例标签名称 |
url_label_value |
实例标签值,{{.Host}} 表示当前主机 |
interval_times |
采集间隔倍数,基于 Categraf 全局采集周期 |
job |
任务名称,方便在 PromQL 中筛选 |
service |
服务类型,这里标记为 psql |
cluster |
PostgreSQL 集群名称,自定义即可 |
env |
环境标签,例如 prod、test、dev |
如果 Categraf 和 postgres_exporter 不在同一台机器,需要把:
urls = ["http://127.0.0.1:9187/metrics"]改成 PostgreSQL 服务器 IP:
urls = ["http://PostgreSQL服务器IP:9187/metrics"]8、配置 Categraf 写入 VictoriaMetrics
如果 Categraf 需要直接写入本机 VictoriaMetrics,可以在 Categraf 主配置文件中配置 writers。
常见配置文件路径:
vim /etc/categraf/conf/config.toml添加或确认如下配置:
# victoria-metrics
[[writers]]
url = "http://127.0.0.1:8428/api/v1/write"配置说明:
| 配置项 | 说明 |
|---|---|
[[writers]] |
Categraf 指标写入目标 |
url |
VictoriaMetrics remote write 地址 |
127.0.0.1:8428 |
本机 VictoriaMetrics 地址 |
/api/v1/write |
VictoriaMetrics remote write 接口 |
如果 VictoriaMetrics 不在本机,需要改成实际地址:
[[writers]]
url = "http://VictoriaMetrics服务器IP:8428/api/v1/write"9、配置 Categraf 心跳上报 Nightingale
如果 Categraf 需要向 Nightingale 上报主机心跳,需要配置 heartbeat。
[heartbeat]
enable = true
# report os version cpu.util mem.util metadata
url = "http://127.0.0.1:17000/v1/n9e/heartbeat"配置说明:
| 配置项 | 说明 |
|---|---|
enable = true |
开启 Categraf 心跳上报 |
url |
Nightingale heartbeat 地址 |
127.0.0.1:17000 |
Nightingale server 地址 |
/v1/n9e/heartbeat |
夜莺心跳接口 |
如果 Nightingale 不在本机,需要修改为实际地址:
[heartbeat]
enable = true
url = "http://Nightingale服务器IP:17000/v1/n9e/heartbeat"重启 Categraf,修改完成后,重启 Categraf:
systemctl restart categraf查看服务状态:
systemctl status categraf查看 Categraf 日志:
journalctl -u categraf -n 100 --no-pager如果需要实时查看:
journalctl -u categraf -f监控大盘:(自定义夜莺仪表盘json)

评论
游客无需注册即可评论。
你提交的昵称、邮箱、网址和评论内容会保存在服务端,用于展示评论身份、接收回复及必要的安全审计。
浏览器会本地保存已填游客信息和评论草稿,方便下次免填。
回复提醒会通过站内消息和邮件通知。