一、背景
在实际工程中,我们通常会组合使用多种数据库:
-
MySQL:关系型事务处理
-
MongoDB:文档存储
-
Redis:缓存 & KV
-
Elasticsearch:全文检索
但问题是:
👉 多数据库架构复杂、运维成本高、数据同步困难
那么有没有一种方案:
用一个数据库覆盖多种能力?
答案是:PostgreSQL 可以做到 70% 的能力整合
二、核心思路
不是"让 PostgreSQL 变成这些数据库",而是:
用一张统一表 + 不同索引 + 不同字段结构,模拟不同数据库的访问模式
三、统一表设计
CREATE TABLE unified_entity (
id BIGSERIAL PRIMARY KEY,
entity_type TEXT NOT NULL,
entity_key TEXT NOT NULL,
tenant_id BIGINT NOT NULL,
name TEXT,
description TEXT,
status INT,
score NUMERIC,
data JSONB,
search_vector tsvector,
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT now(),
expire_at TIMESTAMP
);
字段设计思想:
| 字段 | 用途 |
|---|---|
| entity_key | 模拟 Redis key |
| data(JSONB) | 模拟 MongoDB |
| search_vector | 模拟 Elasticsearch |
| expire_at | 模拟 TTL |
| 普通字段 | 模拟 MySQL |
四、索引设计(关键)
1️⃣ MySQL 风格(B-Tree)
CREATE UNIQUE INDEX ux_entity_type_key
ON unified_entity (entity_type, entity_key);
CREATE INDEX idx_entity_tenant_status_created
ON unified_entity (tenant_id, status, created_at DESC);
2️⃣ MongoDB 风格(JSONB + GIN)
CREATE INDEX idx_entity_data_gin
ON unified_entity USING GIN (data);
CREATE INDEX idx_entity_user_id
ON unified_entity ((data->>'user_id'));
3️⃣ Elasticsearch 风格(全文检索)
CREATE INDEX idx_entity_search
ON unified_entity USING GIN (search_vector);
4️⃣ Redis 风格(TTL)
CREATE INDEX idx_entity_expire_at
ON unified_entity (expire_at);
五、模拟数据
INSERT INTO unified_entity (
entity_type, entity_key, tenant_id,
name, description, status, score,
data, created_at, expire_at
)
VALUES
('user', 'user:1001', 1,
'Alice', 'VIP user from US', 1, 9.5,
'{"user_id":"1001","region":"US","level":"vip"}',
now(), NULL),
('user', 'user:1002', 1,
'Bob', 'Normal user from CN', 1, 7.2,
'{"user_id":"1002","region":"CN","level":"normal"}',
now(), NULL),
('article', 'article:3001', 1,
'PostgreSQL Index', 'PostgreSQL indexing is powerful',
1, 8.8,
'{"category":"tech"}',
now(), NULL);
六、更新全文索引
UPDATE unified_entity
SET search_vector =
to_tsvector('simple', coalesce(name,'') || ' ' || coalesce(description,''));
七、模拟不同数据库查询
✅ 1. MySQL(关系查询)
SELECT *
FROM unified_entity
WHERE entity_type = 'user'
ORDER BY created_at DESC;
✅ 2. MongoDB(文档查询)
SELECT *
FROM unified_entity
WHERE data @> '{"region":"US"}';
✅ 3. Redis(KV + TTL)
SELECT data
FROM unified_entity
WHERE entity_key = 'user:1001'
AND (expire_at IS NULL OR expire_at > now());
✅ 4. Elasticsearch(全文检索)
SELECT id, name,
ts_rank(search_vector, plainto_tsquery('postgresql')) AS rank
FROM unified_entity
WHERE search_vector @@ plainto_tsquery('postgresql')
ORDER BY rank DESC;
八、重点:ts_rank 是怎么计算的?
很多人用 PostgreSQL 搜索时,都会疑惑:
👉 rank 是怎么来的?
1️⃣ 核心本质
rank = "相关性评分"
由多个因素共同决定:
2️⃣ 影响因素
(1)词频(Term Frequency)
出现越多 → 分越高
Bob Bob Bob → 高
Bob → 低
(2)匹配词数量
plainto_tsquery('Bob Alice')
| 文档 | rank |
|---|---|
| 只含 Bob | 低 |
| Bob + Alice | 高 |
(3)位置(Position)
越靠前越重要:
Bob is user → 高
user is Bob → 低
(4)权重(A/B/C/D)
PostgreSQL 支持 4 级权重:
| 权重 | 数值 |
|---|---|
| A | 1.0 |
| B | 0.4 |
| C | 0.2 |
| D | 0.1 |
示例:
setweight(to_tsvector(name), 'A') ||
setweight(to_tsvector(description), 'C')
👉 title 比 content 更重要
(5)文档长度归一化
文档越长 → 分数被稀释
3️⃣ 简化公式
rank ≈ Σ(词频 × 权重 × 位置系数) / 文档长度
4️⃣ 为什么你的 rank 可能都一样?
如果:
- 每条数据只出现一次关键词
那么:
👉 rank 基本一样(很常见)
5️⃣ ts_rank vs ts_rank_cd
| 函数 | 特点 |
|---|---|
| ts_rank | 基础 |
| ts_rank_cd | 更接近搜索引擎(推荐) |
6️⃣ 进阶(归一化参数)
ts_rank(vector, query, normalization)
常见:
| 参数 | 作用 |
|---|---|
| 0 | 不处理 |
| 1 | 除以长度 |
| 2 | 除以词数 |
九、优缺点分析
优点
-
一套数据库覆盖多场景
-
减少系统复杂度
-
无需多数据同步
-
学习成本低
缺点
-
写入成本高(索引多)
-
不如专用数据库极致性能
-
JSONB 更新有写放大
-
搜索能力弱于 Elasticsearch
-
无法替代 Redis 的低延迟