Skip to content

反规范化(宽表) ​

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 拒绝未声明字段,防止意外动态扩展映射;显式声明的字段扩展仍需评审治理

Released under the MIT License.