本文是写者准备的笔记,希望通过分享可以帮助没有接触过postgreSQL的人快速入门,对其底层原理有一个大概了解。但若您没有接触过数据结构及数据库,看起来可能会比较吃力,建议要先了解数据结构和数据库(如:MySQL,Oracle等),不过您也可以先收藏。
PostgreSQL jsonb
:底层原理 + 常用操作 + 索引策略
原理(为什么 jsonb
比 json
快)
jsonb
并不是把 JSON 当文本存起来,而是把 JSON 解析、规范化后保存成二进制内部结构(token 列表 + 类型标记 + 指针/偏移),因此可以直接按键/值访问、做结构化检索和索引;同时重复键会被合并(以最后一个为准),键的顺序不保留。这个二进制/规范化的存储方式是 jsonb
性能优势的根源。 ([PostgreSQL][1])
常用运算符(语义要能背清楚)
->
:返回jsonb
值(对象/数组)。->>
:返回文本(text 值)。@>
:是否包含(containment),比如data @> '{"role":"admin"}'
。?
/?|
/?&
:键存在判断。#>
/#>>
:路径访问(嵌套字段)。
理解这些运算符,能帮你判断哪种索引会生效(比如@>
最容易命中 GIN)。
索引原理(为什么用 GIN?)
- GIN(倒排索引) 的思想:把每个"键/词/lexeme"当做词典项,索引里为每个词保留一个"posting list"(行的引用集合)。查找时先定位词,再去定位对应的行------非常适合"包含/存在/多值"检索(例如
jsonb
、tsvector
、数组)。 - 优点:查找(AND/包含)速度快;缺点:写入/更新开销大(需要维护 posting lists)。 ([PostgreSQL][2])
jsonb_path_ops
vs jsonb_ops
(重点)
jsonb_ops
(默认)为每个键和值都生成单独的索引项,功能全面(可支持存在/包含/键值查找等),但索引更大。jsonb_path_ops
更紧凑,只支持部分操作(最常用于@>
包含查询),因此索引更小、查询@>
更快,但不支持所有jsonb
操作。面试中要能说出:如果你只做包含检查,jsonb_path_ops
更好;如果你需要键/值的更多灵活查找,选jsonb_ops
。 ([PostgreSQL][1])
表达式索引
如果常查 data->>'status' = 'active'
,比起给整个 jsonb 建 GIN,更轻量的方式是为 (data->>'status')
建 btree
表达式索引:
sql
CREATE INDEX idx_users_status ON users ((info->>'status'));
这是 CPU+I/O / 空间折衷的常见做法 ------ 面试里常被要求解释"为什么不用整列 GIN"。
写/读权衡与维护
- GIN 查快、写慢;GiST/BTREE 写快一点。
- 大量写入场景可以:用批量插入 + 临时禁用索引/后建索引,或使用部分索引(只索引活跃数据)来降低开销。 ([PostgreSQL][2])
2) 全文检索(PostgreSQL FTS)--- 原理及讲解
基本原理(词项化 → 词干化 → 向量化)
- 文本通过
to_tsvector(config, text)
被转换成一组词项(lexemes) ,同时移除停用词、做词干化(stemming)、规范化(小写、去标点)。这就是tsvector
的内容:词项 + 出现位置等元信息。查询端把用户查询变成tsquery
(plainto_tsquery
/to_tsquery
/websearch_to_tsquery
)。匹配用@@
运算符。Postgres 提供ts_rank
/ts_rank_cd
用于相关度评分。 ([PostgreSQL][3])
索引 & 实现要点
tsvector
通常用 GIN 索引(倒排索引非常适合检索词项)。在高并发写场景请注意 GIN 的写成本。- 实战建议:把
tsvector
作为**持久列(generated/stored)**保存(避免每次查询运行to_tsvector
),并在其上建 GIN 索引,这样查询速度和稳定性最好。 ([PostgreSQL][4])
排名/短语/前缀匹配
ts_rank
可按词频、位置给予评分。to_tsquery('foo & bar')
支持布尔组合;to_tsquery('foo:*')
支持词前缀。选择合适的字典(english
/simple
等)会影响词干化与停用词。
3) 索引类型内部机理(btree / GIN / GiST / SP-GiST / BRIN)------要点 + 原理
B-tree(经典)
- 基于平衡 B 树页结构(有序分支),适用于等值/范围/排序查询(
=
/<
/>
/ORDER BY
)。典型、用途最广。
GIN(倒排索引)------"词到行"
- 内部维护"词 ➜ posting list";查询时合并 posting lists。非常高效做多值/包含查询(
jsonb
、tsvector
、数组)。但插入/更新需要维护 posting list,开销大(尤其 posting list 很长时)。维护时可通过增加maintenance_work_mem
加速索引构建。 ([PostgreSQL][2])
GiST(广义搜索树)------"可以把很多问题变成范围/相似性问题"
- GiST 不是具体的数据结构,而是"索引框架"------允许扩展通过定义"内涵/分割/距离"的方式索引复杂类型(例如几何的 bounding-box)。GiST 对空间索引(PostGIS)非常重要,也支持 KNN(最近邻)查询(利用
<->
等运算符实现)。GiST 更新通常比 GIN 更轻(更适合动态数据)。 ([PostgreSQL][2], [Crunchy Data][5])
SP-GiST(空间分割)
- 适合把空间分区(quadtrees、k-d 树思想)用到数据库索引,对某些大规模点集合在查询速度和索引大小上有优势(但比 GiST 更特殊)。
BRIN(块范围索引)
- 适合超大表且数据按某规律局部性强(按时间、按空间范围排序的表)。BRIN 占空间小、维护便宜,但适用场景有限。
4) PostGIS(空间)------ 数据存储、SRID、精度与函数原理
geometry vs geography(要能清楚回答"选哪个")
geometry
:平面笛卡尔数学模型,适合投影坐标系(比如 UTM),单位取决于 SRID(SRID=3857/xxx 单位通常为米或投影单位)。计算快、功能多、适用于本地/区域性数据。geography
:大地测量模型(球面/椭球面),以经纬度存储但在距离计算时使用球面或大地线算法,查询距离以米 为单位,适合全球/跨地区距离计算,但函数选择更少、计算更慢。你在面试中要能给出"什么时候选 geography、什么时候选 geometry"的理由(如果数据区域小并且你能处理投影,优先用geometry
+ 正确投影;如果跨洲/跨经度线/你要省去投影学习成本,选geography
)。 ([PostGIS][6], [PostGIS][7])
SRID 与 ST_Transform
(原理)
- SRID 指定了坐标参考系(CRS)。
ST_Transform(geom, SRID)
是把几何从一个 CRS 投影/转换到另一个 CRS(内部调用 PROJ 库做数学变换)。要做精确的面积/距离计量,通常把数据投影到合适的"等距/等面积/局部 UTM"投影再计算。
5) 常用 PostGIS 函数及其"索引/性能"原理(重点)
ST_DWithin(a,b,d)
------ 布尔:两者是否在 d 单位内。
- 非常重要 :
ST_DWithin
在实现上会自动做"bounding-box(包围盒)"预过滤(因此可以利用 GiST/GiST-like 空间索引),随后对候选集合执行精确几何距离计算(避免对全表做精确计算)。所以在 WHERE 中用ST_DWithin
是索引友好的模式(比直接ST_Distance(...) < d
更常推荐)。 ([PostGIS][8], [地理信息系统问答社区][9])
ST_Distance(a,b)
------ 返回精确距离(geometry 的单位取决 SRID;geography 返回米/按 spheroid 计算)。
- 如果需要排序最近邻,通常 先用
ST_DWithin
过滤(利用索引)再对结果做ST_Distance
排序 。或者使用 KNN 运算符<->
做索引辅助的最近邻排序(下节详细)。 ([PostGIS][8])
ST_Intersects
, ST_Within
------ 空间关系测试:这些函数也会被 GiST 索引的预过滤所加速(通常包含 &&
包围盒操作做第一步),因此把空间约束放在 WHERE 子句中能让索引生效。
6) 最近邻(KNN)搜索:<->
运算符与索引机制(原理)
<->
是做什么的?
<->
返回两几何的 2D 距离,并且在ORDER BY
中使用时会触发数据库的 KNN(最近邻)算法 ,让查询使用 GiST 索引做"best-first"/优先队列搜索,从而高效返回最近的 N 个要素(而不是扫描全表再排序)。对geometry
来说这能用 GiST 索引;对geography
,在较新版本的 PostGIS/Postgres 也能得到合适的行为(文档指出对 geography 返回大地球面距离的 KNN 行为)。面试中要能说出:KNN 用<->
,ST_DWithin 用来过滤并结合索引,两者各有用途。 ([PostGIS][10])
7) "查某点 5 公里范围内的商家"------一套标准答案(含执行计划意向)
假设你用 geography(Point,4326)
存经纬度(单位米),表名 shops
:
建表 & 索引
sql
CREATE TABLE shops (
id serial PRIMARY KEY,
name text,
location geography(Point,4326),
props jsonb,
document_tsv tsvector -- 可选:存好全文索引向量
);
-- 空间索引(GiST)
CREATE INDEX idx_shops_location_gist ON shops USING GIST (location);
-- JSONB 包含查询用 GIN(如果你频繁按 props 查询)
CREATE INDEX idx_shops_props_gin ON shops USING GIN (props jsonb_path_ops);
-- 全文搜索向量
CREATE INDEX idx_shops_tsv ON shops USING GIN (document_tsv);
查询:5km 内并按距离排序(索引友好)
sql
WITH ref AS (
SELECT ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326)::geography AS geom
)
SELECT s.id, s.name,
ST_Distance(s.location, r.geom) AS dist_m
FROM shops s, ref r
WHERE ST_DWithin(s.location, r.geom, 5000) -- 先用索引过滤
ORDER BY dist_m
LIMIT 100;
为什么会走索引(执行计划层面)
ST_DWithin
会被内联成先做包围盒(bounding box)比较,从空间索引(GiST)里快速找出候选集合(通常由 Bitmap Index Scan + Bitmap Heap Scan 实现),然后对候选集做精确距离计算并返回。这样避免对全表用ST_Distance
做昂贵计算。你可以用EXPLAIN ANALYZE
看到Bitmap Index Scan
/Bitmap Heap Scan
等节点。 ([PostGIS][8], [地理信息系统问答社区][9])
如果想直接做最近邻(KNN)并让索引做排序
sql
-- KNN,用 <-> 让 GiST 执行最近邻(更快的 N 最近)
SELECT s.id, s.name, s.location
FROM shops s
ORDER BY s.location <-> ST_SetSRID(ST_MakePoint(116.4,39.9),4326)::geometry
LIMIT 10;
(注意这里用 <->
时通常对 geometry
列更普遍;对 geography
的 KNN 行为要看 PostGIS/Postgres 版本与实现细节。) ([PostGIS][10])
8) 性能优化要点(面试常问要点)
- 索引优先:空间索引(GiST / SP-GiST) + JSONB 的 GIN(或 jsonb_path_ops) + tsvector 的 GIN。
- 先过滤再排序 :
ST_DWithin
过滤 →ST_Distance
排序;或使用<->
做 KNN。 ([PostGIS][8]) - 表达式/部分索引 :把常查字段做表达式索引(
(props->>'category')
)或用部分索引(WHERE props @> '{"active":true}'
)降低索引大小。 - 聚簇(CLUSTER)与物理局部性 :对热点数据
CLUSTER
到空间索引上能提高范围查询局部性(注意这是一次性操作,后续更新会使其失效,需要定期维护)。 - VACUUM/ANALYZE:保持统计信息准确,查询规划器才能正确评估索引选择。
- 大表策略:分区(按区域/时间)、预计算网格/桶、或缩小精度用于快速过滤(例如把点映射到 tile-id,先按 tile 匹配再精确过滤)。
- 索引维护参数 :构建大 GIN 索引时可临时增加
maintenance_work_mem
提升性能。 ([PostgreSQL][2])
9) 常见面试追问(短答模板)
-
Q:
geometry
和geography
何时用哪个?A:小范围/需要复杂空间运算/需要更快:
geometry
+ 合适投影;跨大陆/直接以米为单位查询且不想处理投影:geography
。 ([PostGIS][6]) -
Q:为什么
ST_DWithin
比ST_Distance < d
更快?A:
ST_DWithin
内部会做包围盒预过滤并能利用空间索引,减少精确距离计算的行数。 ([PostGIS][8], [地理信息系统问答社区][9]) -
Q:什么时候为 jsonb 建 GIN,什么时候用表达式 btree?
A:做包含/多键匹配/全文类似查询用 GIN;常按某个固定字段查询(稳定的等值)用表达式
btree
更小更快。 -
Q:全文搜索的排名函数如何选?
A:
ts_rank
/ts_rank_cd
,若需位置重要性考虑setweight()
为标题/正文等字段设置不同权重。
10) 练习题
-
建一个
shops
表,插入 10k 点(随机经纬度在某市范围),分别建立 GiST、GIN、tsvector 索引;对以下三种查询用EXPLAIN ANALYZE
比较执行计划:ST_DWithin
的半径搜索。ORDER BY location <-> ref
的 KNN。props @> '{"category":"restaurant"}' AND document_tsv @@ plainto_tsquery('pizza') AND ST_DWithin(...)
(复合索引命中测试)。
-
把某 JSON 字段
props->>'status'
建表达式索引,观察更新/查询表现差异。 -
把
to_tsvector(title||' '||body)
存为列并建 GIN,比较与直接表达式索引to_tsvector(...)
的性能差别。
11) 可运行的示例 SQL(把上面的关键点合并,直接复制到 psql 中跑)
(下面是完整脚本:建表、插索引、插入少量示例、常用查询)
sql
-- 创建扩展(如未安装)
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- 若需要 trgm
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; -- 可选
-- 建表
CREATE TABLE shops (
id serial PRIMARY KEY,
name text NOT NULL,
props jsonb,
location geography(Point,4326),
document_tsv tsvector
);
-- 索引
CREATE INDEX idx_shops_location_gist ON shops USING GIST (location);
CREATE INDEX idx_shops_props_gin ON shops USING GIN (props jsonb_path_ops);
CREATE INDEX idx_shops_tsv ON shops USING GIN (document_tsv);
-- 示例数据(少量)
INSERT INTO shops (name, props, location, document_tsv) VALUES
('Pizza Place', '{"category":"restaurant","active":true}'::jsonb,
ST_SetSRID(ST_MakePoint(116.401,39.905),4326)::geography,
to_tsvector('english','Pizza Place best pizza in town')),
('Coffee Corner', '{"category":"cafe","active":true}'::jsonb,
ST_SetSRID(ST_MakePoint(116.405,39.907),4326)::geography,
to_tsvector('english','Coffee and pastries'));
-- 查询:5km 内
WITH ref AS (
SELECT ST_SetSRID(ST_MakePoint(116.4,39.9),4326)::geography AS geom
)
SELECT s.id, s.name, ST_Distance(s.location, r.geom) AS dist_m
FROM shops s, ref r
WHERE ST_DWithin(s.location, r.geom, 5000)
ORDER BY dist_m
LIMIT 100;
-- KNN 最近邻 (示例)
SELECT id, name
FROM shops
ORDER BY location <-> ST_SetSRID(ST_MakePoint(116.4,39.9),4326)::geometry
LIMIT 10;
结语
jsonb
的优势来自二进制/结构化存储,适配 GIN 索引(倒排索引思想)。([PostgreSQL][1])- 文本搜索通过
tsvector
/tsquery
实现,典型地在tsvector
上建 GIN。([PostgreSQL][3]) - 空间索引由 GiST(或 SP-GiST)驱动;
ST_DWithin
会使用包围盒预过滤从而触发索引;最近邻用<->
。面试中把索引如何被使用讲清楚,比只写 SQL 更加重要。([PostgreSQL][2], [PostGIS][8])