编程闭关教程 · 系列 08–20 · 编制日期 2026-10-14

PostgreSQL 专题:从安装到 pgx 集成到备份全通

数据库四件套 ②。你点名要常用 PostgreSQL,本模块把它独立成册:本机 Docker 起 18.6、Linux 1GB 服务器 apt 部署、核心概念最小必要版、Go 三方案(pgx 首选 / GORM 工作台接入 / sqlc 简介)完整可运行代码、最佳实践、备份恢复、10+ 条常见报错表。照着敲,不跳出教程。
本册承诺一册在手:PG 从 Windows 本机 Docker 安装 → 首次 psql 连入 → 建库建用户 → Go 用 pgx v5.11.0 写 CRUD 与事务 → 工作台 GORM 切 PG → cron 每日备份 → 1GB 服务器调优,全链路照做即通。
18.6
锁定 PostgreSQL 稳定版(2026-08-13)
1 周
本模块学时(W10–W12 段)
pgx 优先
Go 首选驱动 v5.11.0
1 份
可直接抄的 cron 备份方案
版本唯一事实来源:09 模块版本总表(versions.md)。四数据库分工见 08 §8.1;工作台结构与端点见 workbench-contract.md,本模块不新增端点。
08 总纲 09 环境·版本 10 Go 内核 11 工作台 12 Vue3 13 Flutter 14 Python 15 C/C++ 16 MySQL 17 PostgreSQL 18 Redis 19 SQLite3 20 部署
本页目录
  1. 版本与选型:为什么锁 18.6
  2. 安装:本机 Docker 与 Linux apt 两条路
  3. 核心概念(最小必要)
  4. SQL 速查与 psql 常用命令
  5. Go 集成:pgx / GORM / sqlc 三方案
  6. 常见场景与最佳实践
  7. 备份、运维与 1GB 调优
  8. 常见报错表(11 条)
  9. 验收清单与来源
01

版本与选型:为什么锁 18.6

PostgreSQL 每年发行一个大版本(约 9–10 月),每个大版本社区支持 5 年。2024 年到 17、2025 年到 18、2026 年 8 月 18.6 补丁发布——你现在(2026-10)站在 18 线的"支持窗口中点",正是版本纪律规则 2 要的位置。

锁定版本(唯一事实来源:versions.md)

PostgreSQL 18.6(2026-08-13 发布,18 线补丁版);Go 驱动 pgx v5.11.0(2026-09-07);ORM GORM v1.31.2;代码生成器 sqlc v1.31.1。全部照抄,不挑版本。

为什么不用 17 线或等 19

17 线已进入维护后期,按规则 2 不选快 EOL 的;19 大版本通常 2027 年才出 .0 与首批补丁,.0 版按纪律不锁。18.6 是 18 线第 6 个补丁,坑已修得差不多,最稳。

1.1 PG vs MySQL vs SQLite(分工,引用 08 §8.1)

数据库适合不适合 / 你的用法
PostgreSQL复杂数据与合规场景:JSONB 半结构化、全文检索、复杂查询、严格事务、金融/数据型业务。你的 1GB 小机器上跑"重业务"时用它;纯原型仍默认 SQLite(工作台默认)。
MySQL网站/业务系统主力,生态最大、招人最多。→ 16 模块需要 PG 特有特性(JSONB、GIN、丰富类型)时换 PG。
SQLite单机/原型/工作台默认,单文件零运维。→ 19 模块高并发写、多写者场景升级到 PG/MySQL。

一句话选型:工作台本地默认 SQLite 跑通(契约如此);当业务需要 JSONB、全文检索、复杂报表或合规审计时,切到 PG——本模块教你这一刀切下去怎么切。

02

安装:本机 Docker 与 Linux apt 两条路

你的环境:Windows 本机开发 + 1GB Linux 服务器。本机用 Docker Desktop 起容器(干净、可一键删了重来);服务器按纪律不装 Docker,直接 apt 装原生包(1GB 内存扛不住 Docker Desktop 的开销,见 versions.md 备注)。

