用 PostgreSQL 一库模拟 MySQL / MongoDB / Redis / Elasticsearch(附 ts_rank 详解)

一、背景

在实际工程中,我们通常会组合使用多种数据库:

  • 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 的低延迟

相关推荐
2601_962065252 小时前
[MySQL] SQL优化之性能分析
java·sql·mysql
yi.Ist2 小时前
数据定义语言-DDL操作
数据库·学习·mysql·oracle·大海豚
阿kun要赚马内2 小时前
MySQL 索引基础
后端·mysql
2601_962177134 小时前
【MySQL篇】聚合查询,联合查询
android·java·mysql
云贝教育-郑老师4 小时前
MySQL 的“黑匣子“:Performance Schema 把数据库内部变成一张可查的表
数据库·mysql
青春之我_XP6 小时前
MySQL 常用日期函数 实战指南
数据库·sql·mysql·数据分析·数据库开发·日期函数
麻瓜code9 小时前
【MySQL】SQL优化实战:从慢查询定位到查询改写
数据库·sql·mysql
这个DBA有点耶10 小时前
RANGE/LIST/HASH/二级分区怎么选?分区表适用边界与隐藏代价
数据库·mysql·dba
还是大剑师兰特10 小时前
MySQL8.0 Windows完整安装教程
windows·mysql·大剑师
csdn_aspnet11 小时前
mysql 触发器写法及示例
数据库·mysql·触发器·trigger