EAV 反模式是什么
EAV(实体-属性-值)指把维度属性拆成「对象、属性名、属性值」三列的行式存储,看上去能承载任意属性,实际代价很大:取一个属性多一次 join,值列统一成字符串后类型、比较与聚合语义全部丢失,唯一约束与枚举校验也无处落地。数仓中的基本判断是固定属性列化,动态属性走标签表。
基本信息
| EAV 形态 | 一行一属性,attr_name / attr_value |
|---|---|
| 主要代价 | join 次数多、类型弱、校验难 |
| 替代方案 | 固定属性列化 + 标签表承载动态属性 |
| 列化适用 | 属性稳定、经常组合查询与筛选 |
| 标签表适用 | 属性集合动态增长、候选值不固定 |
| 列存影响 | 行式属性在列存引擎下读取放大明显 |
典型使用场景
- 多属性筛选:需要同时按颜色、尺码、材质过滤商品时,列化写法可走索引
- 组合指标:按两个属性交叉统计时,EAV 要自连接多次且性能急剧下降
- 数据校验:列化后能加非空、枚举与范围约束,EAV 只能存字符串
- 新增属性评估:判断该属性是稳定属性(列化)还是长尾属性(标签表)
- 历史包袱梳理:识别已存在的 EAV 表,按访问频次逐批列化
EAV 的代价与列化对照
EAV 的代价与列化对照
-- EAV:每多取一个属性就多一次自连接
SELECT a.entity_id
FROM dim_eav a JOIN dim_eav b ON a.entity_id = b.entity_id
WHERE a.attr = 'color' AND a.val = 'red'
AND b.attr = 'size' AND b.val = 'L'; -- 属性越多,join 越多
-- 列化:属性作为列,条件可直接走索引
SELECT product_id
FROM dim_product
WHERE color = 'red' AND size = 'L'; -- 同一查询在列化后无需自连接列化还是标签表
| 判断条件 | 建议做法 | 原因 |
|---|---|---|
| 属性稳定、必填 | 列化 | 可加约束,查询走索引 |
| 属性长尾、可选 | 标签表(对象 + 标签) | 避免维表列数膨胀 |
| 需要范围比较 | 列化并保留数值类型 | 字符串列无法正确比大小 |
| 属性常一起筛选 | 列化并做组合索引 | EAV 需多次自连接 |
属性稳定且经常组合查询时应直接建列并建索引;判断标准是「属性集合是否由业务方固定下来」,而不是「属性会不会变」——几乎所有属性都会缓慢变化。
标签表仍然是行式结构,因此适合做单标签筛选与标签计数,不适合做多标签的复杂交叉分析;需要交叉分析时应在主题层把常用标签先列化。