2.1 路 ①:Windows 本机 Docker Desktop(docker compose)

前提:Docker Desktop 已装(版本 4.94.0,见 09 模块)。新建目录 pg-lab/,放两个文件:

docker-compose.yml

services:
  pg18:
    image: postgres:18.6
    container_name: pg18
    restart: unless-stopped
    environment:
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: 'Dev#2026Strong'   # 本机开发密码,别照搬到服务器
      POSTGRES_DB: appdb
      TZ: Asia/Shanghai
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data      # 命名卷,容器删了数据还在

volumes:
  pgdata:

启动与验证:

cd pg-lab
docker compose up -d
docker compose ps                 # 看到 pg18 状态 Up
docker compose exec pg18 psql -U appuser -d appdb -c "select version();"

端口冲突预案:若本机 5432 被占(装过原生 PG),把左边端口改成 "15432:5432",后面所有连接串端口用 15432。

2.2 路 ②:Linux 服务器 apt(Debian 13 / Ubuntu 26.04)

Debian 13 与 Ubuntu 26.04 的官方仓库已打包 PostgreSQL 18。1GB 服务器不要 Docker,直接装:

# Debian 13 trixie / Ubuntu 26.04 官方源自带 PG 18
sudo apt update
sudo apt install -y postgresql-18

sudo systemctl enable --now postgresql
systemctl status postgresql --no-pager      # active (exited) 是正常的,主进程在

装完默认:unix socket 在 /var/run/postgresql/,超级用户 postgres,监听 127.0.0.1:5432。外网默认不开放——1GB 小服务器上 PG 只给本机 Go 程序连,绝不对公网开 5432(防火墙也别放)。

2.3 首次连接:psql 建库、建用户

Docker 里已由 compose 环境变量建好 appuser/appdb。服务器上手动建(与本机对齐,方便代码同一份 DSN):

# 服务器:切到 postgres 超级用户进 psql
sudo -u postgres psql

-- 在 psql 里执行:建业务用户与库(密码用你自己的强密码)
CREATE USER appuser WITH PASSWORD 'Prod#2026Xy7!' CREATEDB;
CREATE DATABASE appdb OWNER appuser ENCODING 'UTF8';
\q

# 验证:以 appuser 连入自己的库
psql -h 127.0.0.1 -U appuser -d appdb

pg_hba.conf 一句话(别背,知道在哪改即可)

客户端认证规则在 pg_hba.conf(Debian 在 /etc/postgresql/18/main/pg_hba.conf)。本机 Docker 默认 md5/scram 密码认证;服务器上默认"本机 127.0.0.1 用 scram-sha-256 密码、unix socket 用 peer"。你只需要记住:应用连不上、报 pg_hba 拒绝时,去这文件看最后匹配到的那行认证方式——本模块 §8 报错表第 2 条给了改法。改完执行 SELECT pg_reload_conf(); 热加载。

03

核心概念(最小必要)

只讲你写 Go 代码和排查问题时真用得上的。教科书式铺开的部分,等阶段四再深潜。

3.1 schema:库下面的一层命名空间

一个 database 里可以有多个 schema(默认 public)。工作台这种单应用单库,永远用 public 就够,不要发明 schema;schema 留给"一个库装多个隔离应用"的场景。

3.2 数据类型(重点记这 6 个)

类型用途Go 侧对应 / 备注
bigint / bigserial主键自增。一律用 bigint,别用 int。int64。GORM 里 uint 主键映射到 bigint。
text任意长字符串。PG 里 text 无长度惩罚,别学 MySQL 用 varchar(n) 凑长度。string。
timestamptz带时区时间。存时间一律用它,不要用 timestamp(无时区),见 §6.3。time.Time。
jsonb二进制 JSON,可建索引、可查询。PG 的杀器。[]byte / map[string]any / 自定义 struct。
uuid分布式主键。用 gen_random_uuid()(pgcrypto 已内置)。[16]byte / uuid 库。
integer[] 等数组类型一列存数组(如标签 int[]),比关联表省 JOIN。pgx 支持 []int64 直传。

