PostgreSQL 实战指南(面向 MySQL 开发者)
本文档面向有 MySQL 经验的开发者,采用对比的方式快速上手 PostgreSQL。
按照"快速上手 → 日常开发 → 进阶特性 → 生产实践"的路径组织。
目录
第一部分:快速上手
- 安装和工具
- 5 分钟快速开始
- 核心概念对比
第二部分:日常开发
4. 数据库和用户管理
5. 表和数据类型
6. SQL 语法差异
7. 索引和查询优化
8. 常用命令速查
第三部分:进阶特性
9. JSONB 和半结构化数据
10. UPSERT 和高级 DML
11. 高级 SQL 技巧
12. 触发器和存储过程
13. LISTEN/NOTIFY 通知机制
14. 其他增强特性
第四部分:生产实践
15. VACUUM 和表维护
16. 连接池和并发控制
17. 备份和恢复
18. 监控和诊断
19. 性能调优 Checklist
第五部分:迁移指南
20. 从 MySQL 迁移
21. CDC/逻辑复制(对应 binlog)
22. 常用扩展推荐
第一部分:快速上手
1. 安装和工具
1.1 macOS 推荐方式
方式一:Postgres.app(⭐ 最简单,推荐新手)
https://postgresapp.com/
- 图形化应用,开箱即用
- 包含多个 PostgreSQL 版本,可以一键切换
- 自带常用扩展(PostGIS、plv8 等)
- 菜单栏图标管理,启动/停止方便
安装后配置 PATH(可选,方便命令行使用):
# 添加到 ~/.zshrc 或 ~/.bash_profile
export PATH="/Applications/Postgres.app/Contents/Versions/latest/bin:$PATH"
方式二:Homebrew
brew install postgresql@16
brew services start postgresql@16
1.2 Linux 安装
Ubuntu/Debian:
sudo apt update
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql
CentOS/RHEL:
sudo yum install postgresql-server postgresql-contrib
sudo postgresql-setup initdb
sudo systemctl start postgresql
1.3 命令行工具
默认命令行工具是 psql。
推荐安装 pgcli,有自动补全和语法高亮:
# macOS
brew install pgcli# Linux
pip install pgcli
1.4 图形化工具
- pgAdmin:官方图形化工具(功能全面但较重)
- DBeaver:跨平台,支持多种数据库(推荐)
- DataGrip:JetBrains 出品(付费)
- Postico:macOS 专用,界面美观(付费)
2. 5 分钟快速开始
2.1 连接数据库
使用 psql 或 pgcli:
psql -h 127.0.0.1 -p 5432 -U postgres -d postgres
常用参数:
-h:主机(本机通常是127.0.0.1或留空)-p:端口(默认 5432)-U:用户名(默认安装后有超级用户postgres)-d:数据库名
本地快速连接:
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 快速上手示例
-- 创建数据库和用户
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 - 多数简单项目可以直接用默认
publicschema - 可以通过 schema 实现多租户隔离
类比示例:
- MySQL:
mydb.users - PostgreSQL:
mydb.public.users(通常简写为users,依赖search_path)
查看当前 schema:
SHOW search_path; -- 默认:"$user", public
3.2 用户 / 角色(Role)
关键差异:
- PostgreSQL 中是「角色」(role),带
LOGIN权限的 role 就是用户 - 一个 role 可以拥有数据库、表、序列等对象
- 和 MySQL
user@host不同,PostgreSQL 通常只写用户名,host 控制在pg_hba.conf中
-- 创建角色(可以理解为用户组)
CREATE ROLE readonly;-- 创建用户(带登录权限的角色)
CREATE ROLE app_user WITH LOGIN PASSWORD 'password';-- 角色继承
GRANT readonly TO app_user;
3.3 标识符大小写(⚠️ 重要陷阱)
没加双引号的表名、列名会自动转小写:
CREATE TABLE MyTable (UserId INT); -- 实际创建的是 mytable (userid)
SELECT * FROM MyTable; -- 可以查询(会转成小写)
SELECT * FROM "MyTable"; -- 报错:表不存在
最佳实践:
- 一律使用小写加下划线:
users,created_at,user_id - 避免必须写
"MyTable"这种带引号形式
3.4 自增主键
MySQL:
CREATE TABLE users (id BIGINT PRIMARY KEY AUTO_INCREMENT,...
);
PostgreSQL 推荐方式(两种):
-- 方式一: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 + SEQUENCEIDENTITY更现代,推荐使用
3.5 MVCC 机制(多版本并发控制)
关键差异:
- MySQL InnoDB:通过 undo log 实现 MVCC
- PostgreSQL:通过在表中保留旧版本数据实现
影响:
- PostgreSQL 的 UPDATE/DELETE 会产生"死元组"(dead tuple)
- 需要定期
VACUUM回收空间(见后文) - 性能特点:读不阻塞写,写不阻塞读
第二部分:日常开发
4. 数据库和用户管理
4.1 数据库操作
-- 创建数据库
CREATE DATABASE mydb OWNER myuser ENCODING 'UTF8';-- 删除数据库(需要没有活动连接)
DROP DATABASE mydb;-- 查看当前数据库
SELECT current_database();-- 查看所有数据库
\l
-- 或
SELECT datname FROM pg_database;
4.2 用户/角色管理
创建用户
-- 创建用户(带登录权限)
CREATE ROLE app_user WITH LOGIN PASSWORD 'secure_password';-- 创建用户并设置权限
CREATE ROLE app_user WITH LOGIN PASSWORD 'secure_password'CREATEDB -- 可以创建数据库VALID UNTIL '2025-12-31'; -- 密码过期时间
查看用户
-- psql 命令
\du-- SQL 查询
SELECT * FROM pg_user;
SELECT * FROM pg_roles;
修改密码
ALTER ROLE app_user WITH PASSWORD 'new_password';
删除用户
DROP ROLE app_user;
4.3 权限管理
数据库级权限
-- 授予数据库所有权限
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;
表级权限
-- 授予 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;
查看权限
-- 查看某用户的表权限
SELECT *
FROM information_schema.role_table_grants
WHERE grantee = 'app_user';-- 查看某表的权限
\dp table_name
5. 表和数据类型
5.1 建表示例(MySQL 风格迁移)
PostgreSQL 推荐写法:
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 写法:
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 | JSON 或 JSONB | ⭐ 推荐 JSONB(二进制,支持索引) |
ENUM('a','b') | 自定义 ENUM 类型 | 见下文 |
重要提示:
- 时间类型推荐用
TIMESTAMPTZ(带时区),数据库内部统一存储 UTC,应用层自动转换时区 - 布尔类型用
BOOLEAN,不要用0/1 - JSON 数据推荐用
JSONB,而不是JSON
5.3 ENUM 类型
MySQL 写法:
CREATE TABLE orders (id BIGINT PRIMARY KEY AUTO_INCREMENT,status ENUM('pending', 'paid', 'shipped', 'cancelled') NOT NULL DEFAULT 'pending'
);
PostgreSQL 写法(自定义类型):
-- 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'
);
修改枚举值:
-- 新增值
ALTER TYPE order_status ADD VALUE 'refunded';-- 重命名值(PG 10+)
ALTER TYPE order_status RENAME VALUE 'cancelled' TO 'canceled';
注意事项:
- ⚠️ 不能删除枚举值
- ⚠️ 不能直接修改顺序
- 如果枚举值频繁变化,考虑用
TEXT+CHECK约束,或单独建字典表
替代方案:
-- 方案一: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 特有)
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:
SELECT * FROM users ORDER BY id LIMIT 10, 20; -- 跳过 10 条,取 20 条
PostgreSQL:
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 10; -- 跳过 10 条,取 20 条
6.2 字符串拼接
MySQL:
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
PostgreSQL:
-- 方式一:|| 操作符(推荐)
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:
SELECT IFNULL(nickname, name) FROM users;
PostgreSQL:
-- 方式一: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()) |
示例:
-- 时间运算
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(相同):
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):
-- 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 (不区分) |
正则表达式示例:
-- 匹配邮箱格式
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 分组和聚合
基本相同,但注意:
-- 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 创建索引
-- 默认 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 查看索引
-- 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 = 0AND indexrelname NOT LIKE '%_pkey'
ORDER BY pg_relation_size(indexrelid) DESC;
7.4 EXPLAIN 分析查询计划
基本语法:
-- 查看执行计划
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';
输出解读:
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 查询优化技巧
使用索引
-- ❌ 不走索引:函数包裹列
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 *
-- ❌ 查询不需要的列
SELECT * FROM users WHERE id = 123;-- ✅ 只查询需要的列(可能走 Index Only Scan)
SELECT id, name, email FROM users WHERE id = 123;
使用 LIMIT
-- ✅ 限制返回行数
SELECT * FROM users ORDER BY created_at DESC LIMIT 100;
JOIN 优化
-- ✅ 确保 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;
使用部分索引
-- 如果经常查询 is_active = true 的数据
CREATE INDEX idx_active_users ON users(created_at) WHERE is_active = true;
定期更新统计信息
-- 手动更新统计信息
ANALYZE users;-- 或者全库
ANALYZE;
7.6 慢查询排查
-- 查看当前正在执行的慢查询
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
-- 查看当前数据库
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 基本操作
-- 创建表
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, -- 返回 JSONpayload->'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 索引
-- 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);
查询优化:
-- ✅ 走索引(使用 @> 操作符)
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 函数
-- 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:
REPLACE INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com');
PostgreSQL 的 ON CONFLICT(更灵活):
-- 冲突时更新
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 后返回数据:
INSERT INTO users (name, email)
VALUES ('Bob', 'bob@example.com')
RETURNING id, created_at;
-- 直接返回插入的 id 和 created_at,无需再查询
UPDATE 后返回数据:
UPDATE users
SET status = 'inactive'
WHERE last_login < now() - interval '1 year'
RETURNING id, name, email;
-- 返回所有被更新的行
DELETE 后返回数据:
DELETE FROM users
WHERE is_active = false
RETURNING *;
-- 返回所有被删除的行
在 CTE 中使用:
WITH deleted AS (DELETE FROM old_logsWHERE created_at < now() - interval '90 days'RETURNING *
)
SELECT count(*) FROM deleted; -- 统计删除了多少行
10.3 批量 UPSERT
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:
UPDATE orders o
JOIN users u ON o.user_id = u.id
SET o.user_name = u.name
WHERE u.is_active = true;
PostgreSQL:
UPDATE orders o
SET user_name = u.name
FROM users u
WHERE o.user_id = u.idAND u.is_active = true;
10.5 DELETE … USING(类似 MySQL 的 DELETE JOIN)
PostgreSQL:
DELETE FROM orders o
USING users u
WHERE o.user_id = u.idAND u.is_active = false;
11. 高级 SQL 技巧
11.1 CTE(WITH 语句)
基本 CTE:
WITH recent_orders AS (SELECT user_id, count(*) AS order_countFROM ordersWHERE 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:
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(查询树形结构)
查询组织架构树:
-- 假设有部门表
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 pathFROM departmentsWHERE parent_id IS NULLUNION ALL-- 递归查询:子节点SELECT d.id, d.name, d.parent_id, ot.level + 1, ot.path || ' > ' || d.nameFROM departments dJOIN org_tree ot ON d.parent_id = ot.id
)
SELECT * FROM org_tree ORDER BY path;
查询所有下级:
WITH RECURSIVE subordinates AS (SELECT id, name, manager_idFROM employeesWHERE id = 100 -- 起始员工 IDUNION ALLSELECT e.id, e.name, e.manager_idFROM employees eJOIN subordinates s ON e.manager_id = s.id
)
SELECT * FROM subordinates;
11.3 窗口函数(Window Functions)
排名函数:
-- 每个部门工资排名
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 rnFROM employees
) t
WHERE rn <= 3;
聚合函数作为窗口函数:
-- 计算部门平均工资,但保留所有行
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;
移动平均:
-- 7 天移动平均
SELECT date,amount,avg(amount) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma_7
FROM daily_sales;
累计和:
SELECT date,amount,sum(amount) OVER (ORDER BY date) AS cumulative_total
FROM daily_sales;
LAG 和 LEAD(访问前后行):
-- 计算环比增长
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 条订单:
SELECT u.name, o.*
FROM users u
LEFT JOIN LATERAL (SELECT * FROM ordersWHERE user_id = u.idORDER BY created_at DESCLIMIT 3
) o ON true;
等价于(但 LATERAL 更清晰):
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 rnFROM orders) tWHERE rn <= 3
) o ON o.user_id = u.id;
11.5 DISTINCT ON(PostgreSQL 特有)
每个用户的最新订单:
SELECT DISTINCT ON (user_id) *
FROM orders
ORDER BY user_id, created_at DESC;
注意:
DISTINCT ON的列必须是ORDER BY的前缀- 相当于
GROUP BY+MAX,但保留整行
11.6 数组聚合和展开
聚合为数组:
SELECT user_id, array_agg(order_id) AS orders
FROM orders
GROUP BY user_id;
展开数组:
SELECT unnest(ARRAY[1, 2, 3, 4, 5]);
结合使用:
-- 找出同时购买了多个产品的用户
WITH user_products AS (SELECT user_id, array_agg(product_id) AS productsFROM ordersGROUP 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 字段
-- 1. 创建触发器函数
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGINNEW.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();
示例:审计日志
-- 创建审计表
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 $$
BEGINIF TG_OP = 'DELETE' THENINSERT INTO audit_log (table_name, operation, old_data)VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD));RETURN OLD;ELSIF TG_OP = 'UPDATE' THENINSERT 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' THENINSERT 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 存储过程和函数
创建函数:
CREATE OR REPLACE FUNCTION get_user_order_count(p_user_id BIGINT)
RETURNS INTEGER AS $$
DECLAREv_count INTEGER;
BEGINSELECT count(*) INTO v_countFROM ordersWHERE user_id = p_user_id;RETURN v_count;
END;
$$ LANGUAGE plpgsql;-- 调用
SELECT get_user_order_count(123);
存储过程(PG 11+):
CREATE OR REPLACE PROCEDURE update_user_status(p_user_id BIGINT,p_status TEXT
)
LANGUAGE plpgsql
AS $$
BEGINUPDATE users SET status = p_status WHERE id = p_user_id;-- 可以包含事务控制COMMIT;
END;
$$;-- 调用
CALL update_user_status(123, 'inactive');
返回表的函数:
CREATE OR REPLACE FUNCTION get_top_users(p_limit INT DEFAULT 10)
RETURNS TABLE (user_id BIGINT,name VARCHAR,order_count BIGINT
) AS $$
BEGINRETURN QUERYSELECT u.id, u.name, count(o.id) AS cntFROM users uLEFT JOIN orders o ON o.user_id = u.idGROUP BY u.id, u.nameORDER BY cnt DESCLIMIT p_limit;
END;
$$ LANGUAGE plpgsql;-- 调用
SELECT * FROM get_top_users(5);
13. LISTEN/NOTIFY 通知机制
这是 PostgreSQL 独有的特性,可以在数据库连接之间做轻量级通知。
13.1 基本用法
会话 A(监听):
LISTEN channel_name;
会话 B(发送通知):
NOTIFY channel_name, 'some payload';
会话 A 会收到异步通知。
13.2 配合触发器做变更通知
示例:用户表变更通知
-- 创建通知函数
CREATE OR REPLACE FUNCTION notify_user_changed()
RETURNS TRIGGER AS $$
DECLAREpayload TEXT;
BEGINpayload := 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' THENRETURN OLD;ELSERETURN 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 示例(使用 psycopg2)
import psycopg2
import select
import jsonconn = 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)
基本使用:
-- 创建 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:
-- 安装扩展
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:
-- 启用行级安全
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);
不同操作的策略:
-- 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);
应用层设置参数:
-- 在连接开始时设置
SET app.current_user_id = 123;-- 或在事务中设置
BEGIN;
SET LOCAL app.current_user_id = 123;
SELECT * FROM orders; -- 只能看到 user_id = 123 的订单
COMMIT;
查看策略:
\d+ orders -- psql 命令-- 或 SQL 查询
SELECT * FROM pg_policies WHERE tablename = 'orders';
禁用 RLS(对超级用户和表所有者):
-- 超级用户默认绕过 RLS
-- 如果需要强制执行:
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
适用场景:
- SaaS 多租户隔离
- 数据权限控制
- 把部分权限逻辑下沉到数据库层
14.3 物化视图(Materialized View)
创建物化视图:
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);
刷新物化视图:
-- 普通刷新(会锁表)
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;
查询物化视图:
SELECT * FROM mv_user_stats WHERE order_count > 10;
删除物化视图:
DROP MATERIALIZED VIEW mv_user_stats;
适用场景:
- 复杂的报表查询
- 聚合结果缓存
- 跨表统计
和普通视图的区别:
- 普通视图:每次查询都重新计算(类似 MySQL)
- 物化视图:结果持久化,需要手动刷新
14.4 事务性 DDL
PostgreSQL 的 DDL 可以放在事务中回滚:
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)
内置范围类型:
-- 整数范围
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 索引:
CREATE INDEX idx_bookings_period ON bookings USING GIST (booking_period);
14.6 生成列(Generated Columns,PG 12+)
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 命令
-- 普通 VACUUM:清理死元组,不释放磁盘空间
VACUUM;
VACUUM users;-- VACUUM FULL:重建表,释放磁盘空间(⚠️ 锁表)
VACUUM FULL users;-- ANALYZE:更新统计信息(影响查询计划)
ANALYZE;
ANALYZE users;-- 一起做
VACUUM ANALYZE users;-- VERBOSE:显示详细信息
VACUUM VERBOSE users;
15.3 Autovacuum(自动清理)
检查是否启用:
SHOW autovacuum; -- 应该是 on
配置参数(postgresql.conf):
autovacuum = on # 启用自动清理
autovacuum_max_workers = 3 # 并行 worker 数量
autovacuum_naptime = 1min # 检查间隔# 触发条件
autovacuum_vacuum_threshold = 50 # 最少死元组数
autovacuum_vacuum_scale_factor = 0.2 # 死元组比例(20%)
单表调整:
-- 更激进的清理策略
ALTER TABLE large_table SET (autovacuum_vacuum_scale_factor = 0.05,autovacuum_vacuum_threshold = 1000
);
15.4 监控表膨胀
查看死元组情况:
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;
查看表膨胀率:
SELECTschemaname || '.' || 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 最佳实践
- 确保 autovacuum 开启(生产环境必须)
- 高频更新的表考虑降低阈值
- 定期检查死元组比例
- VACUUM FULL 谨慎使用(会锁表,考虑使用 pg_repack)
- 大表考虑分区
16. 连接池和并发控制
16.1 为什么需要连接池
关键差异:
- MySQL: 轻量级线程模型,支持较多连接
- PostgreSQL: 每个连接是独立进程,开销较大
查看连接数:
SHOW max_connections; -- 默认 100SELECT count(*) FROM pg_stat_activity; -- 当前连接数
16.2 PgBouncer(推荐)
三种模式:
-
session: 连接级复用(最安全,默认)
- 客户端连接断开后才复用
- 支持所有 PostgreSQL 特性
-
transaction: 事务级复用(⭐ 推荐)
- 事务结束后立即复用
- 性能好,适合大多数场景
- 不支持:LISTEN/NOTIFY, PREPARE, CURSOR
-
statement: 语句级复用
- 每条 SQL 后复用
- 限制最多,不推荐
安装和配置:
# macOS
brew install pgbouncer# Ubuntu
sudo apt install pgbouncer
配置文件 pgbouncer.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:
# 原来连接 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):
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
asyncpg (Python):
import asyncpgpool = await asyncpg.create_pool(host='localhost',database='mydb',user='app',password='password',min_size=5,max_size=20
)
16.4 事务隔离级别
PostgreSQL 支持的级别:
-- 查看当前隔离级别
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 锁机制
查看锁等待:
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.locktypeAND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.databaseAND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relationAND 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;
手动锁表:
-- 显式锁表(少用)
BEGIN;
LOCK TABLE users IN EXCLUSIVE MODE;
-- 其他操作
COMMIT;
17. 备份和恢复
17.1 逻辑备份(pg_dump)
导出单个数据库:
# 自定义格式(推荐)
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
导出特定表:
pg_dump -h localhost -U postgres -d mydb -t users -t orders -f tables.dump
导出 schema only(不含数据):
pg_dump -h localhost -U postgres -d mydb -s -f schema.sql
导出所有数据库:
pg_dumpall -h localhost -U postgres -f all_databases.sql
17.2 恢复
从自定义格式恢复:
# 创建数据库
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 文件恢复:
psql -h localhost -U postgres -d mydb < mydb.sql
17.3 物理备份(pg_basebackup)
# 基础备份
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):
wal_level = replica
archive_mode = on
archive_command = 'cp %p /archive/%f'
执行基础备份:
pg_basebackup -h localhost -U postgres -D /backup/base -Fp -Xs -P
恢复到特定时间点:
# 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 查看活动连接
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 查看长时间运行的查询
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 杀死查询
-- 取消查询(温和)
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)
安装扩展:
-- 需要修改 postgresql.conf
-- shared_preload_libraries = 'pg_stat_statements'
-- 重启数据库后CREATE EXTENSION pg_stat_statements;
查看最慢的查询:
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;
查看总耗时最多的查询:
SELECT calls,total_exec_time,mean_exec_time,query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
重置统计:
SELECT pg_stat_statements_reset();
18.5 查看表和索引大小
-- 最大的表
SELECTschemaname || '.' || 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;-- 最大的索引
SELECTschemaname || '.' || 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 缓存命中率
-- 表缓存命中率(应该 > 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 查看数据库统计
SELECT * FROM pg_stat_database WHERE datname = 'mydb';
19. 性能调优 Checklist
19.1 配置优化
postgresql.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 索引优化
检查缺失索引:
-- 查看全表扫描最多的表
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;
检查未使用的索引:
SELECT schemaname,tablename,indexname,idx_scan,pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0AND indexrelname NOT LIKE '%_pkey'
ORDER BY pg_relation_size(indexrelid) DESC;
19.3 查询优化
常见问题和解决:
- N+1 查询 → 使用 JOIN 或 IN
- **SELECT *** → 只查询需要的列
- 没有 LIMIT → 添加合理的 LIMIT
- 函数包裹索引列 → 创建表达式索引
- OR 条件多 → 考虑 UNION ALL
- 子查询 → 考虑 JOIN 或 CTE
19.4 VACUUM 优化
-- 检查需要 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
- 按时间范围查询(如日志表)
- 需要快速删除旧数据
示例:按月分区
CREATE TABLE logs (id BIGSERIAL,log_time TIMESTAMPTZ NOT NULL,message TEXT
) PARTITION BY RANGE (log_time);-- 创建分区
CREATE TABLE logs_2024_01 PARTITION OF logsFOR VALUES FROM ('2024-01-01') TO ('2024-02-01');CREATE TABLE logs_2024_02 PARTITION OF logsFOR VALUES FROM ('2024-02-01') TO ('2024-03-01');-- 自动创建分区扩展:pg_partman
第五部分:迁移指南
20. 从 MySQL 迁移
20.1 使用 pgloader(推荐)
安装:
# macOS
brew install pgloader# Ubuntu
sudo apt install pgloader
一键迁移:
pgloader mysql://root:password@localhost/mydb \postgresql://user:password@localhost/mydb
自定义迁移配置:
LOAD DATABASEFROM mysql://root:password@localhost/mydbINTO postgresql://user:password@localhost/mydbWITH include drop, create tables, create indexes, reset sequencesSET maintenance_work_mem to '512MB',work_mem to '128MB'CAST type datetime to timestamptzdrop 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:
mysqldump -u root -p --no-data mydb > schema.sql
2. 转换 schema:
需要手动修改的地方:
-- 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. 导出数据:
mysqldump -u root -p --no-create-info --complete-insert mydb > data.sql
4. 导入 PostgreSQL:
psql -U postgres -d mydb < schema.sql
psql -U postgres -d mydb < data.sql
20.3 常见问题和解决
问题 1:自增 ID 不连续
-- 重置序列
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));
问题 2:字符串大小写
-- MySQL 默认不区分大小写
-- PostgreSQL 区分大小写-- 解决:使用 ILIKE 或 LOWER
SELECT * FROM users WHERE name ILIKE 'alice';
SELECT * FROM users WHERE LOWER(name) = 'alice';
问题 3:排序规则(Collation)
-- 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):
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
创建发布:
-- 发布所有表
CREATE PUBLICATION my_publication FOR ALL TABLES;-- 发布特定表
CREATE PUBLICATION my_publication FOR TABLE users, orders;-- 查看发布
\dRp+
从库创建订阅:
-- 创建订阅(会自动开始同步)
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):
{"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 的消息格式:
{"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 格式)
安装扩展:
# 需要安装 wal2json 插件
# 参考:https://github.com/eulerto/wal2json
配置:
# postgresql.conf
wal_level = logical
shared_preload_libraries = 'wal2json'
创建逻辑复制槽:
SELECT * FROM pg_create_logical_replication_slot('my_slot', 'wal2json');
读取变更:
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 查看已安装扩展
-- 查看可用扩展
SELECT * FROM pg_available_extensions ORDER BY name;-- 查看已安装扩展
\dx
-- 或
SELECT * FROM pg_extension;
22.2 必备扩展
pg_stat_statements(慢查询分析):
CREATE EXTENSION pg_stat_statements;
pgcrypto(加密函数):
CREATE EXTENSION pgcrypto;-- 生成 UUID
SELECT gen_random_uuid();-- 密码哈希
SELECT crypt('password', gen_salt('bf'));
uuid-ossp(UUID 生成):
CREATE EXTENSION "uuid-ossp";SELECT uuid_generate_v4();
22.3 高级扩展
PostGIS(地理信息):
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(时序数据):
CREATE EXTENSION timescaledb;-- 创建时序表
CREATE TABLE metrics (time TIMESTAMPTZ NOT NULL,device_id INT,temperature DOUBLE PRECISION
);SELECT create_hypertable('metrics', 'time');
pg_trgm(模糊搜索):
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(键值存储):
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):
# 安装
brew install pg_repack# 使用(不锁表)
pg_repack -d mydb -t users
22.4 扩展生态
- citus:分布式 PostgreSQL
- pg_partman:自动分区管理
- pg_cron:数据库内定时任务
- zombodb:Elasticsearch 集成
- pgroonga:全文检索(支持中文)
附录:参考资源
官方文档
- 官方文档:https://www.postgresql.org/docs/
- 中文文档:http://www.postgres.cn/docs/
工具
- pgAdmin:https://www.pgadmin.org/
- DBeaver:https://dbeaver.io/
- Postgres.app:https://postgresapp.com/
- pgcli:https://www.pgcli.com/
社区
- Stack Overflow:
[postgresql]标签 - Reddit:r/PostgreSQL
- Slack:https://postgres-slack.herokuapp.com/
书籍推荐
- 《PostgreSQL 即学即用》
- 《PostgreSQL 修炼之道》
- 《PostgreSQL 技术内幕:查询优化深度探索》
文档维护: 如有问题或建议,欢迎反馈!
