Skip to content

PostgreSQL 能做什么 ​

本节通过具体场景和可运行的 SQL 示例,展示 PostgreSQL 的核心能力边界。学完本节,你将了解它如何用一个数据库同时覆盖事务处理、分析、文档、搜索、地理、时序、队列和向量检索等场景,从而判断哪些需求可以不必再引入其他中间件。

前置知识

阅读本节前,建议先了解:PostgreSQL 是什么

基础概念 ​

PostgreSQL 的独特之处在于它的可扩展架构(Extensible Architecture):数据类型、索引方法、函数语言、外部数据源都可以由扩展(Extension)动态装载,而不需要修改内核。正因如此,同一个 PostgreSQL 实例可以扮演多种角色。

按照工作负载类型,可以把它的能力分为两类:

负载类型全称特征典型场景
OLTPOnline Transaction Processing,联机事务处理大量短小事务、高并发、按主键/索引访问少量行电商下单、账户流水、订单状态流转
OLAPOnline Analytical Processing,联机分析处理少量复杂查询、扫描大量行、聚合与分组报表统计、指标看板、数据仓库
HTAPHybrid Transactional/Analytical Processing,混合负载同时承载事务与分析中小规模业务的一体化数据库

传统做法是"MySQL 跑 OLTP + 数据同步到 ClickHouse/ES 跑 OLAP"。PostgreSQL 在中小数据规模下可以一套实例兼顾两者,靠的是并行查询、JIT 编译、覆盖索引(Index-Only Scan)、分区裁剪(Partition Pruning)等能力;数据量真正大到单机无法承载时,再考虑拆分。

除事务与分析外,PostgreSQL 还能承担以下角色,这些能力大多以扩展形式提供:

能力依赖一句话说明
文档存储内置 JSONB像 MongoDB 一样存半结构化数据,且支持事务
全文搜索内置 tsvector / tsquery像 Elasticsearch 一样做分词检索与相关性排序
地理空间PostGIS 扩展像 PostGIS/MongoDB 一样做经纬度、距离、区域计算
时序数据TimescaleDB 扩展 / 内置分区像 InfluxDB 一样存指标、做时间窗口聚合
消息队列LISTEN/NOTIFY + FOR UPDATE SKIP LOCKED轻量级任务队列,无需引入 Kafka/RabbitMQ
向量检索pgvector 扩展像专用向量库一样做相似度搜索,支撑 RAG
外部数据源postgres_fdw / file_fdw直接查询其他 PostgreSQL 或 CSV 文件
定时任务pg_cron 扩展在库内调度周期性 SQL

下面逐项给出最简示例。注意:扩展类能力需要先安装扩展,示例中已标注 CREATE EXTENSION 语句。

语法与用法 ​

要判断某项能力是否可用,可以先查询当前实例已装有哪些扩展:

sql
-- 查看当前数据库中已安装的扩展
SELECT extname, extversion, nspname AS schema_name
FROM pg_extension e
JOIN pg_namespace n ON n.oid = e.extnamespace
ORDER BY extname;

-- 查看当前实例"可安装"的扩展(需先安装扩展包 RPM/DEB)
SELECT name, default_version, comment
FROM pg_available_extensions
ORDER BY name;

安装扩展的标准语句:

sql
-- 安装扩展,通常需要超级用户权限
CREATE EXTENSION IF NOT EXISTS pgvector;
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- 升级扩展版本
ALTER EXTENSION pgvector UPDATE;

-- 卸载扩展
DROP EXTENSION pgvector;

详细说明 ​

场景一:OLTP 事务处理 ​

这是 PostgreSQL 最核心的用途。它通过多版本并发控制(MVCC, Multi-Version Concurrency Control) 实现"读不阻塞写、写不阻塞读",并支持完整的事务语义。

sql
-- 下单:扣库存 + 建订单,必须在同一个事务内完成
BEGIN;

-- FOR UPDATE 锁定该商品行,防止并发超卖
UPDATE products
SET stock = stock - 1
WHERE sku = 'SKU-1001' AND stock > 0;

INSERT INTO orders (order_no, sku, qty, amount, status)
VALUES ('ORD-20260921-0001', 'SKU-1001', 1, 399.00, 'pending');

