EAV - Hybrid是一个糟糕的数据库设计选择吗?

17
我们需要将一个遗留的POI数据库从MySQL迁移到PostgreSQL。目前,所有实体都有80-120+个属性,代表各自的属性。
我们被要求考虑灵活性以及新数据库的良好设计方法。但是新设计应该允许:
- 任何实体的属性/属性数量为,即任何实体的属性数量不固定,可能会经常更改。 - 允许内容管理员通过管理界面 "on the fly" 添加新属性到现有实体中,而不是一直更改数据库模式。
关于EAV的性能问题有很多讨论,但如果我们不使用混合-EAV,则会出现以下问题:
- 有大量空列(即使99%的数据没有这些属性,我们仍会添加新列) - 在属性不断变化时,花费更多时间维护数据库。 - 没有办法允许内容管理员向现有实体添加新属性
无论如何,这里是我们对新设计的想法(包括基本ERD):
- 对于每个实体都有单独的表,包含一些独有的基本信息,例如id、名称、地址、联系方式、创建日期等等。 - 有两个表,即属性类型和属性表,用于存储属性信息。 - 使用多对多关系将每个实体与属性相关联。 - 将地址存储在不同的表中,并使用外键链接到实体。

alt text

我们认为这将使我们在添加、删除或更新属性时更加灵活。
然而,这种设计会导致获取数据时连接数量增加。例如,为了显示给定体育场的所有“属性”,我们可能需要进行20多个连接查询以在单行中获取所有相关属性。
您对这种设计有什么想法?您有什么建议来改进它?
感谢您的阅读。

看看使用NOSQL数据库是否有帮助?http://nosql-database.org/ - Aravind Yarram
您不需要使用NoSQL将数据存储在数据库表中的JSON或XML字段中,使用PostgreSQL您可以为特定字段创建功能索引。 - MkV
2
生产数据库中的字段不应该“即兴添加”,应该有一些变更控制来防止数据混乱。通过进行一些前期设计来避免这种情况。如果适用,可以添加表格或将字段添加到现有表格中,如果该字段是1-1且更改表格不会造成太大影响。 - MkV
4个回答

32

我正在维护一个有中央EAV模型的10年历史系统,其中包含1000万+实体、5亿+值以及数百个属性。根据我的经验,一些设计考虑因素如下:

如果您有任何适用于特定属性的业务逻辑,则值得将该属性作为显式列。 EAV属性应该是通用的,应用程序不应将属性A与属性B区分开来。 如果您在代码中找到对EAV属性的文字引用,则很可能应该将其作为显式列。

拥有大量空列并不是一个大问题。它确实需要好的编码和文档实践,以将不同关注点分隔到一个表中:

  • 制定约定和规则,使您知道应用程序的哪个部分读取和修改数据的哪个部分。
  • 使用视图轻松地使用调试工具浏览数据库。
  • 创建和维护测试数据生成器,以便轻松地为您当前不感兴趣的模型部分创建符合架构的虚拟数据。
  • 使用严格的数据库版本控制。仅通过跟踪和应用更改脚本的工具进行架构更改。Postgresql具有事务性DDL,这是自动化架构更改的一个杀手特性。

Postgresql实际上不喜欢瘦表。每个属性值都会导致32字节的数据存储开销,除了遍历所有行以汇总数据所需的额外工作之外。如果您主要批量读写属性,请考虑以某种方式将数据序列化到行中。attr_ids int [],attr_values text [] 是一个选项,hstore是另一个选项,或者如果您不需要在数据库端触及任何特定内容,则可以在客户端使用json或protobuf等内容。

不要费力地将所有内容放入单个实体表中。如果它们没有以合理的方式共享任何属性,则使用多个您使用的特定EAV模式的实例。但请尝试使用相同的模式,并在不同的实例之间共享任何访问器代码。您始终可以根据实体名称对代码进行参数化。

