项目介绍

PostgreSQL(常简称 Postgres)是世界上最先进的开源关系型数据库管理系统。它起源于 1986 年加州大学伯克利分校的 POSTGRES 项目,经过 40 多年的持续开发,已成为企业级应用的首选数据库之一。

PostgreSQL 以其卓越的数据完整性、丰富的功能集、可扩展性和标准兼容性而著称。它支持 JSON/JSONB、全文搜索、地理空间数据 (PostGIS)、窗口函数、CTE、表分区等高级特性,被广泛应用于金融、电信、政府、互联网等各个领域。

项目信息详情
开发团队PostgreSQL Global Development Group
GitHub Stars17,000+
官方网站postgresql.org
开源协议PostgreSQL License(类 BSD/MIT,非常宽松)
核心语言C
历史40+ 年(1986 年起源于 UC Berkeley)
最新稳定版PostgreSQL 17(2024 年 9 月发布)
一句话理解:PostgreSQL 就是"开源界的 Oracle" — 拥有商业数据库级别的功能和可靠性,但完全免费、开源、无任何使用限制。

PostgreSQL vs MySQL vs SQLite vs MongoDB

对比维度 PostgreSQL MySQL SQLite MongoDB
类型 关系型 (ORDBMS) 关系型 (RDBMS) 嵌入式关系型 文档型 (NoSQL)
ACID 合规 完全支持 InnoDB 支持 完全支持 4.0+ 多文档事务
JSON 支持 原生 JSONB,可索引 JSON 类型,功能较少 JSON 函数有限 原生文档模型
全文搜索 内置 tsvector/tsquery InnoDB 全文索引 FTS5 扩展 Atlas Search
扩展性 插件生态极丰富 存储引擎可选 嵌入式,不可扩展 分片内置
并发性能 MVCC,高并发读写 InnoDB MVCC 写锁,单写并发低 文档级锁
SQL 标准兼容 最接近 SQL 标准 部分兼容 部分兼容 MQL(非 SQL)
学习曲线 中等 简单 极简 中等
适用场景 企业级、复杂查询、GIS Web 应用、LAMP 栈 嵌入式、移动端、小项目 灵活 schema、大数据量
选择建议:如果你需要复杂查询、数据完整性、JSON 与关系混合建模、GIS 空间数据、或高度可扩展的插件生态,PostgreSQL 是最佳选择。如果追求极简和快速上手,MySQL 更合适;嵌入式场景用 SQLite;纯文档模型且 schema 高度灵活用 MongoDB。

安装部署

方式 1 Linux 安装(Ubuntu/Debian)

使用官方 APT 仓库安装最新版本:
# 添加 PostgreSQL 官方仓库
sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -

# 更新并安装
sudo apt update
sudo apt install -y postgresql-17

# 检查状态
sudo systemctl status postgresql

# 切换到 postgres 用户
sudo -u postgres psql
CentOS / RHEL:使用 sudo yum install -y postgresql17-server,安装后需运行 postgresql-17-setup initdb 初始化。

方式 2 macOS 安装

使用 Homebrew 一键安装:
# 安装
brew install postgresql@17

# 启动服务
brew services start postgresql@17

# 连接
psql postgres
也可以使用图形化安装器 Postgres.app,下载后拖入应用程序文件夹即可。

方式 3 Windows 安装

从官方网站下载安装器:
1. 访问 postgresql.org/download/windows
2. 下载 EDB 安装器(包含 pgAdmin 图形界面)
3. 运行安装向导,设置超级用户密码
4. 安装完成后通过 pgAdmin 或命令行连接
# Windows 命令行连接
psql -U postgres -h localhost

方式 4 Docker 部署(推荐用于开发和测试)

最快速的启动方式:
# 快速启动
docker run -d --name postgres \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  postgres:17-alpine

# 连接
docker exec -it postgres psql -U postgres
Docker Compose 部署(生产推荐):
# docker-compose.yml version: "3.8" services: postgres: image: postgres:17-alpine restart: always ports: - "5432:5432" environment: - POSTGRES_DB=myapp - POSTGRES_USER=myuser - POSTGRES_PASSWORD=strong-password-here volumes: - pgdata:/var/lib/postgresql/data shm_size: '256mb' # 增加共享内存,避免大查询报错 volumes: pgdata:
注意:生产环境务必修改默认密码,配置 SSL,并做好数据卷备份。shm_size 建议设为物理内存的 25%。

核心概念

概念 1 表与数据类型(Tables & Data Types)

PostgreSQL 支持极其丰富的数据类型,远超其他数据库:
CREATE TABLE users (
    id          BIGSERIAL PRIMARY KEY,
    username    VARCHAR(50) UNIQUE NOT NULL,
    email       TEXT NOT NULL,
    profile     JSONB DEFAULT '{}',
    tags        TEXT[] DEFAULT '{}',
    created_at  TIMESTAMPTZ DEFAULT NOW(),
    is_active   BOOLEAN DEFAULT true,
    balance     NUMERIC(12,2) DEFAULT 0.00
);
常用特殊类型:
- SERIAL / BIGSERIAL — 自增主键
- JSONB — 二进制 JSON,可索引,推荐替代 JSON
- TEXT[] — 数组类型
- TIMESTAMPTZ — 带时区的时间戳
- UUID — 通用唯一标识符
- INET / CIDR — IP 地址类型
- NUMERIC — 精确小数,适合金融

概念 2 索引(Indexes)

PostgreSQL 支持多种索引类型,选对索引至关重要:
-- B-Tree 索引(默认,适合等值和范围查询)
CREATE INDEX idx_users_email ON users(email);

-- GIN 索引(适合 JSONB、数组、全文搜索)
CREATE INDEX idx_users_profile ON users USING GIN(profile);

-- GiST 索引(适合地理空间、范围类型)
CREATE INDEX idx_geo ON locations USING GiST(coordinates);

-- BRIN 索引(适合时序数据,极小体积)
CREATE INDEX idx_logs_time ON logs USING BRIN(created_at);

-- 部分索引(只索引满足条件的行)
CREATE INDEX idx_active_users ON users(email) WHERE is_active = true;

-- 表达式索引
CREATE INDEX idx_lower_email ON users(LOWER(email));
选择指南:B-Tree 覆盖 90% 场景;JSONB 用 GIN;GIS 用 GiST;时序大表用 BRIN;高选择性场景用部分索引。

概念 3 约束(Constraints)

约束是保障数据完整性的核心机制:
CREATE TABLE orders (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    amount      NUMERIC(12,2) CHECK (amount > 0),
    status      VARCHAR(20) DEFAULT 'pending'
                CHECK (status IN ('pending','paid','shipped','done','cancelled')),
    order_no    VARCHAR(32) UNIQUE NOT NULL,
    CONSTRAINT  positive_amount CHECK (amount > 0)
);