3.3 索引:三种你真会用到的

索引何时用例子
B-tree(默认)等值、范围、排序。主键和常用 WHERE 列自动/手动建。CREATE INDEX ON tasks(user_id, created_at DESC);
GINJSONB 内部字段、数组包含、全文检索。CREATE INDEX ON tasks USING GIN (tags); / jsonb_path_ops
部分索引(partial)只索引"经常被查的子集",索引更小更快。CREATE INDEX ON tasks(created_at) WHERE done = false;(只关心未完成的)

纪律:不要盲目给每个列建索引。先有慢查询(§3.5),再用 EXPLAIN 确认没走索引,再建。写多读少的表,索引是负担。

3.4 MVCC 与事务隔离(一张表够用)

PG 用 MVCC:读不阻塞写、写不阻塞读,靠多版本实现。默认隔离级别是 READ COMMITTED(够用,别轻易调 SERIALIZABLE)。四个级别你只需知道:

隔离级别解决了什么什么时候用
READ COMMITTED(默认)语句级:每条语句看到的是语句开始时已提交的数据。95% 的业务。工作台默认。
REPEATABLE READ事务级:整个事务看到同一快照。需要"先查后写且中间不能被改"(如转账校验)。
SERIALIZABLE可串行化,防所有幻读;会抛序列化失败需重试。强一致金融场景,且能接受重试逻辑。

并发写冲突怎么办:MVCC 下两个事务改同一行,后提交的那个会阻塞等前者提交;若前者回滚,后者拿到的是新值。Go 里事务越短越好:别把网络请求、RPC 包在事务里。

3.5 EXPLAIN ANALYZE:慢查询怎么优化(一个完整例子)

场景:tasks 表 10 万行,下面这条查询慢:

-- 找某用户未完成、最近的 20 条任务
EXPLAIN ANALYZE
SELECT * FROM tasks
WHERE user_id = 7 AND done = false
ORDER BY created_at DESC LIMIT 20;

EXPLAIN ANALYZE 会真的执行这条 SQL 并打印实际耗时。看输出:

CREATE INDEX CONCURRENTLY idx_tasks_user_undone
ON tasks (user_id, created_at DESC) WHERE done = false;
-- CONCURRENTLY:建索引不锁表(大表必加);部分索引只含未完成行

再跑 EXPLAIN ANALYZE:变成 Index Scan using idx_tasks_user_undone,耗时降到亚毫秒级。优化流程就这三步:EXPLAIN 看 Seq Scan → 建合适索引 → 再 EXPLAIN 验证。

04

SQL 速查与 psql 常用命令

4.1 DDL / DML / 查询速查

-- DDL:建表(工作台 Task 模型对应的 PG 写法)
CREATE TABLE tasks (
  id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  title       text NOT NULL,
  note        text,
  done        boolean NOT NULL DEFAULT false,
  tags        text[] NOT NULL DEFAULT '{}',      -- 数组列
  meta        jsonb NOT NULL DEFAULT '{}'::jsonb,-- JSONB 列
  created_at  timestamptz NOT NULL DEFAULT now(),
  updated_at  timestamptz NOT NULL DEFAULT now()
);

-- DML
INSERT INTO tasks (title) VALUES ('写 PG 专题');
UPDATE tasks SET done = true WHERE id = 1;
DELETE FROM tasks WHERE id = 1;

-- 事务(psql 里)
BEGIN;
  UPDATE tasks SET done = true WHERE id = 1;
  INSERT INTO tasks (title) VALUES ('善后');
COMMIT;          -- 出错就 ROLLBACK;

JSONB 查询(PG 特色)

-- meta 里存了 {"source":"web","priority":3}
SELECT title, meta->>'source' AS source
FROM tasks
WHERE meta @> '{"source":"web"}'::jsonb;     -- @> 包含查询,走 GIN 索引
-- 数字取值:meta->>'priority' 是文本,转数字用 (meta->>'priority')::int

全文检索 tsvector(简介,够用)

