PostgreSQL 实战指南(面向 MySQL 开发者)

本文档面向有 MySQL 经验的开发者,采用对比的方式快速上手 PostgreSQL。

按照"快速上手 → 日常开发 → 进阶特性 → 生产实践"的路径组织。


目录

第一部分:快速上手

  1. 安装和工具
  2. [5 分钟快速开始](#5 分钟快速开始)
  3. 核心概念对比

第二部分:日常开发

  1. 数据库和用户管理

  2. 表和数据类型

  3. [SQL 语法差异](#SQL 语法差异)

  4. 索引和查询优化

  5. 常用命令速查

第三部分:进阶特性

  1. [JSONB 和半结构化数据](#JSONB 和半结构化数据)

  2. [UPSERT 和高级 DML](#UPSERT 和高级 DML)

  3. [高级 SQL 技巧](#高级 SQL 技巧)

  4. 触发器和存储过程

  5. [LISTEN/NOTIFY 通知机制](#LISTEN/NOTIFY 通知机制)

  6. 其他增强特性

第四部分:生产实践

  1. [VACUUM 和表维护](#VACUUM 和表维护)

  2. 连接池和并发控制

  3. 备份和恢复

  4. 监控和诊断

  5. [性能调优 Checklist](#性能调优 Checklist)

第五部分:迁移指南

  1. [从 MySQL 迁移](#从 MySQL 迁移)

  2. [CDC/逻辑复制(对应 binlog)](#CDC/逻辑复制(对应 binlog))

  3. 常用扩展推荐


第一部分:快速上手

1. 安装和工具

1.1 macOS 推荐方式

方式一:Postgres.app(⭐ 最简单,推荐新手)

https://postgresapp.com/

  • 图形化应用,开箱即用
  • 包含多个 PostgreSQL 版本,可以一键切换
  • 自带常用扩展(PostGIS、plv8 等)
  • 菜单栏图标管理,启动/停止方便

安装后配置 PATH(可选,方便命令行使用):

bash 复制代码
# 添加到 ~/.zshrc 或 ~/.bash_profile
export PATH="/Applications/Postgres.app/Contents/Versions/latest/bin:$PATH"

方式二:Homebrew

bash 复制代码
brew install postgresql@16
brew services start postgresql@16

1.2 Linux 安装

Ubuntu/Debian:

bash 复制代码
sudo apt update
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql

CentOS/RHEL:

bash 复制代码
sudo yum install postgresql-server postgresql-contrib
sudo postgresql-setup initdb
sudo systemctl start postgresql

1.3 命令行工具

默认命令行工具是 psql

推荐安装 pgcli,有自动补全和语法高亮:

bash 复制代码
# macOS
brew install pgcli

# Linux
pip install pgcli

1.4 图形化工具

  • pgAdmin:官方图形化工具(功能全面但较重)
  • DBeaver:跨平台,支持多种数据库(推荐)
  • DataGrip:JetBrains 出品(付费)
  • Postico:macOS 专用,界面美观(付费)

2. 5 分钟快速开始

2.1 连接数据库

使用 psqlpgcli

bash 复制代码
psql -h 127.0.0.1 -p 5432 -U postgres -d postgres

常用参数:

  • -h:主机(本机通常是 127.0.0.1 或留空)
  • -p:端口(默认 5432)
  • -U:用户名(默认安装后有超级用户 postgres
  • -d:数据库名

本地快速连接:

bash 复制代码
psql -U postgres

2.2 基本元命令(以 \ 开头)

命令 说明 MySQL 对比
\l 列出所有数据库 SHOW DATABASES;
\c dbname 切换数据库 USE dbname;
\dt 列出当前 schema 的表 SHOW TABLES;
\d table 查看表结构 DESC table;
\d+ table 查看表结构(详细) SHOW CREATE TABLE;
\di 列出索引 SHOW INDEX;
\du 列出用户/角色 SELECT * FROM mysql.user;
\q 退出 exit\q

2.3 快速上手示例

sql 复制代码
-- 创建数据库和用户
CREATE DATABASE myapp;
CREATE USER app_user WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE myapp TO app_user;

-- 切换到新数据库
\c myapp

-- 创建表
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- 插入数据
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');

-- 查询
SELECT * FROM users;

-- 查看表结构
\d users

3. 核心概念对比(和 MySQL 有何不同)

3.1 数据库 + Schema(三层结构)

MySQL: database.table
PostgreSQL: database.schema.table

  • 每个数据库里可以有多个 schema,默认是 public
  • 多数简单项目可以直接用默认 public schema
  • 可以通过 schema 实现多租户隔离

类比示例:

  • MySQL:mydb.users
  • PostgreSQL:mydb.public.users(通常简写为 users,依赖 search_path

查看当前 schema:

sql 复制代码
SHOW search_path;  -- 默认:"$user", public

3.2 用户 / 角色(Role)

关键差异:

  • PostgreSQL 中是「角色」(role),带 LOGIN 权限的 role 就是用户
  • 一个 role 可以拥有数据库、表、序列等对象
  • 和 MySQL user@host 不同,PostgreSQL 通常只写用户名,host 控制在 pg_hba.conf
sql 复制代码
-- 创建角色(可以理解为用户组)
CREATE ROLE readonly;

-- 创建用户(带登录权限的角色)
CREATE ROLE app_user WITH LOGIN PASSWORD 'password';

-- 角色继承
GRANT readonly TO app_user;

3.3 标识符大小写(⚠️ 重要陷阱)

没加双引号的表名、列名会自动转小写:

sql 复制代码
CREATE TABLE MyTable (UserId INT);  -- 实际创建的是 mytable (userid)
SELECT * FROM MyTable;              -- 可以查询(会转成小写)
SELECT * FROM "MyTable";            -- 报错:表不存在

最佳实践:

  • 一律使用小写加下划线:users, created_at, user_id
  • 避免必须写 "MyTable" 这种带引号形式

3.4 自增主键

MySQL:

sql 复制代码
CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  ...
);

PostgreSQL 推荐方式(两种):

sql 复制代码
-- 方式一:BIGSERIAL(传统写法)
CREATE TABLE users (
  id BIGSERIAL PRIMARY KEY,
  ...
);

-- 方式二:IDENTITY(SQL 标准,PG 10+)
CREATE TABLE users (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  ...
);
  • BIGSERIAL 等价于 BIGINT + SEQUENCE
  • IDENTITY 更现代,推荐使用

3.5 MVCC 机制(多版本并发控制)

关键差异:

  • MySQL InnoDB:通过 undo log 实现 MVCC
  • PostgreSQL:通过在表中保留旧版本数据实现

影响:

  • PostgreSQL 的 UPDATE/DELETE 会产生"死元组"(dead tuple)
  • 需要定期 VACUUM 回收空间(见后文)
  • 性能特点:读不阻塞写,写不阻塞读

第二部分:日常开发

4. 数据库和用户管理

4.1 数据库操作

sql 复制代码
-- 创建数据库
CREATE DATABASE mydb OWNER myuser ENCODING 'UTF8';

-- 删除数据库(需要没有活动连接)
DROP DATABASE mydb;

-- 查看当前数据库
SELECT current_database();

-- 查看所有数据库
\l
-- 或
SELECT datname FROM pg_database;

4.2 用户/角色管理

创建用户
sql 复制代码
-- 创建用户(带登录权限)
CREATE ROLE app_user WITH LOGIN PASSWORD 'secure_password';

-- 创建用户并设置权限
CREATE ROLE app_user WITH 
    LOGIN 
    PASSWORD 'secure_password'
    CREATEDB           -- 可以创建数据库
    VALID UNTIL '2025-12-31';  -- 密码过期时间
查看用户
sql 复制代码
-- psql 命令
\du

-- SQL 查询
SELECT * FROM pg_user;
SELECT * FROM pg_roles;
修改密码
sql 复制代码
ALTER ROLE app_user WITH PASSWORD 'new_password';
删除用户
sql 复制代码
DROP ROLE app_user;

4.3 权限管理

数据库级权限
sql 复制代码
-- 授予数据库所有权限
GRANT ALL PRIVILEGES ON DATABASE mydb TO app_user;

-- 授予连接权限
GRANT CONNECT ON DATABASE mydb TO app_user;

-- 撤销权限
REVOKE ALL PRIVILEGES ON DATABASE mydb FROM app_user;
表级权限
sql 复制代码
-- 授予 public schema 下所有表的权限
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_user;

-- 授予特定表的权限
GRANT SELECT, INSERT, UPDATE ON users TO app_user;

-- 授予序列权限(自增主键需要)
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO app_user;

-- 设置默认权限(对未来创建的表生效)
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT ALL ON TABLES TO app_user;
查看权限
sql 复制代码
-- 查看某用户的表权限
SELECT *
FROM information_schema.role_table_grants
WHERE grantee = 'app_user';

-- 查看某表的权限
\dp table_name

5. 表和数据类型

5.1 建表示例(MySQL 风格迁移)

PostgreSQL 推荐写法:

sql 复制代码
CREATE TABLE users (
  id          BIGSERIAL PRIMARY KEY,
  name        VARCHAR(50)      NOT NULL,
  email       VARCHAR(100)     UNIQUE,
  age         INTEGER          CHECK (age >= 0 AND age <= 150),
  is_active   BOOLEAN          NOT NULL DEFAULT TRUE,
  balance     NUMERIC(10, 2)   DEFAULT 0.00,
  created_at  TIMESTAMPTZ      NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ      NOT NULL DEFAULT now()
);

-- 添加索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);

-- 添加注释
COMMENT ON TABLE users IS '用户表';
COMMENT ON COLUMN users.email IS '用户邮箱';

对应的 MySQL 写法:

sql 复制代码
CREATE TABLE users (
  id          BIGINT PRIMARY KEY AUTO_INCREMENT,
  name        VARCHAR(50)      NOT NULL,
  email       VARCHAR(100)     UNIQUE,
  age         INT              CHECK (age >= 0 AND age <= 150),
  is_active   TINYINT(1)       NOT NULL DEFAULT 1,
  balance     DECIMAL(10, 2)   DEFAULT 0.00,
  created_at  DATETIME         NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at  DATETIME         NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

5.2 常见类型对照

MySQL PostgreSQL 说明
TINYINT, SMALLINT, INT, BIGINT SMALLINT, INTEGER, BIGINT 整数类型
TINYINT(1) BOOLEAN 布尔值(PG 有真正的布尔类型)
FLOAT, DOUBLE REAL, DOUBLE PRECISION 浮点数
DECIMAL(p,s) NUMERIC(p,s)DECIMAL(p,s) 精确数值
VARCHAR(n), TEXT VARCHAR(n), TEXT 字符串
DATETIME TIMESTAMP 时间戳(本地时间)
DATETIME TIMESTAMPTZ ⭐ 推荐:带时区的时间戳(存 UTC)
DATE DATE 日期
TIME TIME 时间
BLOB BYTEA 二进制数据
JSON JSONJSONB ⭐ 推荐 JSONB(二进制,支持索引)
ENUM('a','b') 自定义 ENUM 类型 见下文

重要提示:

  • 时间类型推荐用 TIMESTAMPTZ(带时区),数据库内部统一存储 UTC,应用层自动转换时区
  • 布尔类型用 BOOLEAN,不要用 0/1
  • JSON 数据推荐用 JSONB,而不是 JSON

5.3 ENUM 类型

MySQL 写法:

sql 复制代码
CREATE TABLE orders (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  status ENUM('pending', 'paid', 'shipped', 'cancelled') NOT NULL DEFAULT 'pending'
);

PostgreSQL 写法(自定义类型):

sql 复制代码
-- 1. 先创建枚举类型
CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'cancelled');

-- 2. 使用枚举类型
CREATE TABLE orders (
  id     BIGSERIAL PRIMARY KEY,
  status order_status NOT NULL DEFAULT 'pending'
);

修改枚举值:

sql 复制代码
-- 新增值
ALTER TYPE order_status ADD VALUE 'refunded';

-- 重命名值(PG 10+)
ALTER TYPE order_status RENAME VALUE 'cancelled' TO 'canceled';

注意事项:

  • ⚠️ 不能删除枚举值
  • ⚠️ 不能直接修改顺序
  • 如果枚举值频繁变化,考虑用 TEXT + CHECK 约束,或单独建字典表

替代方案:

sql 复制代码
-- 方案一:CHECK 约束
CREATE TABLE orders (
  id     BIGSERIAL PRIMARY KEY,
  status TEXT NOT NULL DEFAULT 'pending' 
         CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled'))
);

-- 方案二:字典表 + 外键
CREATE TABLE order_status_dict (
  code VARCHAR(20) PRIMARY KEY,
  name VARCHAR(50) NOT NULL
);

CREATE TABLE orders (
  id     BIGSERIAL PRIMARY KEY,
  status VARCHAR(20) REFERENCES order_status_dict(code)
);

5.4 数组类型(PostgreSQL 特有)

sql 复制代码
CREATE TABLE posts (
  id      BIGSERIAL PRIMARY KEY,
  title   TEXT NOT NULL,
  tags    TEXT[]  -- 文本数组
);

-- 插入
INSERT INTO posts (title, tags) 
VALUES ('PostgreSQL Guide', ARRAY['database', 'postgresql', 'sql']);

-- 或使用简写语法
INSERT INTO posts (title, tags) 
VALUES ('MySQL vs PG', '{"database", "mysql", "postgresql"}');

-- 查询:包含某个标签
SELECT * FROM posts WHERE 'postgresql' = ANY(tags);

-- 查询:包含所有指定标签
SELECT * FROM posts WHERE tags @> ARRAY['database', 'postgresql'];

-- 数组长度
SELECT title, array_length(tags, 1) FROM posts;

适用场景:

  • 少量标签、关键词
  • 不需要频繁按元素查询
  • 否则建议用关联表

6. SQL 语法差异

6.1 LIMIT 和 OFFSET

MySQL:

sql 复制代码
SELECT * FROM users ORDER BY id LIMIT 10, 20;  -- 跳过 10 条,取 20 条

PostgreSQL:

sql 复制代码
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 10;  -- 跳过 10 条,取 20 条

6.2 字符串拼接

MySQL:

sql 复制代码
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;

PostgreSQL:

sql 复制代码
-- 方式一:|| 操作符(推荐)
SELECT first_name || ' ' || last_name AS full_name FROM users;

-- 方式二:concat 函数
SELECT concat(first_name, ' ', last_name) AS full_name FROM users;

6.3 NULL 处理

MySQL:

sql 复制代码
SELECT IFNULL(nickname, name) FROM users;

PostgreSQL:

sql 复制代码
-- 方式一:COALESCE(SQL 标准)
SELECT COALESCE(nickname, name) FROM users;

-- 方式二:NULLIF
SELECT NULLIF(value, '') FROM users;  -- 如果 value 是空字符串则返回 NULL

6.4 日期和时间函数

功能 MySQL PostgreSQL
当前时间 NOW(), CURRENT_TIMESTAMP now(), CURRENT_TIMESTAMP
当前日期 CURDATE(), CURRENT_DATE CURRENT_DATE
时间运算 DATE_ADD(date, INTERVAL 1 DAY) date + INTERVAL '1 day'
格式化 DATE_FORMAT(date, '%Y-%m-%d') to_char(date, 'YYYY-MM-DD')
解析 STR_TO_DATE('2024-01-01', '%Y-%m-%d') to_date('2024-01-01', 'YYYY-MM-DD')
时间戳 UNIX_TIMESTAMP() extract(epoch from now())

示例:

sql 复制代码
-- 时间运算
SELECT now() + INTERVAL '1 day';           -- 明天
SELECT now() - INTERVAL '1 hour';          -- 1 小时前
SELECT now() + INTERVAL '1 month';         -- 下个月

-- 日期截断
SELECT date_trunc('day', now());           -- 今天 00:00:00
SELECT date_trunc('month', now());         -- 本月 1 号 00:00:00
SELECT date_trunc('hour', timestamp_col);  -- 截断到小时

-- 提取部分
SELECT extract(year from now());           -- 年份
SELECT extract(month from now());          -- 月份
SELECT extract(dow from now());            -- 星期几(0=周日)

-- 格式化
SELECT to_char(now(), 'YYYY-MM-DD HH24:MI:SS');
SELECT to_char(now(), 'Day, DD Mon YYYY');

6.5 条件表达式

CASE WHEN(相同):

sql 复制代码
SELECT 
  name,
  CASE 
    WHEN age < 18 THEN 'minor'
    WHEN age < 60 THEN 'adult'
    ELSE 'senior'
  END AS age_group
FROM users;

IF 函数(MySQL)vs CASE(PostgreSQL):

sql 复制代码
-- MySQL
SELECT name, IF(is_active = 1, 'Active', 'Inactive') FROM users;

-- PostgreSQL(推荐用 CASE)
SELECT name, CASE WHEN is_active THEN 'Active' ELSE 'Inactive' END FROM users;

6.6 字符串函数

功能 MySQL PostgreSQL
长度 LENGTH(str), CHAR_LENGTH(str) length(str), char_length(str)
截取 SUBSTRING(str, pos, len) substring(str, pos, len)substr(str, pos, len)
位置 LOCATE(substr, str) position(substr in str)strpos(str, substr)
替换 REPLACE(str, from, to) replace(str, from, to)
大小写 UPPER(str), LOWER(str) upper(str), lower(str)
去空格 TRIM(str), LTRIM(str), RTRIM(str) trim(str), ltrim(str), rtrim(str)
正则匹配 str REGEXP pattern str ~ pattern (区分大小写) / str ~* pattern (不区分)

正则表达式示例:

sql 复制代码
-- 匹配邮箱格式
SELECT * FROM users WHERE email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$';

-- 不区分大小写
SELECT * FROM users WHERE name ~* 'alice';

-- 替换(正则)
SELECT regexp_replace('abc123def', '\d+', 'NUM');  -- 结果:abcNUMdef

6.7 分组和聚合

基本相同,但注意:

sql 复制代码
-- GROUP_CONCAT(MySQL)vs string_agg(PostgreSQL)
-- MySQL
SELECT category, GROUP_CONCAT(name ORDER BY name SEPARATOR ', ')
FROM products
GROUP BY category;

-- PostgreSQL
SELECT category, string_agg(name, ', ' ORDER BY name)
FROM products
GROUP BY category;

7. 索引和查询优化 ⭐

7.1 索引类型

PostgreSQL 的索引比 MySQL 更丰富:

索引类型 适用场景 对比 MySQL
B-Tree 等值、范围查询(默认) 和 MySQL 的 B+Tree 类似
Hash 等值查询(PG 10+ 支持 WAL) MySQL 8.0 已废弃
GIN JSONB、数组、全文检索 MySQL 无直接对应
GiST 地理数据、范围类型、最近邻 MySQL 无直接对应
BRIN 超大表、时间序列(物理顺序) MySQL 无直接对应
SP-GiST 不规则数据结构 MySQL 无直接对应

7.2 创建索引

sql 复制代码
-- 默认 B-Tree 索引
CREATE INDEX idx_users_email ON users(email);

-- 唯一索引
CREATE UNIQUE INDEX idx_users_email ON users(email);

-- 多列索引
CREATE INDEX idx_users_name_email ON users(name, email);

-- 部分索引(条件索引)
CREATE INDEX idx_active_users ON users(created_at) WHERE is_active = true;

-- 表达式索引
CREATE INDEX idx_users_lower_email ON users(LOWER(email));

-- GIN 索引(JSONB)
CREATE INDEX idx_events_payload ON events USING GIN (payload);

-- BRIN 索引(大表,物理顺序)
CREATE INDEX idx_logs_created_at ON logs USING BRIN (created_at);

-- 并发创建索引(不锁表)
CREATE INDEX CONCURRENTLY idx_users_name ON users(name);

7.3 查看索引

sql 复制代码
-- psql 命令
\di
\di+ table_name  -- 查看某表的索引(带大小)

-- SQL 查询
SELECT * FROM pg_indexes WHERE tablename = 'users';

-- 查看未使用的索引
SELECT 
    schemaname,
    tablename,
    indexname,
    idx_scan,
    pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
  AND indexrelname NOT LIKE '%_pkey'
ORDER BY pg_relation_size(indexrelid) DESC;

7.4 EXPLAIN 分析查询计划

基本语法:

sql 复制代码
-- 查看执行计划
EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';

-- 实际执行并显示统计(推荐)
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'alice@example.com';

-- 更详细的信息
EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT * FROM users WHERE email = 'alice@example.com';

输出解读:

sql 复制代码
EXPLAIN ANALYZE SELECT * FROM users WHERE id > 100;

-- 输出示例:
Seq Scan on users  (cost=0.00..25.50 rows=500 width=100) (actual time=0.020..0.250 rows=450 loops=1)
  Filter: (id > 100)
  Rows Removed by Filter: 50
Planning Time: 0.150 ms
Execution Time: 0.300 ms

关键指标:

  • cost=0.00..25.50:预估成本(启动成本...总成本)
  • rows=500:预估返回行数
  • actual time=0.020..0.250:实际耗时(毫秒)
  • rows=450:实际返回行数
  • Seq Scan:全表扫描(通常需要优化)
  • Index Scan:索引扫描(好)
  • Index Only Scan:索引覆盖扫描(最好)

常见节点类型:

  • Seq Scan:全表扫描(慢)
  • Index Scan:索引扫描
  • Index Only Scan:索引覆盖扫描(不需要回表)
  • Bitmap Heap Scan:位图扫描(多个索引合并)
  • Nested Loop:嵌套循环连接
  • Hash Join:哈希连接
  • Merge Join:归并连接

7.5 查询优化技巧

使用索引
sql 复制代码
-- ❌ 不走索引:函数包裹列
SELECT * FROM users WHERE LOWER(email) = 'alice@example.com';

-- ✅ 走索引:创建表达式索引
CREATE INDEX idx_users_lower_email ON users(LOWER(email));
SELECT * FROM users WHERE LOWER(email) = 'alice@example.com';
避免 SELECT *
sql 复制代码
-- ❌ 查询不需要的列
SELECT * FROM users WHERE id = 123;

-- ✅ 只查询需要的列(可能走 Index Only Scan)
SELECT id, name, email FROM users WHERE id = 123;
使用 LIMIT
sql 复制代码
-- ✅ 限制返回行数
SELECT * FROM users ORDER BY created_at DESC LIMIT 100;
JOIN 优化
sql 复制代码
-- ✅ 确保 JOIN 的列上有索引
SELECT u.name, o.amount
FROM users u
JOIN orders o ON o.user_id = u.id  -- user_id 需要索引
WHERE u.is_active = true;
使用部分索引
sql 复制代码
-- 如果经常查询 is_active = true 的数据
CREATE INDEX idx_active_users ON users(created_at) WHERE is_active = true;
定期更新统计信息
sql 复制代码
-- 手动更新统计信息
ANALYZE users;

-- 或者全库
ANALYZE;

7.6 慢查询排查

sql 复制代码
-- 查看当前正在执行的慢查询
SELECT pid, now() - query_start AS duration, state, query
FROM pg_stat_activity
WHERE state != 'idle'
  AND now() - query_start > interval '5 seconds'
ORDER BY duration DESC;

-- 开启 pg_stat_statements 扩展(需要重启)
-- postgresql.conf:
-- shared_preload_libraries = 'pg_stat_statements'

-- 创建扩展
CREATE EXTENSION pg_stat_statements;

-- 查看最慢的查询
SELECT 
    calls,
    mean_exec_time,
    total_exec_time,
    query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

8. 常用命令速查

8.1 psql 元命令

命令 说明
\? 显示所有元命令帮助
\h COMMAND 显示 SQL 命令帮助(如 \h CREATE TABLE
\l\l+ 列出所有数据库(+ 显示大小)
\c dbname 切换数据库
\dt\dt+ 列出表(+ 显示大小)
\d table 查看表结构
\d+ table 查看表结构(详细信息)
\di\di+ 列出索引
\dv 列出视图
\df 列出函数
\du\du+ 列出用户/角色
\dn 列出 schema
\dp table 查看表权限
\x 切换扩展显示模式(类似 MySQL 的 \G
\timing 开启/关闭查询计时
\e 打开编辑器编辑上一条命令
\i file.sql 执行 SQL 文件
\o file.txt 输出重定向到文件
\q 退出

8.2 MySQL 命令对照表

场景 MySQL PostgreSQL
列出数据库 SHOW DATABASES; \l
切换数据库 USE db; \c db
查看当前数据库 SELECT DATABASE(); SELECT current_database();
列出表 SHOW TABLES; \dt
查看表结构 DESC table; \d table
查看建表语句 SHOW CREATE TABLE table; pg_dump -s -t table dbname
查看索引 SHOW INDEX FROM table; \di table\d table
查看连接 SHOW PROCESSLIST; SELECT * FROM pg_stat_activity;
查看配置 SHOW VARIABLES LIKE 'max%'; SHOW max_connections;
查看表大小 SHOW TABLE STATUS; \dt+SELECT pg_size_pretty(pg_total_relation_size('table'));
杀死连接 KILL connection_id; SELECT pg_terminate_backend(pid);

8.3 信息查询 SQL

sql 复制代码
-- 查看当前数据库
SELECT current_database();

-- 查看当前用户
SELECT current_user;

-- 查看当前 schema
SHOW search_path;

-- 查看 PostgreSQL 版本
SELECT version();

-- 查看表大小
SELECT pg_size_pretty(pg_total_relation_size('users'));

-- 查看索引大小
SELECT pg_size_pretty(pg_indexes_size('users'));

-- 查看数据库大小
SELECT pg_size_pretty(pg_database_size(current_database()));

-- 查看所有表及其大小
SELECT 
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

第三部分:进阶特性

9. JSONB 和半结构化数据

9.1 为什么用 JSONB

PostgreSQL 有两种 JSON 类型:

  • JSON:存储原始文本,读写开销大(很少用)
  • JSONB:二进制格式,支持索引、高效查询(⭐ 推荐)

适用场景:

  • 结构频繁变化的属性(如用户自定义字段)
  • 嵌套层级较深的数据
  • 需要灵活查询的半结构化数据

9.2 基本操作

sql 复制代码
-- 创建表
CREATE TABLE events (
  id         BIGSERIAL PRIMARY KEY,
  event_type VARCHAR(50) NOT NULL,
  payload    JSONB NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- 插入数据
INSERT INTO events (event_type, payload) VALUES 
  ('user_login', '{"user_id": 123, "ip": "1.2.3.4", "device": "iPhone"}'),
  ('order_created', '{"user_id": 123, "order_id": 456, "amount": 99.99}');

-- 查询:提取字段
SELECT 
  payload->>'user_id' AS user_id,        -- 返回文本
  payload->'user_id' AS user_id_json,    -- 返回 JSON
  payload->'device'->>'model' AS model   -- 嵌套字段
FROM events;

-- 查询:条件过滤
SELECT * FROM events WHERE payload->>'event_type' = 'login';

-- 查询:包含某个键
SELECT * FROM events WHERE payload ? 'device';

-- 查询:包含子对象(可走 GIN 索引)
SELECT * FROM events WHERE payload @> '{"user_id": 123}';

-- 更新:修改某个字段
UPDATE events 
SET payload = jsonb_set(payload, '{ip}', '"2.3.4.5"')
WHERE id = 1;

-- 删除:移除某个键
UPDATE events 
SET payload = payload - 'device'
WHERE id = 1;

9.3 JSONB 操作符

操作符 说明 示例
-> 提取 JSON 字段(返回 JSON) payload->'user_id'
->> 提取 JSON 字段(返回文本) payload->>'user_id'
#> 提取嵌套路径(返回 JSON) payload#>'{user,name}'
#>> 提取嵌套路径(返回文本) payload#>>'{user,name}'
? 是否包含键 payload ? 'device'
?& 是否包含所有键 payload ?& array['user_id', 'ip']
`? ` 是否包含任意键
@> 是否包含子对象 payload @> '{"user_id": 123}'
<@ 是否被包含 '{"user_id": 123}' <@ payload
- 删除键 payload - 'device'
` `

9.4 JSONB 索引

sql 复制代码
-- GIN 索引(通用,推荐)
CREATE INDEX idx_events_payload ON events USING GIN (payload);

-- 特定路径索引(更高效)
CREATE INDEX idx_events_user_id ON events ((payload->>'user_id'));

-- 表达式索引
CREATE INDEX idx_events_user_id_int ON events ((payload->>'user_id')::bigint);

查询优化:

sql 复制代码
-- ✅ 走索引(使用 @> 操作符)
EXPLAIN SELECT * FROM events WHERE payload @> '{"user_id": 123}';

-- ❌ 不走索引(使用 ->)
EXPLAIN SELECT * FROM events WHERE payload->>'user_id' = '123';

-- ✅ 走表达式索引
CREATE INDEX idx_events_user_id ON events ((payload->>'user_id'));
EXPLAIN SELECT * FROM events WHERE payload->>'user_id' = '123';

9.5 JSONB 函数

sql 复制代码
-- jsonb_build_object:构建 JSON 对象
SELECT jsonb_build_object('name', 'Alice', 'age', 30);
-- 结果:{"name": "Alice", "age": 30}

-- jsonb_build_array:构建 JSON 数组
SELECT jsonb_build_array(1, 2, 3);
-- 结果:[1, 2, 3]

-- jsonb_agg:聚合为 JSON 数组
SELECT user_id, jsonb_agg(order_id) AS orders
FROM orders
GROUP BY user_id;

-- jsonb_object_agg:聚合为 JSON 对象
SELECT jsonb_object_agg(name, value) FROM settings;

-- jsonb_each:展开为行
SELECT * FROM jsonb_each('{"a": 1, "b": 2}'::jsonb);
-- 结果:
-- a | 1
-- b | 2

-- jsonb_array_elements:展开数组
SELECT * FROM jsonb_array_elements('[1,2,3]'::jsonb);

10. UPSERT 和高级 DML

10.1 UPSERT(INSERT ... ON CONFLICT)

MySQL 的 REPLACE INTO:

sql 复制代码
REPLACE INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com');

PostgreSQL 的 ON CONFLICT(更灵活):

sql 复制代码
-- 冲突时更新
INSERT INTO users (id, name, email)
VALUES (1, 'Alice', 'alice@example.com')
ON CONFLICT (id) 
DO UPDATE SET 
  name = EXCLUDED.name,
  email = EXCLUDED.email,
  updated_at = now();

-- 冲突时忽略
INSERT INTO users (id, name, email)
VALUES (1, 'Alice', 'alice@example.com')
ON CONFLICT (id) 
DO NOTHING;

-- 条件更新(只在满足条件时更新)
INSERT INTO users (id, name, email, version)
VALUES (1, 'Alice', 'alice@example.com', 2)
ON CONFLICT (id) 
DO UPDATE SET 
  name = EXCLUDED.name,
  version = EXCLUDED.version
WHERE users.version < EXCLUDED.version;  -- 乐观锁

EXCLUDED 表:

  • EXCLUDED 代表要插入的新值
  • users.column 代表表中的旧值

10.2 RETURNING 子句(MySQL 没有)

INSERT 后返回数据:

sql 复制代码
INSERT INTO users (name, email)
VALUES ('Bob', 'bob@example.com')
RETURNING id, created_at;
-- 直接返回插入的 id 和 created_at,无需再查询

UPDATE 后返回数据:

sql 复制代码
UPDATE users 
SET status = 'inactive'
WHERE last_login < now() - interval '1 year'
RETURNING id, name, email;
-- 返回所有被更新的行

DELETE 后返回数据:

sql 复制代码
DELETE FROM users
WHERE is_active = false
RETURNING *;
-- 返回所有被删除的行

在 CTE 中使用:

sql 复制代码
WITH deleted AS (
  DELETE FROM old_logs
  WHERE created_at < now() - interval '90 days'
  RETURNING *
)
SELECT count(*) FROM deleted;  -- 统计删除了多少行

10.3 批量 UPSERT

sql 复制代码
INSERT INTO users (id, name, email) VALUES
  (1, 'Alice', 'alice@example.com'),
  (2, 'Bob', 'bob@example.com'),
  (3, 'Charlie', 'charlie@example.com')
ON CONFLICT (id) 
DO UPDATE SET 
  name = EXCLUDED.name,
  email = EXCLUDED.email;

10.4 UPDATE ... FROM(类似 MySQL 的 UPDATE JOIN)

MySQL:

sql 复制代码
UPDATE orders o
JOIN users u ON o.user_id = u.id
SET o.user_name = u.name
WHERE u.is_active = true;

PostgreSQL:

sql 复制代码
UPDATE orders o
SET user_name = u.name
FROM users u
WHERE o.user_id = u.id
  AND u.is_active = true;

10.5 DELETE ... USING(类似 MySQL 的 DELETE JOIN)

PostgreSQL:

sql 复制代码
DELETE FROM orders o
USING users u
WHERE o.user_id = u.id
  AND u.is_active = false;

11. 高级 SQL 技巧

11.1 CTE(WITH 语句)

基本 CTE:

sql 复制代码
WITH recent_orders AS (
  SELECT user_id, count(*) AS order_count
  FROM orders
  WHERE created_at > now() - interval '30 days'
  GROUP BY user_id
)
SELECT u.name, ro.order_count
FROM users u
JOIN recent_orders ro ON u.id = ro.user_id
WHERE ro.order_count > 5;

多个 CTE:

sql 复制代码
WITH 
  active_users AS (
    SELECT id, name FROM users WHERE is_active = true
  ),
  recent_orders AS (
    SELECT user_id, count(*) AS cnt 
    FROM orders 
    WHERE created_at > now() - interval '30 days'
    GROUP BY user_id
  )
SELECT au.name, coalesce(ro.cnt, 0) AS order_count
FROM active_users au
LEFT JOIN recent_orders ro ON au.id = ro.user_id;

11.2 递归 CTE(查询树形结构)

查询组织架构树:

sql 复制代码
-- 假设有部门表
CREATE TABLE departments (
  id INT PRIMARY KEY,
  name VARCHAR(100),
  parent_id INT REFERENCES departments(id)
);

-- 递归查询:从根节点开始遍历
WITH RECURSIVE org_tree AS (
  -- 基础查询:根节点
  SELECT id, name, parent_id, 1 AS level, name::text AS path
  FROM departments
  WHERE parent_id IS NULL
  
  UNION ALL
  
  -- 递归查询:子节点
  SELECT d.id, d.name, d.parent_id, ot.level + 1, ot.path || ' > ' || d.name
  FROM departments d
  JOIN org_tree ot ON d.parent_id = ot.id
)
SELECT * FROM org_tree ORDER BY path;

查询所有下级:

sql 复制代码
WITH RECURSIVE subordinates AS (
  SELECT id, name, manager_id
  FROM employees
  WHERE id = 100  -- 起始员工 ID
  
  UNION ALL
  
  SELECT e.id, e.name, e.manager_id
  FROM employees e
  JOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates;

11.3 窗口函数(Window Functions)

排名函数:

sql 复制代码
-- 每个部门工资排名
SELECT 
  name,
  department,
  salary,
  rank() OVER (PARTITION BY department ORDER BY salary DESC) AS rank,
  dense_rank() OVER (PARTITION BY department ORDER BY salary DESC) AS dense_rank,
  row_number() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num
FROM employees;

-- 取每个部门工资最高的 3 人
SELECT * FROM (
  SELECT 
    name,
    department,
    salary,
    row_number() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
  FROM employees
) t
WHERE rn <= 3;

聚合函数作为窗口函数:

sql 复制代码
-- 计算部门平均工资,但保留所有行
SELECT 
  name,
  department,
  salary,
  avg(salary) OVER (PARTITION BY department) AS dept_avg,
  salary - avg(salary) OVER (PARTITION BY department) AS diff_from_avg
FROM employees;

移动平均:

sql 复制代码
-- 7 天移动平均
SELECT 
  date,
  amount,
  avg(amount) OVER (
    ORDER BY date 
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) AS ma_7
FROM daily_sales;

累计和:

sql 复制代码
SELECT 
  date,
  amount,
  sum(amount) OVER (ORDER BY date) AS cumulative_total
FROM daily_sales;

LAG 和 LEAD(访问前后行):

sql 复制代码
-- 计算环比增长
SELECT 
  date,
  amount,
  lag(amount) OVER (ORDER BY date) AS prev_amount,
  amount - lag(amount) OVER (ORDER BY date) AS growth
FROM daily_sales;

-- 下一个值
SELECT 
  date,
  amount,
  lead(amount) OVER (ORDER BY date) AS next_amount
FROM daily_sales;

11.4 LATERAL JOIN(横向连接)

为每个用户找最近 3 条订单:

sql 复制代码
SELECT u.name, o.*
FROM users u
LEFT JOIN LATERAL (
  SELECT * FROM orders
  WHERE user_id = u.id
  ORDER BY created_at DESC
  LIMIT 3
) o ON true;

等价于(但 LATERAL 更清晰):

sql 复制代码
SELECT u.name, o.*
FROM users u
LEFT JOIN (
  SELECT DISTINCT ON (user_id) *
  FROM (
    SELECT *, row_number() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
    FROM orders
  ) t
  WHERE rn <= 3
) o ON o.user_id = u.id;

11.5 DISTINCT ON(PostgreSQL 特有)

每个用户的最新订单:

sql 复制代码
SELECT DISTINCT ON (user_id) *
FROM orders
ORDER BY user_id, created_at DESC;

注意:

  • DISTINCT ON 的列必须是 ORDER BY 的前缀
  • 相当于 GROUP BY + MAX,但保留整行

11.6 数组聚合和展开

聚合为数组:

sql 复制代码
SELECT user_id, array_agg(order_id) AS orders
FROM orders
GROUP BY user_id;

展开数组:

sql 复制代码
SELECT unnest(ARRAY[1, 2, 3, 4, 5]);

结合使用:

sql 复制代码
-- 找出同时购买了多个产品的用户
WITH user_products AS (
  SELECT user_id, array_agg(product_id) AS products
  FROM orders
  GROUP BY user_id
)
SELECT user_id
FROM user_products
WHERE products @> ARRAY[1, 2, 3];  -- 包含产品 1, 2, 3

12. 触发器和存储过程

12.1 触发器(Trigger)

和 MySQL 的主要区别:

  • PostgreSQL 触发器需要先定义函数,再绑定到表
  • 使用 plpgsql 语言编写
  • 行级触发器必须 RETURN NEW/OLD/NULL

示例:自动更新 updated_at 字段

sql 复制代码
-- 1. 创建触发器函数
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 2. 创建触发器
CREATE TRIGGER update_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();

示例:审计日志

sql 复制代码
-- 创建审计表
CREATE TABLE audit_log (
  id BIGSERIAL PRIMARY KEY,
  table_name TEXT NOT NULL,
  operation TEXT NOT NULL,
  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
  IF TG_OP = 'DELETE' THEN
    INSERT INTO audit_log (table_name, operation, old_data)
    VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD));
    RETURN OLD;
  ELSIF TG_OP = 'UPDATE' THEN
    INSERT INTO audit_log (table_name, operation, old_data, new_data)
    VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), row_to_json(NEW));
    RETURN NEW;
  ELSIF TG_OP = 'INSERT' THEN
    INSERT INTO audit_log (table_name, operation, new_data)
    VALUES (TG_TABLE_NAME, TG_OP, row_to_json(NEW));
    RETURN NEW;
  END IF;
END;
$$ LANGUAGE plpgsql;

-- 绑定到表
CREATE TRIGGER users_audit
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION audit_trigger();

触发器内可用变量:

  • NEW:新行数据(INSERT/UPDATE)
  • OLD:旧行数据(UPDATE/DELETE)
  • TG_OP:操作类型('INSERT', 'UPDATE', 'DELETE')
  • TG_TABLE_NAME:表名
  • TG_WHEN:'BEFORE' 或 'AFTER'
  • TG_LEVEL:'ROW' 或 'STATEMENT'

12.2 存储过程和函数

创建函数:

sql 复制代码
CREATE OR REPLACE FUNCTION get_user_order_count(p_user_id BIGINT)
RETURNS INTEGER AS $$
DECLARE
  v_count INTEGER;
BEGIN
  SELECT count(*) INTO v_count
  FROM orders
  WHERE user_id = p_user_id;
  
  RETURN v_count;
END;
$$ LANGUAGE plpgsql;

-- 调用
SELECT get_user_order_count(123);

存储过程(PG 11+):

sql 复制代码
CREATE OR REPLACE PROCEDURE update_user_status(
  p_user_id BIGINT,
  p_status TEXT
)
LANGUAGE plpgsql
AS $$
BEGIN
  UPDATE users SET status = p_status WHERE id = p_user_id;
  
  -- 可以包含事务控制
  COMMIT;
END;
$$;

-- 调用
CALL update_user_status(123, 'inactive');

返回表的函数:

sql 复制代码
CREATE OR REPLACE FUNCTION get_top_users(p_limit INT DEFAULT 10)
RETURNS TABLE (
  user_id BIGINT,
  name VARCHAR,
  order_count BIGINT
) AS $$
BEGIN
  RETURN QUERY
  SELECT u.id, u.name, count(o.id) AS cnt
  FROM users u
  LEFT JOIN orders o ON o.user_id = u.id
  GROUP BY u.id, u.name
  ORDER BY cnt DESC
  LIMIT p_limit;
END;
$$ LANGUAGE plpgsql;

-- 调用
SELECT * FROM get_top_users(5);

13. LISTEN/NOTIFY 通知机制

这是 PostgreSQL 独有的特性,可以在数据库连接之间做轻量级通知。

13.1 基本用法

会话 A(监听):

sql 复制代码
LISTEN channel_name;

会话 B(发送通知):

sql 复制代码
NOTIFY channel_name, 'some payload';

会话 A 会收到异步通知。

13.2 配合触发器做变更通知

示例:用户表变更通知

sql 复制代码
-- 创建通知函数
CREATE OR REPLACE FUNCTION notify_user_changed()
RETURNS TRIGGER AS $$
DECLARE
  payload TEXT;
BEGIN
  payload := json_build_object(
    'operation', TG_OP,
    'id', COALESCE(NEW.id, OLD.id),
    'name', COALESCE(NEW.name, OLD.name)
  )::text;
  
  PERFORM pg_notify('user_changed', payload);
  
  IF TG_OP = 'DELETE' THEN
    RETURN OLD;
  ELSE
    RETURN NEW;
  END IF;
END;
$$ LANGUAGE plpgsql;

-- 创建触发器
CREATE TRIGGER users_notify
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION notify_user_changed();

应用程序监听:

python 复制代码
# Python 示例(使用 psycopg2)
import psycopg2
import select
import json

conn = psycopg2.connect("dbname=mydb user=postgres")
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)

cursor = conn.cursor()
cursor.execute("LISTEN user_changed;")

print("Waiting for notifications...")

while True:
    if select.select([conn], [], [], 5) == ([], [], []):
        print("Timeout")
    else:
        conn.poll()
        while conn.notifies:
            notify = conn.notifies.pop(0)
            payload = json.loads(notify.payload)
            print(f"Received: {payload}")

适用场景:

  • 刷新应用缓存
  • 实时通知(如聊天应用)
  • 配置变更通知
  • 轻量级事件驱动

限制:

  • Payload 最大 8KB
  • 非持久化(内存队列)
  • 不能替代专业消息队列(Kafka、RabbitMQ)

14. 其他增强特性

14.1 全文检索(Full Text Search)

基本使用:

sql 复制代码
-- 创建 tsvector 列
ALTER TABLE articles ADD COLUMN tsv tsvector;

-- 更新 tsvector
UPDATE articles
SET tsv = to_tsvector('english', coalesce(title, '') || ' ' || coalesce(content, ''));

-- 创建 GIN 索引
CREATE INDEX idx_articles_tsv ON articles USING GIN (tsv);

-- 查询
SELECT * FROM articles
WHERE tsv @@ plainto_tsquery('english', 'postgresql tutorial');

-- 按相关度排序
SELECT *, ts_rank(tsv, plainto_tsquery('english', 'postgresql')) AS rank
FROM articles
WHERE tsv @@ plainto_tsquery('english', 'postgresql')
ORDER BY rank DESC;

中文分词:

PostgreSQL 核心不支持中文分词,需要安装扩展:

  • zhparser(常用)
  • pg_jieba

使用 zhparser:

sql 复制代码
-- 安装扩展
CREATE EXTENSION zhparser;

-- 创建中文配置
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;

-- 使用
SELECT * FROM articles
WHERE to_tsvector('chinese', content) @@ to_tsquery('chinese', '数据库');

14.2 行级安全(Row Level Security, RLS)

启用 RLS:

sql 复制代码
-- 启用行级安全
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

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

-- 或使用 current_user
CREATE POLICY orders_owner_only
ON orders
FOR ALL
USING (owner = current_user);

不同操作的策略:

sql 复制代码
-- SELECT 策略
CREATE POLICY orders_select_policy
ON orders
FOR SELECT
USING (user_id = current_setting('app.current_user_id')::bigint);

-- INSERT 策略
CREATE POLICY orders_insert_policy
ON orders
FOR INSERT
WITH CHECK (user_id = current_setting('app.current_user_id')::bigint);

-- UPDATE 策略(可见 + 可修改)
CREATE POLICY orders_update_policy
ON orders
FOR UPDATE
USING (user_id = current_setting('app.current_user_id')::bigint)
WITH CHECK (user_id = current_setting('app.current_user_id')::bigint);

-- DELETE 策略
CREATE POLICY orders_delete_policy
ON orders
FOR DELETE
USING (user_id = current_setting('app.current_user_id')::bigint);

应用层设置参数:

sql 复制代码
-- 在连接开始时设置
SET app.current_user_id = 123;

-- 或在事务中设置
BEGIN;
SET LOCAL app.current_user_id = 123;
SELECT * FROM orders;  -- 只能看到 user_id = 123 的订单
COMMIT;

查看策略:

sql 复制代码
\d+ orders  -- psql 命令

-- 或 SQL 查询
SELECT * FROM pg_policies WHERE tablename = 'orders';

禁用 RLS(对超级用户和表所有者):

sql 复制代码
-- 超级用户默认绕过 RLS
-- 如果需要强制执行:
ALTER TABLE orders FORCE ROW LEVEL SECURITY;

适用场景:

  • SaaS 多租户隔离
  • 数据权限控制
  • 把部分权限逻辑下沉到数据库层

14.3 物化视图(Materialized View)

创建物化视图:

sql 复制代码
CREATE MATERIALIZED VIEW mv_user_stats AS
SELECT 
  u.id,
  u.name,
  count(o.id) AS order_count,
  sum(o.amount) AS total_amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name;

-- 创建索引
CREATE INDEX idx_mv_user_stats_id ON mv_user_stats(id);

刷新物化视图:

sql 复制代码
-- 普通刷新(会锁表)
REFRESH MATERIALIZED VIEW mv_user_stats;

-- 并发刷新(不锁表,但需要有唯一索引)
CREATE UNIQUE INDEX idx_mv_user_stats_id_unique ON mv_user_stats(id);
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_user_stats;

查询物化视图:

sql 复制代码
SELECT * FROM mv_user_stats WHERE order_count > 10;

删除物化视图:

sql 复制代码
DROP MATERIALIZED VIEW mv_user_stats;

适用场景:

  • 复杂的报表查询
  • 聚合结果缓存
  • 跨表统计

和普通视图的区别:

  • 普通视图:每次查询都重新计算(类似 MySQL)
  • 物化视图:结果持久化,需要手动刷新

14.4 事务性 DDL

PostgreSQL 的 DDL 可以放在事务中回滚:

sql 复制代码
BEGIN;

-- 创建表
CREATE TABLE test_table (id INT, name TEXT);

-- 插入数据
INSERT INTO test_table VALUES (1, 'Alice');

-- 修改表结构
ALTER TABLE test_table ADD COLUMN email TEXT;

-- 回滚所有操作(包括 DDL)
ROLLBACK;

-- test_table 不会被创建

MySQL 对比:

  • MySQL 的大多数 DDL 会隐式提交事务,无法回滚
  • PostgreSQL 支持事务性 DDL,对误操作更友好

限制:

  • 某些操作不支持事务:如 CREATE DATABASE, DROP DATABASE
  • VACUUM, CREATE INDEX CONCURRENTLY 也不能在事务中

14.5 范围类型(Range Types)

内置范围类型:

sql 复制代码
-- 整数范围
CREATE TABLE events (
  id BIGSERIAL PRIMARY KEY,
  event_name TEXT,
  participant_age_range int4range  -- 整数范围
);

INSERT INTO events (event_name, participant_age_range) VALUES
  ('少年赛', '[10,18)'),   -- [10, 18),包含10,不包含18
  ('成年赛', '[18,60)');

-- 查询:年龄 16 适合哪些比赛
SELECT event_name 
FROM events 
WHERE participant_age_range @> 16;  -- 结果:少年赛

-- 时间范围
CREATE TABLE bookings (
  id BIGSERIAL PRIMARY KEY,
  room_id INT,
  booking_period tstzrange  -- 时间范围
);

INSERT INTO bookings (room_id, booking_period) VALUES
  (101, '[2024-01-01 10:00, 2024-01-01 12:00)');

-- 查询:是否有重叠
SELECT * FROM bookings 
WHERE room_id = 101 
  AND booking_period && '[2024-01-01 11:00, 2024-01-01 13:00)'::tstzrange;

范围操作符:

  • @>: 包含
  • <@: 被包含
  • &&: 重叠
  • <<: 严格左侧
  • >>: 严格右侧
  • -|-: 相邻

GiST 索引:

sql 复制代码
CREATE INDEX idx_bookings_period ON bookings USING GIST (booking_period);

14.6 生成列(Generated Columns,PG 12+)

sql 复制代码
CREATE TABLE products (
  id BIGSERIAL PRIMARY KEY,
  price NUMERIC(10, 2),
  quantity INT,
  total NUMERIC(10, 2) GENERATED ALWAYS AS (price * quantity) STORED
);

INSERT INTO products (price, quantity) VALUES (10.50, 3);

SELECT * FROM products;
-- id | price | quantity | total
-- 1  | 10.50 | 3        | 31.50

两种类型:

  • STORED: 物理存储(推荐)
  • VIRTUAL: 虚拟计算(PG 暂不支持)

第四部分:生产实践

15. VACUUM 和表维护 ⭐

15.1 为什么需要 VACUUM

PostgreSQL 的 MVCC 机制:

  • UPDATE/DELETE 不会立即删除旧数据
  • 而是标记为"死元组"(dead tuple)
  • 需要 VACUUM 回收空间

MySQL 对比:

  • MySQL InnoDB 通过 undo log 实现 MVCC
  • 自动清理机制不同

15.2 VACUUM 命令

sql 复制代码
-- 普通 VACUUM:清理死元组,不释放磁盘空间
VACUUM;
VACUUM users;

-- VACUUM FULL:重建表,释放磁盘空间(⚠️ 锁表)
VACUUM FULL users;

-- ANALYZE:更新统计信息(影响查询计划)
ANALYZE;
ANALYZE users;

-- 一起做
VACUUM ANALYZE users;

-- VERBOSE:显示详细信息
VACUUM VERBOSE users;

15.3 Autovacuum(自动清理)

检查是否启用:

sql 复制代码
SHOW autovacuum;  -- 应该是 on

配置参数(postgresql.conf):

conf 复制代码
autovacuum = on  # 启用自动清理
autovacuum_max_workers = 3  # 并行 worker 数量
autovacuum_naptime = 1min  # 检查间隔

# 触发条件
autovacuum_vacuum_threshold = 50  # 最少死元组数
autovacuum_vacuum_scale_factor = 0.2  # 死元组比例(20%)

单表调整:

sql 复制代码
-- 更激进的清理策略
ALTER TABLE large_table SET (
  autovacuum_vacuum_scale_factor = 0.05,
  autovacuum_vacuum_threshold = 1000
);

15.4 监控表膨胀

查看死元组情况:

sql 复制代码
SELECT 
  schemaname,
  tablename,
  n_live_tup AS live_rows,
  n_dead_tup AS dead_rows,
  round(n_dead_tup * 100.0 / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS dead_ratio,
  last_vacuum,
  last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;

查看表膨胀率:

sql 复制代码
SELECT
  schemaname || '.' || tablename AS table_name,
  pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS total_size,
  pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) AS table_size,
  round(100 * pg_relation_size(schemaname||'.'||tablename)::numeric / 
        NULLIF(pg_total_relation_size(schemaname||'.'||tablename), 0), 2) AS table_ratio
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 10;

15.5 最佳实践

  1. 确保 autovacuum 开启(生产环境必须)
  2. 高频更新的表考虑降低阈值
  3. 定期检查死元组比例
  4. VACUUM FULL 谨慎使用(会锁表,考虑使用 pg_repack)
  5. 大表考虑分区

16. 连接池和并发控制

16.1 为什么需要连接池

关键差异:

  • MySQL: 轻量级线程模型,支持较多连接
  • PostgreSQL: 每个连接是独立进程,开销较大

查看连接数:

sql 复制代码
SHOW max_connections;  -- 默认 100

SELECT count(*) FROM pg_stat_activity;  -- 当前连接数

16.2 PgBouncer(推荐)

三种模式:

  1. session: 连接级复用(最安全,默认)

    • 客户端连接断开后才复用
    • 支持所有 PostgreSQL 特性
  2. transaction: 事务级复用(⭐ 推荐)

    • 事务结束后立即复用
    • 性能好,适合大多数场景
    • 不支持:LISTEN/NOTIFY, PREPARE, CURSOR
  3. statement: 语句级复用

    • 每条 SQL 后复用
    • 限制最多,不推荐

安装和配置:

bash 复制代码
# macOS
brew install pgbouncer

# Ubuntu
sudo apt install pgbouncer

配置文件 pgbouncer.ini:

ini 复制代码
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25

应用连接到 PgBouncer:

python 复制代码
# 原来连接 PostgreSQL
conn = psycopg2.connect("host=localhost port=5432 dbname=mydb user=app")

# 改为连接 PgBouncer
conn = psycopg2.connect("host=localhost port=6432 dbname=mydb user=app")

16.3 应用层连接池

HikariCP (Java):

properties 复制代码
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000

asyncpg (Python):

python 复制代码
import asyncpg

pool = await asyncpg.create_pool(
    host='localhost',
    database='mydb',
    user='app',
    password='password',
    min_size=5,
    max_size=20
)

16.4 事务隔离级别

PostgreSQL 支持的级别:

sql 复制代码
-- 查看当前隔离级别
SHOW transaction_isolation;

-- 设置隔离级别
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;

和 MySQL 的对比:

隔离级别 MySQL PostgreSQL
Read Uncommitted ❌(最低是 RC)
Read Committed ✓(默认)
Repeatable Read ✓(默认)
Serializable

PostgreSQL 默认是 Read Committed,MySQL 默认是 Repeatable Read。

16.5 锁机制

查看锁等待:

sql 复制代码
SELECT 
  blocked_locks.pid AS blocked_pid,
  blocked_activity.usename AS blocked_user,
  blocking_locks.pid AS blocking_pid,
  blocking_activity.usename AS blocking_user,
  blocked_activity.query AS blocked_statement,
  blocking_activity.query AS blocking_statement
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.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.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;

手动锁表:

sql 复制代码
-- 显式锁表(少用)
BEGIN;
LOCK TABLE users IN EXCLUSIVE MODE;
-- 其他操作
COMMIT;

17. 备份和恢复

17.1 逻辑备份(pg_dump)

导出单个数据库:

bash 复制代码
# 自定义格式(推荐)
pg_dump -h localhost -U postgres -d mydb -F c -f mydb.dump

# SQL 格式
pg_dump -h localhost -U postgres -d mydb -f mydb.sql

# 压缩
pg_dump -h localhost -U postgres -d mydb | gzip > mydb.sql.gz

导出特定表:

bash 复制代码
pg_dump -h localhost -U postgres -d mydb -t users -t orders -f tables.dump

导出 schema only(不含数据):

bash 复制代码
pg_dump -h localhost -U postgres -d mydb -s -f schema.sql

导出所有数据库:

bash 复制代码
pg_dumpall -h localhost -U postgres -f all_databases.sql

17.2 恢复

从自定义格式恢复:

bash 复制代码
# 创建数据库
createdb -U postgres mydb_restore

# 恢复
pg_restore -h localhost -U postgres -d mydb_restore mydb.dump

# 并行恢复(加速)
pg_restore -h localhost -U postgres -d mydb_restore -j 4 mydb.dump

从 SQL 文件恢复:

bash 复制代码
psql -h localhost -U postgres -d mydb < mydb.sql

17.3 物理备份(pg_basebackup)

bash 复制代码
# 基础备份
pg_basebackup -h localhost -U postgres -D /backup/pgdata -Fp -Xs -P

# 参数说明:
# -D: 备份目录
# -Fp: plain 格式
# -Xs: 包含 WAL 日志(stream 模式)
# -P: 显示进度

17.4 连续归档和 PITR(时间点恢复)

配置 WAL 归档(postgresql.conf):

conf 复制代码
wal_level = replica
archive_mode = on
archive_command = 'cp %p /archive/%f'

执行基础备份:

bash 复制代码
pg_basebackup -h localhost -U postgres -D /backup/base -Fp -Xs -P

恢复到特定时间点:

bash 复制代码
# 1. 恢复基础备份
cp -r /backup/base /var/lib/postgresql/data

# 2. 创建 recovery.conf(PG 12+用 postgresql.auto.conf)
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2024-01-01 12:00:00'

# 3. 启动 PostgreSQL

17.5 推荐工具

  • pgBackRest: 企业级备份工具
  • Barman: 灾难恢复工具
  • wal-g: 云存储备份(S3, GCS)

18. 监控和诊断

18.1 查看活动连接

sql 复制代码
SELECT 
  pid,
  usename,
  datname,
  client_addr,
  state,
  now() - query_start AS duration,
  query
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;

18.2 查看长时间运行的查询

sql 复制代码
SELECT 
  pid,
  now() - query_start AS duration,
  state,
  query
FROM pg_stat_activity
WHERE state != 'idle'
  AND now() - query_start > interval '5 minutes'
ORDER BY duration DESC;

18.3 杀死查询

sql 复制代码
-- 取消查询(温和)
SELECT pg_cancel_backend(pid);

-- 强制终止连接
SELECT pg_terminate_backend(pid);

-- 批量终止空闲连接
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle'
  AND now() - state_change > interval '1 hour';

18.4 慢查询分析(pg_stat_statements)

安装扩展:

sql 复制代码
-- 需要修改 postgresql.conf
-- shared_preload_libraries = 'pg_stat_statements'
-- 重启数据库后

CREATE EXTENSION pg_stat_statements;

查看最慢的查询:

sql 复制代码
SELECT 
  calls,
  total_exec_time,
  mean_exec_time,
  max_exec_time,
  stddev_exec_time,
  rows,
  query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;

查看总耗时最多的查询:

sql 复制代码
SELECT 
  calls,
  total_exec_time,
  mean_exec_time,
  query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

重置统计:

sql 复制代码
SELECT pg_stat_statements_reset();

18.5 查看表和索引大小

sql 复制代码
-- 最大的表
SELECT
  schemaname || '.' || tablename AS table_name,
  pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS total_size,
  pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) AS table_size,
  pg_size_pretty(pg_indexes_size(schemaname||'.'||tablename)) AS indexes_size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 20;

-- 最大的索引
SELECT
  schemaname || '.' || tablename AS table_name,
  indexname,
  pg_size_pretty(pg_relation_size(schemaname||'.'||indexname)) AS index_size
FROM pg_indexes
WHERE schemaname = 'public'
ORDER BY pg_relation_size(schemaname||'.'||indexname) DESC
LIMIT 20;

18.6 缓存命中率

sql 复制代码
-- 表缓存命中率(应该 > 99%)
SELECT 
  sum(heap_blks_read) AS heap_read,
  sum(heap_blks_hit) AS heap_hit,
  round(sum(heap_blks_hit) * 100.0 / NULLIF(sum(heap_blks_hit) + sum(heap_blks_read), 0), 2) AS hit_ratio
FROM pg_statio_user_tables;

-- 索引缓存命中率
SELECT 
  sum(idx_blks_read) AS idx_read,
  sum(idx_blks_hit) AS idx_hit,
  round(sum(idx_blks_hit) * 100.0 / NULLIF(sum(idx_blks_hit) + sum(idx_blks_read), 0), 2) AS hit_ratio
FROM pg_statio_user_indexes;

18.7 查看数据库统计

sql 复制代码
SELECT * FROM pg_stat_database WHERE datname = 'mydb';

19. 性能调优 Checklist

19.1 配置优化

postgresql.conf 关键参数:

conf 复制代码
# 内存设置(根据服务器内存调整)
shared_buffers = 4GB          # 建议:服务器内存的 25%
effective_cache_size = 12GB   # 建议:服务器内存的 50-75%
work_mem = 64MB               # 单个查询操作的内存
maintenance_work_mem = 512MB  # VACUUM, CREATE INDEX 的内存

# 连接数
max_connections = 100         # 根据实际需求,配合连接池

# WAL 设置
wal_buffers = 16MB
checkpoint_timeout = 10min
checkpoint_completion_target = 0.9

# 查询规划
random_page_cost = 1.1        # SSD 建议 1.1,HDD 建议 4
effective_io_concurrency = 200 # SSD 建议 200

# 日志
log_min_duration_statement = 1000  # 记录超过 1 秒的查询
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '

19.2 索引优化

检查缺失索引:

sql 复制代码
-- 查看全表扫描最多的表
SELECT 
  schemaname,
  tablename,
  seq_scan,
  seq_tup_read,
  idx_scan,
  seq_tup_read / seq_scan AS avg_seq_read
FROM pg_stat_user_tables
WHERE seq_scan > 0
ORDER BY seq_scan DESC
LIMIT 20;

检查未使用的索引:

sql 复制代码
SELECT 
  schemaname,
  tablename,
  indexname,
  idx_scan,
  pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
  AND indexrelname NOT LIKE '%_pkey'
ORDER BY pg_relation_size(indexrelid) DESC;

19.3 查询优化

常见问题和解决:

  1. N+1 查询 → 使用 JOIN 或 IN
  2. **SELECT *** → 只查询需要的列
  3. 没有 LIMIT → 添加合理的 LIMIT
  4. 函数包裹索引列 → 创建表达式索引
  5. OR 条件多 → 考虑 UNION ALL
  6. 子查询 → 考虑 JOIN 或 CTE

19.4 VACUUM 优化

sql 复制代码
-- 检查需要 VACUUM 的表
SELECT 
  schemaname,
  tablename,
  n_dead_tup,
  n_live_tup,
  round(n_dead_tup * 100.0 / NULLIF(n_live_tup, 0), 2) AS dead_ratio,
  last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;

19.5 分区表

适用场景:

  • 单表超过 100GB
  • 按时间范围查询(如日志表)
  • 需要快速删除旧数据

示例:按月分区

sql 复制代码
CREATE TABLE logs (
  id BIGSERIAL,
  log_time TIMESTAMPTZ NOT NULL,
  message TEXT
) PARTITION BY RANGE (log_time);

-- 创建分区
CREATE TABLE logs_2024_01 PARTITION OF logs
  FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

CREATE TABLE logs_2024_02 PARTITION OF logs
  FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');

-- 自动创建分区扩展:pg_partman

第五部分:迁移指南

20. 从 MySQL 迁移

20.1 使用 pgloader(推荐)

安装:

bash 复制代码
# macOS
brew install pgloader

# Ubuntu
sudo apt install pgloader

一键迁移:

bash 复制代码
pgloader mysql://root:password@localhost/mydb \
          postgresql://user:password@localhost/mydb

自定义迁移配置:

lisp 复制代码
LOAD DATABASE
  FROM mysql://root:password@localhost/mydb
  INTO postgresql://user:password@localhost/mydb

WITH include drop, create tables, create indexes, reset sequences

SET maintenance_work_mem to '512MB',
    work_mem to '128MB'

CAST type datetime to timestamptz
     drop default drop not null using zero-dates-to-null,
     type date drop not null drop default using zero-dates-to-null,
     type tinyint to boolean using tinyint-to-boolean;

20.2 手动迁移步骤

1. 导出 MySQL schema:

bash 复制代码
mysqldump -u root -p --no-data mydb > schema.sql

2. 转换 schema:

需要手动修改的地方:

sql 复制代码
-- AUTO_INCREMENT → SERIAL
-- MySQL
id BIGINT AUTO_INCREMENT PRIMARY KEY

-- PostgreSQL
id BIGSERIAL PRIMARY KEY

-- TINYINT(1) → BOOLEAN
is_active TINYINT(1)  →  is_active BOOLEAN

-- DATETIME → TIMESTAMPTZ
created_at DATETIME  →  created_at TIMESTAMPTZ

-- ENUM
status ENUM('active', 'inactive')
→
CREATE TYPE user_status AS ENUM ('active', 'inactive');
status user_status

-- 删除 ENGINE, CHARSET
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  →  删除

-- 索引语法
KEY idx_name (column)  →  CREATE INDEX idx_name ON table(column);

3. 导出数据:

bash 复制代码
mysqldump -u root -p --no-create-info --complete-insert mydb > data.sql

4. 导入 PostgreSQL:

bash 复制代码
psql -U postgres -d mydb < schema.sql
psql -U postgres -d mydb < data.sql

20.3 常见问题和解决

问题 1:自增 ID 不连续

sql 复制代码
-- 重置序列
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));

问题 2:字符串大小写

sql 复制代码
-- MySQL 默认不区分大小写
-- PostgreSQL 区分大小写

-- 解决:使用 ILIKE 或 LOWER
SELECT * FROM users WHERE name ILIKE 'alice';
SELECT * FROM users WHERE LOWER(name) = 'alice';

问题 3:排序规则(Collation)

sql 复制代码
-- MySQL 的 utf8mb4_unicode_ci → PostgreSQL 需要指定
CREATE COLLATION case_insensitive (
  provider = icu,
  locale = 'und-u-ks-level2',
  deterministic = false
);

-- 或在查询时使用
SELECT * FROM users ORDER BY name COLLATE "en_US";

20.4 迁移 Checklist

  • 备份 MySQL 数据
  • 转换数据类型
  • 转换 SQL 语法(LIMIT, IFNULL, REPLACE INTO)
  • 测试应用兼容性
  • 性能测试
  • 配置连接池
  • 配置 autovacuum
  • 配置备份策略
  • 配置监控

21. CDC/逻辑复制(对应 binlog)

21.1 逻辑复制基础

PostgreSQL 的逻辑复制(PG 10+)类似 MySQL 的主从复制。

架构:

  • 发布者(Publisher):主库
  • 订阅者(Subscriber):从库

21.2 配置逻辑复制

主库配置(postgresql.conf):

conf 复制代码
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10

创建发布:

sql 复制代码
-- 发布所有表
CREATE PUBLICATION my_publication FOR ALL TABLES;

-- 发布特定表
CREATE PUBLICATION my_publication FOR TABLE users, orders;

-- 查看发布
\dRp+

从库创建订阅:

sql 复制代码
-- 创建订阅(会自动开始同步)
CREATE SUBSCRIPTION my_subscription
CONNECTION 'host=master_host dbname=mydb user=replicator password=password'
PUBLICATION my_publication;

-- 查看订阅
\dRs+

-- 查看同步状态
SELECT * FROM pg_stat_subscription;

21.3 CDC 到消息队列(类似 Canal)

使用 Debezium:

Debezium 是一个开源 CDC 平台,支持 PostgreSQL → Kafka。

配置示例(Kafka Connect):

json 复制代码
{
  "name": "postgres-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "localhost",
    "database.port": "5432",
    "database.user": "postgres",
    "database.password": "password",
    "database.dbname": "mydb",
    "database.server.name": "dbserver1",
    "table.include.list": "public.users,public.orders",
    "plugin.name": "pgoutput"
  }
}

输出到 Kafka 的消息格式:

json 复制代码
{
  "before": null,
  "after": {
    "id": 123,
    "name": "Alice",
    "email": "alice@example.com"
  },
  "op": "c",  // c=create, u=update, d=delete
  "ts_ms": 1640000000000
}

21.4 WAL2JSON(输出 JSON 格式)

安装扩展:

bash 复制代码
# 需要安装 wal2json 插件
# 参考:https://github.com/eulerto/wal2json

配置:

conf 复制代码
# postgresql.conf
wal_level = logical
shared_preload_libraries = 'wal2json'

创建逻辑复制槽:

sql 复制代码
SELECT * FROM pg_create_logical_replication_slot('my_slot', 'wal2json');

读取变更:

sql 复制代码
SELECT * FROM pg_logical_slot_get_changes('my_slot', NULL, NULL);

21.5 对比 MySQL binlog/Canal

特性 MySQL (binlog + Canal) PostgreSQL (逻辑复制)
内置支持 ✓ (PG 10+)
输出格式 Row-based binlog WAL + logical decoding
第三方工具 Canal, Maxwell Debezium, wal2json
表级过滤
DDL 变更 ✓ (部分)
延迟 毫秒级 毫秒级

22. 常用扩展推荐

22.1 查看已安装扩展

sql 复制代码
-- 查看可用扩展
SELECT * FROM pg_available_extensions ORDER BY name;

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

22.2 必备扩展

pg_stat_statements(慢查询分析):

sql 复制代码
CREATE EXTENSION pg_stat_statements;

pgcrypto(加密函数):

sql 复制代码
CREATE EXTENSION pgcrypto;

-- 生成 UUID
SELECT gen_random_uuid();

-- 密码哈希
SELECT crypt('password', gen_salt('bf'));

uuid-ossp(UUID 生成):

sql 复制代码
CREATE EXTENSION "uuid-ossp";

SELECT uuid_generate_v4();

22.3 高级扩展

PostGIS(地理信息):

sql 复制代码
CREATE EXTENSION postgis;

-- 创建地理位置列
ALTER TABLE stores ADD COLUMN location GEOGRAPHY(POINT, 4326);

-- 插入坐标
UPDATE stores SET location = ST_GeogFromText('POINT(-122.4194 37.7749)') WHERE id = 1;

-- 查询附近的店铺(10km 内)
SELECT name, ST_Distance(location, ST_GeogFromText('POINT(-122.4 37.78)')) AS distance
FROM stores
WHERE ST_DWithin(location, ST_GeogFromText('POINT(-122.4 37.78)'), 10000)
ORDER BY distance;

TimescaleDB(时序数据):

sql 复制代码
CREATE EXTENSION timescaledb;

-- 创建时序表
CREATE TABLE metrics (
  time TIMESTAMPTZ NOT NULL,
  device_id INT,
  temperature DOUBLE PRECISION
);

SELECT create_hypertable('metrics', 'time');

pg_trgm(模糊搜索):

sql 复制代码
CREATE EXTENSION pg_trgm;

-- 相似度搜索
SELECT name, similarity(name, 'Alice') AS sim
FROM users
WHERE name % 'Alice'  -- % 操作符表示相似
ORDER BY sim DESC;

-- 创建 GIN 索引加速
CREATE INDEX idx_users_name_trgm ON users USING GIN (name gin_trgm_ops);

hstore(键值存储):

sql 复制代码
CREATE EXTENSION hstore;

CREATE TABLE products (
  id BIGSERIAL PRIMARY KEY,
  name TEXT,
  attributes HSTORE  -- 键值对
);

INSERT INTO products (name, attributes) VALUES
  ('Laptop', 'brand=>Dell, ram=>16GB, cpu=>i7');

-- 查询
SELECT * FROM products WHERE attributes->'brand' = 'Dell';
SELECT * FROM products WHERE attributes @> 'ram=>16GB';

pg_repack(在线 VACUUM FULL):

bash 复制代码
# 安装
brew install pg_repack

# 使用(不锁表)
pg_repack -d mydb -t users

22.4 扩展生态

  • citus:分布式 PostgreSQL
  • pg_partman:自动分区管理
  • pg_cron:数据库内定时任务
  • zombodb:Elasticsearch 集成
  • pgroonga:全文检索(支持中文)

附录:参考资源

官方文档

工具

社区

书籍推荐

  • 《PostgreSQL 即学即用》
  • 《PostgreSQL 修炼之道》
  • 《PostgreSQL 技术内幕:查询优化深度探索》

文档维护: 如有问题或建议,欢迎反馈!

相关推荐
TG:@yunlaoda360 云老大2 小时前
谷歌云数据库服务概览:关系型与 NoSQL 的多元选择与应用场景解析
数据库·nosql·googlecloud
hello_fracong2 小时前
PostgreSQL (零-1) Windows安装PostgreSQL
数据库·windows·postgresql
清空mega2 小时前
第五章《Android 数据存储》
数据库·android studio
q***33373 小时前
Redis简介、常用命令及优化
数据库·redis·缓存
油炸小波3 小时前
09-微服务原理篇(XXLJOB-幂等-MySQL)
android·mysql·微服务
武子康3 小时前
Java-168 Neo4j CQL 实战:WHERE、DELETE/DETACH、SET、排序与分页
java·开发语言·数据库·python·sql·nosql·neo4j
周杰伦_Jay3 小时前
【电商微服务日志处理全方案】从MySQL瓶颈到大数据架构的实战转型
大数据·mysql·微服务·架构
一 乐4 小时前
校园墙|校园社区|基于Java+vue的校园墙小程序系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·小程序