-- 排他约束(防止会议室时间冲突)
CREATE TABLE reservations (
    room_id INT,
    during  TSTZRANGE,
    EXCLUDE USING GiST (room_id WITH =, during WITH &&)
);
常用约束类型:PRIMARY KEYUNIQUENOT NULLCHECKFOREIGN KEYEXCLUDE

概念 4 事务(Transactions)

PostgreSQL 提供完整的 ACID 事务支持:
BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 检查余额
DO $$
BEGIN
    IF (SELECT balance FROM accounts WHERE id = 1) < 0 THEN
        RAISE EXCEPTION '余额不足';
    END IF;
END $$;

COMMIT;

-- 发生错误自动回滚
-- 也可以用 SAVEPOINT 做部分回滚
BEGIN;
SAVEPOINT sp1;
INSERT INTO logs VALUES ('step1');
SAVEPOINT sp2;
INSERT INTO logs VALUES ('step2'); -- 假设这里出错
ROLLBACK TO sp2;                  -- 只回滚 step2
COMMIT;                            -- step1 仍然提交
隔离级别:PostgreSQL 支持 Read Committed(默认)、Repeatable Read、Serializable 三个级别。可通过 SET TRANSACTION ISOLATION LEVEL 设置。

概念 5 MVCC(多版本并发控制)

MVCC 是 PostgreSQL 高并发性能的基石。每行数据都有隐藏的 xmin(创建事务 ID)和 xmax(删除事务 ID)列:

- 读不阻塞写,写不阻塞读 — 读操作看到的是事务开始时的快照
- UPDATE 不是原地修改 — 而是标记旧行删除 + 插入新行
- DELETE 不是立即删除 — 只是标记 xmax,等 VACUUM 回收

这就是为什么 PostgreSQL 需要定期 VACUUM 的原因 — 清理死元组(dead tuples),释放空间。
注意:MVCC 带来的"表膨胀"是 PostgreSQL 最常见的运维问题。务必确保 autovacuum 正常运行。

概念 6 Schema 与命名空间

Schema 是数据库内部的命名空间,用于组织和隔离对象:
-- 创建 schema
CREATE SCHEMA app;
CREATE SCHEMA analytics;

-- 在指定 schema 下建表
CREATE TABLE app.users (id SERIAL PRIMARY KEY, name TEXT);
CREATE TABLE analytics.events (id SERIAL PRIMARY KEY, data JSONB);

-- 设置搜索路径
SET search_path TO app, public;

-- 查看当前搜索路径
SHOW search_path;

- 默认 schema 是 public
- 多租户系统可以用 schema 隔离不同客户的数据
- search_path 决定了不指定 schema 时的查找顺序

SQL 进阶

进阶 1 CTE — 公用表表达式

CTE 让复杂查询变得清晰可读,还支持递归:
-- 基本 CTE
WITH active_users AS (
    SELECT id, username, email
    FROM users
    WHERE is_active = true AND created_at > NOW() - INTERVAL '30 days'
),
user_orders AS (
    SELECT user_id, COUNT(*) as order_count, SUM(amount) as total
    FROM orders
    WHERE user_id IN (SELECT id FROM active_users)
    GROUP BY user_id
)
SELECT u.username, u.email, COALESCE(o.order_count, 0) as orders, COALESCE(o.total, 0) as spent
FROM active_users u
LEFT JOIN user_orders o ON u.id = o.user_id
ORDER BY spent DESC;