-- 给 note 建全文索引(中文需配 zhparser,本模块只讲英文/拼音场景)
ALTER TABLE tasks ADD COLUMN tsv tsvector
  GENERATED ALWAYS AS (to_tsvector('simple', coalesce(note,''))) STORED;
CREATE INDEX ON tasks USING GIN (tsv);

SELECT title FROM tasks
WHERE tsv @@ plainto_tsquery('simple', '备份 恢复');

中文全文检索需要额外装 zhparser 扩展,1GB 小服务器上不急;先用 pg_trgm 三元组模糊搜索过渡(CREATE EXTENSION pg_trgm; + GIN 索引)。

窗口函数(简介:排名/去重)

-- 每个用户最新的 1 条任务
SELECT DISTINCT ON (user_id) user_id, id, title, created_at
FROM tasks
ORDER BY user_id, created_at DESC;
-- 通用窗口:行号/累计
SELECT user_id, title,
       ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM tasks;

4.2 常用 psql 命令(背这 8 个就够)

命令作用
\l列出所有数据库。
\c appdb切换到 appdb 库。
\dt列出当前库的表。
\d tasks看 tasks 表结构、索引、约束。
\dn列出 schema。
\x expanded 显示(行太宽时竖排显示一行记录)。
\timing on每条 SQL 打印耗时。
\h UPDATE / \q查某命令语法帮助 / 退出 psql。
05

Go 集成:pgx / GORM / sqlc 三方案(核心)

这是本模块的心脏。三方案都给"建表 → CRUD → 事务"完整可运行示例,附 go.mod。方案 A(pgx)是 PG 首选驱动,性能最好、特性最全;方案 B(GORM)是工作台接入方式;方案 C(sqlc)简介。

先建模块:下面所有示例共用一个 Go module。先执行:
mkdir pgdemo && cd pgdemo
go mod init pgdemo
go get github.com/jackc/pgx/v5@v5.11.0
go get gorm.io/gorm@v1.31.2
go get gorm.io/driver/postgres
go get github.com/google/uuid
DSN 统一:postgres://appuser:Dev%232026Strong@localhost:5432/appdb?sslmode=disable(密码里的 # 要 URL 编码成 %23)。

5.1 方案 A(首选):pgx v5 —— database/sql 接口与 pgxpool 连接池

pgx 有两种用法:①包装成 database/sql 接口(迁移成本低);②直接用 pgxpool(原生、更快、类型支持更好)。PG 项目推荐直接用 pgxpool。完整单文件示例:

文件:pgxpool_demo.go

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

type Task struct {
	ID        int64
	Title     string
	Done      bool
	CreatedAt time.Time
}

