PostgreSQL 学习笔记

从零开始的 PostgreSQL 学习笔记,覆盖安装、SQL 基础、高级特性、运维管理。

图表在 /PostgreSQL学习笔记/diagrams/ 目录。

1 · PostgreSQL 概述

1.1 什么是 PostgreSQL

  • PostgreSQL(简称 PG)是功能最强大的开源关系型数据库
  • 1986 年始于加州大学伯克利分校,30+ 年历史
  • 号称"世界上最先进的开源数据库"
  • ACID 合规、MVCC 并发控制、丰富的数据类型(JSON、数组、几何、网络地址)
TIP

📌 MySQL vs PostgreSQL:MySQL 简单易用、生态广、Web 场景占比高。PostgreSQL 功能更全、SQL 标准更严格、复杂查询性能更好、支持 JSON/GIS/全文检索。简单 Web 用 MySQL,复杂分析/GIS/JSON 用 PG。

1.2 核心优势

优势 说明
SQL 标准合规 最接近 SQL 标准的数据库
丰富的数据类型 JSON/JSONB、数组、范围类型、几何、UUID、枚举
强大的扩展性 PostGIS(GIS)、pgvector(向量)、TimescaleDB(时序)
MVCC 多版本并发控制,读写互不阻塞
CTE / 窗口函数 复杂分析查询的利器
可编程 PL/pgSQL、PL/Python、PL/Perl 存储过程

2 · 安装与配置

2.1 安装

# Ubuntu/Debian
sudo apt install postgresql postgresql-contrib

# Docker(推荐开发环境)
docker run --name pg -e POSTGRES_PASSWORD=123456 -p 5432:5432 -d postgres:16

# Windows:下载 EnterpriseDB 安装包

2.2 连接

# 切换到 postgres 用户
sudo -u postgres psql

# 远程连接
psql -h 127.0.0.1 -p 5432 -U postgres -d mydb

2.3 核心配置

# postgresql.conf
listen_addresses = '*'              # 监听地址
port = 5432
max_connections = 100
shared_buffers = 2GB                # 推荐物理内存 25%
effective_cache_size = 6GB          # 推荐物理内存 75%
work_mem = 64MB                     # 每个查询的排序/哈希内存
maintenance_work_mem = 512MB        # VACUUM/CREATE INDEX 内存
wal_level = replica                 # WAL 日志级别
random_page_cost = 1.1              # SSD 调低(默认 4.0 适合机械盘)
TIP

📌 PG vs MySQL 内存参数:MySQL 的 innodb_buffer_pool_size 建议 60-70%,PG 的 shared_buffers 建议 25%。因为 PG 还依赖操作系统文件缓存,effective_cache_size(75%)反映总可用缓存。


3 · SQL 基础与差异

3.1 与 MySQL 的关键差异

