当前位置: 首页 > news >正文

ElasticSearch:商品SKU+SPU实现join查询,设计及优化

文章目录

  • 一、SPU+SKU
    • 1、商品SPU和SKU
    • 2、SPU和SKU的关系
    • 3、实现SPU+SKU父子嵌套查询
      • 1. **嵌套对象(Nested Objects)**
      • 2. **父子关系(Parent-Child)**
      • 3. **应用层关联(Application-Side Join)**(推荐)
      • 4. **预连接数据(Denormalization)**(推荐)
      • 方案对比
      • 优化建议

一、SPU+SKU

1、商品SPU和SKU

每个店铺中会包含多个商品,每个商品会有多个规格。

红框上面的部分是商品的SPU(Standard Product Unit 标准产品单位),SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。这里会选择对应店铺的名称,填入商品编号、商品名称、上架状态、定时上架时间、商品标签、商品图片等信息。

下面红色的框体是商品的SKU(stock keeping unit 库存量单位),SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位,SKU是物理上不可分割的最小存货单元。包括商品颜色、商品存储容量、商品价格、商品剩余数量。

在这里插入图片描述

2、SPU和SKU的关系

如图所示,一个店铺会对应多个商品的SPU。同时一个商品的SPU会包含多个商品SKU,以手机为例,一款iPhone12手机就是SPU,如果消费者需要购买该手机的时候,还需要选择手机颜色、内存容量,在商家这边还要查看商品的库存是否足够。因此,一个商品SPU会对
应多个商品SKU的描述。
在这里插入图片描述
如果我们建立数据库表的时候会将店铺、商品SPU和商品SKU建立三张表,通过主外键的关系关联。
如果mysql的数据同步到ES中,用户进行搜索的时候,应该怎么实现呢?

在 Elasticsearch(ES)中实现关联查询(join)的方式与传统关系型数据库不同,因为 ES 是分布式搜索引擎,其设计初衷并非处理复杂关联。以下是几种常见的 ES 关联实现方法:

3、实现SPU+SKU父子嵌套查询

1. 嵌套对象(Nested Objects)

场景:SPU 与 SKU 强关联,查询时需同时检索。
示例:一款手机(SPU)有多个颜色和配置(SKU)。

映射与数据