func main() {
	ctx := context.Background()
	dsn := "postgres://appuser:Dev%232026Strong@localhost:5432/appdb?sslmode=disable"

	// 连接池参数:1GB 服务器上 MaxConns 压小(见 §7.3)
	cfg, err := pgxpool.ParseConfig(dsn)
	if err != nil { log.Fatal(err) }
	cfg.MaxConns = 4                      // 本机可 10;1GB 服务器 4 起步
	cfg.MinConns = 1
	cfg.MaxConnLifetime = 30 * time.Minute
	cfg.MaxConnIdleTime = 5 * time.Minute

	pool, err := pgxpool.NewWithConfig(ctx, cfg)
	if err != nil { log.Fatal(err) }
	defer pool.Close()

	// 0. 建表
	_, err = pool.Exec(ctx, `
CREATE TABLE IF NOT EXISTS tasks (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  title      text NOT NULL,
  done       boolean NOT NULL DEFAULT false,
  created_at timestamptz NOT NULL DEFAULT now()
)`)
	if err != nil { log.Fatal(err) }

	// 1. 插入(命名参数 @title)
	var id int64
	err = pool.QueryRow(ctx,
		`INSERT INTO tasks (title) VALUES (@title) RETURNING id`,
		pgx.NamedArgs{"title": "pgxpool 第一条任务"},
	).Scan(&id)
	if err != nil { log.Fatal(err) }
	fmt.Println("inserted id =", id)

	// 2. 单条查询(Scan 到结构体)
	var t Task
	err = pool.QueryRow(ctx,
		`SELECT id, title, done, created_at FROM tasks WHERE id = $1`, id,
	).Scan(&t.ID, &t.Title, &t.Done, &t.CreatedAt)
	if err != nil { log.Fatal(err) }
	fmt.Printf("row = %+v\n", t)

	// 3. 批量插入(CopyFrom,最快的批量写法)
	rows := [][]any{{"批量一"}, {"批量二"}, {"批量三"}}
	n, err := pool.CopyFrom(ctx,
		pgx.Identifier{"tasks"}, []string{"title"},
		pgx.CopyFromRows(rows))
	if err != nil { log.Fatal(err) }
	fmt.Println("copied rows =", n)

	// 4. 事务(转账式:短事务,包住两条写)
	tx, err := pool.Begin(ctx)
	if err != nil { log.Fatal(err) }
	defer tx.Rollback(ctx) // 提交后再 Rollback 是无害的 no-op

	if _, err = tx.Exec(ctx,
		`UPDATE tasks SET done = true WHERE id = $1`, id); err != nil {
		log.Fatal(err)
	}
	if _, err = tx.Exec(ctx,
		`INSERT INTO tasks (title) VALUES ('事务里的第二条')`); err != nil {
		log.Fatal(err)
	}
	if err = tx.Commit(ctx); err != nil { log.Fatal(err) }
	fmt.Println("transaction committed")

	// 5. 多行查询(Rows 遍历,记得 Close)
	rs, err := pool.Query(ctx,
		`SELECT id, title, done, created_at FROM tasks ORDER BY id DESC LIMIT 5`)
	if err != nil { log.Fatal(err) }
	defer rs.Close()
	for rs.Next() {
		var row Task
		if err := rs.Scan(&row.ID, &row.Title, &row.Done, &row.CreatedAt); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("list: %d %s\n", row.ID, row.Title)
	}
}

运行:go run pgxpool_demo.go。

5.1.1 database/sql 包装用法(迁移旧代码时)

import _ "github.com/jackc/pgx/v5/stdlib"
import "database/sql"

db, err := sql.Open("pgx", dsn)   // 驱动名固定 "pgx"
// 之后就是标准 database/sql 那套 db.QueryRow / db.Exec / tx

选型结论:新写的 PG 项目直接上 pgxpool(原生接口,支持 jsonb/数组/任何类型,不用 ORM 也写得很爽);需要 ORM 时才走方案 B。

5.2 方案 B:GORM PG 驱动 —— 工作台接入方式

工作台契约(workbench-contract.md §2/§3):后端 Gin + GORM,默认 glebarez/sqlite,由环境变量 DB_DRIVER=sqlite|mysql 切换。接 PG 时照同一结构扩展一个分支,不新增端点、不改模型字段。

差异点:DSN 与 SQLite 完全不同

项SQLite(默认)PostgreSQL(本模块)
驱动 importgithub.com/glebarez/sqlitegorm.io/driver/postgres
DSN 形式文件路径,如 ./workbench.dbURL:host=localhost user=appuser password=xxx dbname=appdb port=5432 sslmode=disable TimeZone=Asia/Shanghai
环境变量DB_DRIVER=sqlite + DB_PATHDB_DRIVER=postgres + DB_DSN
并发/CGO纯 Go 无 CGO,可交叉编译gorm postgres 底层走 pgx 纯 Go,同样可交叉编译

工作台 internal/db/db.go 的 PG 分支(照契约结构改)

package db

import (
	"fmt"
	"os"

	"github.com/glebarez/sqlite" // 工作台默认驱动
	"gorm.io/driver/postgres"   // PG 驱动
	"gorm.io/gorm"
)

