目录
1. PostgreSQL 简介
PostgreSQL 是一个功能强大的开源对象-关系数据库系统,拥有超过 30 年的活跃开发历史。它以其可靠性、功能丰富性和对 SQL 标准的严格遵守而闻名。
1.1 与 MySQL 的对比
| 维度 | PostgreSQL | MySQL |
|---|---|---|
| SQL 标准兼容性 | 高度兼容,几乎完全遵循 SQL:2016 | 部分兼容,有较多方言特性 |
| ACID 实现 | 原生 MVCC,完全 ACID | InnoDB 引擎支持 ACID,但默认 autocommit |
| 索引类型 | B-Tree、Hash、GiST、GIN、SP-GiST、BRIN | B-Tree、Hash(Memory 引擎)、R-Tree |
| JSON 支持 | JSONB 二级索引、GIN 索引,性能优异 | JSON 函数有限,无原生二进制存储 |
| 并发控制 | SSI(可序列化快照隔离)、行级锁 | MVCC + 间隙锁 |
| 扩展性 | 扩展生态丰富(PostGIS、TimescaleDB 等) | 插件较少,主要依赖存储引擎切换 |
| 复制 | 流复制、逻辑复制、级联复制 | 异步/半同步复制、组复制 |
| 全文搜索 | 内建 tsvector/tsquery + GIN 索引 | 需全文索引,功能有限 |
| 数组类型 | 原生支持 | 不支持 |
| 窗口函数 | 完善支持 | 8.0+ 支持,功能较 PostgreSQL 弱 |
PostgreSQL 的优势:
- 更丰富的索引类型和查询优化能力
- 原生支持 JSONB、数组、范围类型等复杂数据类型
- 强大的扩展生态(PostGIS 是业界标准的地理空间扩展)
- 更好的并发控制(SSI、不阻塞的 DDL)
- 对 SQL 标准的遵循度更高
PostgreSQL 的劣势:
- 主从复制的配置和管理比 MySQL 复杂
- 部分运维工具不如 MySQL 生态成熟
- 在某些简单只读场景下,MySQL InnoDB 的吞吐量更高
- 社区中文资料相对 MySQL 偏少
1.2 安装(Docker)
PostgreSQL 的安装方式非常灵活,包括源码编译、系统包管理器(apt/yum)、二进制分发包以及 Docker 容器化部署。本节重点介绍使用 Docker 部署的方式,因为它可以跨平台一致地运行,并且非常适合开发和测试环境。
Docker 部署
使用 Docker 快速启动 PostgreSQL 实例:
# 拉取最新 PostgreSQL 镜像
docker pull postgres:16
# 启动 PostgreSQL 容器
docker run -d \
--name postgres-demo \
-e POSTGRES_USER=admin \
-e POSTGRES_PASSWORD=secret123 \
-e POSTGRES_DB=demodb \
-p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
postgres:16
# 验证连接
docker exec -it postgres-demo psql -U admin -d demodb在 docker-compose.yml 中使用:
version: '3.8'
services:
postgres:
image: postgres:16
container_name: postgres-demo
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret123
POSTGRES_DB: demodb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
volumes:
pgdata:1.3 psql 基本使用
psql 是 PostgreSQL 自带的交互式命令行工具,它功能强大,不仅支持 SQL 语句的执行,还提供了丰富的元命令(metacommand)用于数据库管理和维护。熟练掌握 psql 是高效使用 PostgreSQL 的基础。
连接数据库
# 连接数据库
psql -h localhost -p 5432 -U admin -d demodb
# 或者使用连接字符串
psql "postgresql://admin:secret123@localhost:5432/demodb"常用元命令:
-- 列出所有数据库
\l
-- 切换数据库
\c demodb
-- 列出当前数据库中的所有表
\dt
-- 查看表结构
\d+ table_name
-- 列出所有索引
\di
-- 查看当前连接信息
\conninfo
-- 查看所有可用的 schema
\dn
-- 查看函数列表
\df
-- 查看视图
\dv
-- 查看执行计划
EXPLAIN ANALYZE SELECT * FROM users WHERE id = 1;
-- 计时开关
\timing on
-- 查询历史
\s
-- 导入 SQL 文件
\i /path/to/file.sql
-- 输出结果到文件
\o /path/to/output.txt
-- 退出
\q1.4 架构:进程模型、WAL、MVCC
进程模型
PostgreSQL 采用多进程架构(而非 MySQL 的多线程架构),每个客户端连接对应一个独立的操作系统进程。这种设计选择有其深刻的历史和技术原因。
为什么使用多进程而非多线程? PostgreSQL 起源于加州大学伯克利分校的 POSTGRES 项目,诞生于操作系统对多线程支持尚不成熟的年代。多进程架构的优势在于进程隔离——一个客户端连接崩溃不会影响其他连接,内存越界错误不会波及共享内存区域,单个连接的内存泄漏也可以随着进程退出而彻底回收。但这也意味着每个连接占用较多内存资源(每个进程约 5-10MB),因此在处理大量并发连接时,需要配合连接池工具(如 pgpool-II 或 PgBouncer)。
完整的进程结构:
Postmaster(守护进程)
├── Backend Process(每个客户端连接一个)
├── WAL Writer(写入 WAL 日志)
├── Checkpointer(执行检查点)
├── Autovacuum Launcher(自动清理)
├── Archiver(WAL 归档)
├── Stats Collector(统计信息收集)
├── Logical Replication Launcher(逻辑复制)
└── WAL Sender(流复制发送进程)这种架构的优势在于进程隔离——一个客户端连接崩溃不会影响其他连接,但也意味着每个连接占用较多内存资源。
WAL(Write-Ahead Logging)
WAL 是实现事务持久性和崩溃恢复的核心机制:
-- 查看 WAL 相关配置
SHOW wal_level; -- replica, logical, minimal
SHOW wal_buffers; -- WAL 缓冲区大小
SHOW wal_log_hints; -- 是否启用逻辑复制 hint
SHOW checkpoint_timeout; -- 检查点间隔
SHOW max_wal_size; -- WAL 最大大小
SHOW min_wal_size; -- WAL 最小大小
-- 查看当前 WAL 状态
SELECT * FROM pg_stat_wal;WAL 工作流程:
- 事务开始:事务 ID 分配
- 数据修改:将修改写入共享缓冲区
- WAL 记录:将 REDO 记录写入 WAL 缓冲区
- WAL 刷盘:WAL Writer 进程将 WAL 缓冲区写入磁盘
- 事务提交:COMMIT 只有在 WAL 记录落盘后才返回成功
- 检查点:Checkpointer 将脏页面写入数据文件,更新控制文件
MVCC(多版本并发控制)
PostgreSQL 的 MVCC 实现是数据库核心中最精妙的设计之一。与 Oracle 使用 undo 段、MySQL InnoDB 使用回滚段来存储旧版本的实现方式不同,PostgreSQL 采用了一种更为纯粹的方法——直接在数据文件中保留多个行版本。
MVCC 的核心思想: 每个事务在开始时会获取一个唯一的事务 ID(XID),并"看到"在该事务开始前已提交的所有数据版本,而看不到其他未提交事务或在该事务之后开始的事务所做的修改。读者永远不会阻塞写者,写者也不会阻塞读者——这是 MVCC 最根本的承诺。
关键系统列:
xmin:创建该行版本的事务 IDxmax:删除/更新该行版本的事务 ID(0 表示当前可见)ctid:物理行位置(页面号 + 槽位号)
MVCC 演示
为了直观理解 MVCC 的工作原理,我们通过以下实验来观察行版本的变化:
-- 创建实验表
CREATE TABLE mvcc_demo (
id SERIAL PRIMARY KEY,
value TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
INSERT INTO mvcc_demo (value) VALUES ('A'), ('B'), ('C');
-- 查看隐藏的系统列
SELECT ctid, xmin, xmax, * FROM mvcc_demo;MVCC 可视化实验:
-- 会话 1:开启事务并更新数据
BEGIN;
UPDATE mvcc_demo SET value = 'A_updated' WHERE id = 1;
-- 此时不提交,查看元组
SELECT ctid, xmin, xmax, * FROM mvcc_demo;
-- 可以看到 id=1 有两行:旧行 xmax=当前事务ID,新行 xmin=当前事务ID
COMMIT;
-- 清理(vacuum 会清除旧版本)
VACUUM VERBOSE mvcc_demo;2. 数据类型特色
PostgreSQL 拥有关系数据库中最丰富的数据类型系统之一。
2.1 数组(ARRAY)
PostgreSQL 原生支持数组类型,无需额外表:
-- 创建包含数组列的表
CREATE TABLE articles_with_tags (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
tags TEXT[] -- 文本数组
);
-- 插入数组数据
INSERT INTO articles_with_tags (title, tags) VALUES
('PostgreSQL 入门', ARRAY['数据库', '教程', '新手']),
('高级 SQL 技巧', ARRAY['SQL', '高级', '优化']),
('JSON 与 NoSQL', ARRAY['JSON', 'NoSQL', 'PostgreSQL']);
-- 或者使用花括号语法
INSERT INTO articles_with_tags (title, tags) VALUES
('索引优化实战', '{索引,优化,性能}');
-- 查询
SELECT * FROM articles_with_tags;
-- 数组包含(@>)
SELECT * FROM articles_with_tags
WHERE tags @> ARRAY['SQL'];
-- 数组重叠(&&)
SELECT * FROM articles_with_tags
WHERE tags && ARRAY['数据库', 'NoSQL'];
-- 数组长度
SELECT title, array_length(tags, 1) AS tag_count FROM articles_with_tags;
-- 解包数组为行
SELECT title, unnest(tags) AS tag FROM articles_with_tags;
-- 数组索引访问(PostgreSQL 数组从 1 开始)
SELECT title, tags[1] AS first_tag FROM articles_with_tags;
-- 数组追加
UPDATE articles_with_tags
SET tags = array_append(tags, 'PostgreSQL')
WHERE id = 1;
-- 数组去重
SELECT title, array_distinct(tags) FROM articles_with_tags;
-- 多维数组
CREATE TABLE matrix_data (
id SERIAL PRIMARY KEY,
data INTEGER[][]
);
INSERT INTO matrix_data (data) VALUES
(ARRAY[[1,2,3],[4,5,6],[7,8,9]]);
SELECT data[1:2][1:2] AS sub_matrix FROM matrix_data WHERE id = 1;
-- 结果: {{1,2},{4,5}}数组索引: 使用 GIN 索引加速数组包含和重叠查询:
CREATE INDEX idx_articles_tags ON articles_with_tags USING GIN (tags);2.2 JSON / JSONB
PostgreSQL 对 JSON 数据的支持可以说是所有关系数据库中最完善的。它提供了两种 JSON 类型:JSON(文本存储,保留完整格式和重复键)和 JSONB(二进制存储,支持索引,更高效)。在实际开发中,JSONB 几乎总是更优的选择,因为它不仅存储效率更高,还支持在 JSON 数据上直接创建索引和进行高效的包含性查询。
JSONB 的核心优势
JSONB 以二进制格式存储,解析速度远快于 JSON 类型。它在插入时会对 JSON 内容进行标准化处理——删除键之间的空白、压缩重复键(只保留最后一个值)、对键进行排序,这使得相同语义的 JSON 数据总是以一致的格式存储,从而可以实现精确的包含检测。
-- 创建测试表
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
attributes JSONB,
metadata JSON
);
-- 插入 JSONB 数据
INSERT INTO products (name, attributes, metadata) VALUES
('笔记本电脑', '{"brand": "Lenovo", "specs": {"cpu": "i7-13700H", "ram": "32GB", "storage": "1TB SSD"}, "price": 8999, "in_stock": true}', '{"created_by": "admin", "notes": "热门商品"}'),
('机械键盘', '{"brand": "Filco", "specs": {"layout": "87键", "switch": "Cherry MX 青轴"}, "price": 1499, "in_stock": false}', '{"created_by": "manager", "notes": "缺货中"}'),
('显示器', '{"brand": "Dell", "specs": {"size": "27寸", "resolution": "4K", "panel": "IPS"}, "price": 3999, "in_stock": true}', '{"created_by": "admin"}');
-- JSONB 查询操作符
-- 1. -> 返回 JSON 对象字段(保留 JSON 类型)
SELECT attributes -> 'brand' AS brand FROM products;
-- 2. ->> 返回文本
SELECT attributes ->> 'brand' AS brand FROM products;
-- 3. 嵌套路径查询
SELECT
name,
attributes -> 'specs' ->> 'cpu' AS cpu,
attributes -> 'specs' ->> 'ram' AS ram
FROM products
WHERE attributes @> '{"brand": "Lenovo"}'::jsonb;
-- 4. ? 检查存在性
SELECT name FROM products WHERE attributes ? 'warranty';
-- 5. ?| 任意一个存在
SELECT name FROM products WHERE attributes ?| ARRAY['warranty', 'price'];
-- 6. ?& 全部存在
SELECT name FROM products WHERE attributes ?& ARRAY['brand', 'specs', 'price'];
-- 7. @> 包含检查(最常用)
SELECT name FROM products
WHERE attributes @> '{"specs": {"ram": "32GB"}}'::jsonb;
-- 8. <@ 被包含
SELECT name FROM products
WHERE '{"brand": "Lenovo", "price": 8999}'::jsonb <@ attributes;
-- 9. 存在路径检查
SELECT name FROM products
WHERE attributes #>> '{specs, cpu}' = 'i7-13700H';
-- 10. JSONB 聚合
SELECT
jsonb_object_agg(brand, count) AS brand_counts
FROM (
SELECT attributes ->> 'brand' AS brand, count(*) AS count
FROM products
GROUP BY attributes ->> 'brand'
) t;
-- JSONB 修改与更新
-- 设置新字段
UPDATE products
SET attributes = jsonb_set(attributes, '{warranty}', '"2年"')
WHERE name = '笔记本电脑';
-- 删除字段
UPDATE products
SET attributes = attributes - 'price';
-- 合并两个 JSONB
UPDATE products
SET attributes = attributes || '{"discount": 0.1}'::jsonb;
-- JSONB 索引
-- 默认 GIN 索引(支持 @>、?、?|、?&)
CREATE INDEX idx_products_attributes ON products USING GIN (attributes);
-- 针对特定路径的 B-Tree 索引
CREATE INDEX idx_products_brand ON products USING BTREE ((attributes ->> 'brand'));JSONB 与 JSON 的选择:
- JSONB:一般场景首选。支持索引、删除重复键、高效查询、无空白保留
- JSON:仅当需要保留键的插入顺序、重复键、原始格式时才使用
2.3 范围类型
PostgreSQL 提供了丰富的范围类型,非常适合处理时间段、价格区间等数据:
-- 常用范围类型
-- int4range: 整数范围
-- int8range: 大整数范围
-- numrange: 数值范围
-- daterange: 日期范围
-- tsrange: 时间戳范围
-- tstzrange: 带时区的时间戳范围
CREATE TABLE event_bookings (
id SERIAL PRIMARY KEY,
room TEXT NOT NULL,
booking_period TSTZRANGE, -- 带时区的预约时间段
price_range NUMRANGE, -- 价格区间
guest_count INT4RANGE, -- 客人数量范围
date_range DATERANGE -- 日期范围
);
-- 插入范围数据
INSERT INTO event_bookings (room, booking_period, price_range, guest_count, date_range) VALUES
('会议室A', '[2024-09-15 09:00+08, 2024-09-15 12:00+08)', '[500, 1500]', '[10, 20]', '[2024-09-15, 2024-09-16)'),
('宴会厅', '[2024-10-01 18:00+08, 2024-10-01 22:00+08)', '[5000, 20000]', '[50, 200]', '[2024-10-01, 2024-10-02)'),
('培训室', '[2024-09-20 14:00+08, 2024-09-21 18:00+08)', '[2000, 5000]', '[20, 50]', '[2024-09-20, 2024-09-22)');
-- 范围操作符
-- @> 包含
SELECT * FROM event_bookings
WHERE booking_period @> '2024-09-15 10:00+08'::timestamptz;
-- && 重叠
SELECT * FROM event_bookings
WHERE booking_period && '[2024-09-14 00:00+08, 2024-09-16 00:00+08)'::tstzrange;
-- << 严格在左(不重叠且在前)
SELECT * FROM event_bookings
WHERE booking_period << '[2024-10-01 00:00+08, 2024-11-01 00:00+08)'::tstzrange;
-- >> 严格在右
SELECT * FROM event_bookings
WHERE booking_period >> '[2024-08-01 00:00+08, 2024-09-01 00:00+08)'::tstzrange;
-- -|- 相邻
SELECT * FROM event_bookings
WHERE booking_period -|- '[2024-09-15 12:00+08, 2024-09-15 18:00+08)'::tstzrange;
-- 范围函数
-- 获取范围下界
SELECT room, lower(booking_period) AS start_time FROM event_bookings;
-- 获取范围上界
SELECT room, upper(booking_period) AS end_time FROM event_bookings;
-- 检查范围是否为空
SELECT room, isempty(price_range) AS no_price_info FROM event_bookings;
-- 范围长度
SELECT room, upper(price_range) - lower(price_range) AS price_span FROM event_bookings;
-- 合并重叠范围
SELECT int4range(1, 5) + int4range(3, 8) AS union_range; -- [1,8)
SELECT int4range(1, 5) * int4range(3, 8) AS intersect_range; -- [3,5)
-- 使用 GiST 索引加速范围查询
CREATE INDEX idx_booking_period ON event_bookings USING GiST (booking_period);2.4 UUID、网络类型、几何类型
UUID
-- 开启 uuid-ossp 扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE TABLE users_uuid (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- 或使用 uuid_generate_v4()
-- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
-- 时间戳排序的 UUID v7(需要 pg_uuidv7 扩展)
-- CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
INSERT INTO users_uuid (name) VALUES ('Alice'), ('Bob'), ('Charlie');
SELECT * FROM users_uuid;网络类型
CREATE TABLE network_assets (
id SERIAL PRIMARY KEY,
device_name TEXT,
ip_address INET, -- IPv4/IPv6 地址
subnet CIDR, -- CIDR 网段
mac_address MACADDR -- MAC 地址
);
INSERT INTO network_assets VALUES
('web-server', '192.168.1.100/24', '192.168.1.0/24', '08:00:2b:01:02:03'),
('db-server', '10.0.0.50/16', '10.0.0.0/16', '08:00:2b:04:05:06'),
('monitor', '172.16.0.1/12', '172.16.0.0/12', '08:00:2b:07:08:09');
-- 网络包含检查
SELECT * FROM network_assets WHERE ip_address << '192.168.1.0/24'::cidr;
-- 获取网络地址
SELECT device_name, host(ip_address) AS host, netmask(ip_address) AS netmask,
network(ip_address) AS network FROM network_assets;
-- 排序(按 IP 数值)
SELECT * FROM network_assets ORDER BY ip_address;几何类型
CREATE TABLE locations (
id SERIAL PRIMARY KEY,
name TEXT,
coordinates POINT, -- 点 (x,y)
area POLYGON, -- 多边形
route PATH -- 路径
);
INSERT INTO locations VALUES
('公司总部', POINT(116.4074, 39.9042),
POLYGON '((116.40,39.90),(116.41,39.90),(116.41,39.91),(116.40,39.91))',
PATH '[(116.40,39.90),(116.41,39.91),(116.42,39.92)]');
-- 计算两点距离
SELECT name, coordinates <-> POINT(116.5, 39.9) AS distance FROM locations;
-- 圆形范围查询
SELECT * FROM locations
WHERE coordinates <@ CIRCLE '((116.4, 39.9), 0.1)';
-- 检查点是否在多边形内
SELECT name, coordinates @> POINT(116.41, 39.905) AS inside_area FROM locations;2.5 枚举与自定义类型
枚举类型
-- 创建枚举类型
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'shipped', 'delivered', 'cancelled');
-- 使用枚举
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer TEXT NOT NULL,
status order_status DEFAULT 'pending',
total NUMERIC(10,2),
created_at TIMESTAMPTZ DEFAULT now()
);
INSERT INTO orders (customer, status, total) VALUES
('张三', 'pending', 299.00),
('李四', 'shipped', 1599.00),
('王五', 'delivered', 89.90);
-- 枚举排序(按定义顺序)
SELECT * FROM orders ORDER BY status;
-- 枚举值列表
SELECT enum_range(NULL::order_status);
-- 添加新枚举值
ALTER TYPE order_status ADD VALUE 'refunded' AFTER 'cancelled';
-- 注意:枚举一旦创建,不能直接删除值(可用值但不用)自定义复合类型
-- 创建复合类型
CREATE TYPE address AS (
street TEXT,
city TEXT,
state TEXT,
zip_code TEXT,
country TEXT DEFAULT '中国'
);
-- 创建包含复合类型的表
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
home_address address,
work_address address
);
-- 插入复合类型数据
INSERT INTO customers (name, home_address, work_address) VALUES
('张三',
ROW('长安街1号', '北京', '北京', '100000'),
ROW('科技园路100号', '北京', '北京', '100080'));
-- 访问复合类型字段
SELECT
name,
(home_address).street,
(home_address).city,
(work_address).zip_code
FROM customers;
-- 支持索引复合类型字段
CREATE INDEX idx_customers_city ON customers (((home_address).city));
-- 创建域(Domain)——基于已有类型的约束
CREATE DOMAIN email_address AS TEXT
CHECK (VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');
CREATE TABLE contacts (
id SERIAL PRIMARY KEY,
email email_address
);
-- 合法
INSERT INTO contacts (email) VALUES ('user@example.com');
-- 非法,会报错
-- INSERT INTO contacts (email) VALUES ('not-an-email');3. 高级查询功能
3.1 窗口函数
窗口函数在不合并行的情况下对结果集进行聚合计算,是 PostgreSQL 最强大的功能之一。
-- 准备示例数据
CREATE TABLE sales_data (
id SERIAL PRIMARY KEY,
product TEXT NOT NULL,
category TEXT NOT NULL,
sales_amount NUMERIC(10,2),
sale_date DATE
);
INSERT INTO sales_data (product, category, sales_amount, sale_date) VALUES
('笔记本电脑', '电子产品', 15000, '2024-01-15'),
('手机', '电子产品', 12000, '2024-01-16'),
('T恤', '服装', 3000, '2024-01-15'),
('牛仔裤', '服装', 4500, '2024-01-17'),
('笔记本电脑', '电子产品', 18000, '2024-02-15'),
('手机', '电子产品', 11000, '2024-02-18'),
('T恤', '服装', 3500, '2024-02-20'),
('牛仔裤', '服装', 5000, '2024-02-22'),
('笔记本电脑', '电子产品', 20000, '2024-03-15'),
('手机', '电子产品', 13000, '2024-03-16'),
('T恤', '服装', 2800, '2024-03-18'),
('牛仔裤', '服装', 4800, '2024-03-20');
-- ROW_NUMBER:为每一行分配唯一编号
SELECT
product,
category,
sales_amount,
sale_date,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales_amount DESC) AS rank_by_category
FROM sales_data;
-- RANK:排名(相同值并列,跳过后续排名)
SELECT
product,
category,
sales_amount,
RANK() OVER (PARTITION BY category ORDER BY sales_amount DESC) AS rank
FROM sales_data;
-- DENSE_RANK:密集排名(相同值并列,不跳过排名)
SELECT
product,
category,
sales_amount,
DENSE_RANK() OVER (PARTITION BY category ORDER BY sales_amount DESC) AS dense_rank
FROM sales_data;
-- LAG:访问前一行的值
SELECT
product,
category,
sales_amount,
sale_date,
LAG(sales_amount, 1, 0) OVER (PARTITION BY product ORDER BY sale_date) AS prev_month_sales,
sales_amount - LAG(sales_amount, 1, 0) OVER (PARTITION BY product ORDER BY sale_date) AS month_growth
FROM sales_data;
-- LEAD:访问后一行的值
SELECT
product,
category,
sales_amount,
sale_date,
LEAD(sales_amount, 1) OVER (PARTITION BY product ORDER BY sale_date) AS next_month_sales
FROM sales_data;
-- 移动平均(3个月滑动窗口)
SELECT
product,
sale_date,
sales_amount,
AVG(sales_amount) OVER (
PARTITION BY product
ORDER BY sale_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3m
FROM sales_data;
-- FIRST_VALUE / LAST_VALUE
SELECT
product,
category,
sales_amount,
FIRST_VALUE(sales_amount) OVER (
PARTITION BY category ORDER BY sale_date
) AS first_sale_amount,
LAST_VALUE(sales_amount) OVER (
PARTITION BY category ORDER BY sale_date
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS last_sale_amount
FROM sales_data;
-- NTILE:分桶(例如分为4个四分位)
SELECT
product,
sales_amount,
NTILE(4) OVER (ORDER BY sales_amount) AS quartile
FROM sales_data;
-- 聚合函数+窗口
SELECT
product,
category,
sales_amount,
SUM(sales_amount) OVER (PARTITION BY category) AS category_total,
ROUND(sales_amount * 100.0 / SUM(sales_amount) OVER (PARTITION BY category), 2) AS pct_of_category
FROM sales_data;
-- CUME_DIST:累积分布
SELECT
sales_amount,
ROUND(CUME_DIST() OVER (ORDER BY sales_amount)::numeric, 4) AS cum_dist
FROM (SELECT DISTINCT sales_amount FROM sales_data) t
ORDER BY sales_amount;3.2 CTE 与递归 CTE
公用表表达式(Common Table Expression,CTE)是 SQL 中一种强大的查询构造工具,它允许你在一个查询中定义临时结果集,然后在后续的查询中引用它。CTE 不仅能提高复杂查询的可读性,还支持递归查询——这是处理树形结构和层次数据的重要手段。
基本 CTE
CTE 使用 WITH 关键字定义,可以在单个查询中定义多个 CTE,彼此用逗号分隔:
-- 基本 CTE
WITH high_value_sales AS (
SELECT product, SUM(sales_amount) AS total
FROM sales_data
GROUP BY product
HAVING SUM(sales_amount) > 10000
)
SELECT * FROM high_value_sales ORDER BY total DESC;-- 多 CTE
WITH
monthly_totals AS (
SELECT
DATE_TRUNC('month', sale_date) AS month,
SUM(sales_amount) AS total
FROM sales_data
GROUP BY DATE_TRUNC('month', sale_date)
),
top_products AS (
SELECT product, SUM(sales_amount) AS total
FROM sales_data
GROUP BY product
ORDER BY total DESC
LIMIT 3
)
SELECT 'monthly' AS type, month::TEXT AS label, total FROM monthly_totals
UNION ALL
SELECT 'product' AS type, product AS label, total FROM top_products;
-- CTE 可写(修改数据)
WITH deleted_old AS (
DELETE FROM orders
WHERE created_at < '2024-01-01'
RETURNING *
)
SELECT count(*) AS deleted_count FROM deleted_old;递归 CTE
递归 CTE 是 CTE 中最强大的功能之一。它由两个部分组成:基础案例(anchor member)生成初始结果集,递归步骤(recursive member)引用之前的结果进行迭代,直到不再返回新行。递归 CTE 非常适合处理树形结构、组织结构图、物料清单(BOM)等层次数据。
-- 递归 CTE:树形结构查询
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
manager_id INTEGER REFERENCES employees(id)
);
INSERT INTO employees (id, name, manager_id) VALUES
(1, 'CEO', NULL),
(2, 'CTO', 1),
(3, 'CFO', 1),
(4, '技术总监', 2),
(5, '财务主任', 3),
(6, '高级工程师', 4),
(7, '初级工程师', 6);
-- 递归 CTE:查找所有下属
WITH RECURSIVE org_tree AS (
-- 基础案例:根节点
SELECT id, name, manager_id, 0 AS level, ARRAY[id] AS path
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 递归步骤:连接下属
SELECT
e.id,
e.name,
e.manager_id,
ot.level + 1,
ot.path || e.id
FROM employees e
INNER JOIN org_tree ot ON e.manager_id = ot.id
)
SELECT
level,
REPEAT(' ', level) || name AS org_chart,
name,
path
FROM org_tree
ORDER BY path;
-- 递归 CTE:查询所有上级(反向)
WITH RECURSIVE report_chain AS (
SELECT id, name, manager_id, ARRAY[name] AS chain
FROM employees
WHERE name = '初级工程师'
UNION ALL
SELECT
e.id,
e.name,
e.manager_id,
rc.chain || e.name
FROM employees e
INNER JOIN report_chain rc ON e.id = rc.manager_id
)
SELECT chain FROM report_chain
WHERE manager_id IS NULL;
-- 递归 CTE:生成序列
WITH RECURSIVE number_series AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM number_series WHERE n < 10
)
SELECT * FROM number_series;
-- 递归 CTE:生成日历
WITH RECURSIVE calendar AS (
SELECT '2024-01-01'::DATE AS date
UNION ALL
SELECT date + 1 FROM calendar WHERE date < '2024-01-31'
)
SELECT
date,
EXTRACT(DOW FROM date) AS day_of_week,
TO_CHAR(date, 'Day') AS day_name
FROM calendar;3.3 LATERAL JOIN
LATERAL 允许子查询引用前面表(FROM 子句中靠左的表)的列,实现"对每一行执行子查询"的语义:
-- 场景:为每个分类找出销售额最高的前2个产品
-- 不使用 LATERAL(需要窗口函数+子查询)
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY sales_amount DESC) AS rn
FROM sales_data
)
SELECT * FROM ranked WHERE rn <= 2;
-- 使用 LATERAL
SELECT DISTINCT s.category, top_products.*
FROM (SELECT DISTINCT category FROM sales_data) cat
CROSS JOIN LATERAL (
SELECT product, sales_amount, sale_date
FROM sales_data s
WHERE s.category = cat.category
ORDER BY s.sales_amount DESC
LIMIT 2
) top_products;
-- LATERAL 结合 LEFT JOIN
SELECT * FROM categories c
LEFT JOIN LATERAL (
SELECT * FROM products p
WHERE p.category_id = c.id
ORDER BY p.price DESC
LIMIT 5
) top5 ON true;
-- 实战场景:最近交易
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name TEXT
);
CREATE TABLE transactions (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id),
amount NUMERIC(10,2),
created_at TIMESTAMPTZ DEFAULT now()
);
INSERT INTO customers (name) VALUES ('Alice'), ('Bob'), ('Charlie');
INSERT INTO transactions (customer_id, amount, created_at) VALUES
(1, 100, '2024-01-01'),
(1, 200, '2024-02-15'),
(1, 150, '2024-03-10'),
(2, 500, '2024-01-20'),
(2, 300, '2024-03-01'),
(3, 50, '2024-02-28');
-- 每个客户的最近3笔交易
SELECT
c.name,
recent.*
FROM customers c
LEFT JOIN LATERAL (
SELECT amount, created_at
FROM transactions t
WHERE t.customer_id = c.id
ORDER BY t.created_at DESC
LIMIT 3
) recent ON true;
-- LATERAL 在子查询中
SELECT * FROM products
WHERE EXISTS (
SELECT 1 FROM LATERAL (
SELECT (attributes ->> 'price')::NUMERIC AS p
) price_info
WHERE p > 100
);3.4 DISTINCT ON
DISTINCT ON 是 PostgreSQL 特有的语法,用于对每组数据只保留一条记录:
-- 每个分类中销售额最高的产品
SELECT DISTINCT ON (category)
category,
product,
sales_amount,
sale_date
FROM sales_data
ORDER BY category, sales_amount DESC, sale_date;
-- 每个产品的最近一次销售记录
SELECT DISTINCT ON (product)
product,
sales_amount,
sale_date
FROM sales_data
ORDER BY product, sale_date DESC;
-- 每个客户金额最大的订单
SELECT DISTINCT ON (customer_id)
customer_id,
amount,
created_at
FROM transactions
ORDER BY customer_id, amount DESC;
-- 注意:DISTINCT ON 的列必须与 ORDER BY 的起始列匹配
-- ORDER BY 中 DISTINCT ON 列后的额外列决定保留哪一行3.5 RETURNING 子句
RETURNING 子句可以在 INSERT、UPDATE、DELETE 操作后返回被影响行的数据,避免额外的查询:
-- INSERT ... RETURNING
INSERT INTO products (name, attributes) VALUES
('新鼠标', '{"brand": "Logitech", "price": 299}')
RETURNING id, name, created_at;
-- 批量插入并返回所有 ID
INSERT INTO products (name, attributes) VALUES
('键盘A', '{"price": 199}'),
('键盘B', '{"price": 299}'),
('键盘C', '{"price": 399}')
RETURNING id, name;
-- UPDATE ... RETURNING
UPDATE products
SET attributes = attributes || '{"discount": 0.15}'::jsonb
WHERE id = 1
RETURNING id, name, attributes -> 'discount' AS discount;
-- DELETE ... RETURNING
DELETE FROM products
WHERE id = 1
RETURNING id, name, attributes;
-- 结合 CTE 使用
WITH deleted AS (
DELETE FROM orders WHERE status = 'cancelled' RETURNING *
)
SELECT count(*) AS deleted_count, sum(total) AS refund_total FROM deleted;4. 全文搜索
PostgreSQL 内建全文搜索功能,无需外部搜索引擎即可实现高效的文本搜索。
4.1 tsvector / tsquery 基础
全文搜索的核心概念是词素(lexeme)——对单词进行标准化处理后的形式。PostgreSQL 的全文搜索分为两个阶段:文档的向量化(将文本转换为 tsvector)和查询的匹配(将搜索词转换为 tsquery)。
tsvector
tsvector 是文本经分词和标准化后得到的有序词素列表,包含每个词素在文档中的位置信息。例如,文本 "PostgreSQL is a powerful database system" 会被转换为类似 'databas':5 'postgresql':1 'power':3 'system':6 这样的结构,其中数字表示词素在原始文本中的位置。
tsquery
tsquery 表示搜索查询,支持布尔操作符:&(AND)、|(OR)、!(NOT)和短语搜索 <->(紧跟)或 <N>(相隔最多 N 个词)。
-- 创建全文搜索表
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
body TEXT NOT NULL,
search_vector TSVECTOR -- 预计算的 tsvector
);
-- 插入测试数据
INSERT INTO documents (title, body) VALUES
('PostgreSQL 基本概念', 'PostgreSQL 是一个强大的开源关系数据库系统,支持 ACID 事务和复杂查询'),
('全文搜索入门', '全文搜索允许用户快速在大量文本中找到相关文档,PostgreSQL 内置了强大的全文搜索功能'),
('索引优化技巧', '合理的索引设计可以大幅提升查询性能,PostgreSQL 支持多种索引类型包括 B-Tree 和 GIN'),
('高级 SQL 查询', '窗口函数、CTE 和 LATERAL JOIN 是 PostgreSQL 的高级查询特性');
-- 手动创建 tsvector
SELECT to_tsvector('english', 'PostgreSQL is a powerful database system');
-- 解析文本为词素
SELECT to_tsvector('english', 'running runs ran')::TEXT;
-- 结果: 'ran':4 'run':1,3
-- tsquery 查询
SELECT to_tsvector('english', 'PostgreSQL is a powerful database system') @@
to_tsquery('english', 'powerful & database');
-- 更新 search_vector 列
UPDATE documents
SET search_vector = to_tsvector('simple', title || ' ' || body);
-- 或者使用自动更新触发器
CREATE FUNCTION documents_search_vector_update()
RETURNS TRIGGER AS $$
BEGIN
NEW.search_vector := to_tsvector('simple', NEW.title || ' ' || NEW.body);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_documents_search_vector
BEFORE INSERT OR UPDATE ON documents
FOR EACH ROW
EXECUTE FUNCTION documents_search_vector_update();
-- 现在插入时会自动计算 tsvector
INSERT INTO documents (title, body) VALUES
('PostgreSQL 与 NoSQL', 'PostgreSQL 的 JSONB 支持使其兼具关系数据库和 NoSQL 数据库的优势');4.2 全文搜索查询
-- 基本全文搜索
SELECT id, title,
ts_rank(search_vector, query) AS rank
FROM documents,
to_tsquery('simple', 'PostgreSQL & 数据库') AS query
WHERE search_vector @@ query
ORDER BY rank DESC;
-- ts_rank 与 ts_rank_cd(覆盖密度排名)
SELECT id, title,
ts_rank(search_vector, to_tsquery('simple', 'PostgreSQL')) AS rank_standard,
ts_rank_cd(search_vector, to_tsquery('simple', 'PostgreSQL')) AS rank_coverage
FROM documents
WHERE search_vector @@ to_tsquery('simple', 'PostgreSQL');
-- 高亮搜索结果
SELECT id, title,
ts_headline('simple', body, to_tsquery('simple', 'PostgreSQL & 数据库'),
'StartSel=<mark>, StopSel=</mark>, MaxWords=30, MinWords=15') AS snippet
FROM documents
WHERE search_vector @@ to_tsquery('simple', 'PostgreSQL & 数据库');
-- 短语搜索(<-> 操作符)
SELECT * FROM documents
WHERE search_vector @@ to_tsquery('simple', 'PostgreSQL <-> 查询');
-- 距离搜索(<N> 表示两个词之间的最大距离)
SELECT * FROM documents
WHERE search_vector @@ to_tsquery('simple', 'PostgreSQL <2> 数据库');
-- 权重分配(A=标题,B=正文)
ALTER TABLE documents ADD COLUMN search_vector_weighted TSVECTOR;
UPDATE documents SET search_vector_weighted =
setweight(to_tsvector('simple', title), 'A') ||
setweight(to_tsvector('simple', body), 'B');
-- 带权重的搜索排名
SELECT id, title,
ts_rank(search_vector_weighted, query) AS rank
FROM documents,
to_tsquery('simple', 'PostgreSQL') AS query
WHERE search_vector_weighted @@ query
ORDER BY rank DESC;4.3 GIN 索引
没有索引的全文搜索需要全表扫描,GIN(Generalized Inverted Index)索引可将搜索性能提升几个数量级。GIN 索引的核心原理是倒排索引:它为每个词素维护一个包含该词素的文档列表。当执行 @@ 操作符查询时,PostgreSQL 可以快速定位到包含所有搜索词素的文档,而不需要扫描整个表。
GIN 索引的创建与使用
-- 创建 GIN 索引
CREATE INDEX idx_documents_search ON documents USING GIN (search_vector);
-- 查看索引大小
SELECT pg_size_pretty(pg_relation_size('idx_documents_search'));
-- 带索引的查询(自动使用索引)
EXPLAIN ANALYZE
SELECT id, title
FROM documents
WHERE search_vector @@ to_tsquery('simple', 'PostgreSQL')
ORDER BY ts_rank(search_vector, to_tsquery('simple', 'PostgreSQL')) DESC;
-- GiST 索引(备选,构建较慢但更新更快)
CREATE INDEX idx_documents_search_gist ON documents USING GiST (search_vector);
-- 查询性能对比
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM documents
WHERE search_vector @@ to_tsquery('simple', 'JSON & NoSQL');4.4 中文分词
PostgreSQL 原生不支持中文分词,这是因为它内置的分词器是为西方语言(英语、德语等)设计的,这些语言中的单词天然以空格分隔。中文文本没有明显的词边界,因此需要专门的中文分词工具才能实现良好的全文搜索效果。
方案一:使用 zhparser(推荐)
zhparser 是基于 SCWS(Simple Chinese Words Segmentation)的 PostgreSQL 扩展,是目前使用最广泛的中文分词方案。
-- 安装 zhparser(需要 root 权限)
-- git clone https://github.com/amutu/zhparser.git
-- cd zhparser && make && make install
CREATE EXTENSION IF NOT EXISTS 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 是一个强大的开源关系数据库系统');
-- 使用中文分词搜索
SELECT id, title,
ts_rank(search_vector_zh, query) AS rank
FROM documents,
to_tsquery('chinese', 'PostgreSQL & 数据库') AS query
WHERE search_vector_zh @@ query
ORDER BY rank DESC;使用 jieba(Python 方案)
如果 zhparser 不可用,可以使用 Python 的 jieba 分词配合 PostgreSQL:
-- 使用 plpython3u 扩展
CREATE EXTENSION IF NOT EXISTS plpython3u;
-- 创建 jieba 分词函数
CREATE OR REPLACE FUNCTION jieba_segment(text TEXT)
RETURNS TEXT[] AS $$
import jieba
return list(jieba.cut(text, cut_all=False))
$$ LANGUAGE plpython3u;
-- 使用自定义分词
SELECT jieba_segment('PostgreSQL 是一个强大的开源关系数据库系统');4.5 搜索排名优化
-- 综合排名算法
SELECT
id,
title,
-- 综合多个排名指标
ts_rank(search_vector, query, 32) * 0.6 + -- 词频权重
ts_rank_cd(search_vector, query, 32) * 0.4 -- 覆盖密度权重
AS composite_rank,
-- 头部高亮
ts_headline('simple', body, query,
'StartSel=<b>, StopSel=</b>, MaxWords=50, MinWords=20') AS snippet
FROM documents,
to_tsquery('simple', 'PostgreSQL & 查询') AS query
WHERE search_vector @@ query
ORDER BY composite_rank DESC
LIMIT 10;
-- 覆盖所有匹配的聚合搜索
SELECT
COUNT(*) FILTER (WHERE search_vector @@ query) AS total_matches,
COUNT(*) AS total_docs,
ROUND(
COUNT(*) FILTER (WHERE search_vector @@ query) * 100.0 / NULLIF(COUNT(*), 0),
2
) AS match_percentage
FROM documents,
to_tsquery('simple', 'PostgreSQL') AS query;5. 索引类型
PostgreSQL 支持多种索引类型,每种索引适用于不同的查询模式。索引是数据库性能调优中最重要也最复杂的领域之一。PostgreSQL 的索引系统非常灵活——你可以在同一列上创建不同类型的索引,查询优化器会根据查询条件自动选择最合适的索引。
5.1 索引概览
在深入每种索引之前,我们先创建一组测试数据来直观对比不同索引的性能特征。PostgreSQL 的索引类型包括:B-Tree(默认)、Hash、GiST、GIN、SP-GiST 和 BRIN,每种索引都有其特定的适用场景和数据类型的操作符类。
-- 准备测试数据(10 万行)
CREATE TABLE index_benchmark (
id SERIAL PRIMARY KEY,
int_col INTEGER,
text_col TEXT,
range_col INT4RANGE,
json_col JSONB,
ts_col TIMESTAMPTZ DEFAULT now(),
geom_col POINT
);
-- 插入示例数据
INSERT INTO index_benchmark (int_col, text_col, range_col, json_col, geom_col)
SELECT
floor(random() * 100000)::INT,
md5(random()::TEXT),
int4range(floor(random() * 100)::INT, floor(random() * 200 + 100)::INT),
jsonb_build_object('key', 'val_' || floor(random() * 1000)::INT),
POINT(random() * 180 - 90, random() * 360 - 180)
FROM generate_series(1, 100000);5.2 B-Tree 索引
B-Tree 是默认索引类型,适用于等值查询和范围查询:
-- 创建 B-Tree 索引(默认)
CREATE INDEX idx_btree_int ON index_benchmark (int_col);
-- 多列 B-Tree 索引
CREATE INDEX idx_btree_multi ON index_benchmark (int_col, text_col);
-- 降序索引
CREATE INDEX idx_btree_desc ON index_benchmark (int_col DESC, ts_col);
-- NULLS FIRST/LAST
CREATE INDEX idx_btree_nulls ON index_benchmark (text_col NULLS LAST);
-- B-Tree 支持的查询模式
EXPLAIN ANALYZE SELECT * FROM index_benchmark WHERE int_col = 500; -- 等值
EXPLAIN ANALYZE SELECT * FROM index_benchmark WHERE int_col BETWEEN 100 AND 200; -- 范围
EXPLAIN ANALYZE SELECT * FROM index_benchmark WHERE int_col < 1000; -- 小于
EXPLAIN ANALYZE SELECT * FROM index_benchmark ORDER BY int_col; -- 排序
EXPLAIN ANALYZE SELECT * FROM index_benchmark WHERE int_col IN (100, 200, 300); -- IN 列表5.3 Hash 索引
Hash 索引只适用于等值查询:
-- 创建 Hash 索引
CREATE INDEX idx_hash_text ON index_benchmark USING HASH (text_col);
-- 仅支持等值查询
EXPLAIN ANALYZE SELECT * FROM index_benchmark WHERE text_col = 'abc'; -- 可用
-- 不支持范围查询(不会使用 Hash 索引)
EXPLAIN ANALYZE SELECT * FROM index_benchmark WHERE text_col < 'abc';
-- 注意:PostgreSQL 10+ 后的 Hash 索引是 WAL-logged 且崩溃安全的
-- 但 B-Tree 在等值查询上通常也不差,所以 Hash 索引使用较少5.4 GiST 索引
GiST(Generalized Search Tree)是一个平衡树结构框架,支持多种数据类型和操作符:
-- GiST 适用于:几何类型、范围类型、全文搜索、FTS
-- 范围类型 GiST 索引
CREATE INDEX idx_gist_range ON index_benchmark USING GiST (range_col);
-- 支持的操作
EXPLAIN ANALYZE SELECT * FROM index_benchmark
WHERE range_col @> 50; -- 包含
EXPLAIN ANALYZE SELECT * FROM index_benchmark
WHERE range_col && int4range(10, 30); -- 重叠
EXPLAIN ANALYZE SELECT * FROM index_benchmark
WHERE range_col << int4range(200, 300); -- 严格在左
-- 几何类型 GiST 索引
CREATE INDEX idx_gist_point ON index_benchmark USING GiST (geom_col);
-- 最近邻查询(<-> 按距离排序)
EXPLAIN ANALYZE SELECT *
FROM index_benchmark
ORDER BY geom_col <-> POINT(0, 0)
LIMIT 10;
-- 多列 GiST
CREATE INDEX idx_gist_multi ON index_benchmark USING GiST (int_col, range_col);
-- 注意:GiST 多列索引对 int_col 的等值查询支持有限5.5 GIN 索引
GIN(Generalized Inverted Index)适用于包含多个组成元素的数据类型:
-- GIN 适用于:数组、JSONB、全文搜索
-- JSONB GIN 索引
CREATE INDEX idx_gin_json ON index_benchmark USING GIN (json_col);
-- JSONB 路径索引(jsonb_path_ops 更小更快,但仅支持 @>)
CREATE INDEX idx_gin_json_path ON index_benchmark USING GIN (json_col jsonb_path_ops);
-- 支持的操作
EXPLAIN ANALYZE SELECT * FROM index_benchmark
WHERE json_col @> '{"key": "val_500"}'::jsonb; -- 包含
-- 全文搜索 GIN 索引
CREATE INDEX idx_gin_fts ON documents USING GIN (search_vector);
-- 数组 GIN 索引
CREATE INDEX idx_gin_array ON articles_with_tags USING GIN (tags);5.6 SP-GiST 索引
SP-GiST(Space-Partitioned GiST)适用于空间分区数据结构:
-- SP-GiST 适用于:四叉树、k-d 树、trie
-- 几何类型 SP-GiST 索引
CREATE INDEX idx_spgist_point ON index_benchmark USING SPGiST (geom_col);
-- 优点:对某些空间分布比 GiST 更高效
-- 缺点:不支持 <<、>>、~= 等操作符
EXPLAIN ANALYZE SELECT * FROM index_benchmark
WHERE geom_col <@ BOX(POINT(-10, -10), POINT(10, 10)); -- 包含在矩形内5.7 BRIN 索引
BRIN(Block Range INdex)适用于与物理存储顺序高度相关的大表:
-- BRIN 适用于:时间序列、自增 ID、日志表
-- 创建 BRIN 索引
CREATE INDEX idx_brin_ts ON index_benchmark USING BRIN (ts_col);
CREATE INDEX idx_brin_int ON index_benchmark USING BRIN (int_col) WITH (pages_per_range = 32);
-- BRIN 索引大小对比
SELECT
pg_size_pretty(pg_relation_size('idx_btree_int')) AS btree_size,
pg_size_pretty(pg_relation_size('idx_brin_int')) AS brin_size;
-- BRIN 通常比 B-Tree 小 50-100 倍
-- BRIN 扫描
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM index_benchmark
WHERE ts_col BETWEEN '2024-01-01' AND '2024-06-30';
-- 选择 pages_per_range
-- 小值:更精确但索引更大
-- 大值:索引更小但可能返回更多假阳性5.8 部分索引、覆盖索引、表达式索引
-- 部分索引(Partial Index):只索引满足条件的行
CREATE INDEX idx_partial_high_value ON orders (total)
WHERE total > 1000;
-- 查询时自动使用部分索引
EXPLAIN ANALYZE SELECT * FROM orders WHERE total > 5000;
-- 覆盖索引(Covering/INCLUDE Index):包含额外列避免回表
CREATE INDEX idx_covering ON orders (customer_id) INCLUDE (total, status, created_at);
-- 此查询可完全从索引中获取数据(Index Only Scan)
EXPLAIN ANALYZE
SELECT customer_id, total, status
FROM orders
WHERE customer_id = '张三';
-- 表达式索引(Expression Index)
-- 对函数或表达式结果建立索引
CREATE INDEX idx_expr_lower_email ON contacts (lower(email));
CREATE INDEX idx_expr_json_price ON products (((attributes ->> 'price')::NUMERIC));
-- 查询
EXPLAIN ANALYZE SELECT * FROM contacts WHERE lower(email) = 'user@example.com';
EXPLAIN ANALYZE SELECT * FROM products WHERE (attributes ->> 'price')::NUMERIC > 500;5.9 索引与 ORDER BY
-- B-Tree 索引可加速排序
CREATE INDEX idx_order_date ON orders (created_at DESC);
-- 索引排序
EXPLAIN ANALYZE
SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
-- 多列排序与索引匹配
CREATE INDEX idx_order_customer_date ON orders (customer_id, created_at DESC);
-- 索引匹配的 ORDER BY
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = '张三'
ORDER BY created_at DESC;
-- 索引不匹配的 ORDER BY(需要额外排序)
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = '张三'
ORDER BY total DESC;
-- 索引统计信息查看
SELECT * FROM pg_stat_user_indexes WHERE relname = 'orders';6. 事务与并发
6.1 MVCC 实现细节
PostgreSQL 的 MVCC 通过行版本控制实现,每个元组(行版本)都有事务可见性信息。理解 MVCC 的底层机制对于诊断并发问题和优化数据库性能至关重要。
事务 ID 和可见性规则
每个事务在开始时都会获得一个单调递增的事务 ID(XID),由 txid_current() 函数返回。PostgreSQL 使用 xmin(创建者事务 ID)和 xmax(删除者事务 ID)两个隐藏字段来判断每一行对当前事务的可见性。可见性判断的基本规则如下:
- 如果行的
xmin在当前事务的快照之前已提交 → 该行可见 - 如果行的
xmin在当前事务之后开始 → 该行不可见 - 如果行的
xmax在当前事务的快照之前已提交 → 该行已删除,不可见 - 如果行的
xmax为 0 或等于当前事务 ID → 该行是当前行版本
-- 准备工作
CREATE TABLE mvcc_demo2 (
id SERIAL PRIMARY KEY,
value TEXT NOT NULL,
version INTEGER DEFAULT 1
);
-- 查看当前事务 ID
SELECT txid_current();
-- 事务隔离级别
SHOW transaction_isolation; -- 默认: read committed
-- 行可见性系统列详解
-- 插入新行
INSERT INTO mvcc_demo2 (value) VALUES ('Initial');
-- 查看元组信息
SELECT
ctid, -- 物理位置(页面:行偏移)
xmin, -- 创建该行版本的事务 ID
xmax, -- 删除/更新的事务 ID,0 表示可见
cmin, -- 创建命令的序号(事务内)
cmax, -- 删除命令的序号
*
FROM mvcc_demo2;
-- 更新行(实际上创建了一个新版本)
BEGIN;
UPDATE mvcc_demo2 SET value = 'Updated', version = 2 WHERE id = 1;
-- 此时可以看到两个元组
SELECT ctid, xmin, xmax, * FROM mvcc_demo2;
COMMIT;
-- 事务隔离演示
-- 会话 A
BEGIN;
UPDATE mvcc_demo2 SET value = 'SessionA', version = 3 WHERE id = 1;
-- 不提交
-- 会话 B(READ COMMITTED 下)
BEGIN;
SELECT * FROM mvcc_demo2 WHERE id = 1;
-- 看到 'Updated'(旧版本,因为 A 未提交)
-- 会话 A 提交后
-- 会话 B 再次查询(READ COMMITTED 会重新获取快照)
SELECT * FROM mvcc_demo2 WHERE id = 1;
-- 看到 'SessionA'(A 已提交)
-- 清理死元组
VACUUM VERBOSE mvcc_demo2;6.2 序列化快照隔离(SSI)
可序列化快照隔离(Serializable Snapshot Isolation, SSI)是 PostgreSQL 在 9.1 版本中引入的高级事务隔离级别,它通过检测读写依赖关系来发现可序列化冲突,从而完全消除幻读和写偏斜(write skew)等问题。
隔离级别概述
PostgreSQL 支持 SQL 标准定义的所有四种事务隔离级别,但底层实现只有三种不同的行为模式:
- 读未提交(READ UNCOMMITTED):在 PostgreSQL 中,此级别行为等同于读已提交,因为 PostgreSQL 不允许脏读
- 读已提交(READ COMMITTED):默认级别,每个语句都会获取一个新的快照,因此同一个事务内的不同 SELECT 可能看到不同的数据
- 可重复读(REPEATABLE READ):事务开始时获取一个快照,整个事务期间使用该快照,可避免不可重复读和幻读
- 可序列化(SERIALIZABLE):最严格的级别,通过 SSI 算法检测串行化冲突,确保事务执行结果等同于某种串行执行顺序
-- 查看和设置事务隔离级别
-- 读未提交(在 PostgreSQL 中行为等同于读已提交)
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 读已提交(默认)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 可重复读
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 可序列化
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- SSI 演示:检测序列化冲突
-- 会话 A
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 会话 B
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
-- 其中一个会话提交时会收到:
-- ERROR: could not serialize access due to read/write dependencies among transactions
-- DETAIL: Reason code: Canceled on identification as a pivot, during commit attempt.
-- HINT: The transaction might succeed if retried.
-- 查看序列化冲突统计
SELECT * FROM pg_stat_database WHERE datname = current_database();
-- serialization_failures 列显示序列化冲突次数6.3 锁类型与监控
-- PostgreSQL 锁类型
-- 1. 表级锁
LOCK TABLE orders IN ACCESS SHARE MODE; -- SELECT 获取
LOCK TABLE orders IN ROW SHARE MODE; -- SELECT ... FOR UPDATE
LOCK TABLE orders IN EXCLUSIVE MODE; -- DDL 操作
LOCK TABLE orders IN ACCESS EXCLUSIVE MODE; -- DROP TABLE, VACUUM FULL
-- 2. 行级锁
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE; -- 行级排他锁
SELECT * FROM orders WHERE id = 2 FOR NO KEY UPDATE; -- 较弱排他锁
SELECT * FROM orders WHERE id = 3 FOR SHARE; -- 行级共享锁
SELECT * FROM orders WHERE id = 4 FOR KEY SHARE; -- 最弱行级锁
COMMIT;
-- 3. 锁监控
-- 当前锁信息
SELECT
l.locktype,
l.relation::regclass AS relation,
l.mode,
l.granted,
a.pid,
a.state,
a.query,
a.wait_event_type,
a.wait_event
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE a.pid <> pg_backend_pid()
ORDER BY a.query_start;
-- 锁等待统计
SELECT
relation::regclass AS table_name,
mode,
granted,
pid,
wait_event
FROM pg_locks
WHERE NOT granted;
-- 阻塞链查询
WITH blocking_info AS (
SELECT
a.pid AS blocked_pid,
a.query AS blocked_query,
l.pid AS blocking_pid,
l.mode
FROM pg_stat_activity a
JOIN pg_locks l ON l.pid != a.pid
WHERE a.wait_event_type = 'Lock'
)
SELECT * FROM blocking_info;
-- 死锁日志位置
SHOW log_directory;
SHOW log_filename;
-- 设置死锁超时
SHOW deadlock_timeout; -- 默认 1s6.4 死锁检测
-- 死锁演示
-- 准备数据
CREATE TABLE account_a (id INT PRIMARY KEY, balance NUMERIC(10,2));
CREATE TABLE account_b (id INT PRIMARY KEY, balance NUMERIC(10,2));
INSERT INTO account_a VALUES (1, 1000);
INSERT INTO account_b VALUES (1, 1000);
-- 会话 A
BEGIN;
UPDATE account_a SET balance = balance - 100 WHERE id = 1;
-- 然后等待会话 B
-- 会话 B
BEGIN;
UPDATE account_b SET balance = balance - 100 WHERE id = 1;
-- 然后等待会话 A
-- 会话 A 尝试获取 B 的锁
UPDATE account_b SET balance = balance + 100 WHERE id = 1;
-- 阻塞...
-- 会话 B 尝试获取 A 的锁
UPDATE account_a SET balance = balance + 100 WHERE id = 1;
-- 产生死锁,其中一个会话会被终止:
-- ERROR: deadlock detected
-- DETAIL: Process X waits for ShareLock on transaction Y; blocked by process Z.
-- Process Z waits for ShareLock on transaction Y; blocked by process X.
-- HINT: See server log for query details.
-- 预防死锁:使用一致的锁顺序
-- 例如,总是先锁 A 再锁 B7. 扩展生态
PostgreSQL 的扩展生态是其最强优势之一,几乎所有功能都可以通过扩展来增强。
7.1 PostGIS(地理空间)
PostGIS 是 PostgreSQL 的地理空间扩展,也是业界公认最强大、最成熟的开源地理空间数据库引擎。它遵循 OpenGIS 规范,支持数百种空间操作函数和多种空间数据类型,被广泛应用于 GIS 系统、位置服务(LBS)、地理数据分析等领域。
PostGIS 的核心功能
- 空间数据类型:Point、LineString、Polygon、MultiPoint、MultiLineString、MultiPolygon、GeometryCollection 等
- 空间索引:基于 GiST 的 R-Tree 索引,支持高效的空间查询
- 空间函数:ST_Distance、ST_Area、ST_Intersects、ST_Within、ST_Buffer 等 300+ 函数
- 坐标参考系统:支持 EPSG 坐标系,自动进行坐标转换
- 地理类型:GEOGRAPHY 类型支持基于球面的距离计算(更精确)
-- 安装
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
-- 验证安装
SELECT postgis_version();
SELECT postgis_full_version();
-- 创建地理空间表
CREATE TABLE poi_locations (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
location GEOGRAPHY(Point, 4326), -- WGS84 经纬度
geom GEOMETRY(Point, 3857) -- Web Mercator 投影
);
-- 插入地理数据
INSERT INTO poi_locations (name, location, geom) VALUES
('天安门广场', ST_GeogFromText('SRID=4326;POINT(116.3972 39.9087)'),
ST_Transform(ST_GeomFromText('POINT(116.3972 39.9087)', 4326), 3857)),
('故宫博物院', ST_GeogFromText('SRID=4326;POINT(116.3970 39.9163)'),
ST_Transform(ST_GeomFromText('POINT(116.3970 39.9163)', 4326), 3857)),
('上海东方明珠', ST_GeogFromText('SRID=4326;POINT(121.4997 31.2397)'),
ST_Transform(ST_GeomFromText('POINT(121.4997 31.2397)', 4326), 3857));
-- 创建 GiST 空间索引
CREATE INDEX idx_poi_geog ON poi_locations USING GiST (location);
CREATE INDEX idx_poi_geom ON poi_locations USING GiST (geom);
-- 距离查询
SELECT
name,
ST_Distance(location, ST_GeogFromText('SRID=4326;POINT(116.4074 39.9042)')) AS distance_m
FROM poi_locations
ORDER BY location <-> ST_GeogFromText('SRID=4326;POINT(116.4074 39.9042)')
LIMIT 5;
-- 半径搜索(5公里内)
SELECT name,
ST_Distance(location, ST_GeogFromText('SRID=4326;POINT(116.4074 39.9042)')) AS distance_m
FROM poi_locations
WHERE ST_DWithin(location, ST_GeogFromText('SRID=4326;POINT(116.4074 39.9042)'), 5000)
ORDER BY distance_m;
-- 几何计算
SELECT
ST_Area(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')) AS area_sq_units,
ST_Length(ST_GeomFromText('LINESTRING(0 0, 1 1, 2 0)')) AS length_units;
-- 空间连接
SELECT a.name AS point_a, b.name AS point_b,
ST_Distance(a.location, b.location) AS distance
FROM poi_locations a
CROSS JOIN poi_locations b
WHERE a.id < b.id
ORDER BY distance;7.2 pg_stat_statements
pg_stat_statements 是 PostgreSQL 自带的查询性能分析核心工具。它跟踪所有 SQL 语句的执行统计信息,包括执行次数、总耗时、最小/最大耗时、I/O 统计、缓冲区命中率等。对于定位性能瓶颈和慢查询优化至关重要。
安装与配置
-- 安装
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- 配置(在 postgresql.conf 中)
-- shared_preload_libraries = 'pg_stat_statements'
-- pg_stat_statements.max = 10000
-- pg_stat_statements.track = all
-- 重启后查看配置
SHOW shared_preload_libraries;
-- 查询性能统计
SELECT
queryid,
LEFT(query, 80) AS query_preview,
calls,
total_exec_time / calls AS avg_time_ms,
min_exec_time,
max_exec_time,
rows,
shared_blks_hit,
shared_blks_read,
shared_blks_dirtied,
shared_blks_written,
temp_blks_read,
temp_blks_written,
blk_read_time,
blk_write_time
FROM pg_stat_statements
WHERE query NOT LIKE '%pg_stat%'
ORDER BY total_exec_time DESC
LIMIT 20;
-- 查看最频繁的查询
SELECT
LEFT(query, 100) AS query,
calls,
ROUND(total_exec_time::NUMERIC, 2) AS total_ms,
ROUND((total_exec_time / calls)::NUMERIC, 2) AS avg_ms
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 10;
-- 查看 I/O 最多的查询
SELECT
LEFT(query, 100) AS query,
calls,
shared_blks_read,
shared_blks_hit,
ROUND(shared_blks_hit * 100.0 / NULLIF(shared_blks_hit + shared_blks_read, 0), 2) AS hit_ratio
FROM pg_stat_statements
WHERE shared_blks_read > 0
ORDER BY shared_blks_read DESC
LIMIT 10;
-- 重置统计信息
SELECT pg_stat_statements_reset();7.3 pg_partman(分区管理)
自动管理表分区的扩展:
-- 安装
CREATE EXTENSION IF NOT EXISTS pg_partman;
CREATE SCHEMA partman;
-- 创建分区表
CREATE TABLE measurement (
log_time TIMESTAMPTZ NOT NULL,
device_id INTEGER,
value NUMERIC(10,2)
) PARTITION BY RANGE (log_time);
-- 使用 pg_partman 自动创建和管理分区
SELECT partman.create_parent(
p_parent_table := 'public.measurement',
p_control := 'log_time',
p_type := 'native',
p_interval := '1 day', -- 每天一个分区
p_premake := 30 -- 预创建未来 30 天
);
-- 查看分区信息
SELECT * FROM partman.part_config
WHERE parent_table = 'public.measurement';
-- 手动维护分区
-- 创建新分区
CALL partman.create_partitions('public.measurement', 7);
-- 删除旧分区(保留最近 90 天)
CALL partman.partition_data_proc(
'public.measurement',
p_interval := '1 day',
p_batch := 1000,
p_keep_table := 'f'
);
-- 设置自动维护
UPDATE partman.part_config
SET infinite_time_partitions = true,
retention = '90 days',
retention_keep_table = false
WHERE parent_table = 'public.measurement';
-- 创建模板表用于新分区的结构
CREATE TABLE measurement_template (LIKE measurement INCLUDING ALL);
-- 更新配置使用模板
UPDATE partman.part_config
SET template_table = 'public.measurement_template'
WHERE parent_table = 'public.measurement';7.4 pgBackRest 备份
企业级备份和恢复工具:
# pgbackrest 配置示例(/etc/pgbackrest/pgbackrest.conf)
# [demodb]
# pg1-path=/var/lib/postgresql/16/main
# pg1-port=5432
# pg1-user=postgres
#
# [global]
# repo1-path=/backup/pgbackrest
# repo1-retention-full=4
# repo1-retention-diff=14
# repo1-cipher-type=aes-256-cbc
# repo1-cipher-pass=<secret>
# compress-type=zst
# compress-level=6
# process-max=4-- 在数据库内检查备份配置
SELECT * FROM pg_settings WHERE name LIKE '%archive%';
-- 查看归档状态
SELECT * FROM pg_stat_archiver;
-- 使用 pgbackrest 的命令行
-- 全量备份
-- pgbackrest --stanza=demodb --type=full backup
-- 增量备份
-- pgbackrest --stanza=demodb --type=incr backup
-- 差异备份
-- pgbackrest --stanza=demodb --type=diff backup
-- 列出备份
-- pgbackrest --stanza=demodb info
-- 恢复到指定时间点
-- pgbackrest --stanza=demodb --type=time "--target=2024-09-13 12:00:00+08" restore8. 高可用
8.1 流复制(Streaming Replication)
异步和同步流复制是 PostgreSQL 高可用的基础。流复制的核心原理是:主库将 WAL 日志实时传输到从库,从库持续回放这些 WAL 记录,从而与主库保持数据同步。这种方式相比传统的基于文件的日志 shipping 延迟更低、数据一致性更好。
流复制的配置步骤
第一步:主库配置
-- 主库配置(postgresql.conf)
-- listen_addresses = '*'
-- wal_level = replica -- 或 logical
-- max_wal_senders = 10
-- wal_keep_size = 1024 -- MB
-- hot_standby = on
-- 创建复制用户
CREATE USER replicator WITH REPLICATION LOGIN PASSWORD 'replica_secret';
-- 配置 pg_hba.conf
-- host replication replicator 192.168.1.0/24 scram-sha-256第二步:从库初始化
-- 从库创建基础备份
-- pg_basebackup -h primary_ip -p 5432 -U replicator -D /var/lib/postgresql/16/standby -Fp -Xs -P -R
-- 从库配置(standby.signal 文件表示这是一个备用节点)
-- touch /var/lib/postgresql/16/main/standby.signal
-- 从库 postgresql.conf
-- primary_conninfo = 'host=primary_ip port=5432 user=replicator password=replica_secret'
-- recovery_target_timeline = 'latest'
-- hot_standby = on第三步:监控复制状态
-- 监控复制状态(主库)
SELECT
client_addr,
state, -- streaming / catchup / startup
sync_state, -- sync / async / potential
write_lag,
flush_lag,
replay_lag,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), write_lsn)) AS write_delay
FROM pg_stat_replication;
-- 监控复制状态(从库)
SELECT
pg_is_in_recovery(),
pg_last_wal_receive_lsn(),
pg_last_wal_replay_lsn(),
pg_last_xact_replay_timestamp(),
GREATEST(
0,
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))
) AS replay_lag_seconds
FROM pg_stat_wal_receiver;
-- 同步复制配置
-- 在 postgresql.conf 中设置
-- synchronous_standby_names = 'FIRST 2 (*)'
-- 查看同步状态
SELECT application_name, sync_priority, sync_state FROM pg_stat_replication;
-- 级联复制(从库还可以有从库)
-- 分布式从库设置
-- primary_conninfo = 'host=中间层从库_ip port=5432 user=replicator password=replica_secret'8.2 Patroni + etcd 高可用
Patroni 是一个用 Python 编写的 PostgreSQL 高可用框架,它能自动管理故障转移、健康检查和集群拓扑变更。Patroni 结合分布式一致性存储(如 etcd、Consul、ZooKeeper),可以实现真正的自动化主从切换,无需人工干预。
Patroni 的工作原理
Patroni 通过 DCS(分布式配置存储,如 etcd)来协调集群中的各个节点。每个 Patroni 节点定期向 etcd 写入心跳信息,同时从 etcd 读取集群的当前状态。如果主库的心跳超时,Patroni 会通过选举算法从可用的从库中选出新的主库,整个过程通常只需要几十秒。
Patroni 配置文件示例
# patroni.yml 配置示例
scope: pg_cluster
namespace: /db/
name: pg-node1
restapi:
listen: 0.0.0.0:8008
connect_address: 192.168.1.10:8008
etcd:
host: 192.168.1.100:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_wal_senders: 10
max_replication_slots: 10
wal_log_hints: "on"
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 0.0.0.0/0 md5
- host all all 0.0.0.0/0 md5
postgresql:
listen: 0.0.0.0:5432
connect_address: 192.168.1.10:5432
data_dir: /data/postgresql/16/main
bin_dir: /usr/lib/postgresql/16/bin
authentication:
replication:
username: replicator
password: replica_secret
superuser:
username: postgres
password: super_secret
rewind:
username: rewind_user
password: rewind_secret
tags:
nofailover: false
noloadbalance: false
clonefrom: false
replicatefrom: null# 启动 Patroni
patroni /etc/patroni/patroni.yml
# 查看集群状态
patronictl -c /etc/patroni/patroni.yml list
# 手动切换
patronictl -c /etc/patroni/patroni.yml switchover pg_cluster
# 故障转移
patronictl -c /etc/patroni/patroni.yml failover pg_cluster
# 重新初始化失败的节点
patronictl -c /etc/patroni/patroni.yml reinit pg_cluster pg-node2
# 查看集群拓扑
patronictl -c /etc/patroni/patroni.yml topology pg_cluster8.3 pgpool-II 连接池
pgpool-II 是一个功能强大的 PostgreSQL 中间件,提供连接池、读写分离、负载均衡和自动故障转移等功能。在生产环境中,使用 pgpool-II 可以有效解决 PostgreSQL 多进程架构带来的连接数上限问题。
pgpool-II 的核心功能
- 连接池:复用后端数据库连接,减少连接建立的开销,缓解 PostgreSQL 对大量并发连接的支持压力
- 读写分离:将 SELECT 查询路由到从库,INSERT/UPDATE/DELETE 路由到主库,充分利用从库资源
- 负载均衡:支持基于权重的读请求分配策略,在多从库场景下分散查询压力
- 自动故障转移:后端节点故障时自动切换,配合 watchdog 实现 pgpool-II 自身的高可用
pgpool.conf 关键配置
# pgpool.conf 关键配置
# 连接池设置
max_pool = 4 # 每个子进程的连接池大小
num_init_children = 32 # 初始子进程数
child_life_time = 300 # 子进程最大生存时间
child_max_connections = 0 # 子进程处理最大连接数(0=不限)
connection_life_time = 0 # 连接缓存时间(0=不限)
client_idle_limit = 0 # 客户端空闲断开时间(0=不限)
# 后端数据库节点配置
backend_hostname0 = '192.168.1.10'
backend_port0 = 5432
backend_weight0 = 1 # 读权重
backend_data_directory0 = '/data/postgresql/16/main'
backend_flag0 = 'ALLOW_TO_FAILOVER'
backend_hostname1 = '192.168.1.20'
backend_port1 = 5432
backend_weight1 = 2 # 从库分配更多读流量
backend_data_directory1 = '/data/postgresql/16/main'
backend_flag1 = 'ALLOW_TO_FAILOVER'
# 复制模式
replication_mode = off
load_balance_mode = on # 启用负载均衡
master_slave_mode = on # 主从模式
master_slave_sub_mode = 'stream' # 流复制模式
# 健康检查
health_check_period = 10 # 健康检查周期(秒)
health_check_timeout = 20 # 超时
health_check_user = 'pgpool'
health_check_password = 'pgpool_pass'
health_check_max_retries = 3
health_check_retry_delay = 1
# 自动故障转移
failover_command = '/etc/pgpool/failover.sh %H %P %R'
follow_master_command = '/etc/pgpool/follow_master.sh %H %P %R'
# 连接池
connection_cache = on-- 查看 pgpool 节点状态
SHOW pool_nodes;
-- 结果示例
-- node_id | hostname | port | status | lb_weight | role
-- --------+----------------+------+--------+-----------+--------
-- 0 | 192.168.1.10 | 5432 | up | 0.3333 | primary
-- 1 | 192.168.1.20 | 5432 | up | 0.6667 | standby
-- 查看连接池状态
SHOW pool_pools;
-- 查看进程统计
SHOW pool_processes;
-- 验证读写分离
-- 写操作会被路由到主库
INSERT INTO test VALUES (1);
-- 读操作可能被路由到从库(取决于负载均衡策略)
SELECT * FROM test;
-- 强制在主库读取
/*NO LOAD BALANCE*/ SELECT * FROM test;总结
PostgreSQL 作为世界上最先进的开源关系数据库,凭借其丰富的功能集、强大的扩展生态和对 SQL 标准的严格遵循,已经成为现代应用开发的首选数据库之一。无论是初创公司的 MVP 产品,还是大型企业的核心交易系统,PostgreSQL 都能提供稳定可靠的数据存储服务。
核心优势回顾:
- 数据类型丰富:数组、JSONB、范围类型、网络类型、几何类型、枚举和自定义复合类型,可以轻松处理各种结构化数据需求,减少应用层的类型转换工作
- 高级查询能力:窗口函数支持复杂的分组统计和排名计算,递归 CTE 能够优雅地处理树形结构和层次数据,LATERAL JOIN 实现逐行子查询,DISTINCT ON 实现每组取 TOP N,RETURNING 子句减少重复查询
- 全文搜索:内建 tsvector/tsquery 结合 GIN 索引,支持中文分词和搜索排名,在多数场景下可以替代 Elasticsearch 等专用搜索引擎
- 多样索引:B-Tree、Hash、GiST、GIN、SP-GiST、BRIN 六种索引类型配合部分索引、覆盖索引和表达式索引,为各种查询模式提供最优的加速方案
- 并发控制:成熟的 MVCC 实现从不阻塞读写,可序列化快照隔离(SSI)解决了写偏斜等并发异常问题,完整的锁监控机制帮助诊断死锁和锁等待
- 强大扩展:PostGIS 是地理空间领域的黄金标准,pg_stat_statements 是查询性能分析的必备工具,pg_partman 自动管理表分区,pgBackRest 提供企业级备份恢复
- 高可用方案:流复制 + Patroni + pgpool-II 构建生产级高可用架构,自动故障转移确保业务连续性
无论是构建微服务、数据仓库、地理信息系统还是全文搜索应用,PostgreSQL 都能提供企业级的解决方案。随着 PostgreSQL 社区的持续发展和每一年的大版本更新,它正在不断缩小与商业数据库的差距,并在越来越多的领域展现出独特的优势。
本文由 Hermes Agent 撰写,以 PostgreSQL 16 版本为基础。所有示例代码可直接在 PostgreSQL 16+ 环境中运行验证。