COMMIT;
sql
-- PostgreSQL 9.5+:UPSERT,存在则更新、不存在则插入
INSERT INTO products (sku, name, price, stock)
VALUES ('SKU-1001', '机械键盘', 379.00, 100)
ON CONFLICT (sku)
DO UPDATE SET price = EXCLUDED.price,
              stock = products.stock + EXCLUDED.stock
RETURNING id, sku, price, stock;

场景二:OLAP 分析查询 ​

PostgreSQL 支持并行顺序扫描、哈希聚合、窗口函数、分组集合,加上物化视图可以支撑中小规模的分析需求。

sql
-- 按日、按商品分类统计销售额,用窗口函数算占比
SELECT
  date_trunc('day', o.created_at)      AS stat_date,
  p.category,
  SUM(o.amount)                        AS total_amount,
  ROUND(
    100.0 * SUM(o.amount) / SUM(SUM(o.amount)) OVER (PARTITION BY date_trunc('day', o.created_at)),
    2
  )                                    AS pct_of_day
FROM orders o
JOIN products p ON p.sku = o.sku
WHERE o.created_at >= now() - INTERVAL '7 days'
  AND o.status = 'paid'
GROUP BY 1, 2
ORDER BY 1 DESC, total_amount DESC;
sql
-- 分组集合:一次查询同时得到"按分类""按渠道""总计"三层汇总
SELECT
  COALESCE(p.category, '(全部)') AS category,
  COALESCE(o.channel,  '(全部)') AS channel,
  SUM(o.amount) AS total_amount
FROM orders o
JOIN products p ON p.sku = o.sku
WHERE o.status = 'paid'
GROUP BY GROUPING SETS ((p.category), (o.channel), ())
ORDER BY category, channel;

场景三:JSON 文档存储 ​

JSONB 是二进制存储的 JSON,支持索引、操作符与函数,是"关系 + 文档"混用的关键。JSONB 在 PostgreSQL 9.4+ 提供(JSON 类型在 9.2+,但仅存原文不做解析)。

sql
-- 用 JSONB 存储商品的可变属性,避免频繁改表
CREATE TABLE product_attrs (
  id      BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sku     TEXT   NOT NULL UNIQUE,
  attrs   JSONB  NOT NULL DEFAULT '{}'
);

INSERT INTO product_attrs (sku, attrs) VALUES
  ('SKU-1001', '{"color": "黑色", "switch": "青轴", "wireless": false, "weight_g": 890}'),
  ('SKU-1002', '{"color": "灰色", "material": "网布", "adjustable": true}')
ON CONFLICT (sku) DO NOTHING;

-- -> 取 JSON 对象字段(返回 JSONB),->> 取文本值
SELECT sku,
       attrs ->> 'color'      AS color,
       attrs ->> 'switch'     AS switch_type,
       (attrs ->> 'weight_g')::INT AS weight_g
FROM product_attrs
WHERE attrs ? 'switch';      -- ? 判断是否存在该键
sql
-- 为 JSONB 建 GIN 索引,加速包含查询
CREATE INDEX idx_product_attrs_gin ON product_attrs USING GIN (attrs);

-- @> 包含:找出所有"无线且青轴"的商品
SELECT sku, attrs
FROM product_attrs
WHERE attrs @> '{"wireless": true, "switch": "青轴"}'::jsonb;

场景四:全文搜索 ​

PostgreSQL 内置 tsvector(文档向量)与 tsquery(查询向量),支持词干还原、停用词、权重与相关性排序。中文分词需配合 zhparser 等扩展或使用 simple 配置按字切分。

sql
-- 建全文搜索表,用生成列自动维护 tsvector
CREATE TABLE articles (
  id        BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  title     TEXT NOT NULL,
  body      TEXT NOT NULL,
  -- PostgreSQL 12+ 支持 STORED 生成列;18 中默认即为 virtual
  search_vec TSVECTOR
    GENERATED ALWAYS AS (
      setweight(to_tsvector('simple', coalesce(title, '')), 'A') ||
      setweight(to_tsvector('simple', coalesce(body,  '')), 'B')
    ) STORED
);

CREATE INDEX idx_articles_search ON articles USING GIN (search_vec);