// Open 按 DB_DRIVER 环境变量选驱动;模型(User/Task)与契约 §5 完全一致,不改。
func Open() (*gorm.DB, error) {
	switch os.Getenv("DB_DRIVER") {
	case "postgres":
		dsn := os.Getenv("DB_DSN") // 见下方 .env
		return gorm.Open(postgres.Open(dsn), &gorm.Config{})
	default: // sqlite(工作台默认)
		path := os.Getenv("DB_PATH")
		if path == "" { path = "./workbench.db" }
		return gorm.Open(sqlite.Open(path), &gorm.Config{})
	}
}

本地切 PG 的 .env(只加这几行,其他不动)

# .env(工作台根目录)
PORT=8080
JWT_SECRET=换成你自己的随机串
DB_DRIVER=postgres
DB_DSN=host=localhost user=appuser password=Dev#2026Strong dbname=appdb port=5432 sslmode=disable TimeZone=Asia/Shanghai

模型仍是契约 §5 的 User/Task,GORM 自动迁移会在 PG 上建成 bigint 主键 + timestamptz 友好的列。启动 go run ./cmd/server,访问 /healthz 与任务 CRUD 全部照常——这就是"同一结构、切换驱动"的目标。

5.3 方案 C(简介):sqlc v1.31.1 生成类型安全代码

sqlc 不写 ORM,而是你写原生 SQL,它生成类型安全的 Go 代码(编译期检查、无反射、性能接近手写)。三步最小示例:

# 1. 装 sqlc v1.31.1(本机开发用,不上服务器)
go install github.com/sqlc-dev/sqlc/cmd/sqlc@v1.31.1

# 2. 项目里放 sqlc.yaml + query.sql + 建表 schema.sql

sqlc.yaml

version: "2"
sql:
  - engine: "postgresql"
    schema: "schema.sql"
    queries: "query.sql"
    gen:
      go:
        package: "sqlcgen"
        out: "./internal/sqlcgen"

query.sql

-- name: GetTask :one
SELECT id, title, done, created_at FROM tasks WHERE id = $1;

-- name: ListUndone :many
SELECT id, title FROM tasks WHERE done = false ORDER BY id DESC LIMIT $1;

-- name: CreateTask :one
INSERT INTO tasks (title) VALUES ($1) RETURNING id, title, done, created_at;

运行 sqlc generate,得到 internal/sqlcgen/ 下带类型的 Go 代码:调用 q.GetTask(ctx, 5) 直接返回 (Task, error),字段类型由 sqlc 从 PG schema 推导。适合:SQL 写得熟、要编译期安全、不想用 ORM 的中大型服务;工作台阶段不必上,知道有这条路即可。

5.4 三方案怎么选(一页决策)

pgxpool(首选)

新 PG 项目、要榨性能、爱写原生 SQL。本模块主力示例。

GORM PG 驱动

工作台这类已有 ORM 结构的项目,切驱动即用,不重写业务。

sqlc

SQL 熟练后升级:原生 SQL + 编译期类型安全。阶段三再引入。
06

常见场景与最佳实践

6.1 JSONB 存半结构化

-- 订单里塞一段结构常变的 ext_info(收件人备注/渠道参数),不用加列
CREATE TABLE orders (
  id     bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  ext    jsonb NOT NULL DEFAULT '{}'::jsonb
);
-- 查询"所有来自 web 渠道的订单"——配 GIN 索引后很快
CREATE INDEX ON orders USING GIN (ext jsonb_path_ops);
SELECT id FROM orders WHERE ext @> '{"channel":"web"}';

纪律:JSONB 存"结构松散、不常作为查询条件"的字段;核心查询字段仍然拆成独立列+普通索引,不要全塞 jsonb 里再 @> 乱查。

6.2 upsert(ON CONFLICT)

-- 唯一冲突时更新,而不是报错。工作台"同一用户同一任务去重"场景:
INSERT INTO tasks (user_id, title) VALUES (7, '每日备份')
ON CONFLICT (user_id, title)
DO UPDATE SET updated_at = now()
RETURNING id;

6.3 时间与时区:永远 timestamptz

6.4 分页(两种)

