EAV-在所有情况下真的不好吗?


65

我正在考虑对其中一个项目中的某些内容使用实体属性值(EAV)模型,但是在Stack Overflow中有关它的所有问题最终都会导致将EAV称为反模式。

但是我想知道在所有情况下这是否是错误的。

假设商店产品实体具有共同的特征,例如名称,描述,图像和价格,它们在许多地方都参与了逻辑,并且具有(半)独特的特征,例如手表和沙滩球将通过完全不同的方面进行描述。因此,我认为EAV将适合存储那些(半)独特的功能。

所有这些假设是,为了显示产品列表,产品表中有足够的信息(这意味着不涉及EAV),并且仅在显示一个产品/比较最多5个产品/等时。使用通过EAV保存的数据。

我已经在Magento商业中看到了这种方法,并且这种方法非常流行,那么是否存在EAV合理的情况?


2
@busy_wait“实体属性值”表-请参阅Wikipedia上的实体属性值模型
罗斯·帕特森

有关EAV模式运行良好的示例,请查看Datomic数据库。它以EAVT模式存储所有内容(T是一个“时间戳”,实际上更像是一个事务ID)。他们的[索引文档](docs.datomic.com/indexes.html)似乎显示得最好。有关EAV难以解决的示例,请参见Wordpress
丹·罗斯

Answers:


80

https://web.archive.org/web/20140831134758/http://www.dbforums.com/database-concepts-design/1619660-otlt-eav-design-why-do-people-hate.html

EAV使开发人员可以灵活地根据需要定义架构,这在某些情况下是好的。

另一方面,在查询定义不明确的情况下,它的执行效果非常差,并且可以支持其他不良做法。

换句话说,EAV为您提供了足够的绳索来吊死自己,在这个行业中,应该将事物设计为最低的复杂度,因为在项目上代替您的人很可能是个白痴。


32
爱最后一句话。
Zohar Peled

2
烂链接。某处有缓存版本吗?
通配符

1
不要点击链接。该页面加载缓慢,没有帮助。另外,像这样的老式论坛也很臭。请改用堆栈溢出!支持好的/有帮助的答案,并减少垃圾分类。
杰西'18

29

简而言之,当您的属性列表频繁增长时,或者当其过大以至于如果将每个属性都设置为一列时,大多数行将被大多数NULL填充时,EAV很有用。在该上下文之外使用时,它将成为反模式。


16
我将“经常”替换为“需要在运行时更改可能性”。
布朗

3
我们可以通过使用相当容易理解的单词“动态”来进一步缩短Doc Brown的时间-当属性列表可能会动态更改时,EAV很有用。
亚历山大·米尔斯

在这种情况下,“在属性可能会更改时”甚至更进一步-“动态地”有点多余:)
Wranorn

1
它是否一定比使更改属性的表格对CREATE TABLE新属性执行更有用?
达米安·耶里克

@DamianYerrick有趣的方法。您在生产中使用过这个吗?

21

假设商店产品实体具有共同的特征(例如名称,描述,图像,价格等),并在许多地方参与了逻辑,并且具有(半)独特的特征(例如手表和沙滩球)将由完全不同的方面来描述。因此,我认为EAV是否适合存储那些(半)独特功能?

使用EAV结构具有一些折衷的含义。

您正在为“较少的行空间”进行权衡,因为您没有100个null与“更复杂的查询和模型”相对的列。

拥有EAV通常意味着该值是一个字符串,可以将任何数据填充到其中。然后,这对有效性和约束检查具有影响。考虑将EAV表中用作电池的电池数量放入电池中的情况。您想找到一个使用C尺寸电池但少于4个的手电筒。

select P.sku
from
  products P
  attrib Ab on (P.sku = Ab.sku and Ab.key = "batteries")
  attrib Ac on (P.sku = Ac.sku and Ac.key = "count")
where
  cast(Ac.value as int) < 4
  and Ab.value = 'C'
  ...

在这里要意识到的是,您不能合理地在值上使用索引。您也不能阻止某人在其中放入不是整数或无效整数的东西(使用“ -1”电池),因为value列一次又一次地用于不同目的。

这将对尝试编写产品模型产生影响。您将拥有漂亮的类型化的值...但是您也将Map<String,String>坐在那里,那里有各种各样的东西。这在将其序列化为XML或Json时具有进一步的含义,以及尝试对那些结构进行验证或查询的复杂性。

要考虑的模式的某些替代方案或修改方案是使用具有有效键的另一个表来代替自由格式键。这意味着您要检查数据库中外键ID的相等性,而不是在数据库中进行字符串比较。更改密钥本身在一处完成。您有一组已知的键,这意味着它们可以作为枚举来完成。

您还可能具有相关的表,其中包含特定产品类别的属性。杂货店部门可以有另一个表,该表具有与建筑材料不需要的多个属性相关的信息(反之亦然)。

+----------+    +--------+    +---------+
|Grocery   |    |Product |    |BuildMat |
|id (fk)   +--->|id (pk) |<---+id (fk)  |
|expiration|    |desc    |    |material |
|...       |    |img     |    |...      |
+----------+    |price   |    +---------+
                |...     |               
                +--------+               

有时特别需要EAV表。

考虑一下您不仅仅为公司编写库存系统的情况,在公司中您了解每种产品和每种属性。您现在正在编写一个库存系统以出售给其他公司。您无法了解每种产品的每个属性-他们需要定义它们。

出现的一个想法是“我们将让客户修改表”,这很糟糕(您进入表结构的元编程,因为您不再知道什么在哪里,他们可以皇家破坏结构或破坏结构)应用程序,他们就可以执行错误的操作,并且这种访问的含义变得很重要)。MVC4上有更多关于此路径的信息:如何在运行时创建模型?

