一、为什么需要它们俩配合?
很多开发者都遇到过这样的问题:用PostgreSQL存数据挺方便,但一到全文检索就头疼。比如用户想搜索"高性能数据库",PostgreSQL虽然能用LIKE或者pg_trgm扩展,但面对百万级数据时就慢得像老牛拉车。这时候Elasticsearch就派上用场了,它专为搜索而生,能毫秒级返回结果。
举个实际例子:一个电商网站,商品信息存在PostgreSQL里,用户想快速找到"男士纯棉T恤"。如果用PostgreSQL的LIKE '%男士%棉%T恤%',数据库CPU直接飙到100%。而Elasticsearch可以轻松应对这种场景,还能支持同义词、拼音搜索等高级功能。
二、它们各自擅长什么?
PostgreSQL是个全能型选手,事务处理、复杂查询、数据一致性都很强。比如银行转账业务,必须用PostgreSQL保证ACID特性。它还支持JSON、GIS地理信息等高级功能。
Elasticsearch则是搜索专家,底层基于Lucene,特点就是快。它能对文本进行分词、建立倒排索引,还支持:
- 模糊搜索(搜"数据"能匹配到"数据库")
- 高亮显示(把搜索结果中的关键词标红)
- 聚合统计(同时计算不同品牌商品数量)
但Elasticsearch不适合做事务处理,比如不能用它来记账,因为它的数据写入不是实时强一致的。
三、具体怎么配合使用?
技术栈:PostgreSQL 14 + Elasticsearch 8.x
方案1:定时同步
适合数据变更不频繁的场景,比如新闻网站。
-- PostgreSQL示例:创建新闻表
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(200),
content TEXT,
publish_time TIMESTAMP,
-- 其他业务字段...
es_sync_flag BOOLEAN DEFAULT FALSE -- 同步标记位
);
-- 每小时同步一次未同步的数据
INSERT INTO elasticsearch索引
SELECT id, title, content
FROM articles
WHERE es_sync_flag = FALSE;
方案2:实时同步(推荐)
使用PostgreSQL的逻辑解码功能,通过Debezium等工具实时捕获变更。
// Java示例:使用Elasticsearch客户端写入数据
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http"))
);
IndexRequest request = new IndexRequest("products");
request.id("123"); // 与PostgreSQL的ID保持一致
request.source(
"name", "男士纯棉T恤",
"description", "100%纯棉材质...",
"price", 99.9,
"tags", new String[]{"男装", "上衣"} // 数组类型示例
);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
方案3:双写模式
在应用层同时写入两个数据库,适合简单场景。
# Python示例:Django中的双写
def save_product(request):
# 写入PostgreSQL
product = Product.objects.create(
name=request.POST['name'],
description=request.POST['desc']
)
# 同时写入Elasticsearch
es.index(
index='products',
id=product.id,
body={
'name': product.name,
'description': product.description
}
)
四、实际案例演示
假设我们开发一个博客平台,技术栈:Spring Boot + PostgreSQL + Elasticsearch
1. 数据库设计
-- PostgreSQL表结构
CREATE TABLE blog_posts (
id BIGSERIAL PRIMARY KEY,
author_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
tags VARCHAR(100)[],
created_at TIMESTAMP DEFAULT NOW()
);
-- 创建全文检索专用视图(可选)
CREATE VIEW blog_search_view AS
SELECT id, title, content,
to_tsvector('english', title || ' ' || content) AS search_vector
FROM blog_posts;
2. Elasticsearch映射
PUT /blog
{
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "ik_max_word" },
"content": { "type": "text", "analyzer": "ik_max_word" },
"tags": { "type": "keyword" },
"created_at": { "type": "date" }
}
}
}
3. 搜索接口实现
// Java搜索示例
public List<BlogPost> search(String keyword) {
// 构造ES查询
SearchRequest searchRequest = new SearchRequest("blog");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 多字段匹配查询
MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery(keyword, "title", "content");
sourceBuilder.query(query);
sourceBuilder.highlighter(new HighlightBuilder().field("content"));
searchRequest.source(sourceBuilder);
// 执行查询并处理结果
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
return processSearchResponse(response);
}
五、性能优化技巧
索引设计:Elasticsearch中合理设置分片数(建议每个分片不超过30GB)
PUT /products { "settings": { "number_of_shards": 3, "number_of_replicas": 1 } }批量操作:使用批量API减少网络开销
# Python批量插入示例 actions = [ {"_index": "blogs", "_id": i, "_source": data} for i, data in enumerate(blogs_data) ] helpers.bulk(es, actions)查询优化:避免使用通配符查询,改用N-Gram
// 模糊查询优化示例 QueryBuilders.matchQuery("title", "数据库") .fuzziness(Fuzziness.AUTO);
六、常见问题解决方案
问题1:数据不一致怎么办?
- 解决方案:定期全量同步 + 实时增量同步结合
- 示例:每周日凌晨2点全量重建Elasticsearch索引
问题2:中文分词效果不好?
- 安装IK分词插件:
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.4.0/elasticsearch-analysis-ik-8.4.0.zip
问题3:字段类型变更怎么处理?
- Elasticsearch需要重建索引,建议流程:
- 创建新索引blog_v2
- 同步数据到新索引
- 创建别名blog指向新索引
- 删除旧索引
七、到底该选择哪种方案?
根据业务场景选择:
- 简单应用:直接双写(开发快但一致性难保证)
- 中等规模:定时同步(每天1-2次全量同步)
- 大型系统:CDC变更捕获(如Debezium)+ 消息队列
最后提醒几个注意事项:
- Elasticsearch的JVM堆内存不要超过物理内存的50%
- PostgreSQL逻辑复制槽要及时清理
- 生产环境一定要做压力测试
评论