-- ① 偏移分页(页号跳着用,简单但深翻页慢)
SELECT * FROM tasks ORDER BY id DESC LIMIT 20 OFFSET 400;

-- ② 游标分页(列表滚动加载,推荐:记住上一页最后一条的 id)
SELECT * FROM tasks WHERE id < $last_id ORDER BY id DESC LIMIT 20;

6.5 全文检索

见 §4.1 tsvector 简介。英文/数字场景开箱即用;中文上 pg_trgm 过渡、zhparser 进阶。小服务器别一上来上重型搜索引擎,PG 自带的够用就先用着。

07

备份、运维与 1GB 调优

7.1 pg_dump / pg_restore 完整命令

# 逻辑备份(自定义压缩格式,可选择性恢复;服务器上跑)
pg_dump -h 127.0.0.1 -U appuser -d appdb -F c -f /backup/appdb_$(date +%F).dump

# 恢复到新库(先建空库)
createdb -h 127.0.0.1 -U appuser appdb_restore
pg_restore -h 127.0.0.1 -U appuser -d appdb_restore /backup/appdb_2026-10-14.dump

# 纯 SQL 文本备份(小库也可用,便于人读)
pg_dump -h 127.0.0.1 -U appuser -d appdb > /backup/appdb_$(date +%F).sql

7.2 cron 每日备份脚本(完整,可直接抄)

/usr/local/bin/pg_backup.sh

#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR=/backup
RETENTION_DAYS=14                       # 保留 14 天
FILE=appdb_$(date +%F_%H%M).dump
export PGPASSWORD='Prod#2026Xy7!'        # 与建用户时一致;权限 chmod 700 此脚本

mkdir -p "$BACKUP_DIR"
pg_dump -h 127.0.0.1 -U appuser -d appdb -F c -f "$BACKUP_DIR/$FILE"

# 删 14 天前的旧备份
find "$BACKUP_DIR" -name 'appdb_*.dump' -mtime +${RETENTION_DAYS} -delete

echo "[$(date)] backup ok: $FILE"

装 cron:

sudo chmod 700 /usr/local/bin/pg_backup.sh      # 脚本里有密码,权限收紧
sudo crontab -e
# 加一行:每天凌晨 3:10 备份
10 3 * * * /usr/local/bin/pg_backup.sh >> /var/log/pg_backup.log 2>&1

WAL 一句话:WAL(预写日志)默认开启,保证宕机不丢已提交事务;做"时间点恢复(PITR)"才需要归档 WAL——你的 1GB 小工作台用每日 pg_dump 就够,不折腾 PITR。

7.3 1GB 低配服务器调优(postgresql.conf)

PG 默认配置是按大内存服务器写的,1GB 机器必须压。编辑 /etc/postgresql/18/main/postgresql.conf,改完 systemctl restart postgresql:

参数1GB 建议值为什么
shared_buffers64MB(可到 128MB)PG 共享内存。1GB 机器给内存的 1/16~1/8 顶天,别照默认 128MB 以上。
effective_cache_size256MB–384MB给规划器的"OS 缓存预估",不实际分配,用于选择执行计划。
work_mem4MB–8MB每个排序/哈希操作的内存。调大排序快,但 N 个连接并发就是 N×work_mem,1GB 上务必压小。
max_connections20–30默认 100 对 1GB 是浪费(每连接都吃内存)。Go 侧连接池 MaxConns 跟着压到 4–8。
autovacuum保持 on(默认)MVCC 死元组回收,别关;小库默认节奏即可,不用调。

1GB 机器铁律:PG + Go 二进制 + 系统总共 1GB。max_connections 压小、Go 连接池压小、shared_buffers 别贪大。内存吃紧时先砍连接数,不要先砍 shared_buffers。

08

常见报错表(11 条)

照抄报错关键词,对号入座。这些都是真实会踩到的。

