库存系统的 EAV 模型方案或不同的方法?

EAV Model Scheme for Stock System or different apprroach?

我正在使用关系数据库 (MySQL) 和 PHP 开发库存和仓库管理系统。由于库存产品将具有多种特征(宽度、高度、重量、尺寸、颜色等),因此需要一种存储属性的数据库模型方法,并有可能 add/edit 新属性,改变产品类型等。 所以,在目前的概念中,我只能看到 3 个可行的模型:

  1. 将所有属性存储在单个 table 中,作为单独的列,并且 基于产品类型(可能是类别)为他们服务到底 用户填写
  2. 将涉及到的EAV(实体-属性-值)模型 是这样的:
    • 一个类别 table 包含 class 个属性
    • a class 属性 table 将包含具有多个属性的单独 classes(以这种方式我们确保我们可以添加到类别 a class的属性而无需手动将属性一个接一个地添加到相似类别)
    • a 属性 table 负责属性本身
    • a 属性值 table 我们存储值的地方
  3. 将所有公共属性存储在一个 table 中,并为所有不同的类别类型创建多个 table:每次遇到新的类别类型时,此模型都需要更改数据库

第二个模型的灵感来自here

在阅读了很多关于 EAV 模型的资料后,我现在对这个模型产生了怀疑,而且我不太关心我必须在订单/发票等中连接不同产品属性的方式。甚至是表单验证似乎使用 EAV 模型将是一个真正的痛苦,但仍然......我不希望有一个包含 100 多列的 table 然后准备好在新属性出现时添加新列待补充..

所以,问题是:是否有更便宜的解决方案?还是可以改进 EAV 模型?

我知道这是一场由来已久的争论,但每个人都指向 NoSQL 而我只依赖 RDBMS..

编辑:

这些方法(或发现的大多数方法)的缺点是:

到目前为止,唯一可行的解​​决方案是为每个新类别创建一个新的 table,并在该 table 中处理所有自定义属性和规则。但是,再一次,当要设置一个新类别时,它最终会变得非常痛苦。

编辑 2:

在 MySQL 中使用 Json 列的选项在我看来并没有解决上述任何缺点。或者,也许我错了,我没有看清大局..

好吧,这只是一种方法。如果您不需要或不想要所有这些,您可以简化它。

例如,您可以使用 Json column in Mysql 来存储所有额外的属性。另一个想法,在产品类型中,添加一个json列来存储自定义属性和类型,并使用它在屏幕上绘制表单。

如果您想坚持使用关系数据库,可以采用三种方法。

如果您事先知道所有产品的属性,第一个是最好的。您选择了 three ways 之一来在关系模型中存储多态数据。

从关系的角度来看 "clean" - 您只是在使用行和列,但是这 3 个选项中的每一个都有其自身的优点和缺点。

如果您在开发时不知道自己的属性,我建议您不要使用这些解决方案 - 它们需要大量的额外工具。

下一个选项是EAV。优点和缺点都有详细记录 - 但您对 "validating input forms" 的关注只是数据的一个用例,我认为您很容易发现您的数据变成了 "write only"。例如,提供 sorting/filtering 变得非常困难("find all products with a height of at least 12, and sort by material_type" 使用 EAV 模型几乎是不可能的)。

我更喜欢的选项是核心的关系数据、不变数据和可变数据的以文档为中心的 (JSON/XML) 的组合。 MySQL 可以在本地查询 JSON - 因此您可以 sort/filter 通过变体属性。不过,您必须创建自己的验证逻辑 - 可能通过将 JSON Schema 集成到您的数据输入应用程序中。

通过使用 JSON 架构,您可以引入 "belong together" 的概念并提供查找值。例如,如果您有产品重量,您的模式可能会说重量始终必须有一个计量单位,有效选项为千克、毫克、盎司、磅等。

如果您在变体数据中有外键关系,您就会遇到问题 - 例如,"manufacturer" 可能 link 到制造商 table。您可以将其建模为显式列,或者在 JSON 中并且不使用 SQL 的内置外键工具(如连接)。

我建议您先浏览 EAV 数据库,以了解数据库的创建及其值。

您可以遵循使用 EAV 模型的 magento 数据库结构。

EAV代表实体属性和价值模型。让我们仔细看看各个部分。

实体:数据项表示为实体,可以是产品或客户或类别。在数据库中每个实体都有一条记录。

属性:这些属于不同的实体,例如客户实体具有姓名、年龄、地址等属性。在 Magento 数据库中,所有属性都列在一个 table.

