了解EAV设计基本原理的最好方法就是理解行建模(row modelling,其中EAV是广义形式)。 以一超市数据库为例,必须管理数以千计的产品和品牌,其中许多产品存在期很短暂。那么,显而易见,产品名称不应该硬编码为表的列名,反之,产品说明存储在 产品表中,同一个产品的购买/销售记录,则分为多行存储在另一表中,以产品ID与该表关联。 EAV设计概念上涉及包含一个三列的表:实体(entity,如嗅觉受体的ID),属性(attribute,如物种,实为指向元数据表的指针),属性的 值(例如老鼠,rat)。 在EAV设计中,每行保存单一的数据(fact)。 传统的表与此相反,每一属性为一列,因此,一行存储一组数据。 EAV方法适合符合该实体的参数数量远大于具体某一实体所包含的参数数量时的情况。
许多开发人员在定义和分析数据需求时会遇到同样的问题,就是许多不同的属性可用来描述一个对象,但实际上只有很少的属性适用于每个人。一种选择是创建一具有每个属性列的表,这种方法适用于具有固定数量属性的对象,对大多数对象而言,其中全部或大部分属性有值。 然而,在某些情况下,最终得到的记录,其中大部分列将是空的,因为属性可能是未知或不适用的。许多种类的数据必须表现出来,其数量可能出现波动。与此同时,即使不稀疏的属性,然每类数据都非常少。这种情况下,传统的数据模型将使成百的表却只有几行数据。
多种类的数据必须表现出来,其数量可能出现波动。与此同时,即使不稀疏的属性,然每类数据都非常少。这种情况下,传统的数据模型将使成百的表却只有几行数据。
为解决上述问题,可应用EAV(Entity,Attribute,Value)模型。这种模式也有几个可选的称谓,包括“对象-属性-值(object—attribute-value)”模型和“开放架构(open schema)”几种可供选择的名称。在EAV数据模型中,只有非空值才存储在数据库中,每个属性-值(或键-值)对描述一个给定实体的属性。EAV表经常定性为“长瘦(long and skinny)”的,“长”是指描述实体的多个行,而“瘦”则指仅使用少量的列。在本文中将描述EAV模型及其实现,并展示其在现实世界中的应用---包括研究Magento是如何应用这个模式。
对象属性存储在一个有三列的表中:实体,属性和值(entity,attribute,value)。实体(entiry)表示所描述的数据项,例如一个产品或汽车。属性(attribute)表示描述实体的数据,例如一个产品将有价格,重量和许多其他属性。值(value)是属性的值,例如产品可能有一个9.99英镑的价格属性。此外值可以基于数据类型进行分割,所以可将EAV表分为字符串、整数、日期和长文本(long text)表。依据数据类型分割是为了支持索引,使得数据库执行可能的类型检查验证。
稀疏的属性(Sparseness of Attributes)
在数学和计算机科学中,如果一个对象仅包含大量潜在属性中的几个属性,称之为“稀疏矩阵”。在讨论EAV模型时,采用“稀疏”来描述大多数无值的属性。
为说明这一点,来看看超市的收据,超市中有成千上万的库存产品,每日引入新的产品,其它则停止销售。客户购买5个产品的收据只列出实际购买的详细项目,每行一种产品。该收据并没有列出客户可能购买的每一种产品,所以说客户的收据是稀疏的。
按照数据库的术语实体是销售发票,包含如交易ID、日期和时间、存放位置等信息,收据中的每一行对应销售表中的一行记录,存有一个属性及一个或多个值。在这种情况下,属性之一是客户所购买的产品,值则为的数量、单价、折扣和总价。
上面的例子说明了属性的稀疏性(一个客户只选购一个可能的产品),引入一个新的术语:行建模(row modelling)。基于行模型的表,其描述实体的数据记录为多行,每组新的数据在数据库中存为额外的行而非额外的列。行模型是数据库设计时的标准数据建模技术,它仅适用于满足如下两个条件的情况:
- 特定实体的数据是稀疏的。
- 数据时易变的。
行建模是不适用于稀疏且数据非波动的情形,此时,应采用传统的列模型。
行模型示例(Example of Row Modelling)
在这个例子中,有三个实体:产品,客户和发票。产品和客户是标准的关系表。
正如本文前面提到的,满足(上文所述的)两个条件时行模型需是一个不错的选择,即特定实体的数据稀疏,且很容易变化。
我们知道,新产品不断推出且被引入销售,而旧的产品撤回。同时,发票表中不能对每种产品以一列来描述,因为这是不切实际的。
该发票表包含有关销售活动的主要信息,客户、日期和时间、发票ID。每张发票,在表中记录为一行。每一行指定了(顾客)购买的产品、单价和数量。
EAV与行模型比较(EAV vs Row Modelling)
实体-属性-值的设计是行模型的泛化(或)推广。这意味着整个数据库所有类型的数据存储在一张表中,其中一个行模型的表的数据时是均匀。此外,这行模型表中值列的数据类型是预先确定的,而在一EAV表中,特定行其值的数据类型由对应的属性确定。
选择数据模型的最佳方法是很难的,但作为一个准则,如满足如下条件时请考虑EAV模型而非行模型:
- 数据记录中的单个属性的数据类型不同(是/否,数值型;Yes/No,numerical value,string);采用行模型时一张表中很难存储属性的值。
- 许多种类的数据必须表现出来,其数量可能出现波动。与此同时,即使不稀疏的属性,然每类数据都非常少。这种情况下,传统的数据模型将使成百的表却只有几行数据。
- 在一定的环境中,其类别/类必须在动态创建,某些类在原型随后的周期中常常会被省略。
- 某些类归类为混合型的类,这意味着一些类的属性是稀疏的,而其他属性则是非常稀少的。在这种情况下,非稀疏属性存储在传统表中,而稀疏的属性存储在EAV或行建模的格式。这些类通常会满足商业数据库应用的需要,所有产品将共享这些属性,如包装单位和单价。请注意,如果只有一两个混合类,EAV设计可能不值得。
实体、属性和值表示(Representing Entities, Attributes and Values)
实体描述(Representing Entities)
一个实体可以是任何条目,到目前为止,所看到的例子是销售事件实体、商人和产品实体。EAV管理的实体是通过一个对象表,用以获取每个项目的共同数据,如名称、说明等。对象表中的每一实体必须具有唯一的标识符,而这通常是自动生成的。然后,该标识符在整个数据库作为外键。
采用EAV建模并不阻止使用传统的数据表来存贮单个对象的更多细节信息。这是在同一数据库模式(schema)内应用传统关系数据库模型和EAV模型的常见数据建模方法。
属性描述(Representing Attributes)
属性存储在一个专门的属性表。本表的主键是用来作为跨数据库的参考。的属性表通常辅以多个元数据表,更详细地描述了一个属性。此元数据信息通常用于自动化浏览和编辑数据的用户界面生成。元数据表可能包含如下类型的信息部分:
- 验证:验证元数据包括属性的数据类型、默认值、值数量的可能限制,以及可否为空(null)。
- 展现:定义属性如何呈现给用户,是否为文本区域、下拉框或单选按钮/检验框(radio button/checkbox)组。
- 分组:属性大多呈现在用户群体。 分组中的元数据定义了一个属性的显示方式,提出了属性的数量和什么样的字体和颜色类型使用的顺序。
- 正常值范围:在某些情况下正常的值范围,可能会随性别、年龄有所不同,等等。
值描述(Representing Values)
描述EAV模型值的最简单方法就是将其数据存储为一个字符串。但是,这种方法是相对低效的,因为做任何事与该值相关的事情是都需要进行数据类型转换。此外,对存储为字符串的值创建的索引不允许针对数值型和日期型的搜索范围优化,这是采用混合数据类型的键-值对描述数据的公共问题。
为改善这种情况,EAV模型为每一数据类型建立单独的表。属性元数据标识正确的数据类型以及随后存储数据的EAV表。这种方法更有效,因为它允许在访问数据之前,缓存给定属性的元数据。
这种方法的主要缺点在一个属性的数据类型需要改变时是显而易见的,需要将数据从一张表重新分配到另一表中,这很不方便,可以通过存储过程来解决。
一般情况下,空值或不适用于此EAV模型。但某些情况下,存在记录缺失值的必要原因。在这种情况下,解决方法是在表中添加缺失值的编码列,仅在该列的值为空(null)时其值非空。然后,此编码用于查找文字说明清单。
示例(Example)
EAV的最简单实现可能只有三个表:实体,属性和值。如此设置的示例如下所示:
然而,在这种实现中,缺失了元数据信息,不论何种数据类型,所有的值都存为varchar。作为对这一方法的衍生,可以另外选择一个强类型的实现方法,其中一个给定数据类型的值存储为特定类型表的数据记录。下述模式(schema)例子所示,其中包括元数据信息,如前文所讨论。
Magento采用EAV模型(Magento Uses The EAV Model)
在开源和php社区,最著名的EAV实现是Magento,一个电子商务平台。首先来看看Magento的数据库模式(Magento database schema)。虽然一开始显得复杂,我们逐步来浏览。
正如之前提到的,实体可以是任何条目或事件。Magento中包含多个实体,例如:客户,订单,发票和产品。出于本文的目的,将用产品实体来解释EAV的实现。产品的主表为catalog_product_entity。但是,您可能会感到惊讶,只保存了几类的信息,如实体类型,型号(SKU)以及产品创建时间。
为建立一个完整的产品记录,需要找到它的属性,然后找到每一属性的值。在catalog_product_entity表中,会发现entity_type_id列,用来在整个数据库中标识实体的类型。基于实体的类型,可以通过查找eav_attribute来找到要设置产品的那一属性。此表记录了Magento所有实体的全部属性,也包含每一记录的元信息,如数据类型、前端细节等;就产品而言,实体类型ID设置为4(类型在eav_entity_type表中列出),查询欲设置的所有产品属性,简单操作如下:
1 |
|
属性的名称被记录为attribute_code,元数据信息中,一个重要的列为backend_type,这表明一属性为何种数据类型,该属性的值存在何处。Magento的允许下列数据类型:
- static
- datetime
- decimal
- int
- text
- varchar
正如前面所述,值可以基于其数据类型存储在多个表中。检查一特定属性,可以使用如下查询:
1 |
|
上述查询运行后,可以看到,属性“name”的数据类型为varchar,产品属性的值跨多个表存储:
catalog_product_entity_datetime, catalog_product_entity_decimal, catalog_product_entity_int, catalog_product_entity_text, catalog_product_entity_varchar。这些表说明了EAV模型中不同数据类型的存储方式。
为获得所有的产品,可使用的查询列表如下:
1 2 3 4 5 6 7 |
|
这个概念很简单,一旦你知道从哪里开始以及如何查找表中的下一个层次。所有其他实体遵循同样的原则:对于感兴趣的对象先找到实体类型ID,然后基于该ID从eav_attribute中获得所有属性,最后,基于属性的数据类型,从不同的表中查询每一属性的值。
EAV模型的优/缺点(Advantages and Disadvantages of the EAV Model)
EAV模型的主要优点是其灵活性。属性描述表不限制列的数量,这意味着每次新增属性不需要重新设计数据结构(schema);扩展数据库时,属性的数量可以垂直增加(每一新的参数在表中为一的记录),而无需改变数据结构。
事实上,EAV只处理非空属性意味着不需要为空值保留额外的存储空间。这使得EAV模型相当节省空间。
物理数据格式是非常干净,类似于XML,很容易将数据映射为XML格式,只需替换要开始和结束属性标签。
EAV模型可以极好地迅速扩展应用,因为它可以防止(属性)不断变化的后果。可以简单地记录任何结构的新数据,而不需要修改任何数据结构。
当考虑EAV时,确定数据是否稀疏和量大恒重要,因为采用不恰当的数据集时,EAV设计的复杂性超过了其优势所在。相对静态或简单数据选用传统的表结构更为合适。
相较于传统的数据结构,EAV的一个主要缺点是它在检索大容量数据时效率较低。在EAV模型中,数据更加分散,所以查询(select)一个完整实体的记录需要多个表连接。更重要的是,当EAV模型应用于大数据量时,对于同一组EAV建模的数据描述,需要短暂或永久地在列(column)和行之间进行转换。该操作易于出错且是CPU密集型的任务。
EAV模型的另一个局限性,需要制定额外的逻辑来完成传统数据结构(/模式)下自动进行的述务。但是,利用现有的EAV工具可以降低此类工作的成本。
最后,理解EAV模型确实需要时间。它有一个明确的学习曲线,使的初级开发人员在真正理解其概念前,需要为此付出更多的精力。
结论(Conclusion)
应用实体-属性-值时,应考虑以下条件:
- 数据是稀疏的、异构的,一个实体的属性范围较广,且常引入新的属性。
- 类的数量非常大,有许多实例类,即使属性是非稀疏的。
- 有许多混合类,既具有稀疏也具有非稀疏属性。通常情况下,并不是所有的数据类满足EAV建模的要求。
在生产环境中,往往采用混合模式(mixed schema),包括传统的关系、EAV或合适的混合方法。但是,EAV建模,需要引入元数据来获取EAV的逻辑模型数据。我们看到Magento,受EAV影响很大,其中各种不同的产品将有很不同的属性集,是一个运用该模型的非常有效的好例证。本文希望揭示什么是EAV模型,以及如何、何时应用它才更重要。如果您有更多应用EAV模型的例子,或有任何疑问,请作评论!
英文原文: http://techportal.ibuildings.com/2010/10/21/the-eav-data-model/