PUT /products
{"mappings": {"properties": {"spu_id": {"type": "keyword"},"spu_name": {"type": "text"},"brand": {"type": "keyword"},"skus": {"type": "nested",  // 嵌套类型"properties": {"sku_id": {"type": "keyword"},"color": {"type": "keyword"},"size": {"type": "keyword"},"price": {"type": "double"},"stock": {"type": "integer"}}}}}
}PUT /products/_doc/1
{"spu_id": "SPU001","spu_name": "苹果 iPhone 14","brand": "Apple","skus": [{"sku_id": "SKU001","color": "黑色","size": "128GB","price": 5999.00,"stock": 100},{"sku_id": "SKU002","color": "蓝色","size": "256GB","price": 6799.00,"stock": 50}]
}

查询示例

  • 需求:查找所有品牌为 Apple 且有蓝色 256GB 的手机。
  • 实现
{"query": {"bool": {"must": [{"term": {"brand": "Apple"}},{"nested": {  // 嵌套查询"path": "skus","query": {"bool": {"must": [{"term": {"skus.color": "蓝色"}},{"term": {"skus.size": "256GB"}}]}}}}]}}
}

2. 父子关系(Parent-Child)

场景:SPU 与 SKU 生命周期独立,需灵活管理(如动态添加 SKU)。
示例:一款手机(SPU)的 SKU 库存需实时更新。

映射与数据

PUT /products
{"mappings": {"properties": {"join_field": {"type": "join","relations": {"spu": "sku"  // 定义父子关系}}}}
}// 索引 SPU(父文档)
PUT /products/_doc/SPU001
{"spu_id": "SPU001","name": "苹果 iPhone 14","brand": "Apple","join_field": {"name": "spu"}
}// 索引 SKU(子文档)
PUT /products/_doc/SKU001?routing=SPU001  // 必须使用父 ID 作为路由
{"sku_id": "SKU001","color": "黑色","size": "128GB","price": 5999.00,"stock": 100,"join_field": {"name": "sku", "parent": "SPU001"}
}PUT /products/_doc/SKU002?routing=SPU001
{"sku_id": "SKU002","color": "蓝色","size": "256GB","price": 6799.00,"stock": 50,"join_field": {"name": "sku", "parent": "SPU001"}
}

查询示例

  • 需求:查找价格低于 6000 的 SKU 所属的 SPU 信息。
  • 实现
{"query": {"has_child": {"type": "sku","query": {"range": {"price": {"lt": 6000}}},"inner_hits": {}  // 返回匹配的子文档}}
}

3. 应用层关联(Application-Side Join)(推荐)

场景:SPU 和 SKU 存储在不同索引,需跨索引关联。
示例:SPU 数据在 spu_index,SKU 数据在 sku_index

数据结构

// SPU 索引
PUT /spu_index/_doc/SPU001
{"spu_id": "SPU001","name": "苹果 iPhone 14","brand": "Apple","category": "手机","description": "2023年新款智能手机..."
}// SKU 索引
PUT /sku_index/_doc/SKU001
{"sku_id": "SKU001","spu_id": "SPU001",  // 关联字段"color": "黑色","size": "128GB","price": 5999.00,"stock": 100,"sales": 2000
}

查询流程(Python)

from elasticsearch import Elasticsearches = Elasticsearch()# 1. 查询手机分类下的所有 SPU
spu_result = es.search(index="spu_index",body={"query": {"term": {"category": "手机"}},"_source": ["spu_id", "name", "brand"]},size=100
)# 2. 提取 SPU IDs
spu_ids = [hit["_source"]["spu_id"] for hit in spu_result["hits"]["hits"]]# 3. 批量查询 SKU(按价格排序)
sku_result = es.search(index="sku_index",body={"query": {"terms": {"spu_id": spu_ids}},"sort": {"price": "asc"},"_source": ["spu_id", "color", "size", "price"]},size=1000
)# 4. 应用层组装结果(SPU + 最低价格 SKU)
spu_sku_map = {}
for sku in sku_result["hits"]["hits"]["_source"]:spu_id = sku["spu_id"]if spu_id not in spu_sku_map or sku["price"] < spu_sku_map[spu_id]["price"]:spu_sku_map[spu_id] = sku# 5. 合并 SPU 和 SKU 信息
merged_result = []
for spu_hit in spu_result["hits"]["hits"]:spu = spu_hit["_source"]sku = spu_sku_map.get(spu["spu_id"], {})merged_result.append({"spu": spu,"lowest_price_sku": sku})

4. 预连接数据(Denormalization)(推荐)

场景:读多写少,需快速查询(如商品列表页)。
示例:将常用 SKU 信息冗余到 SPU 文档中。

数据结构

PUT /products/_doc/SPU001
{"spu_id": "SPU001","name": "苹果 iPhone 14","brand": "Apple","category": "手机","min_price": 5999.00,  // 预计算:最低价格"max_price": 6799.00,  // 预计算:最高价格"default_sku": {  // 预定义:默认 SKU"sku_id": "SKU001","color": "黑色","size": "128GB","price": 5999.00},"sku_summary": [  // 预聚合:SKU 摘要{"color": "黑色", "size": "128GB", "price": 5999.00},{"color": "蓝色", "size": "256GB", "price": 6799.00}]
}

查询示例

  • 需求:查找价格区间在 5000-6500 的手机,返回 SPU 及默认 SKU。
  • 实现
{"query": {"range": {"min_price": {"gte": 5000, "lte": 6500}}},"_source": ["spu_id", "name", "brand", "default_sku"]
}

方案对比

方法查询性能写入性能数据一致性适用场景
嵌套对象SKU 数量少,需原子性操作
父子关系SKU 动态变化,需独立管理
应用层关联跨索引关联,数据量大
预连接数据最高读多写少,实时性要求低

优化建议

  1. 嵌套对象深度限制:避免超过 1000 个嵌套文档,否则性能下降。
  2. 父子关系分片一致性:父子文档必须在同一分片(通过 routing 参数保证)。
  3. 应用层关联批量查询:使用 terms 查询替代循环单条查询。
  4. 预连接数据更新策略:通过异步任务(如消息队列)更新冗余字段。

根据实际业务场景(如 SKU 数量、读写比例、实时性要求)选择合适的方案,或组合使用多种方案。

http://www.xdnf.cn/news/1150093.html

相关文章:

  • 【数据结构】二叉树初阶详解(一):树与二叉树基础 + 堆结构全解析
  • 计算机网络:(十)虚拟专用网 VPN 和网络地址转换 NAT
  • 智能体之变:深度解析OpenAI ChatGPT Agent如何重塑人机协作的未来
  • 【Linux】基本指令
  • c++——友元函数
  • Java大视界:Java大数据在智能医疗电子健康档案数据挖掘与健康服务创新>
  • Android无需授权直接访问Android/data目录漏洞
  • K8s与Helm实战:从入门到精通
  • MySQL数据丢失救援办法
  • 物联网iot、mqtt协议与华为云平台的综合实践(万字0基础保姆级教程)
  • 抖音回应:没有自建外卖,就是在团购的基础上增加的配送功能
  • 游戏开发日志
  • Android回调机制入门
  • Ambiguity-Resolved Waveform Design for Cell-free OFDM-Based ISAC Systems
  • QuecPython-正则表达式
  • 研华PCI-1285/1285E 系列------(一概述)
  • LVS(Linux virtual server)-实现四层负载均衡
  • Spring Boot 与微服务详细总结
  • 软件测试理论02—测试流程体系
  • 云端成本治理利器:亚马逊云科技智能仪表盘(AWS Cost Intelligence Dashboard)深度解析
  • 练习三:熟知前端知识
  • Django3 - Web前端开发基础 HTML、CSS和JavaScript
  • web.m3u8流媒体视频处理
  • 服装公司生产管理系统,如何实战提升生产效率?
  • 张量交换维度(转置),其实是交换了元素的排列顺序
  • 如何实现电脑自动关机与定时任务管理
  • ABP VNext + Temporal:分布式工作流与 Saga
  • react+antd 可拖拽模态框组件
  • 尚庭公寓-----day2 业务功能实现
  • 物联网系统中的可视化大屏定义