永远要记住,代码就是数据,数据就是代码。你需要找到正确的平衡点,在将决策推入元模型和将其表达为代码之间取得平衡。如果你让元模型做太多事情,修改它将需要与您的代码相同的系统理解能力、版本控制工具、QA程序和分期环境,但它却没有这些工具。本质上,你将会用一种非常尴尬的非标准语言来进行编程。另一方面,如果你在代码中留下了太多东西,每个微小的改变都需要一个新版本的软件。人们往往犯错误,让元模型变得太复杂。为元模型构建开发者工具是艰难而乏味的工作,并且效益有限。另一方面,通过自动化从提交到部署的所有过程,使发布流程更加便宜,有许多附带好处。


感谢您的回复,我们正在尝试使用混合EAV模型,其中每个实体都有自己的表和属性,但只保留常用属性在“属性”表中,您可以将其视为标签,因此我们将能够创建或附加任意数量的标签到任何给定的实体。 - nka
不要使用列来处理某些逻辑,可以考虑创建两种属性:简单属性和复杂属性。其中,复杂属性可以在代码中具有业务逻辑。 - djmj

7
EAV可以在某些情况下很有用。但它有点像“黑暗面”。它非常强大、灵活和诱人。但这是一种简单的逃避方式。逃避了正确的分析和设计。
我认为“实体”有点过于笼统了。你似乎知道应该将哪些内容连接到该实体,比如地址和联系方式。如果您决定在模型中添加“书籍”,那么它们也会有地址和联系方式吗?我认为您应该尝试找到正确的概括,并将模型的EAV部分保持最小化。每当您想要显示某个属性子集,或测试值是否存在,或根据值确定行为时,您都应该将其建模为列。
现在您不会有更好的机会来设计此系统了。自上一个版本以来,已知需求以及有效和无效的方案。(只是不要成为第二系统效应的受害者)

1
谢谢您的回答。那么更好的方法是什么呢?假设我们有100多个与每个实体相关联的是/否标志,您会将它们分开并按类别分组后单独存储吗(顺便说一下,许多这些标志被许多表共享)。然后可以使用外键将这些标志集链接到不同的实体上? - nka
现在看到你的最后一条评论,我明白你想做什么了。对于一组类似“有厕所”、“出售糖果”等的是/否标志,我认为我也会使用属性/值模型。你基本上正在为你的兴趣点创建一个高级标记系统,并添加了一些元数据。我被“实体”这个名称误导了。 - Ronnis

4

EAV的实现方式在Magento中有很好的例子,它是一个用于电子商务的CMS。近来对EAV的评价并不好,但是我挑战任何人能提出比EAV更好的解决方案来处理无限的产品属性。

当然,你可以枚举出世界上每个产品所需的所有列,但那需要花费大量时间,并且你很可能会忘记其中的某些产品属性。

因此,结论是:对于无限的东西请使用EAV,但不要将EAV用于所有数据库表。因此,当正确使用混合EAV和关系型数据库时,这是一种强大的工具,并且仅使用固定列是无法实现的。


PostgreSQL 中的 JSON 是一种替代方案。 - Samir Alajmovic
@SamirAlajmovic的评论提到了GIN-indexed jsonb,它存在于PostgreSQL中,但无法移植到其他DBMS。 - Damian Yerrick

2
基本上,EAV试图在数据库中实现一个数据库,这会导致混乱。用于提取数据的查询变得过于复杂,您的数据没有稳定的、特定的模型来保持一定的顺序。
我曾为有限的应用程序编写过EAV系统,但作为通用解决方案,这通常是一个糟糕的想法。

好的,很好的观点。你对处理数十个不断变化的是/否属性有什么建议? - nka
序列化数据并在应用程序中处理。 - Scott Marlowe
6
但是,如果你将所有属性存储在一个"BLOB"类型的"attribute"字段中,那么如何使用属性进行查询过滤呢?这必须在应用程序中完成,而且如果我们需要处理大量结果,这可能会导致高度(不必要的)内存消耗。 - acme

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接