-- 检索并按相关性排序
SELECT id, title, ts_rank(search_vec, query) AS rank
FROM articles,
     to_tsquery('simple', 'postgresql & 索引') AS query
WHERE search_vec @@ query
ORDER BY rank DESC
LIMIT 10;
sql
-- pg_trgm 扩展:支持模糊匹配与 LIKE '%...%' 的索引加速
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_articles_title_trgm ON articles USING GIN (title gin_trgm_ops);

-- 相似度匹配,% 是相似度阈值操作符
SELECT title, similarity(title, 'postgres 索引优化') AS sim
FROM articles
WHERE title % 'postgres 索引优化'
ORDER BY sim DESC;

场景五:地理空间(PostGIS) ​

PostGIS 是 PostgreSQL 最著名的扩展之一,提供几何类型、空间索引(GiST/SP-GiST)与数百个空间函数。

sql
CREATE EXTENSION IF NOT EXISTS postgis;

-- 存储门店经纬度,geography 类型以米为单位计算,适合跨经度场景
CREATE TABLE stores (
  id       BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name     TEXT NOT NULL,
  location GEOGRAPHY(POINT, 4326) NOT NULL
);

INSERT INTO stores (name, location) VALUES
  ('深圳南山店', ST_SetSRID(ST_MakePoint(113.9304, 22.5333), 4326)::geography),
  ('深圳福田店', ST_SetSRID(ST_MakePoint(114.0550, 22.5410), 4326)::geography);

CREATE INDEX idx_stores_location ON stores USING GIST (location);

-- 查询 3 公里内的门店,并按距离升序
SELECT name,
       ROUND(ST_Distance(location, ST_MakePoint(113.9345, 22.5400)::geography)::NUMERIC, 1) AS distance_m
FROM stores
WHERE ST_DWithin(location, ST_MakePoint(113.9345, 22.5400)::geography, 3000)
ORDER BY distance_m;

场景六:时序数据 ​

时序场景不一定要装 TimescaleDB:PostgreSQL 10+ 的声明式分区(Declarative Partitioning) 配合 BRIN 索引,已能支撑相当规模的指标存储。

sql
-- 按月分区存储设备指标
CREATE TABLE metrics (
  device_id  BIGINT      NOT NULL,
  ts         TIMESTAMPTZ NOT NULL,
  cpu_usage  REAL        NOT NULL,
  mem_usage  REAL        NOT NULL
) PARTITION BY RANGE (ts);

-- 创建 2026 年 9 月与 10 月的分区
CREATE TABLE metrics_2026_09 PARTITION OF metrics
  FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
CREATE TABLE metrics_2026_10 PARTITION OF metrics
  FOR VALUES FROM ('2026-10-01') TO ('2026-11-01');

-- BRIN 索引对"按时间递增写入"的数据极其高效,体积仅为 B-tree 的几百分之一
CREATE INDEX idx_metrics_ts_brin ON metrics USING BRIN (ts);

-- 时间窗口聚合,分区裁剪会自动跳过无关分区
SELECT date_trunc('hour', ts) AS hour,
       ROUND(AVG(cpu_usage)::NUMERIC, 2) AS avg_cpu,
       ROUND(MAX(mem_usage)::NUMERIC, 2) AS max_mem
FROM metrics
WHERE device_id = 1001
  AND ts >= '2026-09-01' AND ts < '2026-10-01'
GROUP BY 1
ORDER BY 1;

场景七:轻量消息队列 ​

不引入 Kafka 也能做任务队列:SELECT ... FOR UPDATE SKIP LOCKED(PostgreSQL 9.5+)让多个消费者并行抢任务而互不阻塞;LISTEN/NOTIFY 则用于实时通知。