报错原文(关键字)原因解决
password authentication failed for user "appuser"(scram)密码错;或客户端用旧 md5、服务器要 scram。核对密码;DSN 里密码特殊字符(#、@)要 URL 编码;重置:ALTER USER appuser PASSWORD '新密码';
pg_hba.conf rejects connection for host ... user ... databasepg_hba 里没有匹配的放行行。在 pg_hba.conf 加一行:host appdb appuser 127.0.0.1/32 scram-sha-256,再 SELECT pg_reload_conf(); 或重启。
port 5432 already in use / 连不上本机装过原生 PG 占了 5432;或容器端口映射冲突。compose 左边端口改 15432;或停掉原生服务;DSN 端口同步改。
database "appdb" does not exist库名拼错 / 没建库。\l 看有哪些库;CREATE DATABASE appdb OWNER appuser;
relation "tasks" does not exist连错库,或表没建(自动迁移没跑)。\dt 确认在哪个库;GORM 重启自动迁移;pgx 方案先跑建表 SQL。
invalid input syntax for type integer / jsonb传参类型对不上列;jsonb 传了非法 JSON 字符串。核对列类型;jsonb 列传合法 JSON(pgx 用 []byte 或 map,别传裸字符串)。
duplicate key value violates unique constraint唯一索引/主键冲突。业务上用 ON CONFLICT upsert(§6.2);或先查后插。
canceling statement due to statement timeout慢 SQL 撞上超时阈值。EXPLAIN ANALYZE 找缺的索引(§3.5);别盲目调大 statement_timeout。
GORM 用了 pgx 驱动又想直连,报错驱动混用同时注册 database/sql 的 pgx 驱动与 GORM 的 postgres 驱动,连接串/驱动名冲突。二选一:GORM 项目就用 gorm.io/driver/postgres 全权接管;要原生 pgx 就别在同一处混用,各自独立建池。
时间读出来差 8 小时列用了 timestamp 无时区,或服务器/连接串没设时区。列改 timestamptz;连接串加 TimeZone=Asia/Shanghai;服务器 timedatectl set-timezone Asia/Shanghai。
could not connect to server: connection refusedPG 没起 / 端口不对 / 服务器只监听 127.0.0.1。systemctl status postgresql / docker compose ps;本机走 127.0.0.1;远程连需改 listen_addresses 并放行防火墙(本教程不推荐对公网开)。
09

验收清单与来源

9.1 本模块验收(过了才算完)

#验收项通过标准
1本机 Docker 起 postgres:18.6,psql 能 \l 看到 appdbdocker compose ps 显示 Up;version() 返回 18.6
2pgxpool_demo.go 跑通:建表/插入/查询/批量/事务全打印go run 无报错,看到 inserted id 与 committed
3工作台 .env 切 DB_DRIVER=postgres,/healthz + 任务 CRUD 正常工作台 PG 模式本地跑通(契约端点不变)
4一次备份恢复演练:pg_dump → 建新库 → pg_restore → 数据条数对得上备份文件生成、恢复后行数一致
5(服务器)postgresql.conf 五项调优落地,systemctl 重启正常shared_buffers/max_connections 已改,服务 active

9.2 版本与来源表

PostgreSQL 18.6(2026-08-13,18 线补丁版;每年一大版本)https://www.postgresql.org/docs/release/
pgx v5.11.0(2026-09-07,PG Go 首选驱动 v5 线)https://github.com/jackc/pgx/releases
GORM v1.31.2(模块 tag v1.x、API 为 v2 线)与 postgres 驱动https://pkg.go.dev/gorm.io/gorm
sqlc v1.31.1(2026-04-22,类型安全 SQL 生成)https://github.com/sqlc-dev/sqlc/releases
工作台结构/端点/模型与"默认 SQLite 可切驱动"约束workbench-contract.md(本项目契约,离线)
四数据库分工(PG=复杂数据与合规场景)08-闭关总纲.html §8.1(本系列)
本模块最小必要概念、SQL 示例、调优与报错表编者整理,基于 PG 18 官方文档惯例与 1GB 低配实践,无外部独立来源

下一步:PG 模式工作台跑通后,进入 18 Redis 专题(缓存/队列);生产化部署细节(systemd/Nginx/监控)见 20 部署运维专题。