Value:只是属性的值,例如 Name 属性的值将为“Rajat”。

当实体有许多属性并且这些属性是动态的 (added/removed) 时,使用 EAV。 此外,这些属性中的许多很可能在大多数情况下都具有空值或 null 值。 在这种情况下,EAV 结构有很多优点,主要是优化 mysql 存储

对于您的情况 - 类别也可以具有属性,产品也可以具有客户等属性......

我们以类别为例。以下是 magento 提供的 tables:

1. catalog_category_entity
2. catalog_category_entity_datetime
3. catalog_category_entity_decimal
4. catalog_category_entity_int
5. catalog_category_entity_text
6. catalog_category_entity_varchar
7. catalog_category_flat

关注此 link 了解更多 table

Magento Category Tables

对于属于select框的属性。您可以将下拉值放在选项值下。

跟随此 link 了解 magento eav 结构,这将使您清楚地了解 EAV 模型的工作原理以及如何充分利用它。

magento table structure

我了解到这些是您的主要要求:

  1. 灵活的属性
    • 您在这里的确切需求尚不清楚:听起来您要么期望属性发生变化,要么至少期望所有属性并不总是适用于所有产品(即稀疏矩阵)
  2. 产品也被分类,类别将(至少部分地)确定哪些属性适用于产品
  3. 属性本身可能具有除其值之外的其他属性,这些属性必须由用户提供(即带有重量的单位)
  4. 输入验证是必须的,检查如下:
    • 所有必需的属性都存在
    • 不存在不适用的属性
    • 属性具有有效值
    • 用户提供的属性具有有效值
  5. 您可能还想确保您可以 search/filter 有效地通过属性

这些不同的需求都会导致不同的技术需求,不同的技术方案。有些是数据库的问题,有些则无论数据库选择如何都必须在代码中解决。显然你知道其中的一些问题,但我认为值得真正分解它:

灵活属性

具有灵活属性列表(如您所知)不适用于必须预先定义 table 架构的 RDBMS 系统。这几乎包括所有 SQL,当然还有 MySQL。问题是更改 table 模式的成本很高,对于大型 table 可能需要几分钟或几小时,如果必须向 table 添加一列,则几乎不可能添加属性去做。

即使您的属性列表很少更改,但如果大多数产品没有大多数属性的值(即稀疏矩阵),那么大量 table 属性的效率非常低。

在长 运行 中,如果您的属性作为列存储在 table 中,您将一无所获。即使您按类别将其分解,您仍然会有大量空 tables,您无法动态添加列。

如果您坚持使用 RDBMS,那么您唯一的选择就是 EAV 系统。在考虑、研究和实施 EAV 系统之后,我不会太担心您在互联网上听到的所有关于它们的炒作。我知道外面有很多文章都在谈论 EAV "anti-pattern",而且我是那种认真对待正确使用软件设计模式的人,但 EAV 确实有一个完全有效的时间和地点,并且就是这个。在长 运行 中,您将无法在没有 EAV 的 RDBMS 上执行此操作。您当然可以查看专为此类特定问题设计的 NoSQL 系统,但是当数据库的其余部分位于标准 RDBMS 中时,安装或切换到 NoSQL 系统只是为了存储你的属性值几乎肯定是矫枉过正。您当然不想失去 RDMBS 附带的 ACID 合规性,而且大多数 NoSQL 系统不保证 ACID 合规性。有一波新的 SQL 系统被设计成两全其美,但如果这只是一个更大的应用程序的一部分(我确信是这种情况),它可能不是仅仅为了实现这一功能就值得研究全新的技术。您还可以考虑在 MySQL 中使用 JSON 存储之类的东西来存储您的属性值。这是一个可行的选择,因为 MySQL 有更好的 JSON 支持,但这只会对大局做出很小的改变:您仍然需要所有其他 EAV table 来跟踪允许的属性、类别等。您只能将属性值放入 JSON 数据中,因此 JSON 存储的潜在好处相对较小(并且存在其他问题我会在后面提到)。

所以总而言之,只要您的应用程序的其余部分 运行 位于 RDBMS 上,使用 EAV 来管理灵活的属性是完全合理的。如果您试图在 RDBMS 内的 EAV 中构建整个系统,那么您肯定会浪费时间,我会告诉您去找一个适合您正在尝试的问题的好的 NoSQL 数据库解决。不过,EAV 的缺点仍然存在:您无法在 RDBMS 系统中轻松执行一致性检查,必须自己在代码中执行。

具有类别特定属性的分类产品