相反,您可以创建EAV表的管理界面并允许使用该界面。如果客户想为“圆点”创建一个条目,它将进入EAV表,您已经知道如何处理。

Redmine数据库模型中可以看到一个示例,您可以看到custom_fields表和custom_values表-这些是允许扩展系统的EAV的一部分。


请注意,如果您发现整个表结构看起来像EAV而不是关系式,则可能要看一下NoSQLKV风格(cassandra,redis,Mongo等)。意识到这些通常会在设计中进行其他折衷,这些折衷可能与您使用它的目的不同,也可能不合适。但是,它们是针对 EAV结构而专门设计的。

您可能希望阅读库存管理系统的SQL vs NoSQL

按照面向文档的NoSQL数据库(沙发,mongo)的这种方法,您可以将每个清单项目都视为磁盘上的文档...快速提取单个文档中的所有内容。此外,文档结构合理,因此您可以快速拉出任何一件东西。另一方面,在所有文档中搜索与特定属性匹配的事物可能会降低性能(将“ grep”与所有文件进行比较)……这是一个折衷方案。

另一种方法是LDAP,LDAP的所有相关项都将以该基础为基础,但随后还将对其他类型的项应用其他对象类。(请参阅使用LDAP的系统清单

一旦走上这条路,您可能会发现一些与您要寻找的东西完全匹配的东西……尽管所有事情都有一些折衷。


10

6年后

现在已经有了Postgres中的JSON,对于使用Postgres的用户,我们还有另一个选择。如果您只想在产品上附加一些额外的数据,那么您的需求就非常简单。例:

CREATE TABLE products (sku VARCHAR(30), shipping_weight REAL, detail JSON);
INSERT INTO products ('beachball', 1.0, '{"colors": ["red", "white"], "diameter": "50cm"}');

SELECT * FROM products;
    sku    | weight |               detail               
-----------+--------+------------------------------------
 beachball |      1 | {"colors": ["red", "white"], "diameter": "50cm"}

这是Postgres中对JSON的更平滑介绍:https : //www.compose.com/articles/is-postgresql-your-next-json-database/

请注意,Postgres实际上存储JSONB,而不是纯文本JSON,并且它确实支持JSONB文档/字段内部的字段的索引,以防万一您发现确实确实想查询该数据。

另外,请注意,JSONB字段中的字段无法使用UPDATE查询单独进行修改;您将不得不替换JSONB字段的全部内容。

这个答案可能不会直接解决问题,但确实提供了EAV模式的替代方法,任何正在考虑原始问题的人都应该考虑使用。


3
我认为发布替代解决方案是个好主意。为了保持其他人的正常运行,MS SQL支持XML列,并具有对它们进行索引的能力,并且从2016年开始,它可以对JSON进行相同的操作(尽管JSON不是MS SQL中的本机列类型,您仍然可以对其进行索引)。另一方面,据我所读,Postgres JSON支持更好,例如,它看起来确实支持JSON数组属性中数据的索引。
Giedrius

1
“ ...无法使用UPDATE查询单独修改JSONB字段中的字段;您必须替换JSONB字段的全部内容。” 这是过时的,不是吗?正是jsonb_set()在Postgres 9.5和更高版本中有一个功能。(您链接到的文章依次链接到讨论9.5功能新增内容的新文章。)
Wildcard

7

通常,如果您将其用于查找表,或者其他好处是不必为一个或两个存储的值创建表,则人们会采用另一种方式。您描述的情况(基本上是在其中存储项目属性)听起来完全正常(并且已规范化)。扩展表以存储可变数量的项目属性是一个坏主意。

对于将完全不同的数据存储在一个细长的表中的一般情况下…… 如果需要,您不必害怕创建新表,而只有一个或两个细长的表并不比只有一个或两个瘦表好得多。两个矮胖桌。

话虽如此,我因使用EAV表进行记录而臭名昭著。他们确实有一些很好的效用。


请定义“皮表”和“脂肪表”。
TulainsCórdova17年

@TulainsCórdova:“瘦”表将是几行多列的表,而胖表将是几列多行的表。一个示例是建立一个查找表,其中您拥有书籍等属性。一个胖表每本书有一条记录,每行有特定数据的许多列,而一个瘦表可能有四列,id,book,field_name,field_data。第一个优点是记录较少,但是不利的是某些字段可能为空,整个内容很难扩展。
Satanicpuppy

@Satanicpuppy我认为您的瘦/胖定义是混杂的-它们是相同的。您是说一个瘦表少列多行吗?
查尔斯伍德

1

EAV将显式结构的问题更改为隐含的感知。而不是说X是具有A和B列的表。您暗示A和B列构成了表X。从某种意义上来说,这是相反的,但是不一定存在一对一的映射。您可以说A和B都映射到表(或类型)X和Y。这在涉及上下文的更为复杂的域中可能很重要。

我一直在研究Datomic,用于这种类型的方法,我认为这是一个非常有用且功能强大的系统,它对您应该使用的方法有所限制(并非您不能这样做)。

EAV会变慢,或者“给你足够的绳索来吊死自己”,这并不是我同意的说法。相反,我将更加强调EAV的优势,如果它适合您的问题空间,则应考虑使用它。

我的经验是,这是一种极好的几乎不受限制的建模方法。具体来说,在Datomic的情况下,它们在所有内容之上都施加了固定的语义。任何对关系进行建模的建模决策都可以自由地从一个变为多个,而不必重新设计列/表。只要约束不违反不变式,您也可以返回。内幕都是一样的。

在我看来,EAV的问题在于缺少像Datomic这样的实现。既然这是关于EAV的问题,我不想谈Datomic,但这是我认为他们在EAV方面一切都正确的事情之一。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.