sql
-- 任务表
CREATE TABLE jobs (
  id          BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  payload     JSONB       NOT NULL,
  status      TEXT        NOT NULL DEFAULT 'queued',  -- queued/running/done/failed
  run_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
  locked_by   TEXT,
  locked_at   TIMESTAMPTZ,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_jobs_queue ON jobs (run_at) WHERE status = 'queued';

-- 消费者原子领取一个任务:SKIP LOCKED 跳过别人已锁定的行
WITH job AS (
  SELECT id FROM jobs
  WHERE status = 'queued' AND run_at <= now()
  ORDER BY run_at
  LIMIT 1
  FOR UPDATE SKIP LOCKED
)
UPDATE jobs j
SET status = 'running', locked_by = 'worker-1', locked_at = now()
FROM job
WHERE j.id = job.id
RETURNING j.id, j.payload;
sql
-- LISTEN/NOTIFY:发布-订阅式通知,适合缓存失效、配置热更新
-- 会话 A(订阅方)
LISTEN order_created;

-- 会话 B(发布方)
SELECT pg_notify('order_created', '{"order_no": "ORD-20260921-0001"}');

场景八:向量检索(pgvector) ​

pgvector 扩展让 PostgreSQL 直接存储 embedding 并做相似度检索,是构建 RAG(检索增强生成)应用的常见选择。

sql
CREATE EXTENSION IF NOT EXISTS pgvector;

CREATE TABLE documents (
  id        BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  content   TEXT   NOT NULL,
  embedding VECTOR(1024) NOT NULL      -- 维度需与模型输出一致
);

-- HNSW 索引:近似最近邻检索,查询快、构建慢
CREATE INDEX idx_documents_embedding ON documents
  USING hnsw (embedding vector_cosine_ops);

-- <=> 是余弦距离操作符,越小越相似
SELECT id, content, 1 - (embedding <=> '[...]'::vector) AS similarity
FROM documents
ORDER BY embedding <=> '[...]'::vector
LIMIT 5;

能力选型速查 ​

需求首选方案何时该换专门系统
事务写入内置写入超过单机 IOPS 上限 → 分库分表
分析报表内置 + 物化视图单表数十亿行且要求亚秒响应 → ClickHouse
文档存储JSONB + GIN无固定 schema 且写入极密集 → MongoDB
全文搜索tsvector + GIN需复杂分词/聚合/高亮 → Elasticsearch
地理空间PostGIS需要全球瓦片服务 → 专用 GIS 平台
时序分区 + BRIN / TimescaleDB千万级/秒写入 → InfluxDB/Timescale
消息队列SKIP LOCKED 队列需要跨机房多副本高吞吐 → Kafka
向量检索pgvector(HNSW)十亿级向量 → Milvus/Qdrant

实战示例 ​

综合案例:一个"电商商品中心"用同一套表同时支撑事务、文档、搜索与向量推荐。

bash
# 建库并连接
createdb -h 127.0.0.1 -U postgres demo
psql -h 127.0.0.1 -U postgres -d demo
sql
-- 第 1 步:安装需要用到的基础扩展
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- 第 2 步:建表 —— 结构化列 + JSONB 属性列 + 全文搜索生成列
CREATE TABLE products (
  id          BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sku         TEXT           NOT NULL UNIQUE,
  name        TEXT           NOT NULL,
  category    TEXT           NOT NULL,
  price       NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
  stock       INTEGER        NOT NULL DEFAULT 0 CHECK (stock >= 0),
  attrs       JSONB          NOT NULL DEFAULT '{}',
  search_vec  TSVECTOR
    GENERATED ALWAYS AS (
      setweight(to_tsvector('simple', coalesce(name, '')), 'A') ||
      setweight(to_tsvector('simple', coalesce(category, '')), 'B')
    ) STORED,
  created_at  TIMESTAMPTZ    NOT NULL DEFAULT now()
);

-- 第 3 步:建三类索引,分别服务 JSONB 查询、模糊搜索、全文检索
CREATE INDEX idx_products_attrs_gin  ON products USING GIN (attrs);
CREATE INDEX idx_products_search_gin ON products USING GIN (search_vec);
CREATE INDEX idx_products_name_trgm  ON products USING GIN (name gin_trgm_ops);

-- 第 4 步:灌入测试数据
INSERT INTO products (sku, name, category, price, stock, attrs) VALUES
  ('SKU-1001', '机械键盘',     '外设',  399.00, 120, '{"color": "黑色", "wireless": false}'),
  ('SKU-1002', '人体工学椅',   '家具', 1299.00,  35, '{"material": "网布", "adjustable": true}'),
  ('SKU-1003', '显示器支架',   '配件',  199.00, 200, '{"material": "铝合金", "max_load_kg": 9}'),
  ('SKU-1004', '无线机械键盘', '外设',  559.00,  80, '{"color": "白色", "wireless": true}');
sql
-- 第 5 步:建订单表,并用一次事务完成"扣库存 + 建订单"
CREATE TABLE IF NOT EXISTS orders (
  id         BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  order_no   TEXT           NOT NULL UNIQUE,
  sku        TEXT           NOT NULL REFERENCES products (sku),
  qty        INTEGER        NOT NULL CHECK (qty > 0),
  amount     NUMERIC(10, 2) NOT NULL CHECK (amount >= 0),
  status     TEXT           NOT NULL DEFAULT 'pending',
  created_at TIMESTAMPTZ    NOT NULL DEFAULT now()
);

BEGIN;

-- 带 stock >= 1 的条件,保证并发下不会扣成负数
UPDATE products SET stock = stock - 1
WHERE sku = 'SKU-1004' AND stock >= 1;

INSERT INTO orders (order_no, sku, qty, amount, status)
VALUES ('ORD-20260921-0001', 'SKU-1004', 1, 559.00, 'paid');

COMMIT;
sql
-- 第 6 步:文档式查询 —— 找出"无线"的键盘
SELECT sku, name, price, attrs ->> 'color' AS color
FROM products
WHERE attrs @> '{"wireless": true}'::jsonb
  AND name LIKE '%键盘%';
text
   sku    |     name     | price  | color
----------+--------------+--------+-------
 SKU-1004 | 无线机械键盘 | 559.00 | 白色
(1 row)
sql
-- 第 7 步:全文检索 —— 检索词必须与分词结果精确匹配
SELECT sku, name
FROM products
WHERE search_vec @@ to_tsquery('simple', '机械键盘')
ORDER BY name;
text
   sku    |   name
----------+------------
 SKU-1001 | 机械键盘
(1 row)

注意这里只命中了 SKU-1001,没有命中 SKU-1004(无线机械键盘)。原因正如后文"注意事项"所述:默认解析器把连续的汉字当作一个词元,机械键盘 与 无线机械键盘 是两个不同的词元,因此无法互相匹配。再验证一下:

sql
-- 查询 '键盘' 什么也匹配不到,因为不存在单独的词元 '键盘'
SELECT sku, name
FROM products
WHERE search_vec @@ to_tsquery('simple', '键盘');
text
 sku | name
-----+------
(0 rows)

这正是中文全文搜索必须引入 zhparser / pg_jieba 等分词扩展的根本原因。

sql
-- 第 8 步:模糊匹配 —— 输入有错别字也能命中
-- 相似度阈值默认为 0.3;`%` 操作符即"相似度 >= 阈值",走 GIN 索引
SHOW pg_trgm.similarity_threshold;

SELECT sku, name, ROUND(similarity(name, '无线机械健盘')::NUMERIC, 3) AS sim
FROM products
WHERE name % '无线机械健盘'
ORDER BY sim DESC;
text
   sku    |     name     |  sim
----------+--------------+-------
 SKU-1004 | 无线机械键盘 | 0.400
(1 row)

pg_trgm 的原理:把字符串切成连续三元组(Trigram)集合,similarity(a, b) 返回"两个集合的交集大小 / 并集大小"。即使输入有错别字,只要大部分三元组仍然相同,相似度就会保持在阈值之上,因此能被 % 命中。值得注意的是,相似度是基于字符的,与语义无关——这正是它与向量检索的本质区别。

sql
-- 全量查看相似度排名,便于理解阈值的作用
SELECT sku, name, ROUND(similarity(name, '无线机械健盘')::NUMERIC, 3) AS sim
FROM products
ORDER BY sim DESC;
text
   sku    |     name     |  sim
----------+--------------+-------
 SKU-1004 | 无线机械键盘 | 0.400
 SKU-1001 | 机械键盘     | 0.000
 SKU-1002 | 人体工学椅   | 0.000
 SKU-1003 | 显示器支架   | 0.000
(4 rows)
sql
-- 第 9 步:聚合 —— 按分类看库存与均价,过滤掉库存过低的分类
SELECT category,
       COUNT(*)                      AS sku_count,
       SUM(stock)                    AS total_stock,
       ROUND(AVG(price), 2)          AS avg_price,
       MIN(price)                    AS min_price,
       MAX(price)                    AS max_price
FROM products
GROUP BY category
HAVING SUM(stock) >= 50
ORDER BY total_stock DESC;
text
 category | sku_count | total_stock | avg_price | min_price | max_price
----------+-----------+-------------+-----------+-----------+-----------
 配件     |         1 |         200 |    199.00 |    199.00 |    199.00
 外设     |         2 |         199 |    479.00 |    399.00 |    559.00
(2 rows)

注意"家具"分类(总库存 35)被 HAVING 过滤掉了——这正是 HAVING 与 WHERE 的分工:WHERE 过滤行,HAVING 过滤分组结果。

注意事项 ​

  • JSONB 不是"万能列"。高频过滤、排序、连接的字段应建为普通列;把一切都塞进 JSONB 会丧失约束、统计信息与查询优化能力。
sql
-- 不推荐:把业务关键字段全部塞进 JSONB
CREATE TABLE orders_bad (
  id     BIGINT PRIMARY KEY,
  data   JSONB NOT NULL   -- order_no、amount、status 全在里面,无法建约束
);

-- 推荐:关键字段建列,附加属性用 JSONB 补充
CREATE TABLE orders (
  id         BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  order_no   TEXT           NOT NULL UNIQUE,
  amount     NUMERIC(10, 2) NOT NULL CHECK (amount >= 0),
  status     TEXT           NOT NULL DEFAULT 'pending',
  extra      JSONB          NOT NULL DEFAULT '{}'
);
  • JSON 与 JSONB 别混用。JSON 保留原始文本(含重复键、空格),每次访问都要重新解析;JSONB 解析后二进制存储,支持索引。除非要保留原始格式,否则一律用 JSONB。
  • 全文搜索的中文分词需要额外配置。默认的 simple 配置按空白与标点切分,中文会被当成一个整词,需配合 zhparser、pg_jieba 等扩展。
sql
-- 错误!默认 english 配置会把中文整段当成一个词,几乎搜不到
SELECT to_tsvector('english', '机械键盘手感很好');
-- 结果:'机械键盘手感很好':1   (整段成为一个词元)

-- 正确:装 zhparser 后用中文配置分词
-- CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
SELECT to_tsvector('chinese', '机械键盘手感很好');
  • FOR UPDATE SKIP LOCKED 只适合队列,不适合业务加锁。它会让查询"跳过"被锁行,用在库存扣减上会静默漏掉数据。
  • 分区不是免费的。分区数过多会拖慢规划阶段(Planning Time),建议单表控制在几百到几千个分区以内。
  • 扩展需要操作系统层面安装。CREATE EXTENSION pgvector 报 "could not open extension control file" 说明只装了数据库、没装扩展包。
  • 向量索引的维度必须与模型一致。VECTOR(1024) 的行插入 768 维向量会直接报错。
  • LISTEN/NOTIFY 不保证持久化。服务重启期间的通知会丢失,不能当作可靠消息队列使用。

最佳实践 ​

  • 先用内置能力,再考虑引入扩展。能用标准 SQL 与 JSONB 解决的,不要急着装扩展。
  • 一个数据库实例承担的职责不宜过多。搜索、向量这类重负载若 QPS 很高,独立实例部署比混布更稳。
  • 为 JSONB 建 GIN 索引时注意写放大。GIN 索引更新成本高于 B-tree,写入密集的表要评估 fastupdate 参数(默认 on)带来的延迟。
  • 全文检索的 tsvector 用生成列维护,避免在应用层手写触发器同步。
  • 时序数据优先用"分区 + BRIN",只有在需要自动压缩、连续聚合、降采样时才引入 TimescaleDB。
  • 队列表要定期归档已完成任务,否则 jobs 表会随业务量无限膨胀,拖慢 SKIP LOCKED 扫描。
  • CREATE EXTENSION 纳入版本管理。把扩展定义写进迁移脚本,避免各环境扩展版本不一致。
  • 评估"一体化"的真实边界:单机 PostgreSQL 在千万级行、数百 QPS 的规模下表现优异;超过这个量级就该认真考虑拆分,而不是继续堆参数。

下一节

继续学习:发展历史与版本演进

参考链接 ​