你已经差不多明白了。这在 EAV 系统中相对简单。你将拥有你的属性 table,你将拥有一个类别 table,然后你将需要属性和类别之间的标准一对多或多对多关系 table 这将确定哪些属性可用于哪个类别。你显然也有产品和类别之间的关系,所以你知道哪些产品因此需要哪些属性。

您的选项 #3 旨在满足此要求,但是将每个属性作为列的 table 将随着系统的增长而扩展得很差,并且如果您需要动态添加肯定会中断属性。你不想运行宁 ALTER TABLE 语句,尤其是当你有超过几千条记录时。

管理属性属性

存储动态属性和值是一回事。存储动态属性、值和关联的元数据(即存储权重以及权重所在的单位)完全是另一个问题。然而,这不再是数据库问题,而是代码问题。在实际存储信息方面,您最好的选择可能是将元数据存储在属性值 table 中,并依靠一些代码抽象来处理输入验证和表单构建。这可能会很快变得非常复杂,尤其是如果做错了,并且通过这样的系统进行对话将需要整个 post。但是,我认为您走在正确的轨道上:对于需要值和元数据的更高级的属性,您需要以某种方式分配一个 class 负责输入处理和表单验证。例如,对于一个简单的文本字段,您有一个 "text" class 从表单中读取用户值并将其存储在适当的 "attribute_values" table 中,没有元数据存储。然后,对于您的 "weight" 属性,您将拥有一个 "weight" 属性,该属性存储用户给出的数字(即 0.5),然后还存储用户用该数字指定的单位(即 'lbs' ) 并持续到 "attribute_values" table(在伪 SQL 中):INSERT INTO attribute_values value='0.5', meta_data='{"unit":"lbs"}', product_id=X, attribute_id=X。具有讽刺意味的是,JSON 可能是存储此元数据的好方法,因为保留的确切元数据也会因属性类型而异,我怀疑您是否会使用另一个级别的 table 来处理这种变化你的 EAV tables.

同样,这更像是一个代码问题,而不是存储问题。如果您决定执行 JSON tables 满足此要求的总体情况不会改变:您的 "attribute type classes" 只会以不同的方式存储元数据。这可能看起来像:UPDATE products SET attributes='{"weight":0.5,"unit":"lbs"}' WHERE id=X

输入验证

无论您如何存储数据,这都必须由代码专门处理,因此就决定您的数据库结构而言,这一要求并不重要。如果正确执行,如上所述的基于 class 的系统也将能够处理输入验证。

Sort/Search/Filter

如果您只对数据使用属性,这并不重要 storage/retrieval,但您是否会搜索属性?有了适当的 EAV 系统和良好的索引,您实际上可以 search/sort 在 RDBMS 系统中高效地工作(尽管如果您一次搜索多个索引可能会开始变得痛苦)。我没有详细查看,但我很确定使用 JSON 进行存储在搜索时不会很好地扩展。虽然 MySQL 可以 现在使用 JSON 并直接搜索列,但我严重怀疑这样的 searching/sorting 使用 MySQL 索引,这意味着它不适用于大型数据库。不过我可能在那个问题上是错的。如果您打算做类似的事情,那么在提交 MySQL/JSON 存储设置之前值得深入研究。

根据您的需要,这也是用 NoSQL 系统补充 RDBMS 系统的好地方。之前管理过大型(约 150 万种产品)电子商务系统,我发现 MySQL 在 searching/sorting 类别中趋于平缓, 尤其是 如果您正在进行任何类型的文本搜索。在电子商务系统中,像 "Show me the results that best match the term 'blue truck' and have the attribute 'For ages 3-5'" 这样的查询很常见,但在 MySQL 中做这样的查询几乎是不可能的,主要是因为需要基于相关性的排序和评分。我们使用 Apache Solr(Elastic 是一个类似的解决方案)解决了这个问题,它很好地管理了我们的 searching/sorting/搜索词评分 非常。在这种情况下,它是一个两个数据库的解决方案。 MySQL 将所有实际数据和存储的属性保存在 EAV table 中,每当有更新时,我们都会将所有内容的记录推送到 Apache Solr 以进行额外存储。当用户查询时,我们会查询 Apache Solr,它是文本搜索专家,也可以毫不费力地处理属性过滤,然后我们会从 MySQL 数据库中提取完整的产品记录。该系统运行良好。我们有 150 万种产品,数千种自定义属性,并且可以毫不费力地 运行 将所有东西都从一个虚拟服务器中剥离出来。显然有很多代码在幕后进行,但关键是它确实有效并且不难维护。 MySQL 或 Solr 的性能从未有过任何问题。