Entity–Attribute–Value table (EAV table) 是否需要单独的 ID 字段作为主键或复合主键(如 entityId+attributeId)?
Does an Entity–Attribute–Value table (EAV table) need a separate ID field as primary key or composite primary key (like entityId+attributeId)?
这是我的 EAV table 结构(我知道 EAV 不好,但我需要存储的属性数量超过一万,所以规范化的 table 不起作用)
Table name - propertyAssign
entityId - int - indexed
attributeId - smallint - composite index with valueId
valueId - smallint - composite index with attributeId
我只需要通过两种方式查询这个table
- select attributeId,来自 propertyAssign 的 valudId,其中 entityId=x
- select 来自 propertyAssign 的 entityId,其中 attributeId=x 且 valueId=y
所以我相应地添加了索引。
问题 => 我需要在这里添加任何主键吗?
已编辑:
考虑到您必须执行的查询,我认为最好的做法是:
- 删除超过
entityId
的索引;
- 声明所有三个属性为主键(按顺序
entityId, attributeId, valueId
,不引入显式(代理)主键。
主键声明将导致在 entityId, attributeId, valueId
上创建复合索引。
这有两个作用:
- 可以减少由于缺少主键而导致的问题(有关问题的示例,请参见 this)和
- 它将加速第一种类型的查询 (
select attributeId, valudId from propertyAssign where entityId=x
),因为访问计划可以仅使用索引来给出查询结果。
Renzo 的回答包括 Select #1,但不包括
select entityId from propertyAssign where attributeId=x and valueId=y
那需要
INDEX(attributeId, valueId, entityId)
会是
- 高效,因为它完美地处理了
WHERE
子句,并且
- 效率更高,因为
INDEX
包含所有需要的字段 ("Covering index")。
是的,这基本上使 table 的大小翻了一番(数据+PK,然后是包含所有数据的索引)。但这比 table 扫描 Select #2 好 多 。
闻起来像 attributeId
和 valueId
是指向 'normalization' table 的链接,它们具有实际的字符串和值??完成代码所需的 JOIN
在哪里?如果您在单独的 SELECT
中执行此操作,则效率低于 JOIN
,因为它是到服务器的两次(或三次?)往返。
EAV 是一个非常糟糕的设计模式;祝你好运。
编辑
提到的两个SELECTs
将受益于这两个索引:
INDEX(entityId, attributeId, valueId) -- for Select #1
INDEX(attributeId, valueId, entityId) -- for Select #2
而且,由于该三元组是唯一的,因此其中一个 INDEX
也可能是 PRIMARY KEY
。现在,选择哪个...
当 INSERTing
时,PK 以 entityId
开头使得一个实体的所有三元组都 'clustered' 在一起。这加快了 INSERT
和 SELECT
#1。所以我投票支持它作为PK。让另一个PK不会加速INSERTs
。这是因为创建具有大量属性的实体会导致大量分散写入。
两个 SELECTs
中的每一个都由一个或另一个索引最佳处理;所以 SELECTs
尽可能快。好吧,我忽略了您规范化属性名称和值这一事实。稍后我会咬你,并提出更丑陋的问题。
我说这是一个糟糕的设计,部分原因是因为一个非常相似的模式的基准。压力测试在 table 中填充了超过可缓存的内容。插入速率不能超过每秒 7 个实体。那是在 RAID 条带化磁盘 运行 满容量的情况下。规范化属性等导致 lots 随机磁盘命中。
这是我的 EAV table 结构(我知道 EAV 不好,但我需要存储的属性数量超过一万,所以规范化的 table 不起作用)
Table name - propertyAssign
entityId - int - indexed
attributeId - smallint - composite index with valueId
valueId - smallint - composite index with attributeId
我只需要通过两种方式查询这个table
- select attributeId,来自 propertyAssign 的 valudId,其中 entityId=x
- select 来自 propertyAssign 的 entityId,其中 attributeId=x 且 valueId=y
所以我相应地添加了索引。
问题 => 我需要在这里添加任何主键吗?
已编辑:
考虑到您必须执行的查询,我认为最好的做法是:
- 删除超过
entityId
的索引; - 声明所有三个属性为主键(按顺序
entityId, attributeId, valueId
,不引入显式(代理)主键。
主键声明将导致在 entityId, attributeId, valueId
上创建复合索引。
这有两个作用:
- 可以减少由于缺少主键而导致的问题(有关问题的示例,请参见 this)和
- 它将加速第一种类型的查询 (
select attributeId, valudId from propertyAssign where entityId=x
),因为访问计划可以仅使用索引来给出查询结果。
Renzo 的回答包括 Select #1,但不包括
select entityId from propertyAssign where attributeId=x and valueId=y
那需要
INDEX(attributeId, valueId, entityId)
会是
- 高效,因为它完美地处理了
WHERE
子句,并且 - 效率更高,因为
INDEX
包含所有需要的字段 ("Covering index")。
是的,这基本上使 table 的大小翻了一番(数据+PK,然后是包含所有数据的索引)。但这比 table 扫描 Select #2 好 多 。
闻起来像 attributeId
和 valueId
是指向 'normalization' table 的链接,它们具有实际的字符串和值??完成代码所需的 JOIN
在哪里?如果您在单独的 SELECT
中执行此操作,则效率低于 JOIN
,因为它是到服务器的两次(或三次?)往返。
EAV 是一个非常糟糕的设计模式;祝你好运。
编辑
提到的两个SELECTs
将受益于这两个索引:
INDEX(entityId, attributeId, valueId) -- for Select #1
INDEX(attributeId, valueId, entityId) -- for Select #2
而且,由于该三元组是唯一的,因此其中一个 INDEX
也可能是 PRIMARY KEY
。现在,选择哪个...
当 INSERTing
时,PK 以 entityId
开头使得一个实体的所有三元组都 'clustered' 在一起。这加快了 INSERT
和 SELECT
#1。所以我投票支持它作为PK。让另一个PK不会加速INSERTs
。这是因为创建具有大量属性的实体会导致大量分散写入。
两个 SELECTs
中的每一个都由一个或另一个索引最佳处理;所以 SELECTs
尽可能快。好吧,我忽略了您规范化属性名称和值这一事实。稍后我会咬你,并提出更丑陋的问题。
我说这是一个糟糕的设计,部分原因是因为一个非常相似的模式的基准。压力测试在 table 中填充了超过可缓存的内容。插入速率不能超过每秒 7 个实体。那是在 RAID 条带化磁盘 运行 满容量的情况下。规范化属性等导致 lots 随机磁盘命中。