反规范化(宽表)
1. 什么是反规范化
反规范化是将关联数据冗余存储到同一个文档中,避免运行时 JOIN 操作。在 ES 中,这是最常见的关系建模方式。
冗余消除了查询时的关联操作,代价是写入放大、存储冗余和更新传播成本。是否采用宽表,取决于查询路径、更新频率、文档大小和关联基数,而不是单纯读多写少。读多写少只是常见适用场景,不是无条件结论。
json
// 规范化设计(类似 MySQL)
// orders 表:order_id, user_id, product_id
// users 表:user_id, user_name
// products 表:product_id, product_name, price
// 反规范化设计(ES 宽表)
{
"order_id": "ORD001",
"user_id": "U001",
"user_name": "张三",
"product_id": "P001",
"product_name": "iPhone 15",
"product_price": 7999,
"product_category": "手机",
"order_time": "2024-01-15T10:00:00Z"
}2. 反规范化的代价
| 代价 | 说明 |
|---|---|
| 数据冗余 | 同一数据存储多份,占用更多磁盘 |
| 更新成本 | 用户改名需要更新所有相关订单 |
| 一致性风险 | 部分更新成功部分失败导致数据不一致 |
3. 更新策略
3.1 批量更新
json
// 用户改名后,批量更新所有相关订单
POST /orders/_update_by_query
{
"query": {
"term": { "user_id": "U001" }
},
"script": {
"source": "ctx._source.user_name = params.new_name",
"params": { "new_name": "张三丰" }
}
}3.2 异步更新
通过消息队列异步更新冗余数据:
txt
用户改名 → 更新 users 表 → 发送 MQ 消息 → 消费者批量更新 orders 索引这条链路本身不保证一致,消息可能丢失、重复或乱序,批量更新也可能部分成功。要让最终一致成立,需要变更可靠捕获、消费端幂等与失败重试、对乱序更新的版本控制,以及定期对账和从 MySQL 重建 ES 数据的能力。失败事件不能永久丢弃,系统恢复后要能继续处理积压事件;对同一实体的更新,写入 ES 时要拒绝过期事件,避免旧值覆盖新值。
4. 反规范化设计模式
4.1 数据冗余
json
{
"product_id": "P001",
"product_name": "iPhone 15",
"brand_name": "Apple",
"category_name": "手机",
"category_path": "电子产品/手机/智能手机"
}4.2 聚合字段
json
{
"user_id": "U001",
"user_name": "张三",
"order_count": 15,
"total_spent": 25000,
"last_order_time": "2024-01-15T10:00:00Z"
}4.3 标签字段
json
{
"product_id": "P001",
"product_name": "iPhone 15",
"tags": ["苹果", "5G", "高端", "拍照"],
"price_range": "high",
"brand": "Apple"
}5. 宽表设计原则
| 原则 | 说明 |
|---|---|
| 查询驱动 | 根据查询需求决定冗余哪些字段 |
| 读写权衡 | 读多写少的场景更适合反规范化 |
| 更新频率 | 频繁变更的字段不适合冗余 |
| 更新传播成本 | 评估字段变更频率、受影响文档数量和批量更新开销 |
6. 与 MySQL 的配合
txt
MySQL(主数据源) ES(搜索引擎/分析引擎)
┌──────────────┐ ┌──────────────────┐
│ users │ │ orders_flat │
│ orders │ ──同步──→│ (反规范化宽表) │
│ products │ │ │
│ categories │ │ │
└──────────────┘ └──────────────────┘
写入/更新 查询/聚合7. 最佳实践
- 根据查询模式决定冗余字段,不要盲目冗余所有数据
- 对实时性要求高、更新频繁且无法接受同步延迟的字段(如库存),应谨慎冗余
- 使用 Canal + MQ 异步同步,实现最终一致性的前提是变更可靠捕获、失败可恢复、重复或乱序事件被正确处理
- 定期对账,并支持从 MySQL 重建 ES 数据
- 按查询需求、映射复杂度、文档大小与字段增长趋势评估宽表规模,不设固定字段数上限
- 使用
dynamic: strict拒绝未声明字段,防止意外动态扩展映射;显式声明的字段扩展仍需评审治理