特性 MySQL PostgreSQL
引擎 多引擎(InnoDB/MyISAM) 单引擎(统一)
大小写敏感 不敏感 敏感(需加引号)
自增 AUTO_INCREMENT SERIAL / GENERATED ALWAYS AS IDENTITY
字符串拼接 CONCAT(a, b) `a
引号 双引号/单引号都可 双引号=标识符,单引号=字符串
布尔类型 无(用 0/1) TRUE / FALSE
默认事务隔离 REPEATABLE READ READ COMMITTED

3.2 建库建表

-- 创建数据库
CREATE DATABASE shop WITH ENCODING 'UTF8';

-- 创建表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,              -- 自增主键
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100),
    is_active BOOLEAN DEFAULT TRUE,     -- 原生布尔类型
    tags TEXT[],                         -- 数组类型
    metadata JSONB,                      -- JSON 文档
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 新版自增写法(推荐)
CREATE TABLE orders (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    order_no VARCHAR(20) NOT NULL
);

3.3 特色 SQL

-- 字符串拼接
SELECT 'Hello' || ' ' || 'World';          -- → Hello World

-- 返回前 N 行(PG 用 LIMIT,也支持 FETCH)
SELECT * FROM users LIMIT 10;
SELECT * FROM users FETCH FIRST 10 ROWS ONLY;

-- 类型转换
SELECT '123'::INTEGER;                     -- → 123
SELECT created_at::DATE FROM users;        -- 时间戳转日期

-- 数组操作
SELECT tags FROM users WHERE 'admin' = ANY(tags);   -- 数组包含
SELECT array_length(tags, 1) FROM users;             -- 数组长度

-- DISTINCT ON(PG 特有:每组取第一条)
SELECT DISTINCT ON (department) * FROM employees ORDER BY department, salary DESC;
-- 每个部门薪资最高的员工
TIP

📌 DISTINCT ON 是 PG 独有功能:DISTINCT ON (department) 按部门去重,每组保留排序后的第一条。等价于 MySQL 的窗口函数 ROW_NUMBER() OVER(PARTITION BY department ORDER BY salary DESC) = 1,但写法更简洁。


4 · 索引

图 1 \xb7 PostgreSQL 索引类型

4.1 索引类型

类型 说明 适用场景
B-Tree(默认) 平衡树 等值、范围、排序
Hash 哈希表 仅等值查询
GIN 倒排索引 JSONB、数组、全文搜索
GiST 通用搜索树 几何、范围、最近邻
BRIN 块范围索引 大表、有序数据(时间序列)
SP-GiST 空间分区 非平衡数据结构
-- B-Tree(默认)
CREATE INDEX idx_name ON users(username);

-- GIN(JSONB 查询)
CREATE INDEX idx_metadata ON users USING GIN(metadata);

-- 查询 JSONB 字段
SELECT * FROM users WHERE metadata @> '{"role": "admin"}';

-- BRIN(大表时间列)
CREATE INDEX idx_created ON logs USING BRIN(created_at);

-- 部分索引(只索引活跃用户)
CREATE INDEX idx_active_users ON users(email) WHERE is_active = TRUE;

-- 表达式索引
CREATE INDEX idx_lower_name ON users(LOWER(username));
SELECT * FROM users WHERE LOWER(username) = 'alice';  -- 命中索引
TIP

📌 GIN 索引是 PG 的杀手锏:JSONB + GIN 可以高效查询 JSON 文档中的任意字段。@>(包含)操作符配合 GIN 索引,让 PG 可以当文档数据库用。

📌 部分索引:只索引满足条件的行,索引更小更快。比如只索引活跃用户 WHERE is_active = TRUE,跳过大量历史数据。


5 · 事务与 MVCC

5.1 事务基础

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 或 ROLLBACK;

5.2 MVCC 机制

图 2 \xb7 MVCC 多版本并发控制

TIP

📌 PG 的 MVCC:每行数据有 xmin(创建事务 ID)和 xmax(删除事务 ID)。UPDATE 不是原地修改,而是标记旧行为删除 + 插入新行。读操作只看到在当前事务之前已提交的版本,读不阻塞写,写不阻塞读。

📌 MVCC 的代价:死元组(dead tuples)需要 VACUUM 清理。定期运行 VACUUM 和 ANALYZE 是 PG 运维的核心任务。

5.3 VACUUM

-- 手动 VACUUM(标记死元组空间可复用)
VACUUM users;

-- VACUUM FULL(锁表回收空间,慎用)
VACUUM FULL users;

-- ANALYZE(更新统计信息,优化器用)
ANALYZE users;

-- 自动清理配置
ALTER TABLE users SET (autovacuum = on, fillfactor = 90);
TIP

📌 VACUUM vs VACUUM FULL:普通 VACUUM 标记空间可复用但不还操作系统(不锁表)。VACUUM FULL 物理回收空间但锁表。生产环境用普通 VACUUM,低峰期偶尔 VACUUM FULL。


6 · 数据类型

6.1 PG 丰富类型一览

类型 说明 示例
UUID 唯一标识 gen_random_uuid()
JSONB 二进制 JSON {"name": "alice", "age": 30}
TEXT[] 文本数组 {admin, user, guest}
INET IP 地址 192.168.1.1
INTERVAL 时间间隔 '1 day'::INTERVAL
MONEY 货币 $19.99
POINT 几何点 (1.5, 2.3)
HSTORE 键值对 "key"=>"value"
ENUM 枚举 CREATE TYPE status AS ENUM ('active', 'inactive')

6.2 JSONB 操作

-- 插入 JSON
INSERT INTO users (metadata) VALUES ('{"name": "alice", "age": 30, "tags": ["admin", "vip"]}');

-- 查询 JSON 字段
SELECT metadata->>'name' AS name FROM users;              -- 文本提取
SELECT metadata->'age' FROM users;                         -- JSON 提取
SELECT * FROM users WHERE metadata @> '{"tags": ["admin"]}';  -- 包含
SELECT * FROM users WHERE metadata ? 'name';               -- 键存在
SELECT * FROM users WHERE metadata->>'age' > '25';         -- 条件查询

-- 更新 JSON
UPDATE users SET metadata = jsonb_set(metadata, '{age}', '31');
UPDATE users SET metadata = metadata || '{"city": "Shanghai"}';  -- 合并
TIP

📌 JSON vs JSONB:JSON 存原始文本(保留空格/顺序),JSONB 存解析后的二进制(更快查询、支持索引)。生产环境用 JSONB。


7 · JSON/JSONB

PG 的 JSONB 支持让它可以兼做文档数据库:

-- 创建 GIN 索引
CREATE INDEX idx_meta ON users USING GIN(metadata);

-- 高效查询 JSON 任意字段
SELECT * FROM users WHERE metadata @> '{"role": "admin"}';
-- 等价于 MongoDB: db.users.find({"role": "admin"})

-- 聚合 JSON
SELECT metadata->>'city' AS city, COUNT(*) 
FROM users 
GROUP BY metadata->>'city';

8 · 窗口函数与 CTE

8.1 窗口函数

-- 每个部门薪资排名
SELECT 
    name, department, salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rank,
    AVG(salary) OVER (PARTITION BY department) AS dept_avg,
    SUM(salary) OVER () AS total_salary
FROM employees;

-- 累计求和
SELECT 
    date, revenue,
    SUM(revenue) OVER (ORDER BY date) AS cumulative
FROM daily_sales;

8.2 CTE(公共表表达式)

-- 基本 CTE
WITH high_value_customers AS (
    SELECT customer_id, SUM(amount) AS total
    FROM orders
    GROUP BY customer_id
    HAVING SUM(amount) > 10000
)
SELECT c.name, h.total
FROM customers c
JOIN high_value_customers h ON c.id = h.customer_id;

-- 递归 CTE:组织架构树
WITH RECURSIVE org_tree AS (
    SELECT id, name, parent_id, 0 AS level
    FROM departments WHERE parent_id IS NULL
    UNION ALL
    SELECT d.id, d.name, d.parent_id, t.level + 1
    FROM departments d
    JOIN org_tree t ON d.parent_id = t.id
)
SELECT id, name, level FROM org_tree ORDER BY level;
TIP

📌 递归 CTE:PG 的递归 CTE 可以遍历树形结构(组织架构、评论回复、分类树)。MySQL 8.0 也支持了,但 PG 的实现更早更成熟。


9 · 存储过程与触发器

9.1 PL/pgSQL 函数

CREATE OR REPLACE FUNCTION transfer_funds(
    p_from INT, p_to INT, p_amount DECIMAL(10,2)
) RETURNS BOOLEAN AS $$
BEGIN
    UPDATE accounts SET balance = balance - p_amount WHERE id = p_from;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Source account not found';
    END IF;
    
    UPDATE accounts SET balance = balance + p_amount WHERE id = p_to;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Target account not found';
    END IF;
    
    RETURN TRUE;
END;
$$ LANGUAGE plpgsql;

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

9.2 触发器

-- 审计触发器
CREATE OR REPLACE FUNCTION audit_log() RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO audit_table (table_name, operation, old_data, new_data, changed_at)
    VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), row_to_json(NEW), NOW());
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

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

10 · 分区表

-- 按范围分区(如按日期)
CREATE TABLE orders (
    id BIGINT GENERATED ALWAYS AS IDENTITY,
    order_date DATE NOT NULL,
    amount DECIMAL(10,2)
) PARTITION BY RANGE (order_date);

-- 创建分区
CREATE TABLE orders_2024_01 PARTITION OF orders
    FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE orders_2024_02 PARTITION OF orders
    FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');

-- 查询自动路由到对应分区
SELECT * FROM orders WHERE order_date = '2024-01-15';
-- 只扫描 orders_2024_01 分区
TIP

📌 分区表的好处:① 查询只扫描相关分区(分区裁剪)② 可以快速删除整个分区(DROP PARTITION 比 DELETE 快)③ 每个分区可以独立维护。


11 · 备份与恢复

# 逻辑备份
pg_dump -U postgres mydb > mydb_backup.sql
pg_dump -U postgres -Fc mydb > mydb.dump    # 自定义压缩格式

# 恢复
psql -U postgres mydb < mydb_backup.sql
pg_restore -U postgres -d mydb mydb.dump

# 全量备份(所有数据库)
pg_dumpall -U postgres > all_backup.sql

# 物理备份(推荐):pg_basebackup
pg_basebackup -U postgres -D /backup/base -Ft -z -P

12 · 流复制

# 主库 postgresql.conf
wal_level = replica
max_wal_senders = 10

# 从库
primary_conninfo = 'host=192.168.1.100 port=5432 user=repl password=xxx'
TIP

📌 PG 流复制 vs MySQL 主从:原理类似(基于 WAL/binlog),但 PG 支持同步复制(数据零丢失)和级联复制。PG 14+ 增加了逻辑复制(可选择性复制表)。


13 · 性能调优

13.1 EXPLAIN ANALYZE

EXPLAIN ANALYZE SELECT * FROM users WHERE username = 'alice';
-- EXPLAIN:显示执行计划
-- ANALYZE:实际执行并显示耗时
关键指标 关注点
Seq Scan 全表扫描,大表需优化
Index Scan 索引扫描
Index Only Scan 覆盖索引(最佳)
Hash Join 哈希连接(大表)
Nested Loop 嵌套循环(小表)
Sort 排序,大数据量需关注

13.2 调优清单

  • shared_buffers = 物理内存 25%
  • effective_cache_size = 物理内存 75%
  • work_mem 适当增大(排序/哈希)
  • SSD 设 random_page_cost = 1.1
  • 高频查询建索引,JSONB 用 GIN
  • 定期 VACUUM ANALYZE
  • 大表用分区
  • 慢查询用 pg_stat_statements 分析

附录 · MySQL ↔ PostgreSQL 对照表

功能 MySQL PostgreSQL
自增 AUTO_INCREMENT SERIAL / IDENTITY
布尔 TINYINT(1) BOOLEAN
字符串拼接 CONCAT() ||
类型转换 CAST() ::
日期函数 NOW() NOW() / CURRENT_TIMESTAMP
分页 LIMIT offset, count LIMIT count OFFSET offset
JSON JSON 类型 JSONB(更强大)
数组 无 TEXT[] 等
窗口函数 8.0+ 早就支持
递归 CTE 8.0+ 早就支持
GIS 较弱 PostGIS(业界标准)