-- 递归 CTE(遍历树形结构)
WITH RECURSIVE category_tree AS (
    SELECT id, name, parent_id, 1 as depth
    FROM categories WHERE parent_id IS NULL
    UNION ALL
    SELECT c.id, c.name, c.parent_id, ct.depth + 1
    FROM categories c
    JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree ORDER BY depth, name;

进阶 2 窗口函数(Window Functions)

窗口函数在不减少行数的情况下进行聚合计算:
-- 排名:每个部门内按薪资排名
SELECT name, department, salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank_with_ties,
    DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dense_rank
FROM employees;

-- 累计求和
SELECT date, revenue,
    SUM(revenue) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as running_total
FROM daily_sales;

-- 同比/环比
SELECT month, revenue,
    LAG(revenue, 1) OVER (ORDER BY month) as prev_month,
    revenue - LAG(revenue, 1) OVER (ORDER BY month) as mom_change,
    LAG(revenue, 12) OVER (ORDER BY month) as prev_year,
    revenue - LAG(revenue, 12) OVER (ORDER BY month) as yoy_change
FROM monthly_revenue;

进阶 3 JSONB 操作

PostgreSQL 的 JSONB 让你兼具关系型和文档型数据库的优势:
-- 查询 JSONB 字段
SELECT * FROM users WHERE profile->>'city' = '北京';
SELECT * FROM users WHERE profile @> '{"level": "vip"}';

-- JSONB 路径查询(PostgreSQL 12+)
SELECT * FROM users WHERE profile @? '$.addresses[*] ? (@.city == "上海")';

-- 更新 JSONB 字段
UPDATE users SET profile = profile || '{"verified": true}' WHERE id = 1;
UPDATE users SET profile = jsonb_set(profile, '{address,city}', '"深圳"') WHERE id = 1;

-- JSONB 聚合
SELECT profile->>'city' as city, COUNT(*) as user_count
FROM users
WHERE profile ? 'city'
GROUP BY profile->>'city'
ORDER BY user_count DESC;

-- JSONB 展开为行
SELECT id, elem->>'name' as tag_name
FROM users, jsonb_array_elements(profile->'tags') as elem;
性能提示:对常查询的 JSONB 键建立 GIN 索引:CREATE INDEX idx_profile ON users USING GIN(profile)

进阶 4 全文搜索(Full-Text Search)

PostgreSQL 内置全文搜索引擎,无需外部依赖:
-- 添加 tsvector 列并建立索引
ALTER TABLE articles ADD COLUMN search_vector TSVECTOR;
UPDATE articles SET search_vector =
    setweight(to_tsvector('chinese', title), 'A') ||
    setweight(to_tsvector('chinese', content), 'B');
CREATE INDEX idx_search ON articles USING GIN(search_vector);

-- 全文搜索查询
SELECT title, ts_rank(search_vector, query) as rank
FROM articles, to_tsquery('chinese', '数据库 & 优化') as query
WHERE search_vector @@ query
ORDER BY rank DESC;

-- 搜索结果高亮
SELECT ts_headline('chinese', content, to_tsquery('chinese', '数据库'),
    'StartSel=<mark>, StopSel=</mark>, MaxWords=50') as snippet
FROM articles
WHERE search_vector @@ to_tsquery('chinese', '数据库');
中文搜索:需要安装 zhparserpg_jieba 分词插件。默认分词器不支持中文。

进阶 5 表分区(Partitioning)

分区表将大表分割为小块,提升查询和维护效率:
-- 创建范围分区表(按时间)
CREATE TABLE logs (
    id      BIGSERIAL,
    msg     TEXT,
    level   VARCHAR(10),
    ts      TIMESTAMPTZ NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (ts);

-- 创建分区
CREATE TABLE logs_2025q1 PARTITION OF logs FOR VALUES FROM ('2025-01-01') TO ('2025-04-01');
CREATE TABLE logs_2025q2 PARTITION OF logs FOR VALUES FROM ('2025-04-01') TO ('2025-07-01');
CREATE TABLE logs_2025q3 PARTITION OF logs FOR VALUES FROM ('2025-07-01') TO ('2025-10-01');
CREATE TABLE logs_2025q4 PARTITION OF logs FOR VALUES FROM ('2025-10-01') TO ('2026-01-01');

-- 在分区表上创建索引(自动应用到所有分区)
CREATE INDEX idx_logs_ts ON logs(ts);

-- 删除旧分区(比 DELETE 快得多)
DROP TABLE logs_2024q1;
分区策略:RANGE(时间序列)、LIST(枚举值分类)、HASH(均匀分布)。

进阶 6 LATERAL JOIN

LATERAL 让子查询可以引用外层查询的列,类似"for each"循环:
-- 每个用户最近 3 条订单
SELECT u.username, r.order_no, r.amount, r.created_at
FROM users u
CROSS JOIN LATERAL (
    SELECT order_no, amount, created_at
    FROM orders
    WHERE user_id = u.id
    ORDER BY created_at DESC
    LIMIT 3
) r;

-- 比传统方式(窗口函数 + 子查询)更直观高效

进阶 7 生成列与计算列

PostgreSQL 12+ 支持 GENERATED ALWAYS AS 存储计算列:
CREATE TABLE products (
    id         SERIAL PRIMARY KEY,
    price      NUMERIC(10,2),
    tax_rate   NUMERIC(4,2) DEFAULT 0.13,
    total      NUMERIC(10,2) GENERATED ALWAYS AS (price * (1 + tax_rate)) STORED,
    full_name  TEXT GENERATED ALWAYS AS (brand || ' ' || model) STORED,
    brand      TEXT,
    model      TEXT
);
STORED 表示值物理存储在磁盘上,插入/更新时自动计算。

进阶 8 UPSERT(INSERT ON CONFLICT)

PostgreSQL 的 UPSERT 语法优雅地处理"存在则更新,不存在则插入":
-- 存在则更新
INSERT INTO user_stats (user_id, login_count, last_login)
VALUES (1, 1, NOW())
ON CONFLICT (user_id)
DO UPDATE SET
    login_count = user_stats.login_count + 1,
    last_login = EXCLUDED.last_login;

-- 存在则跳过
INSERT INTO tags (name)
VALUES ('postgresql'), ('database'), ('sql')
ON CONFLICT (name) DO NOTHING;

-- 批量 UPSERT(配合 RETURNING)
INSERT INTO inventory (sku, quantity)
VALUES ('A001', 10), ('A002', 20), ('A003', 30)
ON CONFLICT (sku)
DO UPDATE SET quantity = inventory.quantity + EXCLUDED.quantity
RETURNING *;

进阶 9 物化视图(Materialized Views)

将复杂查询结果缓存为物理表,极大提升重复查询速度:
-- 创建物化视图
CREATE MATERIALIZED VIEW mv_daily_stats AS
SELECT date_trunc('day', created_at) as day,
       COUNT(*) as total_orders,
       SUM(amount) as total_revenue,
       AVG(amount) as avg_order_value
FROM orders
GROUP BY date_trunc('day', created_at)
WITH DATA;

-- 在物化视图上建索引
CREATE UNIQUE INDEX idx_mv_daily ON mv_daily_stats(day);

-- 刷新(全量重算)
REFRESH MATERIALIZED VIEW mv_daily_stats;

-- 并发刷新(不阻塞读,需要唯一索引)
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_stats;
最佳实践:配合 cron 定时刷新,或使用触发器在数据变化时刷新。适合报表、仪表盘等查询频繁但数据更新不频繁的场景。

进阶 10 PL/pgSQL 存储过程与函数

PostgreSQL 内置过程语言,可编写复杂业务逻辑:
-- 创建函数
CREATE OR REPLACE FUNCTION transfer_funds(
    from_id BIGINT,
    to_id BIGINT,
    amount NUMERIC
) RETURNS VOID AS $$
DECLARE
    current_balance NUMERIC;
BEGIN
    SELECT balance INTO current_balance FROM accounts WHERE id = from_id FOR UPDATE;

    IF current_balance < amount THEN
        RAISE EXCEPTION '余额不足:当前 %, 需要 %', current_balance, amount;
    END IF;

    UPDATE accounts SET balance = balance - amount WHERE id = from_id;
    UPDATE accounts SET balance = balance + amount WHERE id = to_id;

    INSERT INTO transactions (from_id, to_id, amount, ts)
    VALUES (from_id, to_id, amount, NOW());
END;
$$ LANGUAGE plpgsql;

-- 调用
SELECT transfer_funds(1, 2, 100.00);

性能优化

优化 1 EXPLAIN ANALYZE — 查询分析利器

任何性能优化都从 EXPLAIN ANALYZE 开始:
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.username, COUNT(o.id) as order_count
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2025-01-01'
GROUP BY u.username
ORDER BY order_count DESC
LIMIT 10;
关键指标解读:
- Seq Scan — 全表扫描(大表应避免)
- Index Scan — 索引扫描(理想)
- Bitmap Index Scan — 位图索引扫描(多条件 OR 查询)
- actual time — 实际执行时间(毫秒)
- rows — 实际返回行数 vs 估计行数(差异大说明统计信息不准)
- Buffers: shared hit / read — 缓存命中 vs 磁盘读取
可视化工具:推荐 explain.depesz.comexplain.dalibo.com 分析执行计划。

优化 2 索引优化策略

索引不是越多越好,需要精准设计:
-- 1. 复合索引(遵循最左前缀原则)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);

-- 2. 覆盖索引(INCLUDE 避免回表)
CREATE INDEX idx_orders_cover ON orders(user_id) INCLUDE (amount, status);

-- 3. 查找未使用的索引
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND indexname NOT LIKE '%pkey%'
ORDER BY pg_relation_size(indexrelid) DESC;

-- 4. 查找缺失索引(慢查询中的 Seq Scan)
SELECT relname, seq_scan, seq_tup_read,
       idx_scan, seq_tup_read / GREATEST(seq_scan, 1) as avg_seq_rows
FROM pg_stat_user_tables
WHERE seq_scan > 100 AND seq_tup_read / GREATEST(seq_scan, 1) > 1000
ORDER BY seq_tup_read DESC;
注意:每个索引都会减慢 INSERT/UPDATE/DELETE。定期清理不使用的索引:DROP INDEX CONCURRENTLY idx_name;

优化 3 连接池 — PgBouncer

PostgreSQL 的进程模型(每连接一个进程)在高并发时开销很大,PgBouncer 是必备的连接池中间件:
# 安装 PgBouncer
sudo apt install -y pgbouncer
# /etc/pgbouncer/pgbouncer.ini [databases] myapp = host=127.0.0.1 port=5432 dbname=myapp [pgbouncer] listen_addr = 0.0.0.0 listen_port = 6432 pool_mode = transaction # 推荐事务级别池化 max_client_conn = 1000 default_pool_size = 50 min_pool_size = 10 reserve_pool_size = 5 auth_type = md5 auth_file = /etc/pgbouncer/userlist.txt
应用连接 PgBouncer 的 6432 端口而非直连 PostgreSQL 的 5432 端口。
效果:1000 个应用连接可以复用 50 个数据库连接,内存开销降低 95%。

优化 4 VACUUM 与 Autovacuum 调优

由于 MVCC 机制,PostgreSQL 需要 VACUUM 回收死元组空间:
-- 手动 VACUUM
VACUUM VERBOSE users;       -- 回收空间(不锁表)
VACUUM FULL users;          -- 重写表(锁表,释放磁盘空间)
VACUUM ANALYZE users;       -- 回收空间 + 更新统计信息

-- 查看表膨胀情况
SELECT relname,
    n_dead_tup,
    n_live_tup,
    round(n_dead_tup * 100.0 / GREATEST(n_live_tup + n_dead_tup, 1), 1) as dead_pct,
    last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
Autovacuum 关键参数调优:
# postgresql.conf — 高写入量场景 autovacuum_max_workers = 5 # 默认 3 autovacuum_naptime = 30s # 默认 1min autovacuum_vacuum_scale_factor = 0.05 # 默认 0.2(5% 死元组就触发) autovacuum_vacuum_cost_limit = 2000 # 默认 200(加速 vacuum)
严禁:不要关闭 autovacuum!这会导致表膨胀、事务 ID 回卷,最终数据库可能拒绝服务。

优化 5 关键配置参数调优

根据服务器硬件调整 postgresql.conf 核心参数:
# 内存相关(假设 16GB RAM) shared_buffers = 4GB # RAM 的 25% effective_cache_size = 12GB # RAM 的 75% work_mem = 64MB # 排序/哈希操作内存(注意:每个操作都会分配) maintenance_work_mem = 1GB # VACUUM/CREATE INDEX 用 # WAL 相关 wal_buffers = 64MB max_wal_size = 4GB min_wal_size = 1GB checkpoint_completion_target = 0.9 # 并行查询 max_parallel_workers_per_gather = 4 max_parallel_workers = 8 parallel_tuple_cost = 0.01 # 连接 max_connections = 200 # 配合 PgBouncer 可以设低一些 # 日志 log_min_duration_statement = 500 # 记录超过 500ms 的慢查询 log_checkpoints = on log_lock_waits = on
快速生成配置:使用 PGTune 在线工具,输入服务器规格即可获得推荐配置。

备份恢复

备份 1 pg_dump — 逻辑备份

最常用的备份方式,适合中小型数据库:
# 备份单个数据库(自定义格式,支持并行恢复)
pg_dump -Fc -j 4 -f mydb.dump mydb

# 备份为 SQL 文件
pg_dump -f mydb.sql mydb

# 仅备份 schema(不含数据)
pg_dump --schema-only -f schema.sql mydb

# 仅备份数据
pg_dump --data-only -f data.sql mydb

# 备份指定表
pg_dump -t users -t orders -f tables.dump mydb

# 备份所有数据库
pg_dumpall -f all_databases.sql

# 恢复
pg_restore -d mydb -j 4 mydb.dump       # 自定义格式
psql -d mydb -f mydb.sql                  # SQL 格式
定时备份脚本:配合 cron 每日执行,保留最近 7 天备份,定期上传至对象存储。

备份 2 pg_basebackup — 物理备份

复制整个数据目录,适合大型数据库和搭建流复制备库:
# 基础物理备份
pg_basebackup -D /backup/base -Ft -z -P -U replicator

# 参数说明:
# -D: 备份目录
# -Ft: tar 格式
# -z: gzip 压缩
# -P: 显示进度
# -U: 复制用户

# 搭建流复制备库
pg_basebackup -D /var/lib/postgresql/17/standby \
  -h primary_host -p 5432 -U replicator -R -P
-R 参数会自动生成 standby.signal 和连接配置,备库启动后自动追赶主库。

备份 3 WAL 归档与持续归档

WAL(Write-Ahead Log)归档是实现增量备份和时间点恢复的基础:
# postgresql.conf — 开启 WAL 归档 wal_level = replica # 或 logical(逻辑复制需要) archive_mode = on archive_command = 'cp %p /archive/%f' # 生产环境建议用 rsync/S3 archive_timeout = 300 # 最长 5 分钟强制归档
归档的 WAL 文件配合 pg_basebackup 实现完整的增量备份方案。

备份 4 PITR — 时间点恢复

将数据库恢复到任意历史时间点,适用于误操作恢复:
# 1. 确保有基础备份 + WAL 归档
# 2. 创建恢复配置
# postgresql.conf(恢复服务器) restore_command = 'cp /archive/%f %p' recovery_target_time = '2026-03-12 14:30:00+08' # 恢复到误操作前 recovery_target_action = 'promote' # 恢复完成后提升为主库
# 3. 创建 recovery.signal 文件
touch /var/lib/postgresql/17/main/recovery.signal

# 4. 启动 PostgreSQL,自动回放 WAL 到目标时间点
sudo systemctl start postgresql
关键:PITR 恢复前务必确认目标时间点正确。恢复是不可逆的(除非再用备份重新恢复)。建议先在测试环境验证。

常见问题 (50 个)

连接与认证 (#1 — #8)

#1 FATAL: password authentication failed for user "postgres"

FATAL: password authentication failed for user "postgres"
密码认证失败。
1. 确认输入的密码正确
2. 修改密码:sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'newpassword';"
3. 检查 pg_hba.conf 中的认证方式,确保对应行使用 md5scram-sha-256
4. 修改后重新加载配置:SELECT pg_reload_conf();

#2 FATAL: no pg_hba.conf entry for host

FATAL: no pg_hba.conf entry for host "192.168.1.100", user "myuser", database "mydb", SSL off
客户端 IP 未被允许连接。编辑 pg_hba.conf 添加规则:
# 允许特定 IP
host    mydb    myuser    192.168.1.0/24    scram-sha-256

# 允许所有 IP(不推荐用于生产)
host    all     all       0.0.0.0/0         scram-sha-256
修改后执行 SELECT pg_reload_conf();systemctl reload postgresql

#3 FATAL: Peer authentication failed

FATAL: Peer authentication failed for user "myuser"
Peer 认证要求操作系统用户名与数据库用户名一致。
1. 切换到对应系统用户:sudo -u myuser psql
2. 或修改 pg_hba.confpeer 改为 md5
# 将
local   all   all   peer
# 改为
local   all   all   md5
然后重新加载配置。

#4 FATAL: too many connections for role

FATAL: too many connections for role "myuser"
该用户的连接数超出限制。
1. 查看当前连接:SELECT count(*) FROM pg_stat_activity WHERE usename = 'myuser';
2. 增加用户连接限制:ALTER ROLE myuser CONNECTION LIMIT 100;
3. 清理空闲连接:SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE usename = 'myuser' AND state = 'idle' AND query_start < NOW() - INTERVAL '10 minutes';
4. 长期方案:使用 PgBouncer 连接池。

#5 FATAL: remaining connection slots are reserved

FATAL: remaining connection slots are reserved for non-replication superuser connections
所有普通连接槽位已满。
1. 用超级用户连接后释放空闲连接:
sudo -u postgres psql
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle' AND pid != pg_backend_pid();

2. 增加最大连接数:修改 postgresql.confmax_connections = 300(需重启)
3. 根本解决方案:部署 PgBouncer 连接池。

#6 could not connect to server: Connection refused

psql: error: could not connect to server: Connection refused Is the server running on host "localhost" and accepting TCP/IP connections on port 5432?
PostgreSQL 服务未运行或未监听指定端口。
1. 检查服务状态:sudo systemctl status postgresql
2. 启动服务:sudo systemctl start postgresql
3. 检查监听配置:postgresql.conflisten_addresses = '*'(默认只监听 localhost)
4. 检查端口:ss -tlnp | grep 5432

#7 SSL connection is required

FATAL: no pg_hba.conf entry for host "x.x.x.x", user "myuser", database "mydb", no encryption
服务器要求 SSL 连接但客户端未使用 SSL。
1. 客户端连接时添加 SSL 参数:psql "host=myhost dbname=mydb sslmode=require"
2. 或修改 pg_hba.confhostssl 改为 host(不推荐,降低安全性)
3. 应用连接字符串添加:?sslmode=require

#8 FATAL: database "xxx" does not exist

FATAL: database "myapp" does not exist
尝试连接的数据库不存在。
1. 列出所有数据库:psql -U postgres -l
2. 创建数据库:CREATE DATABASE myapp OWNER myuser;
3. 注意数据库名区分大小写(除非用双引号包裹)

查询与 SQL 错误 (#9 — #20)

#9 ERROR: relation "xxx" does not exist

ERROR: relation "users" does not exist LINE 1: SELECT * FROM users;
表不存在或不在搜索路径中。
1. 确认表名拼写正确:\dt *users*
2. 检查 schema:\dt *.users
3. 设置搜索路径:SET search_path TO myschema, public;
4. 使用完整限定名:SELECT * FROM myschema.users;

#10 ERROR: column "xxx" must appear in the GROUP BY clause

ERROR: column "users.name" must appear in the GROUP BY clause or be used in an aggregate function
PostgreSQL 严格遵守 SQL 标准,SELECT 中的非聚合列必须在 GROUP BY 中。
1. 将列加入 GROUP BY:GROUP BY id, name
2. 或使用聚合函数:MAX(name)STRING_AGG(name, ',')
3. 注意:MySQL 默认允许此行为(ONLY_FULL_GROUP_BY 关闭时),迁移时需修改查询。

#11 ERROR: operator does not exist: character varying = integer

ERROR: operator does not exist: character varying = integer HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
PostgreSQL 不做隐式类型转换(不像 MySQL)。
1. 显式转换:WHERE id = '123'::integerWHERE id::text = '123'
2. 使用 CAST:WHERE CAST(id AS text) = '123'
3. 检查应用代码传参类型是否与字段类型匹配。

#12 ERROR: deadlock detected

ERROR: deadlock detected DETAIL: Process 12345 waits for ShareLock on transaction 67890; blocked by process 67891.
两个事务互相等待对方持有的锁。
1. PostgreSQL 会自动检测并中止其中一个事务,应用需捕获错误并重试
2. 预防措施:所有事务以相同顺序访问表/行
3. 缩短事务时间,减少锁持有时间
4. 查看锁等待:SELECT * FROM pg_stat_activity WHERE wait_event_type = 'Lock';
5. 开启死锁日志:log_lock_waits = ondeadlock_timeout = 1s

#13 ERROR: value too long for type character varying(N)

ERROR: value too long for type character varying(50)
插入的字符串超过字段定义的长度限制。
1. 扩大字段长度:ALTER TABLE users ALTER COLUMN username TYPE VARCHAR(100);
2. 或改用 TEXT 类型(无长度限制,性能与 VARCHAR 相同)
3. 在应用层做数据验证和截断处理。

#14 ERROR: duplicate key value violates unique constraint

ERROR: duplicate key value violates unique constraint "users_email_key" DETAIL: Key (email)=(user@example.com) already exists.
插入了重复的唯一键值。
1. 使用 UPSERT:INSERT ... ON CONFLICT (email) DO UPDATE SET ...
2. 使用 ON CONFLICT DO NOTHING 跳过重复
3. 插入前先检查(注意并发安全性,推荐用 UPSERT)
4. 如果是序列跳号导致,重置序列:SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));

#15 ERROR: null value in column violates not-null constraint

ERROR: null value in column "email" of relation "users" violates not-null constraint
尝试向 NOT NULL 列插入空值。
1. 确保插入数据包含所有 NOT NULL 字段的值
2. 为字段设置默认值:ALTER TABLE users ALTER COLUMN email SET DEFAULT '';
3. 如果允许为空:ALTER TABLE users ALTER COLUMN email DROP NOT NULL;

#16 ERROR: insert or update on table violates foreign key constraint

ERROR: insert or update on table "orders" violates foreign key constraint "orders_user_id_fkey" DETAIL: Key (user_id)=(999) is not present in table "users".
外键引用的父表中不存在对应记录。
1. 确保先插入父表数据再插入子表
2. 检查引用的 ID 是否正确
3. 如果不需要外键约束可以删除:ALTER TABLE orders DROP CONSTRAINT orders_user_id_fkey;
4. 批量导入时可以先禁用触发器:ALTER TABLE orders DISABLE TRIGGER ALL;(导入后务必重新启用)

#17 ERROR: current transaction is aborted

ERROR: current transaction is aborted, commands ignored until end of transaction block
事务中某条语句出错后,整个事务进入失败状态,后续所有语句都会被拒绝。
1. 执行 ROLLBACK; 结束当前事务
2. 然后重新开始事务并执行正确的语句
3. 使用 SAVEPOINT 做部分回滚可以避免此问题
4. ORM 用户注意:许多框架会自动开启事务,单条语句出错也会导致后续查询全部失败。

#18 ERROR: syntax error at or near "LIMIT"

ERROR: syntax error at or near "LIMIT" LINE 1: DELETE FROM logs LIMIT 1000;
PostgreSQL 的 DELETE/UPDATE 不支持 LIMIT 语法(MySQL 支持)。
使用子查询替代:
DELETE FROM logs
WHERE id IN (
    SELECT id FROM logs
    WHERE created_at < '2025-01-01'
    ORDER BY created_at
    LIMIT 1000
);

#19 ERROR: column reference "id" is ambiguous

ERROR: column reference "id" is ambiguous
JOIN 查询中多个表都有同名列,PostgreSQL 无法确定你引用的是哪个表的列。
使用表名或别名限定列名:
-- 错误
SELECT id, name FROM users u JOIN orders o ON u.id = o.user_id;

-- 正确
SELECT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id;

#20 ERROR: cannot change return type of existing function

ERROR: cannot change return type of existing function
修改函数返回类型时不能使用 CREATE OR REPLACE
1. 先删除旧函数:DROP FUNCTION my_func(INT, TEXT);(需要指定参数类型)
2. 再创建新函数
3. 如果有依赖对象(视图、其他函数),需要用 CASCADEDROP FUNCTION my_func(INT, TEXT) CASCADE;

性能问题 (#21 — #30)

#21 查询突然变慢 — Seq Scan 替代了 Index Scan

EXPLAIN 显示 Seq Scan,但之前用的是 Index Scan
通常是统计信息过期导致查询计划器做出错误决策。
1. 更新统计信息:ANALYZE table_name;
2. 检查 default_statistics_target,对关键列提高精度:ALTER TABLE t ALTER COLUMN c SET STATISTICS 1000;
3. 确认索引没有损坏:REINDEX INDEX idx_name;
4. 如果数据分布极度倾斜,考虑使用扩展统计信息:CREATE STATISTICS st ON col1, col2 FROM table;

#22 查询因锁等待卡住

查询长时间不返回,状态显示 "waiting"
-- 查看锁等待关系
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 bl ON blocked.pid = bl.pid AND NOT bl.granted
JOIN pg_locks gl ON bl.locktype = gl.locktype
    AND bl.database IS NOT DISTINCT FROM gl.database
    AND bl.relation IS NOT DISTINCT FROM gl.relation
    AND bl.page IS NOT DISTINCT FROM gl.page
    AND bl.tuple IS NOT DISTINCT FROM gl.tuple
    AND bl.pid != gl.pid AND gl.granted
JOIN pg_stat_activity blocking ON gl.pid = blocking.pid;

-- 终止阻塞进程
SELECT pg_terminate_backend(blocking_pid);
预防:设置 lock_timeout = '5s'statement_timeout = '30s'

#23 表膨胀严重 — dead tuples 过多

pg_stat_user_tables 显示 n_dead_tup 远大于 n_live_tup
MVCC 更新/删除产生的死元组未被及时清理。
1. 检查 autovacuum 是否正常:SELECT relname, last_autovacuum, last_autoanalyze FROM pg_stat_user_tables;
2. 手动执行:VACUUM VERBOSE table_name;
3. 严重膨胀需要 VACUUM FULL(会锁表)或使用 pg_repack(不锁表):
# 安装 pg_repack
sudo apt install postgresql-17-repack

# 在线重建表(不锁表)
pg_repack -d mydb -t bloated_table

4. 调优 autovacuum 参数(参见"性能优化"章节)。

#24 慢查询:大量 Disk Sort / Hash

EXPLAIN 显示 Sort Method: external merge Disk / Hash: Batches
work_mem 不够,排序或哈希操作溢出到磁盘。
1. 增加 work_mem(注意是每个操作分配,不要全局设太大):
-- 仅对当前会话增大
SET work_mem = '256MB';

-- 或全局调整(postgresql.conf)
work_mem = '64MB'

2. 优化查询减少排序量(添加索引、减少结果集)
3. 使用 EXPLAIN (ANALYZE, BUFFERS) 确认内存使用情况。

#25 高 CPU 使用率

PostgreSQL 进程持续占用高 CPU

1. 找到占用 CPU 的查询:
SELECT pid, now() - query_start AS duration, state, query
FROM pg_stat_activity
WHERE state = 'active' AND pid != pg_backend_pid()
ORDER BY duration DESC;

2. 检查是否有长事务导致的 autovacuum 无法完成
3. 使用 pg_stat_statements 找出最耗资源的查询:
CREATE EXTENSION pg_stat_statements;
SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC LIMIT 10;

4. 优化 top SQL 的执行计划。

#26 索引创建导致表锁定

CREATE INDEX 执行期间表无法写入
默认的 CREATE INDEX 会获取表的写锁。在生产环境使用并发创建:
-- 并发创建索引(不锁表,但更慢)
CREATE INDEX CONCURRENTLY idx_name ON table_name(column);

-- 如果失败,会留下 INVALID 索引,需要删除后重建
DROP INDEX CONCURRENTLY idx_name;
CREATE INDEX CONCURRENTLY idx_name ON table_name(column);
注意:CONCURRENTLY 不能在事务块中使用,且需要执行两次表扫描,时间大约是普通创建的 2-3 倍。

#27 Checkpoint 频繁导致 IO 飙升

LOG: checkpoints are occurring too frequently (N seconds apart)
WAL 写入过多导致频繁 checkpoint。
1. 增大 max_wal_sizemax_wal_size = '4GB'(默认 1GB)
2. 调整 checkpoint_completion_target = 0.9(平摊 IO)
3. 检查是否有大量批量写入操作,考虑错峰执行
4. 监控:log_checkpoints = on 查看 checkpoint 频率和耗时。

#28 序列(Sequence)跳号

SERIAL/BIGSERIAL 自增 ID 出现跳号
这是正常行为。序列在事务回滚、服务器重启后不会回退已分配的值。
- 序列保证唯一性,不保证连续性
- 不要依赖序列值的连续性做业务逻辑
- 如果确实需要连续编号,使用应用层逻辑生成(如订单号)
- 手动重置序列:SELECT setval('my_table_id_seq', (SELECT MAX(id) FROM my_table));

#29 连接数持续增长不释放

pg_stat_activity 中 idle 连接越来越多
应用未正确关闭数据库连接。
1. 设置空闲连接超时(PostgreSQL 14+):idle_session_timeout = '10min'
2. 使用 PgBouncer 管理连接池
3. 手动清理:
-- 终止空闲超过 30 分钟的连接
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
AND query_start < NOW() - INTERVAL '30 minutes';

4. 检查应用代码确保使用 try-finally 或 context manager 关闭连接。

#30 OOM — PostgreSQL 被操作系统杀死

LOG: server process (PID xxxx) was terminated by signal 9: Killed dmesg 显示 Out of memory: Kill process xxxx (postgres)
PostgreSQL 使用的内存超过系统可用量。
1. 减少 shared_buffers(不超过 RAM 的 25%)
2. 减少 work_mem(考虑并发连接数 * work_mem)
3. 减少 max_connections(每个连接约占 5-10MB)
4. Linux 禁用 overcommit:vm.overcommit_memory = 2
5. 设置 vm.overcommit_ratio = 80
重要:OOM Killer 会直接杀死 PostgreSQL 进程,可能导致数据损坏。务必在生产环境配置合理的内存参数。

数据管理与迁移 (#31 — #38)

#31 ERROR: cannot drop table because other objects depend on it

ERROR: cannot drop table "users" because other objects depend on it DETAIL: constraint orders_user_id_fkey on table orders depends on table users
表有外键等依赖关系。
1. 级联删除:DROP TABLE users CASCADE;(会删除所有依赖对象)
2. 先删除依赖:ALTER TABLE orders DROP CONSTRAINT orders_user_id_fkey;
3. 查看所有依赖:
SELECT dependent_ns.nspname, dependent_view.relname
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
JOIN pg_class dependent_view ON pg_rewrite.ev_class = dependent_view.oid
JOIN pg_namespace dependent_ns ON dependent_view.relnamespace = dependent_ns.oid
WHERE pg_depend.refobjid = 'users'::regclass;

#32 ALTER TABLE ADD COLUMN 锁表时间过长

ALTER TABLE 大表添加列时长时间锁定
添加带默认值的列在 PostgreSQL 11+ 已经是即时操作(不需要重写表)。
如果是老版本或特殊情况:
1. 先添加列(不带默认值,瞬间完成):ALTER TABLE t ADD COLUMN c TYPE;
2. 再设置默认值:ALTER TABLE t ALTER COLUMN c SET DEFAULT value;
3. 分批更新已有数据:
-- 分批更新避免长事务
DO $$
DECLARE batch_size INT := 10000; affected INT;
BEGIN
  LOOP
    UPDATE t SET c = default_value WHERE c IS NULL AND id IN (
      SELECT id FROM t WHERE c IS NULL LIMIT batch_size
    );
    GET DIAGNOSTICS affected = ROW_COUNT;
    EXIT WHEN affected = 0;
    PERFORM pg_sleep(0.1);
  END LOOP;
END $$;

#33 COPY 导入大量数据比 INSERT 快得多

INSERT 逐行导入数据非常慢
使用 COPY 命令批量导入,比 INSERT 快 10-100 倍:
# 从 CSV 导入
COPY users(name, email, age) FROM '/tmp/users.csv' WITH (FORMAT csv, HEADER true);

# 从标准输入导入
cat data.csv | psql -c "COPY users FROM STDIN WITH (FORMAT csv, HEADER true)" mydb

# 加速技巧
# 1. 导入前禁用索引和约束
# 2. 增大 maintenance_work_mem
# 3. 设置 synchronous_commit = off(注意风险)
# 4. 导入后重建索引和 ANALYZE

#34 从 MySQL 迁移到 PostgreSQL 的常见问题

MySQL 语法在 PostgreSQL 中报错
常见差异及解决方法:
- AUTO_INCREMENTSERIALGENERATED ALWAYS AS IDENTITY
- IFNULL()COALESCE()
- GROUP_CONCAT()STRING_AGG()
- LIMIT x, yLIMIT y OFFSET x
- 反引号 `column` → 双引号 "column"
- NOW() + INTERVAL 1 DAYNOW() + INTERVAL '1 day'
- UNSIGNED → 不支持,用 CHECK (col >= 0)
迁移工具推荐:pgloader(一键迁移)。

#35 ERROR: out of shared memory

ERROR: out of shared memory HINT: You might need to increase max_locks_per_transaction.
通常在操作大量表(如分区表)时发生。
1. 增大 max_locks_per_transaction:默认 64,设为 256 或更高
2. 增大 max_pred_locks_per_transaction
3. 如果是分区表操作,减少单次 DDL 涉及的分区数
4. 修改后需要重启 PostgreSQL。

#36 ERROR: could not extend file — 磁盘空间不足

ERROR: could not extend file "base/16384/12345": No space left on device
磁盘空间已满。
1. 紧急释放空间:删除旧 WAL 文件、旧备份、临时文件
2. 清理旧数据:DELETE + VACUUM FULL
3. 查看空间使用:
SELECT pg_database.datname,
       pg_size_pretty(pg_database_size(pg_database.datname))
FROM pg_database ORDER BY pg_database_size(pg_database.datname) DESC;

-- 查看最大的表
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;

4. 设置磁盘空间监控告警。

#37 pg_dump 备份超时或失败

pg_dump: error: server closed the connection unexpectedly
大数据库备份中断。
1. 增大超时:statement_timeout = 0(备份会话中)
2. 使用并行备份:pg_dump -Fd -j 4 -f /backup/mydb mydb
3. 检查磁盘空间是否足够
4. 对于超大数据库,考虑使用 pg_basebackup 物理备份
5. 网络备份添加压缩:pg_dump -Fc -Z 6 mydb > mydb.dump

#38 pg_restore 报错:已有对象存在

pg_restore: error: could not execute query: ERROR: relation "users" already exists
恢复到非空数据库。
1. 先清空目标数据库:DROP DATABASE mydb; CREATE DATABASE mydb;
2. 或使用 --clean 参数:pg_restore --clean -d mydb backup.dump
3. 使用 --if-exists 避免删除不存在的对象报错:pg_restore --clean --if-exists -d mydb backup.dump

复制与高可用 (#39 — #44)

#39 流复制备库延迟过大

备库数据延迟主库几分钟甚至更长

1. 检查复制延迟:
-- 主库上查看
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
    pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes
FROM pg_stat_replication;

-- 备库上查看
SELECT now() - pg_last_xact_replay_timestamp() AS replay_lag;

2. 常见原因:网络带宽不足、备库硬件性能差、备库负载过高
3. 备库优化:增大 max_standby_streaming_delay,关闭备库的热备查询(或设置查询超时)
4. 检查 WAL 发送速率和网络延迟。

#40 FATAL: could not start WAL streaming

FATAL: could not start WAL streaming: ERROR: replication slot "standby_slot" does not exist
复制槽不存在或已被删除。
1. 在主库创建复制槽:SELECT pg_create_physical_replication_slot('standby_slot');
2. 查看现有槽:SELECT * FROM pg_replication_slots;
3. 如果不使用复制槽,从备库 primary_conninfo 中移除 slot_name 配置
注意:未被消费的复制槽会阻止 WAL 清理,导致磁盘空间持续增长。定期检查:SELECT slot_name, active, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes FROM pg_replication_slots;

#41 备库提升(Promote)后的处理

主库故障后需要将备库提升为新主库
# 提升备库为主库
pg_ctl promote -D /var/lib/postgresql/17/main
# 或
SELECT pg_promote();
提升后需要做的工作:
1. 修改应用的数据库连接指向新主库
2. 重新配置其他备库指向新主库
3. 旧主库恢复后,需要用 pg_rewind 或重新做 basebackup 加入集群
# 使用 pg_rewind 让旧主库跟随新主库
pg_rewind --target-pgdata=/var/lib/postgresql/17/main \
  --source-server="host=new_primary port=5432 user=replicator"

#42 逻辑复制冲突

ERROR: duplicate key value violates unique constraint (在逻辑复制订阅端)
逻辑复制目标端已有冲突数据。
1. 跳过冲突事务:ALTER SUBSCRIPTION my_sub SKIP (lsn = 'X/XXXXXXXX');
2. 删除目标端冲突数据后继续
3. 设置冲突处理策略(PostgreSQL 15+):
-- 创建订阅时设置
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=primary dbname=mydb'
PUBLICATION my_pub
WITH (origin = none);

4. 保持主从数据一致是使用逻辑复制的前提。

#43 WAL 文件堆积导致磁盘满

/var/lib/postgresql/17/main/pg_wal 目录占用大量磁盘空间
WAL 文件无法被清理。常见原因:
1. 未被消费的复制槽:SELECT slot_name, active FROM pg_replication_slots; → 删除无用槽:SELECT pg_drop_replication_slot('unused_slot');
2. 归档失败:检查 archive_command 是否正常执行
3. 长事务阻止 WAL 回收:SELECT pid, xact_start, query FROM pg_stat_activity WHERE xact_start < NOW() - INTERVAL '1 hour';
4. 降低 wal_keep_size(PostgreSQL 13+)或 wal_keep_segments(旧版本)。

#44 搭建高可用集群(Patroni)

如何实现 PostgreSQL 自动故障切换
推荐使用 Patroni + etcd/ZooKeeper 搭建高可用集群:
- Patroni:管理 PostgreSQL 实例的生命周期和故障切换
- etcd/ZooKeeper:分布式共识存储
- HAProxy/PgBouncer:连接路由

架构:
1. 部署 3 节点 etcd 集群
2. 每个 PostgreSQL 节点部署 Patroni
3. HAProxy 做读写分离和自动切换
4. 当主库故障时,Patroni 自动选举新主库并更新路由
替代方案:Stolon、repmgr、Crunchy PGO (Kubernetes)。

安全与权限 (#45 — #48)

#45 ERROR: permission denied for table

ERROR: permission denied for table users
当前用户没有该表的操作权限。
-- 授予权限
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO myuser;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO myuser;

-- 设置默认权限(对将来创建的表也生效)
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO myuser;

-- 查看当前权限
\dp users

#46 行级安全策略(Row Level Security)

需要限制用户只能访问自己的数据
PostgreSQL 原生支持行级安全 (RLS):
-- 启用 RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

-- 创建策略:用户只能看到自己的订单
CREATE POLICY user_orders ON orders
FOR ALL
USING (user_id = current_setting('app.current_user_id')::bigint);

-- 在应用连接时设置用户 ID
SET app.current_user_id = '123';
SELECT * FROM orders;  -- 只返回 user_id=123 的订单

-- 超级用户不受 RLS 限制
-- 要对表所有者也启用 RLS:
ALTER TABLE orders FORCE ROW LEVEL SECURITY;

#47 SQL 注入防护

如何防止 PostgreSQL 中的 SQL 注入
核心原则:永远使用参数化查询,不要拼接 SQL。
1. 应用层:使用预处理语句和参数绑定
# Python (psycopg2)
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

# Node.js (pg)
client.query('SELECT * FROM users WHERE id = $1', [userId])

# Java (JDBC)
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, userId);

2. 数据库层:使用最小权限原则,应用账户不给 DROP/CREATE 权限
3. 函数中使用 quote_ident()quote_literal() 转义动态标识符。

#48 审计日志配置

需要记录谁在什么时候做了什么操作
方案一:pgAudit 扩展(推荐)
# 安装
sudo apt install postgresql-17-pgaudit

# postgresql.conf
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write, ddl'
pgaudit.log_catalog = off
方案二:触发器实现审计
CREATE TABLE audit_log (
    id BIGSERIAL PRIMARY KEY,
    table_name TEXT, action TEXT,
    old_data JSONB, new_data JSONB,
    changed_by TEXT DEFAULT current_user,
    changed_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE OR REPLACE FUNCTION audit_trigger() RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO audit_log(table_name, action, old_data, new_data)
    VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD)::jsonb, row_to_json(NEW)::jsonb);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER audit_users AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW EXECUTE FUNCTION audit_trigger();

扩展与生态 (#49 — #50)

#49 常用扩展推荐

PostgreSQL 有哪些必装扩展
必备扩展:
- pg_stat_statements:SQL 性能统计(必装)
- pgcrypto:加密函数(UUID 生成、密码哈希)
- uuid-ossp:UUID 生成
- pg_trgm:模糊搜索(LIKE/ILIKE 加速)
- PostGIS:地理空间数据处理
- pg_repack:在线表重组(替代 VACUUM FULL)
- pgAudit:审计日志
- pgvector:向量搜索(AI 嵌入向量相似度检索)
- Citus:分布式水平扩展
- TimescaleDB:时序数据优化
-- 安装扩展
CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION pgcrypto;
CREATE EXTENSION "uuid-ossp";

-- 查看已安装扩展
SELECT * FROM pg_extension;

-- 查看可用扩展
SELECT * FROM pg_available_extensions WHERE name LIKE 'pg%';

#50 PostgreSQL 的局限性与替代方案

什么场景不适合用 PostgreSQL
PostgreSQL 功能强大但并非万能,以下场景可能需要其他方案:
- 超大规模写入(百万 TPS):单机 PostgreSQL 有瓶颈 → 考虑 TiDB、CockroachDB
- 嵌入式/移动端:PostgreSQL 是服务器数据库 → 考虑 SQLite
- 纯键值缓存:不需要关系模型 → 考虑 Redis、Memcached
- 大规模全文搜索:内置全文搜索功能有限 → 考虑 Elasticsearch、Meilisearch
- 实时流处理:PostgreSQL 是存储而非流引擎 → 考虑 Apache Kafka、Flink
- 图数据库场景:复杂图遍历性能不如专用方案 → 考虑 Neo4j、Apache AGE(PostgreSQL 图扩展)
- 水平分片需求:原生不支持自动分片 → 考虑 Citus(PostgreSQL 扩展)、YugabyteDB