實驗準備中…
實驗準備中…
本文档基于 PostgreSQL 18(2025 年 9 月 25 日正式发布)整理,涵盖从安装、基础操作到高级特性(全文搜索、空间数据库、向量数据库)的完整知识体系。文中所有命令均经过 PG 18 验证,并标注了关键术语的英文全称与中文释义,便于记忆与查阅。
PostgreSQL(常简称为 Postgres 或 pgsql)是一个功能强大的开源对象-关系型数据库管理系统(ORDBMS,Object-Relational Database Management System,对象关系型数据库管理系统)。它起源于 1986 年美国加州大学伯克利分校的 POSTGRES 项目,1996 年正式更名为 PostgreSQL 并采用 SQL 作为查询语言。经过近四十年的发展,PostgreSQL 以其卓越的稳定性、丰富的功能集、严格的 SQL 标准遵从性和极强的可扩展性,成为全球最受欢迎的开源数据库之一。
PostgreSQL 的核心设计哲学是"正确性优先"。它严格遵守 ACID(Atomicity, Consistency, Isolation, Durability,原子性、一致性、隔离性、持久性)事务特性,默认使用 MVCC(Multi-Version Concurrency Control,多版本并发控制)机制实现高并发读写,读操作不会阻塞写操作,写操作也不会阻塞读操作。这种设计使得 PostgreSQL 在金融、电信、医疗等对数据一致性要求极高的行业中拥有广泛的应用。
与 MySQL 相比,PostgreSQL 在复杂查询优化、窗口函数、CTE(Common Table Expression,公共表表达式)、物化视图、部分索引、表达式索引、丰富的数据类型(JSON/JSONB、数组、范围类型、几何类型、自定义类型)等方面具有明显优势。同时,PostgreSQL 通过扩展机制(Extension)支持全文搜索、空间数据(PostGIS)、向量检索(pgvector)等高级能力,使其能够在一个数据库中满足多种业务场景需求。
PostgreSQL 18 于 2025 年 9 月 25 日正式发布,是近年来最重要的版本之一。本节列出 PG 18 的关键新特性,后续章节会结合具体场景展开说明。
uuidv7() 函数,生成时间有序的 UUID(Universally Unique Identifier,通用唯一识别码),相比 UUIDv4 在 B-tree 索引插入性能上有显著提升。PostgreSQL 适合几乎所有需要关系型数据库的场景,尤其在以下领域表现突出:Web 应用后端主库、地理信息系统(GIS,Geographic Information System,地理信息系统)借助 PostGIS、AI 应用中的向量检索借助 pgvector、全文搜索引擎、数据仓库与 OLAP 分析、微服务架构中的服务独享数据库。其强大的扩展生态使得开发者可以在一个数据库内解决多种数据存储与查询需求,避免引入过多中间件。
PostgreSQL 官方推荐使用由 EDB(EnterpriseDB,企业级数据库公司)提供的图形化安装器(Graphical Installer)进行安装,该安装器覆盖 Windows、macOS 和 Linux(如 Fedora、CentOS、Ubuntu 等桌面版)平台。安装器集成了 PostgreSQL 服务器、pgAdmin(图形化管理工具)、StackBuilder(堆栈构建器,用于下载附加组件和驱动)等组件,适合初学者快速搭建本地学习或开发环境。
图形化安装器的主要优势在于"一键式"体验:用户无需手动配置环境变量、初始化数据目录、注册系统服务,安装向导会引导完成所有步骤。同时,安装过程中可以设置超级用户密码、监听端口、字符集(Locale,区域设置)等关键参数,避免后续手动修改配置文件。
以下以 Windows 11 为例,说明 EDB 安装器的典型流程,其他平台流程类似。
https://www.postgresql.org/download/windows/,点击"Download the installer"跳转到 EDB 的下载页,选择 PostgreSQL 18.x 的 Windows x86-64 版本下载。C:\Program Files\PostgreSQL\18),建议路径不含空格和中文。C:\Program Files\PostgreSQL\18\data),该目录将存放所有数据库文件,后续不可随意移动。postgres 的密码,请使用强密码并妥善保存,遗忘密码需要修改 pg_hba.conf(Host-Based Authentication Configuration,基于主机的认证配置文件)后重置。5432,如已被占用可改为 5433 等。Default locale 或 Chinese (Simplified)_China;若需要更好的 Unicode 排序支持,可选择 C 或 C.UTF-8。安装完成后,可以通过以下方式验证 PostgreSQL 是否正常运行。
postgresql-x64-18 服务处于"正在运行"状态。postgres 用户密码,连接到本地服务器。psql -U postgres -h localhost,输入密码后进入 psql 终端,执行 \version 查看版本,应显示 PostgreSQL 18.x。pgAdmin 是 PostgreSQL 官方推荐的功能最全的图形化管理工具,支持数据库对象浏览、SQL 编辑与执行、查询计划可视化、备份恢复、用户权限管理、性能仪表盘等功能。StackBuilder 是一个独立的模块下载器,可用于下载 ODBC(Open Database Connectivity,开放数据库互连)、JDBC(Java Database Connectivity,Java 数据库连接)、.NET 驱动、psqlODBC、pgJDBC 等附加组件,但在 PG 18 时代,更推荐直接使用各语言的官方包管理器(如 pip install psycopg、npm install pg、go get github.com/jackc/pgx)安装驱动。
Docker 是一种轻量级容器化技术,相比传统安装方式,使用 Docker 安装 PostgreSQL 有几个显著优势:环境隔离,可以在同一台机器上运行多个 PG 版本(如 16、17、18 并存)互不干扰;部署一致,开发、测试、生产环境使用相同的镜像,避免"在我机器上能跑"的问题;快速销毁重建,几秒钟即可重置整个数据库,非常适合学习与自动化测试;易于扩展,结合 docker-compose 可以一键启动 PG + pgAdmin + 应用 的完整技术栈。
PostgreSQL 官方在 Docker Hub 维护了官方镜像 postgres,PG 18 对应的标签为 18、18-alpine、18-bookworm 等。alpine 版本基于 Alpine Linux,体积小(约 80MB);bookworm 版本基于 Debian 12,兼容性更好。生产环境建议使用 18-bookworm,学习环境可用 18-alpine。
# 拉取 PostgreSQL 18 官方镜像(Debian 版本)
docker pull postgres:18-bookworm
# 拉取 Alpine 版本(更小)
docker pull postgres:18-alpine使用 docker run 启动一个 PostgreSQL 18 容器,关键参数说明如下。
--name pg18:为容器指定名称,便于后续管理。-e POSTGRES_PASSWORD=your_password:设置超级用户 postgres 的密码,必须设置,否则容器无法启动。-e POSTGRES_DB=mydb:可选,初始化时自动创建的数据库。-e POSTGRES_USER=myuser:可选,自定义超级用户名(默认 postgres)。-p 5432:5432:端口映射,宿主机端口:容器端口。-v pg18_data:/var/lib/postgresql/data:数据卷挂载,实现数据持久化。-d:后台运行。postgres:18-bookworm:使用的镜像。docker run -d \
--name pg18 \
-e POSTGRES_PASSWORD=your_password \
-e POSTGRES_DB=mydb \
-p 5432:5432 \
-v pg18_data:/var/lib/postgresql/data \
postgres:18-bookwormDocker 容器是临时的,删除容器后容器内的数据会丢失。为持久化数据,必须使用数据卷(Volume)或绑定挂载(Bind Mount)。上面的示例使用了命名卷 pg18_data,Docker 会将其存储在宿主机的 /var/lib/docker/volumes/pg18_data/_data 目录下。即使容器被删除,只要卷还在,数据就不会丢失。
绑定挂载则将宿主机指定目录映射到容器内,便于直接查看和备份数据文件:
docker run -d \
--name pg18 \
-e POSTGRES_PASSWORD=your_password \
-p 5432:5432 \
-v /home/z/pgdata:/var/lib/postgresql/data \
postgres:18-bookworm对于多容器场景(如 PG + pgAdmin),推荐使用 docker-compose 编排。下面是一个完整的 docker-compose.yml 示例,同时启动 PostgreSQL 18 和 pgAdmin 4。
version: "3.9"
services:
postgres:
image: postgres:18-bookworm
container_name: pg18
environment:
POSTGRES_PASSWORD: your_password
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- pg18_data:/var/lib/postgresql/data
restart: unless-stopped
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin_password
ports:
- "8080:80"
volumes:
- pgadmin_data:/var/lib/pgadmin
restart: unless-stopped
volumes:
pg18_data:
pgadmin_data:启动命令:docker compose up -d,停止并删除容器:docker compose down(数据卷保留),同时删除数据卷:docker compose down -v。
容器启动后,可以使用 docker exec 进入容器执行 psql 命令:
# 进入 psql 终端
docker exec -it pg18 psql -U postgres
# 执行单条 SQL
docker exec -it pg18 psql -U postgres -c "SELECT version();"PostgreSQL 的逻辑层级与 MySQL 有明显不同,理解这一层级结构是掌握 PG 的基础。从上到下,PG 的层级结构如下:
PGDATA)。一个集群包含多个数据库、共享的系统目录、配置文件和 WAL(Write-Ahead Logging,预写式日志)文件。postgres_fdw 等外部数据包装器)。public 模式。MySQL 的层级是"实例 → 数据库(即 schema)→ 表",数据库和模式在 MySQL 中是同义词。而 PostgreSQL 严格区分数据库和模式:一个 PG 数据库下可以有多个模式,模式之间可以相互访问(只要有权限),这使得 PG 在多租户、模块化设计上更加灵活。例如,可以将 users、orders、products 分别放在不同模式下,避免表名冲突,同时仍能在同一数据库内 JOIN 查询。
一个新初始化的 PostgreSQL 集群包含以下默认对象:
postgres(默认连接库)、template1(创建新数据库时的默认模板)、template0(不可修改的纯净模板,用于修复 template1 损坏的情况)。public:所有用户默认有 CREATE 权限(PG 15 之后默认撤销,需显式授权)。pg_catalog:存放所有系统表和视图,如 pg_class、pg_attribute、pg_database 等。information_schema:SQL 标准定义的元数据视图,跨数据库兼容。当 SQL 中引用一个未限定模式名的对象(如 SELECT * FROM users)时,PostgreSQL 会按 search_path(搜索路径)参数指定的模式顺序查找。默认值为 "$user", public,即先查找与当前用户同名的模式,再查找 public 模式。可以通过以下命令查看和修改:
-- 查看当前 search_path
SHOW search_path;
-- 临时修改(仅当前会话有效)
SET search_path TO my_schema, public;
-- 永久修改(针对角色)
ALTER ROLE myuser SET search_path TO my_schema, public;理解 search_path 对于多模式管理至关重要,建议在应用配置中显式指定模式名(如 my_schema.users),避免因 search_path 不同导致的歧义。
-- 基本语法
CREATE DATABASE db_name;
-- 完整语法(指定属主、模板、编码、排序规则、连接限制)
CREATE DATABASE mydb
WITH OWNER = myuser
TEMPLATE = template1
ENCODING = 'UTF8'
LC_COLLATE = 'en_US.UTF-8'
LC_CTYPE = 'en_US.UTF-8'
CONNECTION LIMIT = 100;参数说明:
template1。若需要复制已安装扩展的模板库,可指定自定义模板。UTF8。ORDER BY 的排序结果。-1 表示无限制。注意:CREATE DATABASE 不能在事务块中执行,也不能从 psql 的连接中创建当前已连接的数据库。通常先连接到 postgres 库再创建新库。
-- 重命名
ALTER DATABASE mydb RENAME TO newdb;
-- 修改属主
ALTER DATABASE mydb OWNER TO newuser;
-- 修改连接限制
ALTER DATABASE mydb CONNECTION LIMIT 50;
-- 修改配置参数(针对该数据库)
ALTER DATABASE mydb SET work_mem = '64MB';
ALTER DATABASE mydb SET timezone = 'Asia/Shanghai';-- 删除空数据库
DROP DATABASE mydb;
-- 强制删除(即使有连接,PG 13+)
DROP DATABASE mydb WITH (FORCE);WITH (FORCE) 会先终止所有到该数据库的连接,再执行删除,使用需谨慎。
-- 基本语法
CREATE SCHEMA my_schema;
-- 指定属主
CREATE SCHEMA my_schema AUTHORIZATION myuser;
-- 在创建模式的同时创建其中的对象
CREATE SCHEMA my_schema
CREATE TABLE t1 (id serial PRIMARY KEY)
CREATE TABLE t2 (id serial PRIMARY KEY);-- 重命名
ALTER SCHEMA my_schema RENAME TO new_schema;
-- 修改属主
ALTER SCHEMA my_schema OWNER TO newuser;-- 仅删除空模式
DROP SCHEMA my_schema;
-- 级联删除模式及其下所有对象
DROP SCHEMA my_schema CASCADE;CASCADE 会删除模式下的所有表、视图、函数等对象,操作不可逆,使用前务必确认。
PostgreSQL 15 起,默认撤销了所有用户在 public 模式上的 CREATE 权限,只保留 USAGE 权限。这是为了增强多用户环境下的安全性。如果需要恢复旧行为(允许任何用户在 public 模式建表),可执行:
GRANT ALL ON SCHEMA public TO PUBLIC;但生产环境强烈不建议这样做,推荐为每个应用或租户创建独立的模式。
-- 列出所有数据库
\l
-- 列出当前数据库下所有模式
\dn
-- 列出当前数据库下所有表
\dt
-- 列出指定模式下的表
\dt my_schema.*
-- 使用 SQL 查询所有数据库
SELECT datname FROM pg_database WHERE datistemplate = false;
-- 查询当前数据库下所有模式
SELECT schema_name FROM information_schema.schemata;
-- 查询某模式下的所有表
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'my_schema';CREATE TABLE 是 PostgreSQL 中最基础也最丰富的 DDL(Data Definition Language,数据定义语言)语句。一个完整的建表语句包含表名、列定义、约束、表选项等部分。
CREATE TABLE employees (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
email text UNIQUE,
salary numeric(10, 2) CHECK (salary > 0),
dept_id bigint REFERENCES departments(id),
created_at timestamptz DEFAULT now(),
metadata jsonb DEFAULT '{}'::jsonb
);PostgreSQL 10 起推荐使用 GENERATED ... AS IDENTITY 标识列替代传统的 SERIAL,它符合 SQL 标准,且更易于管理。
-- 推荐方式:标识列
CREATE TABLE t1 (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
-- GENERATED ALWAYS 表示不允许手动插入 id
-- GENERATED BY BY DEFAULT 表示手动插入时使用用户值,否则自动生成
name text
);
-- 旧方式:SERIAL(仍兼容,但不推荐新项目使用)
CREATE TABLE t2 (
id serial PRIMARY KEY,
name text
);ALTER TABLE 支持丰富的修改操作,包括添加/删除/修改列、添加/删除约束、重命名、修改属主、设置存储参数等。
-- 添加列
ALTER TABLE employees ADD COLUMN phone text;
-- 添加带默认值的列(PG 11+ 不锁表,瞬时完成)
ALTER TABLE employees ADD COLUMN status text DEFAULT 'active';
-- 删除列
ALTER TABLE employees DROP COLUMN phone;
-- 删除列并级联删除依赖对象
ALTER TABLE employees DROP COLUMN phone CASCADE;
-- 重命名列
ALTER TABLE employees RENAME COLUMN name TO full_name;
-- 修改列类型
ALTER TABLE employees ALTER COLUMN salary TYPE numeric(12, 2);
-- 设置/删除默认值
ALTER TABLE employees ALTER COLUMN status SET DEFAULT 'active';
ALTER TABLE employees ALTER COLUMN status DROP DEFAULT;
-- 设置/删除非空约束
ALTER TABLE employees ALTER COLUMN name SET NOT NULL;
ALTER TABLE employees ALTER COLUMN name DROP NOT NULL;
-- 添加约束
ALTER TABLE employees ADD CONSTRAINT chk_salary CHECK (salary > 0);
-- 删除约束
ALTER TABLE employees DROP CONSTRAINT employees_email_key;-- 删除表
DROP TABLE employees;
-- 级联删除依赖对象(如外键引用、视图)
DROP TABLE employees CASCADE;
-- 仅在存在时删除
DROP TABLE IF EXISTS employees;PostgreSQL 提供两种特殊表类型,适用于不同场景。
-- 临时表
CREATE TEMP TABLE temp_stats AS SELECT * FROM employees WHERE dept_id = 1;
-- UNLOGGED 表
CREATE UNLOGGED TABLE cache_data (
key text PRIMARY KEY,
value jsonb
);表空间(Tablespace)是 PG 中物理存储位置的抽象,允许将不同表存储在不同磁盘上以优化 I/O 性能。默认有两个表空间:pg_default(存储用户数据库的默认位置)和 pg_global(存储集群级系统表)。
-- 创建表空间
CREATE TABLESPACE fastspace LOCATION '/mnt/ssd/pgdata';
-- 建表时指定表空间
CREATE TABLE big_table (id bigint, data text) TABLESPACE fastspace;
-- 修改表的表空间
ALTER TABLE big_table SET TABLESPACE fastspace;PostgreSQL 支持表继承(Inheritance),这是其对象-关系特性的体现,也是声明式分区表的底层基础。子表继承父表的所有列,并可以添加自己的列。
CREATE TABLE cities (
name text,
population bigint,
altitude float
);
CREATE TABLE capitals (
state char(2)
) INHERITS (cities);现代应用中,分区表(Partitioned Table)更常用,详见后续章节。
-- 基本插入
INSERT INTO employees (name, email, salary, dept_id)
VALUES ('Alice', 'alice@example.com', 8000, 1);
-- 多行插入
INSERT INTO employees (name, email, salary, dept_id)
VALUES
('Bob', 'bob@example.com', 7500, 1),
('Carol', 'carol@example.com', 9000, 2),
('Dave', 'dave@example.com', 8500, 2);
-- 插入查询结果
INSERT INTO employees_archive
SELECT * FROM employees WHERE created_at < '2024-01-01';
-- 仅插入冲突时不报错(DO NOTHING)
INSERT INTO employees (id, name) VALUES (1, 'Alice')
ON CONFLICT DO NOTHING;
-- 冲突时更新(UPSERT)
INSERT INTO employees (id, name, salary)
VALUES (1, 'Alice', 9000)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
salary = EXCLUDED.salary;ON CONFLICT 子句是 PostgreSQL 实现 UPSERT(Update or Insert,更新或插入)的标准方式,EXCLUDED 是一个特殊表名,引用试图插入但被冲突拒绝的行。
-- 基本更新
UPDATE employees SET salary = 9500 WHERE name = 'Alice';
-- 多列更新
UPDATE employees SET salary = salary * 1.1, metadata = metadata || '{"raised": true}'::jsonb
WHERE dept_id = 1;
-- 基于子查询更新
UPDATE employees SET salary = sub.avg_salary
FROM (
SELECT dept_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept_id
) sub
WHERE employees.dept_id = sub.dept_id;
-- 基于另一个表更新
UPDATE employees e
SET salary = d.budget_salary
FROM departments d
WHERE e.dept_id = d.id;
-- RETURNING 返回更新后的行
UPDATE employees SET salary = salary * 1.1
WHERE dept_id = 1
RETURNING id, name, salary;RETURNING 子句是 PG 的特色功能,可以在 INSERT/UPDATE/DELETE 后返回受影响的行,避免再次 SELECT 查询,特别适合 ORM 和触发器场景。
-- 基本删除
DELETE FROM employees WHERE name = 'Alice';
-- 基于子查询删除
DELETE FROM employees
WHERE dept_id IN (
SELECT id FROM departments WHERE name = 'Sales'
);
-- 使用 USING 连接其他表
DELETE FROM employees e
USING departments d
WHERE e.dept_id = d.id AND d.name = 'Sales';
-- RETURNING 返回删除的行
DELETE FROM employees
WHERE created_at < '2023-01-01'
RETURNING id, name;TRUNCATE 用于快速清空整个表,相比 DELETE 不带 WHERE,TRUNCATE 不扫描表,速度极快,且会重置标识列。
-- 清空表
TRUNCATE TABLE employees;
-- 级联清空(同时清空外键引用的表)
TRUNCATE TABLE departments CASCADE;
-- 重启标识列
TRUNCATE TABLE employees RESTART IDENTITY;
-- 多表同时清空
TRUNCATE TABLE employees, departments;注意:TRUNCATE 是 DDL 操作,隐式提交,无法回滚;且会触发表级锁,影响并发。生产环境慎用。
PostgreSQL 15 起支持 SQL 标准的 MERGE 语句,可以在一条语句中根据条件执行 INSERT、UPDATE 或 DELETE,是数据同步场景的利器。
MERGE INTO employees AS e
USING new_employees AS n
ON e.id = n.id
WHEN MATCHED AND n.salary > e.salary THEN
UPDATE SET salary = n.salary
WHEN MATCHED AND n.deleted = true THEN
DELETE
WHEN NOT MATCHED THEN
INSERT (id, name, salary) VALUES (n.id, n.name, n.salary);COPY 是 PostgreSQL 高性能批量数据导入导出命令,比逐行 INSERT 快几十倍。
-- 导出为 CSV
COPY employees TO '/tmp/employees.csv' WITH CSV HEADER;
-- 从 CSV 导入
COPY employees (name, email, salary) FROM '/tmp/employees.csv' WITH CSV HEADER;
-- 客户端 COPY(\copy,使用客户端文件系统权限)
\copy employees TO '/tmp/employees.csv' WITH CSV HEADERPostgreSQL 拥有所有主流数据库中最丰富的数据类型,本节按类别介绍常用类型。
smallint / int2:2 字节整数,范围 到 。integer / int / int4:4 字节整数,范围 到 。bigint / int8:8 字节整数,范围 到 。decimal(p, s) / numeric(p, s):任意精度小数,p 为总位数,s 为小数位数,适合存储金额。real / float4:4 字节单精度浮点数。double precision / float8:8 字节双精度浮点数。serial / bigserial / smallserial:自增整数(旧语法,推荐用 GENERATED AS IDENTITY)。money:货币类型,8 字节,固定小数位数。character(n) / char(n) / bpchar(n):定长字符串,不足补空格。character varying(n) / varchar(n):变长字符串,有长度上限。text:变长字符串,无长度上限,PG 中 varchar 和 text 性能相同,推荐 text。date:日期,仅年月日。time / time without time zone:时间,仅时分秒。timetz / time with time zone:带时区时间。timestamp / timestamp without time zone:时间戳,无时区。timestamptz / timestamp with time zone:带时区时间戳,推荐用于存储时间。interval:时间间隔,如 INTERVAL '1 day'。boolean / bool:布尔值,true / false / NULL。CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE person (name text, current_mood mood);json:原始 JSON 文本,保留空格和键顺序,每次查询需重新解析。jsonb:二进制 JSON,解析后存储,查询快,支持索引,推荐使用。CREATE TABLE events (id bigint, data jsonb);
INSERT INTO events VALUES (1, '{"user": "alice", "tags": ["a", "b"]}'::jsonb);
-- 查询 JSON 字段
SELECT data->'user' FROM events; -- 返回 jsonb
SELECT data->>'user' FROM events; -- 返回 text
SELECT data->'tags'->0 FROM events; -- 返回 jsonb 数组第一个元素
-- JSONB 包含查询
SELECT * FROM events WHERE data @> '{"user": "alice"}';PostgreSQL 原生支持数组类型,任何基础类型都可以加 [] 变为数组。
CREATE TABLE posts (
id bigint,
tags text[],
scores integer[]
);
INSERT INTO posts VALUES (1, ARRAY['postgres', 'db'], ARRAY[90, 85]);
-- 数组查询
SELECT * FROM posts WHERE 'postgres' = ANY(tags);
SELECT * FROM posts WHERE tags && ARRAY['postgres']; -- 数组有交集uuid:UUID 类型,PG 18 内置 uuidv7() 函数。bytea:二进制数据(BLOB)。bit(n) / bit varying(n):定长/变长位串。cidr / inet / macaddr:网络地址类型。tsvector / tsquery:全文搜索类型。约束(Constraint)是保证数据完整性的核心机制,PostgreSQL 支持以下约束类型。
-- 列级
CREATE TABLE t1 (id bigint PRIMARY KEY, name text);
-- 表级(复合主键)
CREATE TABLE t2 (
a bigint,
b bigint,
PRIMARY KEY (a, b)
);CREATE TABLE orders (
id bigint PRIMARY KEY,
user_id bigint REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
total numeric(10, 2)
);外键的引用动作(Referential Action)选项:
ON DELETE NO ACTION:默认,删除被引用行时报错(可延迟检查)。ON DELETE RESTRICT:禁止删除被引用行(不可延迟)。ON DELETE CASCADE:级联删除引用行。ON DELETE SET NULL:将引用行的外键列设为 NULL。ON DELETE SET DEFAULT:将引用行的外键列设为默认值。CREATE TABLE users (
id bigint PRIMARY KEY,
email text UNIQUE,
username text,
CONSTRAINT uk_users_username UNIQUE (username)
);CREATE TABLE products (
id bigint PRIMARY KEY,
price numeric(10, 2) CHECK (price > 0),
stock integer CHECK (stock >= 0),
-- 多列检查
CONSTRAINT chk_price_stock CHECK (price > 0 AND stock >= 0)
);CREATE TABLE t1 (
name text NOT NULL,
email text -- 默认允许 NULL
);排除约束(EXCLUDE)是 PG 独有的高级约束,用于确保满足特定条件的行之间不存在某种关系,常用于防止时间区间重叠。
CREATE TABLE bookings (
id bigint PRIMARY KEY,
room_id integer,
during tstzrange,
EXCLUDE USING gist (room_id WITH =, during WITH &&)
);该约束确保同一房间的预订时间区间不重叠,使用 GiST 索引加速。
CREATE TABLE t1 (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
created_at timestamptz DEFAULT now(),
status text DEFAULT 'pending',
score integer DEFAULT 0
);PostgreSQL 18 对生成列做了重要增强,支持两种生成列类型。
-- PG 18 默认 VIRTUAL(不存储)
CREATE TABLE products (
id bigint PRIMARY KEY,
price numeric(10, 2),
quantity integer,
-- 虚拟生成列:读取时计算
total_price numeric GENERATED ALWAYS AS (price * quantity) VIRTUAL
);
-- 显式指定 STORED
CREATE TABLE products_stored (
id bigint PRIMARY KEY,
price numeric(10, 2),
quantity integer,
total_price numeric GENERATED ALWAYS AS (price * quantity) STORED
);生成列不能直接 INSERT 或 UPDATE,其值由表达式自动计算。VIRTUAL 生成列不能直接创建 B-tree 索引(PG 18 限制),但可以通过表达式索引间接索引。
PostgreSQL 支持为数据库对象添加注释,便于团队协作和文档化。
COMMENT ON TABLE employees IS '员工信息表';
COMMENT ON COLUMN employees.salary IS '员工月薪,单位元';
COMMENT ON DATABASE mydb IS '生产环境主库';SELECT 是 SQL 中最核心的语句,PostgreSQL 完全支持 SQL 标准的 SELECT 语法,并扩展了许多功能。基本语法结构如下:
SELECT [DISTINCT | DISTINCT ON (expr)] column_list
FROM table_name
[WHERE condition]
[GROUP BY column_list]
[HAVING condition]
[ORDER BY column_list [ASC | DESC]]
[LIMIT n] [OFFSET m]
[FETCH FIRST n ROWS ONLY];SQL 语句的执行顺序(逻辑顺序,非书写顺序)为:FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET。理解这一顺序对于编写复杂查询和别名使用至关重要——例如 WHERE 中不能使用 SELECT 中定义的列别名,因为 WHERE 先于 SELECT 执行。
-- 选择所有列
SELECT * FROM employees;
-- 选择指定列
SELECT name, salary FROM employees;
-- 列别名(AS 可省略)
SELECT name AS employee_name, salary * 12 AS annual_salary FROM employees;
-- 表达式
SELECT name, salary, salary * 1.1 AS new_salary FROM employees;
-- 字符串拼接(|| 操作符 或 concat 函数)
SELECT name || ' (' || email || ')' AS info FROM employees;
SELECT concat(name, ' (', email, ')') AS info FROM employees;WHERE 子句用于过滤行,支持丰富的运算符。
=, <>, !=, <, >, <=, >= 是基础比较运算符。PostgreSQL 还支持 IS DISTINCT FROM 和 IS NOT DISTINCT FROM,它们将 NULL 视为可比较的值,非常适合处理含 NULL 的列。
SELECT * FROM employees WHERE salary > 8000;
SELECT * FROM employees WHERE dept_id IS DISTINCT FROM 1; -- 包含 NULL 情况AND、OR、NOT 用于组合条件,建议使用括号明确优先级。
SELECT * FROM employees
WHERE (dept_id = 1 OR dept_id = 2) AND salary > 8000;BETWEEN x AND y:闭区间 [x, y]。NOT BETWEEN x AND y:不在闭区间内。IN (v1, v2, ...):在指定集合中。NOT IN (v1, v2, ...):不在指定集合中(注意 NULL 陷阱,建议用 NOT EXISTS 或 <> ALL)。SELECT * FROM employees WHERE salary BETWEEN 5000 AND 10000;
SELECT * FROM employees WHERE dept_id IN (1, 2, 3);LIKE:简单模式匹配,% 匹配任意长度字符,_ 匹配单个字符,区分大小写。ILIKE:不区分大小写的 LIKE。SIMILAR TO:SQL 正则表达式(介于 LIKE 和 POSIX 正则之间)。~、~*、!~、!~*:POSIX 正则表达式,* 表示不区分大小写,! 表示否定。SELECT * FROM employees WHERE name LIKE 'A%'; -- 以 A 开头
SELECT * FROM employees WHERE name ILIKE 'a%'; -- 不区分大小写
SELECT * FROM employees WHERE name ~ '^A[a-z]+$'; -- POSIX 正则
SELECT * FROM employees WHERE email ~* '@example\.com$';SELECT * FROM employees WHERE email IS NULL;
SELECT * FROM employees WHERE email IS NOT NULL;
SELECT * FROM employees WHERE email IS [NOT] DISTINCT FROM NULL;-- 升序(默认)
SELECT * FROM employees ORDER BY salary;
-- 降序
SELECT * FROM employees ORDER BY salary DESC;
-- 多列排序
SELECT * FROM employees ORDER BY dept_id ASC, salary DESC;
-- 使用表达式排序
SELECT * FROM employees ORDER BY salary * 12 DESC;
-- NULL 处理
SELECT * FROM employees ORDER BY email DESC NULLS LAST; -- NULL 排在最后
SELECT * FROM employees ORDER BY email ASC NULLS FIRST; -- NULL 排在最前PostgreSQL 默认 NULL 排序规则:升序时 NULL 在最后,降序时 NULL 在最前。可通过 NULLS FIRST / NULLS LAST 显式控制。
-- 取前 10 条
SELECT * FROM employees LIMIT 10;
-- 跳过 20 条,取 10 条(分页)
SELECT * FROM employees ORDER BY id LIMIT 10 OFFSET 20;
-- SQL 标准语法(PG 18 推荐)
SELECT * FROM employees ORDER BY id
OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY;FETCH FIRST n ROWS ONLY 是 SQL 标准语法,与 LIMIT n 等价,但更标准。注意:分页查询必须配合 ORDER BY,否则结果顺序不保证。
-- 基本去重
SELECT DISTINCT dept_id FROM employees;
-- 多列去重
SELECT DISTINCT dept_id, status FROM employees;
-- DISTINCT ON:每组取第一行(PG 特色)
SELECT DISTINCT ON (dept_id) dept_id, name, salary
FROM employees
ORDER BY dept_id, salary DESC;DISTINCT ON (expr) 是 PostgreSQL 独有的强大功能,类似于"分组取每组的第一行",必须配合 ORDER BY 使用,ORDER BY 的第一个字段必须与 DISTINCT ON 的字段一致。
CASE 表达式用于条件逻辑,相当于其他语言的 if-else。
-- 简单 CASE
SELECT name,
CASE dept_id
WHEN 1 THEN 'Engineering'
WHEN 2 THEN 'Sales'
ELSE 'Other'
END AS dept_name
FROM employees;
-- 搜索 CASE
SELECT name, salary,
CASE
WHEN salary >= 10000 THEN 'High'
WHEN salary >= 5000 THEN 'Medium'
ELSE 'Low'
END AS salary_level
FROM employees;聚合函数(Aggregate Function)对一组行计算返回单个值,是数据分析的基础。
COUNT(*):统计行数(含 NULL)。COUNT(expr):统计 expr 非 NULL 的行数。COUNT(DISTINCT expr):统计 expr 不同值的数量。SUM(expr):求和,忽略 NULL。AVG(expr):平均值,忽略 NULL。MIN(expr) / MAX(expr):最小值 / 最大值。STRING_AGG(expr, delimiter):字符串聚合(PG 特色)。ARRAY_AGG(expr):数组聚合(PG 特色)。BOOL_AND(expr) / BOOL_OR(expr):布尔聚合。stddev、variance、percentile_cont、mode 等。SELECT
COUNT(*) AS total_count,
COUNT(email) AS email_count,
AVG(salary) AS avg_salary,
MIN(salary) AS min_salary,
MAX(salary) AS max_salary,
SUM(salary) AS total_salary,
STRING_AGG(name, ', ' ORDER BY name) AS all_names,
ARRAY_AGG(name ORDER BY salary DESC) AS name_array
FROM employees;GROUP BY 按指定列分组,对每组应用聚合函数。
-- 按部门分组统计
SELECT dept_id,
COUNT(*) AS emp_count,
AVG(salary) AS avg_salary
FROM employees
GROUP BY dept_id
ORDER BY avg_salary DESC;
-- 多列分组
SELECT dept_id, status,
COUNT(*) AS cnt
FROM employees
GROUP BY dept_id, status;
-- GROUP BY 与表达式
SELECT DATE_TRUNC('month', created_at) AS month,
COUNT(*) AS new_hires
FROM employees
GROUP BY month
ORDER BY month;PostgreSQL 18 支持在 GROUP BY 中使用列别名(如上例的 month),这是 PG 的扩展特性,标准 SQL 要求使用完整表达式。
HAVING 用于过滤分组后的结果,类似于 WHERE 但作用于聚合后的行。WHERE 过滤行,HAVING 过滤组。
SELECT dept_id, COUNT(*) AS cnt, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id
HAVING COUNT(*) > 5 AND AVG(salary) > 8000
ORDER BY avg_sal DESC;PostgreSQL 支持多维分组聚合,用于生成交叉报表。
GROUPING SETS:指定多个分组维度组合。ROLLUP:层级汇总,从最细到最粗。CUBE:所有维度组合的笛卡尔积。-- ROLLUP:先按 (dept, status) 分组,再按 dept,最后总计
SELECT dept_id, status, COUNT(*) AS cnt
FROM employees
GROUP BY ROLLUP (dept_id, status);
-- CUBE:所有维度组合
SELECT dept_id, status, COUNT(*) AS cnt
FROM employees
GROUP BY CUBE (dept_id, status);
-- GROUPING SETS:自定义维度组合
SELECT dept_id, status, COUNT(*) AS cnt
FROM employees
GROUP BY GROUPING SETS ((dept_id, status), (dept_id), ());窗口函数(Window Function)是 PostgreSQL 最强大的分析功能之一,它在不聚合行的前提下,对一组行(窗口)计算结果。窗口函数语法为 function() OVER (PARTITION BY ... ORDER BY ...)。
-- 排名函数
SELECT name, dept_id, salary,
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn,
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rank,
DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dense_rank,
PERCENT_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS pct_rank,
NTILE(4) OVER (PARTITION BY dept_id ORDER BY salary DESC) AS quartile
FROM employees;排名函数区别:
ROW_NUMBER():唯一连续编号,1, 2, 3, 4。RANK():相同值同排名,后续跳号,1, 2, 2, 4。DENSE_RANK():相同值同排名,后续不跳号,1, 2, 2, 3。-- 累计求和
SELECT name, salary,
SUM(salary) OVER (ORDER BY id) AS running_total,
AVG(salary) OVER (ORDER BY id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM employees;
-- 部门内排名与占比
SELECT name, dept_id, salary,
salary * 1.0 / SUM(salary) OVER (PARTITION BY dept_id) AS salary_pct
FROM employees;LAG(expr, n, default):取当前行之前第 n 行的值。LEAD(expr, n, default):取当前行之后第 n 行的值。FIRST_VALUE(expr) / LAST_VALUE(expr):窗口内第一个/最后一个值。NTH_VALUE(expr, n):窗口内第 n 个值。-- 与上一行比较
SELECT name, salary,
LAG(salary, 1, 0) OVER (ORDER BY id) AS prev_salary,
salary - LAG(salary, 1, 0) OVER (ORDER BY id) AS diff
FROM employees;窗口帧定义窗口函数作用的具体行范围,语法为 ROWS | RANGE BETWEEN start AND end,其中 start 和 end 可以是 UNBOUNDED PRECEDING、n PRECEDING、CURRENT ROW、n FOLLOWING、UNBOUNDED FOLLOWING。
-- 计算移动平均(当前行及前两行)
SELECT name, salary,
AVG(salary) OVER (
ORDER BY id
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3
FROM employees;子查询(Subquery)是嵌套在另一个查询中的查询,分为标量子查询、行子查询、表子查询。
-- 标量子查询(返回单值)
SELECT name, salary,
(SELECT AVG(salary) FROM employees) AS company_avg
FROM employees;
-- 表子查询(在 FROM 中)
SELECT dept_id, avg_sal
FROM (
SELECT dept_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id
) sub
WHERE avg_sal > 8000;
-- EXISTS / NOT EXISTS
SELECT name FROM employees e
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = e.id
);
-- ANY / ALL
SELECT * FROM employees
WHERE salary > ALL (SELECT salary FROM employees WHERE dept_id = 1);LATERAL 关键字允许子查询引用前面 FROM 项的列,类似于循环嵌套。
-- 取每个部门薪资最高的 3 名员工
SELECT d.name AS dept, e.name, e.salary
FROM departments d,
LATERAL (
SELECT name, salary
FROM employees
WHERE dept_id = d.id
ORDER BY salary DESC
LIMIT 3
) e;-- 长度
SELECT length('hello'); -- 5(字符数)
SELECT char_length('hello'); -- 5(字符数)
SELECT octet_length('你好'); -- 6(字节数,UTF-8 中文 3 字节)
-- 大小写转换
SELECT upper('hello'); -- 'HELLO'
SELECT lower('HELLO'); -- 'hello'
SELECT initcap('hello world'); -- 'Hello World'
-- 拼接
SELECT 'a' || 'b'; -- 'ab'
SELECT concat('a', 'b', 'c'); -- 'abc'(忽略 NULL)
SELECT concat_ws(',', 'a', 'b'); -- 'a,b'(带分隔符)
-- 截取
SELECT substring('hello' FROM 2 FOR 3); -- 'ell'
SELECT substr('hello', 2, 3); -- 'ell'
SELECT left('hello', 3); -- 'hel'
SELECT right('hello', 3); -- 'llo'
-- 查找与替换
SELECT position('l' IN 'hello'); -- 3
SELECT strpos('hello', 'l'); -- 3
SELECT replace('hello', 'l', 'L'); -- 'heLLo'
SELECT translate('hello', 'el', 'ip'); -- 'hippo'
-- 填充
SELECT lpad('5', 3, '0'); -- '005'
SELECT rpad('5', 3, '0'); -- '500'
-- 去除空白
SELECT trim(' hello '); -- 'hello'
SELECT ltrim(' hello'); -- 'hello'
SELECT rtrim('hello '); -- 'hello'
-- 分割
SELECT split_part('a,b,c', ',', 2); -- 'b'
SELECT string_to_array('a,b,c', ','); -- {a,b,c}
SELECT array_to_string(ARRAY[1,2,3], '-'); -- '1-2-3'
-- 正则
SELECT regexp_replace('Hello World', '[A-Z]', '*', 'g'); -- '*ello *orld'
SELECT regexp_matches('abc123', '([a-z]+)([0-9]+)'); -- {abc,123}
SELECT regexp_split_to_array('a,b,,c', ','); -- {a,b,"",c}-- 取整
SELECT ceil(3.2); -- 4(向上取整)
SELECT floor(3.8); -- 3(向下取整)
SELECT round(3.5); -- 4(四舍五入)
SELECT round(3.567, 2); -- 3.57
SELECT trunc(3.9); -- 3(截断小数)
SELECT trunc(3.567, 1); -- 3.5
-- 数学
SELECT abs(-5); -- 5(绝对值)
SELECT power(2, 10); -- 1024(幂)
SELECT sqrt(16); -- 4(平方根)
SELECT cbrt(27); -- 3(立方根)
SELECT exp(1); -- 2.7182818284590452(e 的幂)
SELECT ln(10); -- 自然对数
SELECT log(100); -- 常用对数(底为 10)
SELECT log(2, 8); -- 3(指定底)
-- 三角函数(弧度)
SELECT sin(0), cos(0), tan(0);
SELECT asin(1), acos(0), atan(1);
SELECT radians(180); -- 3.141592653589793(角度转弧度)
SELECT degrees(3.141592653589793); -- 180(弧度转角度)
-- 随机
SELECT random(); -- 0 到 1 之间随机数
SELECT setseed(0.5); -- 设置随机种子
-- 取最大最小
SELECT greatest(1, 2, 3); -- 3
SELECT least(1, 2, 3); -- 1-- 当前时间
SELECT now(); -- 当前时间戳(带时区)
SELECT current_timestamp; -- 同 now()
SELECT current_date; -- 当前日期
SELECT current_time; -- 当前时间
SELECT clock_timestamp(); -- 实时时间戳(每次调用都不同)
SELECT statement_timestamp(); -- 语句开始时间
SELECT transaction_timestamp(); -- 事务开始时间(同 now())
-- 提取
SELECT extract(year FROM now()); -- 提取年份
SELECT extract(month FROM now());
SELECT extract(dow FROM now()); -- day of week(0=周日)
SELECT date_part('year', now()); -- 同 extract
SELECT date_trunc('month', now()); -- 截断到月初
-- 加减
SELECT now() + INTERVAL '1 day';
SELECT now() - INTERVAL '2 hours';
SELECT now() + INTERVAL '1 year 3 months';
-- 计算
SELECT age('2024-01-01', '2020-01-01'); -- 4 years
SELECT age(timestamp '2000-01-01'); -- 距今多久
-- 格式化
SELECT to_char(now(), 'YYYY-MM-DD HH24:MI:SS');
SELECT to_char(now(), 'YYYY"年"MM"月"DD"日"');
SELECT to_date('2024-01-15', 'YYYY-MM-DD');
SELECT to_timestamp('2024-01-15 10:30:00', 'YYYY-MM-DD HH24:MI:SS');常用日期格式模板:YYYY(4 位年)、MM(月)、DD(日)、HH24(24 小时制时)、MI(分)、SS(秒)、Day(星期全名)、Month(月份全名)。
PostgreSQL 提供三种类型转换方式。
-- CAST 标准语法
SELECT CAST('123' AS integer);
SELECT CAST(now() AS date);
-- :: 简写语法(PG 特色)
SELECT '123'::integer;
SELECT '2024-01-01'::date;
SELECT 3.14::numeric(10, 2);
-- CONVERT 风格(不推荐,仅用于编码转换)
SELECT convert('hello', 'UTF8', 'LATIN1');-- COALESCE:返回第一个非 NULL 值
SELECT COALESCE(email, phone, 'no contact') FROM employees;
-- NULLIF:两值相等返回 NULL,否则返回第一个
SELECT NULLIF(score, 0); -- score 为 0 时返回 NULL,避免除零
-- GREATEST / LEAST 忽略 NULL
SELECT GREATEST(1, NULL, 3); -- 3
-- ISNULL / NOTNULL(PG 扩展)
SELECT * FROM employees WHERE email ISNULL;-- 提取
SELECT data->'name' FROM t; -- jsonb
SELECT data->>'name' FROM t; -- text
SELECT data#>'{a,b}' FROM t; -- 路径提取 jsonb
SELECT data#>>'{a,b}' FROM t; -- 路径提取 text
-- 包含
SELECT data @> '{"name": "alice"}' FROM t;
SELECT data ? 'name' FROM t; -- 是否存在键
SELECT data ?| ARRAY['a','b']; -- 任一键存在
SELECT data ?& ARRAY['a','b']; -- 所有键存在
-- 修改
SELECT jsonb_set(data, '{name}', '"bob"');
SELECT data || '{"age": 30}'::jsonb; -- 合并
SELECT data - 'name'; -- 删除键
-- 转换
SELECT jsonb_array_elements(data->'tags'); -- 展开数组
SELECT jsonb_each(data); -- 展开对象为 (key, value)
SELECT jsonb_object_keys(data); -- 获取所有键-- 构造
SELECT ARRAY[1, 2, 3];
SELECT array_agg(name) FROM employees;
-- 访问
SELECT arr[1]; -- 第一个元素(PG 数组下标从 1 开始)
SELECT array_length(arr, 1); -- 数组长度
-- 查找
SELECT 2 = ANY(arr); -- 是否包含
SELECT array_position(arr, 2); -- 元素位置
-- 操作
SELECT array_append(arr, 4);
SELECT array_prepend(0, arr);
SELECT array_cat(arr1, arr2); -- 拼接
SELECT array_remove(arr, 2); -- 删除所有等于 2 的元素
SELECT unnest(arr); -- 展开为多行一对一关系(One-to-One Relationship)指两张表中的行存在一一对应关系,即表 A 的每一行最多对应表 B 的一行,反之亦然。在实际设计中,一对一关系通常用于以下场景:将一张大表拆分为多张小表以提升查询效率(如将常用字段和不常用字段分离);将敏感字段单独存放以加强权限控制;实现表继承(如 persons 表与 employees、customers 子表)。
一对一关系通过"外键 + 唯一约束"实现:在从表中定义一个外键引用主表主键,并对外键列添加 UNIQUE 约束,确保从表的外键列值不重复,从而保证一一对应。
-- 主表:用户基本信息
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username text NOT NULL UNIQUE,
email text NOT NULL UNIQUE,
created_at timestamptz DEFAULT now()
);
-- 从表:用户详情(一对一)
CREATE TABLE user_profiles (
user_id bigint PRIMARY KEY, -- 主键即外键,天然唯一
full_name text,
avatar_url text,
bio text,
birthday date,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);上述设计中,user_profiles.user_id 既是主键又是外键,作为主键保证唯一性(一对一),作为外键保证引用完整性。也可以单独定义外键列并加 UNIQUE 约束,效果相同。
-- INNER JOIN:仅返回有详情的用户
SELECT u.id, u.username, u.email, p.full_name, p.avatar_url
FROM users u
INNER JOIN user_profiles p ON u.id = p.user_id;
-- LEFT JOIN:返回所有用户,无详情的为 NULL
SELECT u.id, u.username, p.full_name
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;一对多关系(One-to-Many Relationship)是最常见的关系类型,指表 A 的一行可以对应表 B 的多行,但表 B 的一行只能对应表 A 的一行。例如:一个部门有多个员工、一个用户有多个订单、一个分类下有多篇文章。
一对多关系通过在"多"的一方添加外键引用"一"的一方主键实现。
-- 一的一方:部门
CREATE TABLE departments (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL UNIQUE,
location text,
created_at timestamptz DEFAULT now()
);
-- 多的一方:员工
CREATE TABLE employees (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
salary numeric(10, 2) CHECK (salary > 0),
dept_id bigint, -- 外键列
created_at timestamptz DEFAULT now(),
FOREIGN KEY (dept_id) REFERENCES departments(id)
ON DELETE SET NULL
ON UPDATE CASCADE
);外键的引用动作选择:
ON DELETE SET NULL:部门被删除时,员工的 dept_id 设为 NULL(员工保留)。ON DELETE CASCADE:部门被删除时,级联删除所有员工(慎用)。ON DELETE RESTRICT:禁止删除有员工的部门(默认行为,最安全)。-- 查询每个员工及其部门
SELECT e.name, e.salary, d.name AS dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.id;
-- 查询每个部门的员工数量
SELECT d.name AS dept, COUNT(e.id) AS emp_count
FROM departments d
LEFT JOIN employees e ON e.dept_id = d.id
GROUP BY d.id, d.name
ORDER BY emp_count DESC;
-- 查询员工数大于 5 的部门
SELECT d.name, COUNT(e.id) AS cnt
FROM departments d
JOIN employees e ON e.dept_id = d.id
GROUP BY d.id, d.name
HAVING COUNT(e.id) > 5;PostgreSQL 支持所有标准 JOIN 类型:
INNER JOIN / JOIN:仅返回两表匹配的行。LEFT [OUTER] JOIN:返回左表所有行,右表无匹配则为 NULL。RIGHT [OUTER] JOIN:返回右表所有行,左表无匹配则为 NULL。FULL [OUTER] JOIN:返回两表所有行,无匹配处为 NULL。CROSS JOIN:笛卡尔积,左表每行与右表每行组合。NATURAL JOIN:自动按同名列连接(不推荐,易出错)。-- CROSS JOIN 示例:生成所有组合
SELECT d.name AS dept, p.name AS project
FROM departments d
CROSS JOIN projects p;多对多关系(Many-to-Many Relationship)指表 A 的一行可以对应表 B 的多行,反之亦然。例如:学生与课程(一个学生选多门课,一门课有多名学生)、文章与标签、用户与角色。关系型数据库无法直接表达多对多关系,必须通过中间表(Junction Table / Associative Table / 关联表)拆解为两个一对多关系。
-- 学生表
CREATE TABLE students (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
student_no text UNIQUE NOT NULL
);
-- 课程表
CREATE TABLE courses (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
credit numeric(3, 1) CHECK (credit > 0)
);
-- 中间表:选课记录
CREATE TABLE enrollments (
student_id bigint NOT NULL,
course_id bigint NOT NULL,
enrolled_at timestamptz DEFAULT now(),
score numeric(5, 2),
PRIMARY KEY (student_id, course_id), -- 复合主键防止重复选课
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE
);
-- 为反向查询创建索引
CREATE INDEX idx_enrollments_course ON enrollments(course_id);中间表的设计要点:
(student_id, course_id) 确保同一学生不会重复选同一课程。ON DELETE CASCADE,删除学生或课程时自动清理选课记录。enrolled_at 选课时间、score 成绩)。-- 查询每个学生选了哪些课
SELECT s.name AS student, c.name AS course, e.score
FROM students s
JOIN enrollments e ON e.student_id = s.id
JOIN courses c ON e.course_id = c.id
ORDER BY s.name, c.name;
-- 查询每门课的选课人数
SELECT c.name AS course, COUNT(e.student_id) AS student_count
FROM courses c
LEFT JOIN enrollments e ON e.course_id = c.id
GROUP BY c.id, c.name
ORDER BY student_count DESC;
-- 查询选了"数据库原理"课程的学生
SELECT s.name
FROM students s
JOIN enrollments e ON e.student_id = s.id
JOIN courses c ON e.course_id = c.id
WHERE c.name = '数据库原理';
-- 查询同时选了课程 1 和课程 2 的学生(交集)
SELECT s.name
FROM students s
WHERE s.id IN (SELECT student_id FROM enrollments WHERE course_id = 1)
AND s.id IN (SELECT student_id FROM enrollments WHERE course_id = 2);自连接(Self-Join)是特殊的关联查询,表与自身连接,常用于层级数据(如员工-经理、分类树)。
-- 员工表(含经理字段)
CREATE TABLE employees (
id bigint PRIMARY KEY,
name text NOT NULL,
manager_id bigint REFERENCES employees(id)
);
-- 查询每个员工及其经理
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;索引(Index)是提升查询性能的关键数据结构,类似于书的目录。PostgreSQL 索引是独立的物理结构,存储索引键值和指向表行的指针(TID,Tuple ID,元组标识)。索引可以大幅加速查询,但会增加写入开销和存储空间,需要权衡。
PostgreSQL 支持多种索引类型,每种适用于不同场景。
B-tree(Balanced Tree,平衡树)是默认索引类型,适用于等值查询、范围查询、排序。所有比较运算符(=, <, >, <=, >=, BETWEEN, IN, IS NULL)都能利用 B-tree 索引。
CREATE INDEX idx_employees_salary ON employees(salary);
CREATE INDEX idx_employees_dept_salary ON employees(dept_id, salary);Hash 索引仅支持等值查询(=),不支持范围查询和排序,但等值查询性能略优于 B-tree,且占用空间小。PG 10 之前 Hash 索引不记录 WAL,崩溃后可能损坏;PG 10 起已修复,可安全使用。
CREATE INDEX idx_users_email_hash ON users USING hash (email);GiST(Generalized Search Tree,通用搜索树)是一种通用的索引框架,支持自定义运算符。常用于范围类型、几何类型、全文搜索、地理空间数据。
-- 范围类型索引
CREATE INDEX idx_bookings_during ON bookings USING gist (during);
-- 几何类型索引
CREATE INDEX idx_cities_location ON cities USING gist (location);GIN(Generalized Inverted Index,通用倒排索引)适合多值字段,如数组、JSONB、全文搜索的 tsvector。GIN 索引支持 @>, ?, ?|, ?& 等运算符。
-- JSONB 索引
CREATE INDEX idx_events_data ON events USING gin (data);
-- 数组索引
CREATE INDEX idx_posts_tags ON posts USING gin (tags);
-- 全文搜索索引
CREATE INDEX idx_docs_content ON docs USING gin (to_tsvector('english', content));BRIN(Block Range Index,块范围索引)存储每个数据块范围的摘要(最小值、最大值),适合有序大表(如时间序列数据)。BRIN 索引体积小(通常只有 B-tree 的 1/100),但查询精度低,需要回表过滤。
-- 时间序列数据
CREATE INDEX idx_logs_created ON logs USING brin (created_at);SP-GiST(Space-Partitioned Generalized Search Tree,空间分区通用搜索树)支持非平衡数据结构,如四叉树、基数树,适用于电话区号、IP 路由等场景。
CREATE INDEX idx_routes_ip ON routes USING spgist (ip_prefix);-- 基本创建
CREATE INDEX idx_name ON table(column);
-- 复合索引
CREATE INDEX idx_dept_salary ON employees(dept_id, salary);
-- 唯一索引
CREATE UNIQUE INDEX uk_email ON users(email);
-- 部分索引(仅索引满足条件的行)
CREATE INDEX idx_active_users ON users(last_login)
WHERE active = true;
-- 表达式索引(索引函数结果)
CREATE INDEX idx_lower_email ON users(lower(email));
-- 并发创建(不阻塞写入,但耗时更长)
CREATE INDEX CONCURRENTLY idx_name ON table(column);
-- 指定索引类型
CREATE INDEX idx_tags ON posts USING gin (tags);WHERE lower(email) = '...'。EXPLAIN 和 EXPLAIN ANALYZE 验证索引是否被使用。-- 查看索引
\di
SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'employees';
-- 查看索引大小
SELECT pg_size_pretty(pg_relation_size('idx_employees_salary'));
-- 查看索引使用情况
SELECT * FROM pg_stat_user_indexes;
-- 重建索引
REINDEX INDEX idx_employees_salary;
REINDEX TABLE employees; -- 重建表上所有索引
REINDEX TABLE CONCURRENTLY employees; -- 并发重建(PG 12+)
-- 删除索引
DROP INDEX idx_employees_salary;
DROP INDEX CONCURRENTLY idx_employees_salary; -- 不阻塞写入EXPLAIN 显示查询的执行计划,EXPLAIN ANALYZE 实际执行并显示真实耗时,是性能调优的核心工具。
-- 显示执行计划
EXPLAIN SELECT * FROM employees WHERE salary > 8000;
-- 实际执行并显示统计
EXPLAIN ANALYZE SELECT * FROM employees WHERE salary > 8000;
-- 显示更多详细信息
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM employees WHERE salary > 8000;关键指标:
Seq Scan:顺序扫描(全表扫描),通常需要优化。Index Scan:索引扫描,先查索引再回表。Index Only Scan:仅索引扫描,所有需要的列都在索引中,无需回表。Bitmap Index Scan + Bitmap Heap Scan:位图索引扫描,适合匹配多行。cost:估算成本(启动成本..总成本)。rows:估算行数。actual time、actual rows、loops:实际执行统计。事务(Transaction)是数据库操作的最小逻辑单元,具有 ACID 四大特性:
-- 开始事务
BEGIN;
-- 或
BEGIN TRANSACTION;
-- 或
START TRANSACTION;
-- 提交事务
COMMIT;
-- 或
COMMIT TRANSACTION;
-- 回滚事务
ROLLBACK;
-- 或
ROLLBACK TRANSACTION;PostgreSQL 默认每条 SQL 语句自动提交(Autocommit 模式),使用 BEGIN 显式开启事务后会进入事务块,直到 COMMIT 或 ROLLBACK。
保存点(Savepoint)允许在事务内部设置标记,可以回滚到该标记而不影响整个事务。
BEGIN;
INSERT INTO accounts (id, balance) VALUES (1, 100);
SAVEPOINT sp1;
INSERT INTO accounts (id, balance) VALUES (2, 200);
-- 发现错误,回滚到 sp1
ROLLBACK TO sp1;
-- 继续其他操作
INSERT INTO accounts (id, balance) VALUES (3, 300);
COMMIT;
-- 最终结果:账户 1 和 3 存在,账户 2 不存在PostgreSQL 支持四种 SQL 标准隔离级别,但实际实现只有三种(READ UNCOMMITTED 被当作 READ COMMITTED 处理)。
-- 设置当前会话隔离级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 设置事务隔离级别
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT * FROM accounts WHERE id = 1;
COMMIT;
-- 设置会话默认隔离级别
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;隔离级别与并发异常对照表:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 序列化异常 |
|---|---|---|---|---|
| READ UNCOMMITTED | 不可能(PG 当作 RC) | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 不可能(PG) | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 | 不可能 |
PostgreSQL 使用 MVCC(Multi-Version Concurrency Control,多版本并发控制)实现高并发。每行数据保留多个版本,读操作读取快照版本,写操作创建新版本,互不阻塞。旧版本称为"死元组"(Dead Tuples),由 VACUUM 清理。
-- 查看表的死元组
SELECT relname, n_live_tup, n_dead_tup
FROM pg_stat_user_tables
WHERE relname = 'employees';
-- 手动 VACUUM(不锁表)
VACUUM employees;
-- VACUUM FULL(重建表,锁表,回收空间)
VACUUM FULL employees;
-- ANALYZE 更新统计信息
ANALYZE employees;
-- VACUUM + ANALYZE
VACUUM ANALYZE employees;-- 查看当前事务 ID
SELECT txid_current();
-- 查看当前快照
SELECT txid_current_snapshot();事务可见性规则:一个事务只能看到在它快照之前提交的事务所做的修改。这是 MVCC 的核心,确保读一致性。
锁(Lock)是数据库实现并发控制的核心机制。PostgreSQL 的锁分为两类:表级锁(Table-level Lock)和行级锁(Row-level Lock)。此外还有咨询锁(Advisory Lock)用于应用层协调。理解锁机制对于排查死锁、性能问题至关重要。
PostgreSQL 定义了 8 种表级锁模式,冲突强度从弱到强依次为:
ACCESS SHARE:最弱,SELECT 自动获取。ROW SHARE:SELECT ... FOR UPDATE/SHARE 自动获取。ROW EXCLUSIVE:INSERT/UPDATE/DELETE 自动获取。SHARE UPDATE EXCLUSIVE:VACUUM(非 FULL)、ANALYZE、CREATE INDEX CONCURRENTLY 获取。SHARE:CREATE INDEX(非 CONCURRENTLY)获取。SHARE ROW EXCLUSIVE:较少见。EXCLUSIVE:只允许 ACCESS SHARE 并发。ACCESS EXCLUSIVE:最强,DROP/TRUNCATE/ALTER TABLE 获取,阻塞一切。显式加锁使用 LOCK TABLE 语句:
-- 显式加锁
LOCK TABLE employees IN SHARE MODE;
LOCK TABLE employees IN EXCLUSIVE MODE;
-- 8 级锁模式
LOCK TABLE employees IN ACCESS SHARE MODE;
LOCK TABLE employees IN ROW SHARE MODE;
LOCK TABLE employees IN ROW EXCLUSIVE MODE;
LOCK TABLE employees IN SHARE UPDATE EXCLUSIVE MODE;
LOCK TABLE employees IN SHARE MODE;
LOCK TABLE employees IN SHARE ROW EXCLUSIVE MODE;
LOCK TABLE employees IN EXCLUSIVE MODE;
LOCK TABLE employees IN ACCESS EXCLUSIVE MODE;行级锁在 DML 语句中自动获取,无需显式声明。PostgreSQL 的行级锁不会阻塞读操作(MVCC 特性),只阻塞其他事务对同一行的写操作。
FOR UPDATE:获取排他行锁,阻塞其他 FOR UPDATE/SHARE,最严格。FOR NO KEY UPDATE:获取排他行锁,但不阻塞 FOR SHARE 和 FOR KEY SHARE,性能更好。FOR SHARE:获取共享行锁,允许其他事务读,阻塞写。FOR KEY SHARE:获取共享行锁,仅锁主键,允许非主键列更新。-- 行级锁示例
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 此时其他事务无法修改 id=1 的行
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- NOWAIT 选项:锁不可用时立即报错而非等待
SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;
-- SKIP LOCKED 选项:跳过已锁定的行(适合任务队列)
SELECT * FROM task_queue WHERE status = 'pending'
ORDER BY id
FOR UPDATE SKIP LOCKED
LIMIT 10;SKIP LOCKED 是构建任务队列、消息队列的核心特性,可以高效地并发获取任务而不互相阻塞。
死锁(Deadlock)指两个或多个事务互相等待对方持有的锁,导致永久阻塞。PostgreSQL 自动检测死锁,并终止其中一个事务以打破死锁,被终止的事务收到 ERROR: deadlock detected 错误。
-- 死锁示例
-- 事务 A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 此时事务 B 执行: UPDATE accounts SET balance = balance - 100 WHERE id = 2;
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 等待事务 B
-- 事务 B 执行: UPDATE accounts SET balance = balance + 100 WHERE id = 1;
-- 死锁!PG 自动检测并终止其中一个事务避免死锁的常用策略:所有事务按相同顺序访问表和行;保持事务简短;使用合适的隔离级别;必要时使用 NOWAIT 或 SKIP LOCKED。
咨询锁(Advisory Lock)是应用层显式获取的锁,不与任何具体数据绑定,用于跨事务的应用层协调。咨询锁分为会话级(Session-level)和事务级(Transaction-level)。
-- 会话级咨询锁(直到显式释放或会话结束)
pg_advisory_lock(12345); -- 整数键
pg_advisory_lock(1, 1); -- 双整数键
pg_advisory_unlock(12345); -- 释放
-- 事务级咨询锁(事务结束时自动释放)
pg_advisory_xact_lock(12345);
-- 尝试获取,不阻塞
pg_try_advisory_lock(12345); -- 返回 boolean
-- 共享咨询锁(多个会话可同时持有)
pg_advisory_lock_shared(12345);咨询锁典型应用:分布式任务调度(确保同一时刻只有一个 worker 处理某任务)、限流、序列号生成、跨进程互斥。
-- 查看当前锁情况
SELECT * FROM pg_locks;
-- 查看阻塞关系
SELECT
blocked.pid AS blocked_pid,
blocked.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking.query AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_locks bl ON bl.pid = blocked.pid AND NOT bl.granted
JOIN pg_locks ul ON ul.locktype = bl.locktype
AND ul.database IS NOT DISTINCT FROM bl.database
AND ul.relation IS NOT DISTINCT FROM bl.relation
AND ul.granted
JOIN pg_stat_activity blocking ON ul.pid = blocking.pid;
-- 终止会话
SELECT pg_terminate_backend(pid);
SELECT pg_cancel_backend(pid); -- 仅取消当前查询CTE(Common Table Expression,公用表表达式)是通过 WITH 子句定义的临时结果集,仅在主查询中有效。CTE 提升了复杂 SQL 的可读性,并支持递归查询,是 PostgreSQL 中极为重要的特性。
-- 基本 CTE
WITH dept_stats AS (
SELECT dept_id, COUNT(*) AS emp_count, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id
)
SELECT d.name, ds.emp_count, ds.avg_sal
FROM departments d
JOIN dept_stats ds ON ds.dept_id = d.id
WHERE ds.avg_sal > 8000;
-- 多个 CTE
WITH
active_users AS (
SELECT * FROM users WHERE active = true
),
recent_orders AS (
SELECT * FROM orders WHERE created_at > now() - INTERVAL '30 days'
)
SELECT au.username, COUNT(ro.id) AS order_count
FROM active_users au
LEFT JOIN recent_orders ro ON ro.user_id = au.id
GROUP BY au.id, au.username;递归 CTE(Recursive CTE)是处理层级数据、树形结构、图遍历的利器。语法为 WITH RECURSIVE name AS (非递归部分 UNION [ALL] 递归部分)。
-- 员工层级(自引用)
WITH RECURSIVE emp_tree AS (
-- 锚点:顶层(无经理)
SELECT id, name, manager_id, 1 AS level, name::text AS path
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 递归:下属
SELECT e.id, e.name, e.manager_id, et.level + 1, et.path || ' > ' || e.name
FROM employees e
JOIN emp_tree et ON e.manager_id = et.id
)
SELECT id, name, level, path
FROM emp_tree
ORDER BY path;递归 CTE 的执行原理:
典型应用场景:组织架构树、评论回复树、物料清单(BOM,Bill of Materials)展开、最短路径查询、生成序列。
-- 生成 1 到 10 的序列
WITH RECURSIVE nums(n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM nums WHERE n < 10
)
SELECT * FROM nums;
-- 斐波那契数列
WITH RECURS fib(a, b) AS (
SELECT 0, 1
UNION ALL
SELECT b, a + b FROM fib WHERE b < 100
)
SELECT a FROM fib;默认情况下,CTE 是"内联"的优化屏障(PG 12 起可内联非递归 CTE)。使用 MATERIALIZED 关键字强制 CTE 物化为临时表,多次引用时只计算一次;使用 NOT MATERIALIZED 强制内联。
-- 强制物化(适合多次引用且计算昂贵的 CTE)
WITH expensive AS MATERIALIZED (
SELECT * FROM big_table WHERE complex_condition
)
SELECT * FROM expensive e1
JOIN expensive e2 ON e1.id = e2.parent_id;
-- 强制内联(适合简单 CTE,让优化器下推条件)
WITH simple AS NOT MATERIALIZED (
SELECT * FROM users
)
SELECT * FROM simple WHERE name = 'alice';视图(View)是虚拟表,本质是存储的 SQL 查询定义。视图不存储数据,每次查询时动态执行底层 SQL。视图用于简化复杂查询、提供数据抽象、实现权限控制。
-- 创建视图
CREATE VIEW v_high_salary_employees AS
SELECT id, name, salary, dept_id
FROM employees
WHERE salary > 10000;
-- 使用视图
SELECT * FROM v_high_salary_employees;
SELECT * FROM v_high_salary_employees WHERE dept_id = 1;
-- 修改视图
CREATE OR REPLACE VIEW v_high_salary_employees AS
SELECT id, name, salary, dept_id, email
FROM employees
WHERE salary > 10000;
-- 重命名视图
ALTER VIEW v_high_salary_employees RENAME TO v_top_earners;
-- 删除视图
DROP VIEW v_high_salary_employees;
DROP VIEW IF EXISTS v_high_salary_employees CASCADE;PostgreSQL 支持简单视图的自动更新(INSERT/UPDATE/DELETE 直接作用于底层表)。视图满足以下条件即为可更新视图:FROM 子句只有一个表;不包含聚合、窗口函数、DISTINCT、GROUP BY、HAVING、LIMIT 等;列都是基础表的列引用。
-- 可更新视图
CREATE VIEW v_active_users AS
SELECT id, username, email FROM users WHERE active = true;
-- 直接插入(active 自动设为 true)
INSERT INTO v_active_users (id, username, email) VALUES (1, 'alice', 'a@b.com');
-- 直接更新
UPDATE v_active_users SET email = 'new@b.com' WHERE id = 1;
-- 直接删除
DELETE FROM v_active_users WHERE id = 1;对于不满足自动更新条件的视图,可通过 INSTEAD OF 触发器或 INSTEAD 规则实现更新。
物化视图(Materialized View)将查询结果实际存储到磁盘,查询时直接读取,适合不频繁变更但查询昂贵的场景。与普通视图不同,物化视图需要手动或定时刷新。
-- 创建物化视图
CREATE MATERIALIZED VIEW mv_dept_stats AS
SELECT dept_id, COUNT(*) AS emp_count, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id;
-- 创建时带数据
CREATE MATERIALIZED VIEW mv_dept_stats AS
SELECT dept_id, COUNT(*) AS emp_count, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id
WITH DATA; -- 默认,WITH NO DATA 则不立即填充
-- 刷新(默认锁表)
REFRESH MATERIALIZED VIEW mv_dept_stats;
-- 并发刷新(不锁表,需要唯一索引)
CREATE UNIQUE INDEX idx_mv_dept_stats_dept ON mv_dept_stats(dept_id);
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_dept_stats;
-- 删除
DROP MATERIALIZED VIEW mv_dept_stats;物化视图与普通视图的区别:
PostgreSQL 使用"角色"(Role)统一管理用户和组。在 PG 8.1 之前,USER 和 GROUP 是独立概念;之后合并为 ROLE。具有 LOGIN 属性的角色相当于"用户",不具有 LOGIN 属性的角色相当于"组"。角色可以继承其他角色的权限,形成层级结构。
-- 创建可登录角色(用户)
CREATE ROLE alice LOGIN PASSWORD 'secure_password';
CREATE USER bob PASSWORD 'secure_password'; -- CREATE USER 默认带 LOGIN
-- 创建不可登录角色(组)
CREATE ROLE dev_team;
-- 创建带属性的角色
CREATE ROLE admin SUPERUSER CREATEDB CREATEROLE REPLICATION LOGIN PASSWORD 'admin_pass';
-- 完整属性说明
CREATE ROLE role_name WITH
LOGIN -- 允许登录
PASSWORD 'pass' -- 密码
SUPERUSER -- 超级用户(慎用)
CREATEDB -- 可创建数据库
CREATEROLE -- 可创建角色
REPLICATION -- 可做流复制
CONNECTION LIMIT 100 -- 最大连接数
VALID UNTIL '2025-12-31' -- 密码有效期
IN ROLE dev_team; -- 加入 dev_team 组-- 修改密码
ALTER ROLE alice PASSWORD 'new_password';
-- 修改属性
ALTER ROLE alice CREATEDB;
ALTER ROLE alice NOLOGIN; -- 禁止登录
-- 重命名
ALTER ROLE alice RENAME TO alice_new;
-- 设置角色参数
ALTER ROLE alice SET work_mem = '64MB';
ALTER ROLE alice SET search_path TO myapp, public;
-- 删除角色
DROP ROLE alice;
-- 如果角色有依赖对象,需先转移或删除依赖
DROP ROLE IF EXISTS alice;PostgreSQL 权限通过 GRANT 和 REVOKE 管理。新建对象默认只有所有者有权限,其他角色需显式授权。
SELECT:查询。INSERT:插入。UPDATE:更新。DELETE:删除。TRUNCATE:清空表。REFERENCES:创建外键。TRIGGER:创建触发器。CREATE:在 schema/database 中创建对象。CONNECT:连接数据库。TEMPORARY / TEMP:创建临时表。EXECUTE:执行函数/过程。USAGE:使用 schema、sequence、domain 等。ALL PRIVILEGES:所有权限。-- 授予表权限
GRANT SELECT, INSERT, UPDATE ON employees TO alice;
GRANT ALL PRIVILEGES ON employees TO admin;
-- 授予列级权限
GRANT SELECT (id, name), UPDATE (salary) ON employees TO alice;
-- 授予 schema 权限
GRANT USAGE, CREATE ON SCHEMA myapp TO alice;
-- 授予数据库权限
GRANT CONNECT, TEMPORARY ON DATABASE mydb TO alice;
-- 授予序列权限
GRANT USAGE, SELECT, UPDATE ON SEQUENCE users_id_seq TO alice;
-- 授予函数权限
GRANT EXECUTE ON FUNCTION my_func(integer) TO alice;
-- 授予所有表(schema 内)
GRANT SELECT ON ALL TABLES IN SCHEMA myapp TO alice;
-- 授予所有序列
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA myapp TO alice;
-- 撤销权限
REVOKE INSERT, UPDATE ON employees FROM alice;
REVOKE ALL PRIVILEGES ON employees FROM admin;ALTER DEFAULT PRIVILEGES 设置未来新建对象的默认权限,避免每次建表后手动授权。
-- 设置默认权限:alice 在 myapp schema 下新建的表,dev_team 自动有 SELECT 权限
ALTER DEFAULT PRIVILEGES FOR ROLE alice IN SCHEMA myapp
GRANT SELECT ON TABLES TO dev_team;
-- 默认序列权限
ALTER DEFAULT PRIVILEGES FOR ROLE alice IN SCHEMA myapp
GRANT USAGE ON SEQUENCES TO dev_team;
-- 默认函数权限
ALTER DEFAULT PRIVILEGES FOR ROLE alice IN SCHEMA myapp
GRANT EXECUTE ON FUNCTIONS TO dev_team;角色可以加入其他角色(作为成员),继承父角色的权限。
-- 创建组角色
CREATE ROLE dev_team;
CREATE ROLE dev_lead;
-- 将 alice 加入 dev_team
GRANT dev_team TO alice;
-- 将 dev_team 加入 dev_lead(层级)
GRANT dev_team TO dev_lead;
-- 设置 dev_team 权限
GRANT SELECT ON ALL TABLES IN SCHEMA myapp TO dev_team;
-- alice 通过继承获得 dev_team 的权限
-- 默认角色带 INHERIT 属性,自动继承成员角色权限
CREATE ROLE alice LOGIN INHERIT; -- INHERIT 是默认
-- 切换到成员角色(SET ROLE)
SET ROLE dev_team; -- 当前会话以 dev_team 身份操作
RESET ROLE; -- 恢复原始角色-- 查看所有角色
\du
SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin
FROM pg_roles;
-- 查看角色成员关系
SELECT r.rolname AS role, m.rolname AS member
FROM pg_auth_members am
JOIN pg_roles r ON r.oid = am.roleid
JOIN pg_roles m ON m.oid = am.member;
-- 查看表权限
\dp employees
SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_name = 'employees';
-- 查看当前会话角色
SELECT current_user, session_user, current_role;PostgreSQL 通过 pg_hba.conf(Host-Based Authentication,基于主机的认证)文件控制客户端连接。文件位于数据目录下,每行一条规则,格式为 TYPE DATABASE USER ADDRESS METHOD。
# TYPE DATABASE USER ADDRESS METHOD
local all all peer # 本地连接用 peer(操作系统用户认证)
host all all 127.0.0.1/32 scram-sha-256 # IPv4 本地
host all all ::1/128 scram-sha-256 # IPv6 本地
host all all 0.0.0.0/0 scram-sha-256 # 所有 IPv4(生产慎用)
hostssl all all 0.0.0.0/0 scram-sha-256 # 仅 SSL 连接认证方法(METHOD)说明:
trust:无需密码,直接允许(仅开发环境)。reject:拒绝连接。password:明文密码(不安全)。md5:MD5 加密密码(PG 14 起不推荐)。scram-sha-256:SCRAM-SHA-256 认证(PG 10+,推荐,PG 14 起默认)。peer:操作系统用户认证(仅本地连接)。cert:SSL 客户端证书认证。gss / sspi:GSSAPI / SSPI 认证(Kerberos)。ldap / radius / pam:外部认证服务。修改 pg_hba.conf 后需执行 SELECT pg_reload_conf(); 或 pg_ctl reload 重新加载。
PostgreSQL 18 新增 OAuth 2.0(Open Authorization 2.0,开放授权 2.0)认证支持,允许通过外部身份提供商(IdP,Identity Provider)进行认证,适合与 Keycloak、Okta、Azure AD 等集成。
# pg_hba.conf 中配置 OAuth
host all all 0.0.0.0/0 oauth issuer="https://idp.example.com" scope="openid email"OAuth 认证流程:客户端从 IdP 获取 access token → 连接 PG 时携带 token → PG 验证 token → 验证通过则建立连接。这是 PG 18 在企业级安全方面的重要增强。
PostgreSQL 备份分为逻辑备份和物理备份两大类。逻辑备份导出 SQL 语句或数据,跨版本兼容性好,但速度慢;物理备份复制数据文件,速度快,支持 PITR(Point-in-Time Recovery,时间点恢复)。生产环境通常结合两者:定期全量物理备份 + 持续 WAL 归档 + 定期逻辑备份。
pg_dump 是最常用的逻辑备份工具,备份单个数据库,输出 SQL 脚本或自定义归档格式。
# 备份为纯 SQL 脚本
pg_dump -h localhost -U postgres -d mydb -F p -f mydb.sql
# 备份为自定义压缩格式(推荐,支持并行恢复)
pg_dump -h localhost -U postgres -d mydb -F c -f mydb.dump
# 备份为目录格式(支持并行备份)
pg_dump -h localhost -U postgres -d mydb -F d -j 4 -f mydb_dir/
# 备份为 tar 格式
pg_dump -h localhost -U postgres -d mydb -F t -f mydb.tar
# 仅备份 schema(结构)
pg_dump -h localhost -U postgres -d mydb --schema-only -f schema.sql
# 仅备份数据
pg_dump -h localhost -U postgres -d mydb --data-only -f data.sql
# 备份指定表
pg_dump -h localhost -U postgres -d mydb -t employees -t departments -f tables.dump
# 备份指定 schema
pg_dump -h localhost -U postgres -d mydb -n myapp -f myapp.dump
# 排除某些表
pg_dump -h localhost -U postgres -d mydb --exclude-table-data='logs*' -f mydb.dump常用参数:
-h:主机。-U:用户。-d:数据库名。-F:格式(p plain, c custom, d directory, t tar)。-f:输出文件。-j:并行任务数(仅 directory 格式)。-t:指定表。-n:指定 schema。-N:排除 schema。--schema-only / -s:仅结构。--data-only / -a:仅数据。--no-owner:不导出所有者信息。--no-privileges / -x:不导出权限。--inserts:用 INSERT 而非 COPY(慢但兼容性好)。--column-inserts:INSERT 带列名。pg_restore 用于恢复 pg_dump -F c/d/t 生成的归档文件。
# 恢复到指定数据库
pg_restore -h localhost -U postgres -d newdb -1 mydb.dump
# 并行恢复
pg_restore -h localhost -U postgres -d newdb -j 4 mydb.dump
# 仅恢复 schema
pg_restore -h localhost -U postgres -d newdb --schema-only mydb.dump
# 仅恢复数据
pg_restore -h localhost -U postgres -d newdb --data-only mydb.dump
# 恢复指定表
pg_restore -h localhost -U postgres -d newdb -t employees mydb.dump
# 创建数据库并恢复
pg_restore -h localhost -U postgres -C -d postgres mydb.dump
# 清空后恢复(危险)
pg_restore -h localhost -U postgres -d newdb --clean --if-exists mydb.dumppg_dumpall 备份整个集群的所有数据库,包括角色、表空间等全局对象。
# 全量备份(所有数据库 + 全局对象)
pg_dumpall -h localhost -U postgres -f cluster.sql
# 仅备份全局对象(角色、表空间)
pg_dumpall -h localhost -U postgres --globals-only -f globals.sql
# 仅备份角色
pg_dumpall -h localhost -U postgres --roles-only -f roles.sql
# 仅备份表空间
pg_dumpall -h localhost -U postgres --tablespaces-only -f tablespaces.sql恢复 pg_dumpall 输出时,直接用 psql 执行 SQL 脚本即可:
psql -h localhost -U postgres -f cluster.sql物理备份复制数据目录,配合 WAL 归档实现时间点恢复(PITR,Point-in-Time Recovery)。
# 使用 pg_basebackup 进行基础备份
pg_basebackup -h localhost -U replication -D /backup/base -Ft -z -P
# 参数说明
# -D:目标目录
# -Ft:tar 格式
# -z:gzip 压缩
# -P:显示进度
# -X stream:流式传输 WAL
# -R:自动创建 recovery 配置
pg_basebackup -h primary -U replication -D /backup/base -X stream -R -P在 postgresql.conf 中配置 WAL 归档:
# 开启归档
archive_mode = on
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
wal_level = replica%p 是 WAL 文件路径,%f 是 WAL 文件名。归档命令应保证不覆盖已有文件。
recovery.signal 或 standby.signal。postgresql.conf 或 recovery.conf 中配置恢复目标:# 恢复到指定时间点
recovery_target_time = '2025-06-21 14:30:00'
# 恢复到指定事务 ID
recovery_target_xid = '1234567'
# 恢复到指定 LSN(Log Sequence Number)
recovery_target_lsn = '0/7000028'
# 恢复到指定 WAL 位置
recovery_target_name = 'my_restore_point'
# 恢复目标动作
recovery_target_action = 'promote' # promote | pause | shutdown
# 恢复命令
restore_command = 'cp /archive/%f %p'COPY 是 PostgreSQL 最快的数据批量导入导出方式,比 INSERT 快一个数量级。
-- 导出为 CSV
COPY employees TO '/tmp/employees.csv' WITH CSV HEADER;
-- 导出指定列
COPY employees(id, name, salary) TO '/tmp/emp_partial.csv' WITH CSV HEADER;
-- 从 CSV 导入
COPY employees FROM '/tmp/employees.csv' WITH CSV HEADER;
-- 导出为二进制(最快)
COPY employees TO '/tmp/employees.bin' WITH BINARY;
-- 使用 STDIN/STDOUT(配合管道)
COPY employees FROM STDIN WITH CSV HEADER;
COPY (SELECT * FROM employees WHERE active) TO STDOUT WITH CSV;# 命令行使用 \copy(客户端执行,权限要求低)
psql -h localhost -U postgres -d mydb -c "\copy employees TO '/tmp/emp.csv' WITH CSV HEADER"
psql -h localhost -U postgres -d mydb -c "\copy employees FROM '/tmp/emp.csv' WITH CSV HEADER"COPY 是服务端命令(需超级用户或对文件有写权限),\copy 是 psql 客户端命令(任何用户可用,文件在客户端)。
全文搜索(Full-Text Search,FTS)是对文本内容进行分词、索引、检索的技术,比 LIKE 模糊查询高效得多。PostgreSQL 内置强大的全文搜索引擎,支持 tsvector(分词向量)、tsquery(查询向量)、GIN 索引,无需额外安装即可使用。
全文搜索的核心流程:原始文本 → 分词(Parsing)→ 词干化(Stemming)→ 去停用词(Stop Words)→ tsvector → 索引 → 匹配 tsquery → 排序。
tsvector 是分词后的有序词列表,每个词附带位置信息。
-- 基本分词
SELECT to_tsvector('english', 'The quick brown fox jumps over the lazy dog');
-- 结果: 'brown':3 'dog':9 'fox':4 'jump':5 'lazi':8 'quick':2
-- 中文(默认按字分词,效果差)
SELECT to_tsvector('simple', '我喜欢PostgreSQL数据库');观察结果:定冠词 "the" 被去除(停用词),"jumps" 变为 "jump"(词干化),"lazy" 变为 "lazi"(词干化),词按字母排序并记录位置。
tsquery 是查询表达式,支持 AND、OR、NOT、FOLLOWED BY 操作符。
-- 构造 tsquery
SELECT to_tsquery('english', 'quick & brown'); -- AND
SELECT to_tsquery('english', 'quick | brown'); -- OR
SELECT to_tsquery('english', 'quick & !brown'); -- AND NOT
SELECT to_tsquery('english', 'quick <-> brown'); -- FOLLOWED BY(相邻)
SELECT to_tsquery('english', 'quick <2> brown'); -- 间隔 2 个词
-- plainto_tsquery:自然语言转 tsquery(自动处理)
SELECT plainto_tsquery('english', 'quick brown fox');
-- 结果: 'brown' & 'fox' & 'quick'
-- phraseto_tsquery:短语查询(保持顺序)
SELECT phraseto_tsquery('english', 'quick brown fox');
-- 结果: 'quick' <-> 'brown' <-> 'fox'
-- websearch_to_tsquery:类搜索引擎语法(推荐)
SELECT websearch_to_tsquery('english', 'quick brown -fox OR cat');
-- 结果: 'quick' & 'brown' & !'fox' | 'cat'-- @@ 匹配操作符
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ to_tsquery('english', 'postgres & tutorial');
-- ts_rank 相关度排序
SELECT title, ts_rank(tsv, query) AS rank
FROM articles, to_tsquery('english', 'postgres & tutorial') query
WHERE tsv @@ query
ORDER BY rank DESC;
-- ts_rank_cd 覆盖密度排序
SELECT title, ts_rank_cd(tsv, query) AS rank
FROM articles, to_tsquery('english', 'postgres & tutorial') query
WHERE tsv @@ query
ORDER BY rank DESC;
-- 高亮显示
SELECT ts_headline('english', content, query, 'MaxWords=35, MinWords=15')
FROM articles, to_tsquery('english', 'postgres') query
WHERE tsv @@ query;-- 创建带 tsvector 列的表
CREATE TABLE articles (
id bigint PRIMARY KEY,
title text,
content text,
tsv tsvector GENERATED ALWAYS AS (
to_tsvector('english', coalesce(title, '') || ' ' || coalesce(content, ''))
) STORED
);
-- 创建 GIN 索引
CREATE INDEX idx_articles_tsv ON articles USING gin (tsv);
-- 查询(使用索引)
SELECT title, ts_rank(tsv, query) AS rank
FROM articles, websearch_to_tsquery('english', 'postgres tutorial') query
WHERE tsv @@ query
ORDER BY rank DESC
LIMIT 10;PostgreSQL 支持多种语言配置,每种配置包含解析器、字典、停用词。
-- 查看可用配置
SELECT cfgname FROM pg_ts_config;
-- 常用配置
-- english, simple, german, french, spanish, russian, chinese (需扩展)
-- 查看停用词
SELECT ts_lexize('english_stem', 'the'); -- NULL(停用词被去除)
-- 自定义配置
CREATE TEXT SEARCH CONFIGURATION my_english (COPY = english);
ALTER TEXT SEARCH CONFIGURATION my_english
ALTER MAPPING FOR asciiword WITH my_stem;
-- 设置默认配置
SET default_text_search_config = 'english';对于拼写错误容忍的模糊搜索,pg_trgm 扩展提供 trigram(三元组)相似度匹配。
-- 启用扩展
CREATE EXTENSION pg_trgm;
-- 相似度
SELECT similarity('hello', 'helo'); -- 0.5
-- 模糊匹配
SELECT name FROM users WHERE name % 'alise' ORDER BY similarity(name, 'alise') DESC;
-- 距离
SELECT word_similarity('hello', 'hello world');
-- 创建 GIN trigram 索引
CREATE INDEX idx_users_name_trgm ON users USING gin (name gin_trgm_ops);
-- LIKE 加速(trigram 索引可加速 LIKE)
SELECT * FROM users WHERE name LIKE '%ali%';PostgreSQL 内置的全文搜索针对英文等空格分隔语言设计,对中文(无空格分隔)效果差。默认 simple 配置按字分词,无法识别词语边界,导致搜索精度低。要实现高质量中文全文搜索,需要安装中文分词扩展。主流方案有 zhparser(基于 SCWS)和 pg_jieba(基于结巴分词)。
zhparser 是基于 SCWS(Simple Chinese Word Segmentation,简易中文分词)的 PostgreSQL 中文分词扩展,是最成熟的中文 FTS 方案。
# Ubuntu/Debian
apt install postgresql-18-zhparser
# 或从源码编译
git clone https://github.com/amutu/zhparser.git
cd zhparser
make && make install-- 创建扩展
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 to_tsvector('chinese', '我喜欢使用PostgreSQL数据库');
-- 结果: '喜欢':2 '我':1 '数据库':6 '使用':3
-- 设置默认配置
SET default_text_search_config = 'chinese';-- 创建带中文分词列的表
CREATE TABLE docs (
id bigint PRIMARY KEY,
title text,
content text,
tsv tsvector GENERATED ALWAYS AS (
to_tsvector('chinese', coalesce(title, '') || ' ' || coalesce(content, ''))
) STORED
);
-- 创建 GIN 索引
CREATE INDEX idx_docs_tsv ON docs USING gin (tsv);
-- 插入数据
INSERT INTO docs (id, title, content) VALUES
(1, 'PostgreSQL 教程', 'PostgreSQL 是一个强大的开源关系型数据库'),
(2, '中文分词', '全文搜索需要中文分词支持');
-- 中文搜索
SELECT title, ts_rank(tsv, query) AS rank
FROM docs, websearch_to_tsquery('chinese', '数据库') query
WHERE tsv @@ query
ORDER BY rank DESC;
-- 高亮
SELECT ts_headline('chinese', content, query)
FROM docs, websearch_to_tsquery('chinese', '数据库') query
WHERE tsv @@ query;pg_jieba 是基于结巴分词(CppJieba)的 PostgreSQL 中文分词扩展,分词质量高,支持自定义词典。
-- 创建扩展
CREATE EXTENSION pg_jieba;
-- 创建配置
CREATE TEXT SEARCH CONFIGURATION jiebaconfig (PARSER = jieba);
ALTER TEXT SEARCH CONFIGURATION jiebaconfig
ADD MAPPING FOR n,v,a,i,e,l WITH simple;
-- 测试
SELECT to_tsvector('jiebaconfig', '我爱北京天安门');
-- 使用
SELECT * FROM docs
WHERE to_tsvector('jiebaconfig', content) @@ to_tsquery('jiebaconfig', '北京');-- zhparser 添加自定义词
INSERT INTO zhparser.zhprs_custom_word VALUES('区块链');
SELECT zhparser.sync_zhprs_custom_word();
-- pg_jieba 自定义词典(编辑词典文件后重新加载)
-- 通常在 /usr/share/postgresql/18/tsearch_data/jieba.dictzhparser:成熟稳定,基于 SCWS,社区活跃,适合大多数场景。pg_jieba:分词质量高,基于结巴,支持多种分词模式,适合对精度要求高的场景。simple 配置:按字分词,无需扩展,但搜索精度低,仅适合简单场景。PostGIS 是 PostgreSQL 最著名的空间数据库扩展,为 PostgreSQL 添加了地理空间对象、空间索引、空间分析函数,使其具备与商业 GIS(Geographic Information System,地理信息系统)软件相当的能力。PostGIS 遵循 OGC(Open Geospatial Consortium,开放地理空间联盟)SFA(Simple Features for SQL)标准,广泛应用于地图服务、LBS(Location-Based Service,基于位置的服务)、地理分析等领域。
截至本文档编写时,PostGIS 3.6.x 支持 PostgreSQL 18。
# Ubuntu/Debian
apt install postgis postgresql-18-postgis-3
# Docker(推荐,自带 PostGIS)
docker run -d --name postgis \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
postgis/postgis:18-3.6-- 在数据库中启用 PostGIS
CREATE EXTENSION postgis;
-- 验证安装
SELECT PostGIS_Version();
SELECT PostGIS_Full_Version();
-- 启用额外扩展(可选)
CREATE EXTENSION postgis_topology; -- 拓扑
CREATE EXTENSION postgis_raster; -- 栅格
CREATE EXTENSION postgis_sfcgal; -- 3D 几何PostGIS 提供两种主要几何类型:geometry(笛卡尔坐标,平面几何)和 geography(球面坐标,大地测量)。
-- 创建空间表
CREATE TABLE cities (
id bigint PRIMARY KEY,
name text NOT NULL,
location geometry(Point, 4326), -- 经纬度点,SRID 4326 (WGS84)
boundary geometry(Polygon, 4326) -- 多边形边界
);
-- 插入点(经度 纬度)
INSERT INTO cities (id, name, location) VALUES
(1, '北京', ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326)),
(2, '上海', ST_SetSRID(ST_MakePoint(121.4737, 31.2304), 4326)),
(3, '广州', ST_SetSRID(ST_MakePoint(113.2644, 23.1291), 4326));
-- 使用 geography 类型(自动球面计算)
CREATE TABLE cities_geo (
id bigint PRIMARY KEY,
name text,
location geography(Point, 4326)
);SRID(Spatial Reference System Identifier,空间参考系统标识符)定义坐标系统,4326 是 WGS84(World Geodetic System 1984,全球大地坐标系 1984),即 GPS 使用的经纬度坐标系。
-- 点
SELECT ST_MakePoint(116.40, 39.90);
SELECT ST_SetSRID(ST_MakePoint(116.40, 39.90), 4326);
SELECT ST_GeomFromText('POINT(116.40 39.90)', 4326);
SELECT ST_GeogFromText('POINT(116.40 39.90)');
-- 线
SELECT ST_MakeLine(ARRAY[ST_MakePoint(0,0), ST_MakePoint(1,1), ST_MakePoint(2,2)]);
-- 多边形
SELECT ST_MakePolygon(ST_GeomFromText('LINESTRING(0 0, 0 1, 1 1, 1 0, 0 0)'));
-- 缓冲区(指定半径生成多边形)
SELECT ST_Buffer(ST_MakePoint(116.40, 39.90)::geography, 1000); -- 1km 缓冲
-- 从 GeoJSON 导入
SELECT ST_GeomFromGeoJSON('{"type":"Point","coordinates":[116.40,39.90]}');
-- 输出 GeoJSON
SELECT ST_AsGeoJSON(location) FROM cities;PostGIS 使用 GiST 索引加速空间查询,是空间数据库性能的关键。
-- 创建空间索引
CREATE INDEX idx_cities_location ON cities USING gist (location);
CREATE INDEX idx_cities_boundary ON cities USING gist (boundary);
-- geography 类型同样支持
CREATE INDEX idx_cities_geo_location ON cities_geo USING gist (location);-- 计算两点距离(米,球面)
SELECT ST_Distance(
'POINT(116.40 39.90)'::geography,
'POINT(121.47 31.23)'::geography
); -- 北京到上海约 1067000 米
-- 平面距离(度)
SELECT ST_Distance(
'POINT(0 0)'::geometry,
'POINT(3 4)'::geometry
); -- 5
-- 查找附近的城市(5km 内)
SELECT name, ST_Distance(location, ref.location) AS distance
FROM cities, (SELECT location FROM cities WHERE name = '北京') ref
WHERE ST_DWithin(location, ref.location, 5000) -- 5000 米
ORDER BY distance;
-- 包含关系(点是否在多边形内)
SELECT ST_Contains(
'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'::geometry,
'POINT(5 5)'::geometry
); -- true
-- 相交
SELECT ST_Intersects(
'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'::geometry,
'LINESTRING(5 5, 15 15)'::geometry
); -- true
-- 计算面积(平方米)
SELECT ST_Area(
ST_Buffer('POINT(116.40 39.90)'::geography, 1000)
);
-- 计算长度
SELECT ST_Length('LINESTRING(0 0, 0 1)'::geography); -- 约 111195 米
-- 求交集
SELECT ST_Intersection(
'POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))'::geometry,
'POLYGON((5 5, 5 15, 15 15, 15 5, 5 5))'::geometry
);ST_Distance(geom1, geom2):距离。ST_DWithin(geom1, geom2, dist):是否在指定距离内。ST_Contains(geom1, geom2):geom1 是否包含 geom2。ST_Within(geom1, geom2):geom1 是否在 geom2 内。ST_Intersects(geom1, geom2):是否相交。ST_Touches(geom1, geom2):是否相切。ST_Overlaps(geom1, geom2):是否重叠。ST_Disjoint(geom1, geom2):是否不相交。ST_Equals(geom1, geom2):是否相等。ST_Area(geom):面积。ST_Length(geom):长度。ST_Perimeter(geom):周长。ST_Buffer(geom, dist):缓冲区。ST_Intersection(geom1, geom2):交集。ST_Union(geom1, geom2):并集。ST_Difference(geom1, geom2):差集。ST_Centroid(geom):质心。ST_Transform(geom, srid):坐标转换。pgvector 是 PostgreSQL 的开源向量相似度搜索扩展,为 PostgreSQL 添加向量类型和相似度搜索索引,使其能够存储和检索 AI 嵌入向量(Embedding),支撑大模型 RAG(Retrieval-Augmented Generation,检索增强生成)、推荐系统、图像搜索等 AI 应用。pgvector 让 PostgreSQL 成为向量数据库,无需单独部署 Milvus、Pinecone 等专用向量数据库。
截至本文档编写时,pgvector 0.8.x 支持 PostgreSQL 18。
# Ubuntu/Debian
apt install postgresql-18-pgvector
# Docker(推荐,自带 pgvector)
docker run -d --name pgvector \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
pgvector/pgvector:pg18
# 或从源码编译
git clone https://github.com/pgvector/pgvector.git
cd pgvector
make && make install-- 启用扩展
CREATE EXTENSION vector;
-- 验证
SELECT extversion FROM pg_extension WHERE extname = 'vector';pgvector 支持 vector 单精度浮点向量,支持多种维度。
-- 创建带向量的表
CREATE TABLE items (
id bigint PRIMARY KEY,
name text,
embedding vector(3) -- 3 维向量
);
-- 插入数据
INSERT INTO items (id, name, embedding) VALUES
(1, 'item1', '[1.0, 2.0, 3.0]'),
(2, 'item2', '[4.0, 5.0, 6.0]'),
(3, 'item3', '[1.5, 2.5, 3.5]');
-- halfvec 类型(半精度,节省空间,PG 0.7+)
CREATE TABLE items_half (
id bigint PRIMARY KEY,
embedding halfvec(3)
);
-- binary vector(二进制向量,0.7+)
CREATE TABLE items_binary (
id bigint PRIMARY KEY,
embedding bit(3)
);pgvector 支持三种主流相似度度量:
<-> 操作符,值越小越相似。<#> 操作符(负内积),值越小越相似。<=> 操作符,值越小越相似(1 - 余弦相似度)。-- L2 距离(欧几里得)
SELECT name, embedding <-> '[1,2,3]' AS l2_distance
FROM items
ORDER BY embedding <-> '[1,2,3]'
LIMIT 5;
-- 余弦距离
SELECT name, embedding <=> '[1,2,3]' AS cosine_distance
FROM items
ORDER BY embedding <=> '[1,2,3]'
LIMIT 5;
-- 内积(负值,越小越相似)
SELECT name, embedding <#> '[1,2,3]' AS inner_product
FROM items
ORDER BY embedding <#> '[1,2,3]'
LIMIT 5;相似度度量选择:
对于大规模向量数据,顺序扫描太慢,必须使用近似最近邻(ANN,Approximate Nearest Neighbor)索引。pgvector 提供两种 ANN 索引:HNSW 和 IVFFlat。
HNSW(Hierarchical Navigable Small World,分层可导航小世界)是 pgvector 0.5+ 引入的索引,查询速度快、召回率高,是当前推荐的选择。
-- 创建 HNSW 索引(L2 距离)
CREATE INDEX idx_items_embedding_hnsw ON items
USING hnsw (embedding vector_l2_ops);
-- HNSW 索引(余弦距离)
CREATE INDEX idx_items_embedding_hnsw_cosine ON items
USING hnsw (embedding vector_cosine_ops);
-- HNSW 索引(内积)
CREATE INDEX idx_items_embedding_hnsw_ip ON items
USING hnsw (embedding vector_ip_ops);
-- 自定义参数
CREATE INDEX idx_items_embedding_hnsw ON items
USING hnsw (embedding vector_l2_ops)
WITH (m = 16, ef_construction = 64);HNSW 参数:
m:每层最大连接数,默认 16,越大召回越高但内存占用越大。ef_construction:构建时搜索宽度,默认 64,越大构建越慢但质量越高。ef_search(默认 40)控制搜索宽度,越大召回越高但越慢。-- 查询时调整 ef_search
SET hnsw.ef_search = 100;
SELECT name FROM items ORDER BY embedding <=> '[1,2,3]' LIMIT 10;IVFFlat(Inverted File with Flat Compression)是 pgvector 早期的索引,通过聚类将向量分桶,查询时只搜索最相关的几个桶。
-- 创建 IVFFlat 索引(需先有数据)
CREATE INDEX idx_items_embedding_ivf ON items
USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
-- 查询时调整 probes
SET ivfflat.probes = 10;
SELECT name FROM items ORDER BY embedding <=> '[1,2,3]' LIMIT 10;IVFFlat 参数:
lists:聚类中心数,建议 rows / 1000,最少 10。probes:查询时搜索的桶数,默认 1,越大召回越高但越慢。pgvector 与 PostgreSQL 原生功能结合,可实现强大的混合查询。
-- 向量相似度 + 元数据过滤
SELECT name, embedding <=> '[1,2,3]' AS distance
FROM items
WHERE category = 'electronics' AND price < 1000
ORDER BY embedding <=> '[1,2,3]'
LIMIT 10;
-- 与全文搜索结合
SELECT name, ts_rank(tsv, query) AS text_rank, embedding <=> '[1,2,3]' AS vec_dist
FROM items, websearch_to_tsquery('english', 'phone') query
WHERE tsv @@ query
ORDER BY vec_dist
LIMIT 10;
-- 批量插入优化
INSERT INTO items (id, embedding)
SELECT i, array_agg(random())::vector(3)
FROM generate_series(1, 10000) i
CROSS JOIN generate_series(1, 3)
GROUP BY i;RAG(检索增强生成)是 pgvector 最典型的应用场景:将文档向量化存储,用户提问时检索相关文档,作为大模型上下文。
-- 文档表
CREATE TABLE documents (
id bigint PRIMARY KEY,
content text,
embedding vector(1536), -- OpenAI text-embedding-3-small 维度
metadata jsonb
);
-- 创建 HNSW 索引
CREATE INDEX idx_documents_embedding ON documents
USING hnsw (embedding vector_cosine_ops);
-- 检索相关文档
SELECT content, embedding <=> $1 AS distance
FROM documents
ORDER BY embedding <=> $1
LIMIT 5;
-- $1 是用户问题的嵌入向量应用流程:用户提问 → 调用嵌入 API 将问题向量化 → pgvector 检索 Top-K 相关文档 → 将文档作为上下文调用 LLM → 生成回答。
pgvector:基于 PostgreSQL,事务、ACID、SQL 全功能,适合中小规模(千万级)和需要关系查询的场景。Milvus:专用向量数据库,超大规模(亿级)性能优,但功能单一。Pinecone:托管云服务,免运维,但成本高。Weaviate:开源,支持混合搜索,社区活跃。Qdrant:Rust 实现,性能优,支持过滤。选择建议:已有 PostgreSQL 基础设施且数据量在千万级以内,优先 pgvector;超大规模或纯向量场景考虑 Milvus/Qdrant;免运维需求选 Pinecone。
PostgreSQL 18 于 2025 年 9 月 25 日正式发布,带来多项重要增强:
uuidv7(),时间有序的 UUID 利于索引和分布式系统。更多详情参考官方发布说明:https://www.postgresql.org/docs/release/18.0/