SKU文档不应嵌套规格数组,而应使用扁平的attributes对象存储键值对规格,以支持灵活扩展、高效查询和复合索引;库存扣减需原子操作,规格名须统一标准化。

SKU文档要不要嵌套规格数组?
不要直接嵌套 specifications 数组。电商场景中,SKU 的规格组合(如“颜色:红色,尺码:L,材质:棉”)本质是键值对集合,且后期常新增/删减规格项(比如突然加个“是否带帽”)。用固定字段或数组硬编码会导致 schema 频繁变更、查询难写、索引失效。
正确做法是把规格抽象为属性集合,用 attributes 字段存对象,每个键是规格名,值是规格值:
{
"_id": "sku_123",
"product_id": "p456",
"price": 299.0,
"stock": 42,
"attributes": {
"color": "red",
"size": "L",
"material": "cotton"
}
}
这样增减规格只需改应用层逻辑,MongoDB 文档结构零改动。
如何高效查“红色+L码”的所有SKU?
用点号路径 + $and 查询,但必须确保 attributes 是扁平对象(不是数组),否则无法利用索引:
db.skus.find({
"attributes.color": "red",
"attributes.size": "L"
})
关键点:
- 必须为常用查询路径建复合索引:
db.skus.createIndex({"attributes.color": 1, "attributes.size": 1}) - 如果规格名含空格或特殊字符(如
"USB-C 充电"),MongoDB 不允许作为字段名 —— 此时要转义成下划线或短ID(如usb_c_charging),并在应用层维护映射表 - 避免用
$elemMatch去查数组式规格,那会强制全表扫描
Attribute Pattern 和 EAV 模型有什么区别?
别把 attributes 对象当成 EAV(Entity-Attribute-Value)来用。EAV 把每个属性拆成独立文档({entity_id, attr_key, attr_value}),查询 N 个规格要 N 次 $lookup 或聚合,性能灾难。
Attribute Pattern 的核心优势在于「单文档内属性聚合」:
- 所有规格在同一个
attributes对象里,一次磁盘 IO 就能读全 - 支持原生点号查询和复合索引,不依赖聚合框架
- 不需要额外 collection 存属性定义 —— 规格元信息(如是否必填、前端展示名)可存在 product 文档的
spec_schema字段里,与 SKU 解耦
典型错误是把 attributes 设计成数组:[{"key":"color","value":"red"}],这等于主动放弃 MongoDB 的文档查询能力。
库存扣减和并发更新怎么保证原子性?
SKU 库存字段 stock 必须单独拎出来,不能藏在 attributes 里。扣减用 findAndModify 或 updateOne 配合 $gte 条件:
db.skus.updateOne(
{ "_id": "sku_123", "stock": { "$gt": 0 } },
{ "$inc": { "stock": -1 } }
)
注意:
- 不要先
find再update—— 中间可能被其他请求扣掉库存 - 如果业务要求“扣减前校验多个条件”(如库存 > 0 且状态为 on_sale),把这些条件全写进 filter,MongoDB 保证整个 update 原子执行
-
attributes本身不参与并发控制,它只读;所有写操作聚焦在stock、price等核心字段上
规格变动本身不触发并发冲突 —— 它只是静态描述,改 attributes 是低频管理操作,走普通 update 即可。
真正容易被忽略的是规格名的命名一致性:前端传 "colour",后台存 "color",搜索就匹配不上。得在 API 入口做标准化映射,而不是靠程序员手写字段名。


















