我一直在阅读EAV database,而且大多数缺点似乎都是 与真实,真实,糟糕的EAV设计或难度generating reports from the data相关。
通常当您看到有人抱怨EAV时,他们使用的表少于三个,以尝试在RDBMS中复制功能上的单独表+列。有时这意味着在单个TEXT
值列中存储从小数到字符串的所有内容。如果你不小心,EAV也会对数据完整性的保护措施感到困惑。如果你不小心的话,这可能非常糟糕。
但是,EAV确实提供了一种跟踪历史数据的简便方法,并允许我们在SQL和键值存储系统之间来回移动系统的各个部分。
如果我们根据类型分隔不同的实体属性,该怎么办?这将使我们仍然可以处理 除了与特定属性和实体绑定的正确索引值之外,belongsTo,Has,HasMany和HasManyThrough关系。
考虑以下两个基本实体
products (price -> decimal, title -> string, desc -> text, etc...)
attributes
options
[...]
int
datetime
string
text
decimal
relation
[id,foreign_key]
users (gender -> options, age -> int, username -> string, etc...)
attributes
options
[...]
int
datetime
string
text
decimal
relation
[id,foreign_key]
众所周知,用户资料和产品是世界上最多样化的产品之一。每家公司都以不同的方式处理它们,并根据需要提供不同的“列”或“属性”。
以下是如何处理多个(嵌套和/或关系)实体的视图。
这个想法是,每个实体都有这个主属性表,然后指定如何查找和解释这些值。这允许我们处理特殊情况,例如其他实体的外键以及“选项”或十进制数字等。
entity_type { ID, type,//“blog”,“user”,“product”等。 created_at }
entity {
id,
entity_type_id,
created_at
}
attr {
id,
entity_id,
type,
name,
created_at
}
option {
id,
attr_id,
entity_id,
multiple, // multiple values allowed?
name,
created_at
}
attr_option {
id
attr_id,
entity_id,
option_id
option,
created_at
}
attr_int {
attr_id,
entity_id,
int,
created_at
}
attr_relation {
attr_id,
entity_id,
entity_fk_id,
created_at
}
attr_datetime {
attr_id,
entity_id,
datetime,
created_at
}
attr_string {
attr_id,
entity_id,
var_char,
created_at
}
attr_text {
attr_id,
entity_id,
text,
created_at
}
attr_decimal {
attr_id,
entity_id,
decimal,
created_at
}
这样的表格允许我们永远不必UPDATE ...
,因为我们可以INSERT INTO ...
为每个更改值的新属性添加created_at
以了解最近的值是。这非常适合保存历史数据的记录(当然仍可以例外)。
首先,它是什么类型的实体? (用户,帖子,评论等..)
SELECT * FROM entity_type et LEFT JOIN entity e ON e.entity_type_id = et.id WHERE e.id = ?
接下来,这个实体的属性是什么? (表attr)
SELECT * FROM attr WHERE entity_id = ?
接下来,此实体的属性中存在哪些值? (attr _ ### tables)
SELECT * FROM attr_option, attr_int, attr_relation, attr_text, ... WHERE entity_id = ?
vs
SELECT * FROM attr_option WHERE entity_id = ? if( ! multiple) ORDER BY created_at DESC LIMIT 1
SELECT * FROM attr_int WHERE entity_id = ? ORDER BY created_at DESC LIMIT 1
SELECT * FROM attr_relation WHERE entity_id = ? ORDER BY created_at DESC LIMIT 1
SELECT * FROM attr_text WHERE entity_id = ? ORDER BY created_at DESC LIMIT 1
...
这个实体有什么关系?
假设我们有一个ID为34的“post”实体,我们想要它的“评论”(entity_type = 2),这可以让我们在产品实体上获取评论实体ID:
SELECT * FROM entity AS e
LEFT JOIN attr_relation AS ar ON ar.entity_id = e.id
WHERE ar.entity_id = 34 AND e.entity_type = 2;
除了多个查询(无论如何都需要键值存储),这种方法会出现什么问题?
答案 0 :(得分:5)
EAV'数据库'[原文如此] 功能,以表征关系,查询关系,查询元数据,或键入检查,或维护完整性,或优化,或原子地交易,或控制并发。
软件工程原则规定,声音EAV数据库[sic]的使用完全包括定义适当的抽象(类型,运算符,进程,解释器,模块),以重建DBMS的功能。
从一个人的EAV三元组及其含义到一个(碎片化的)数据库描述的映射的机械性质使这很容易显示。
要解释Greenspun,任何足够复杂的EAV项目都包含一个特殊的,非正式指定的,错误缠身的,一半DBMS的缓慢实现。
我再说一遍:EAV是数据库及其元数据三元组中未记录的描述,没有DBMS。仅将EAV用于已证明DDL解决方案无法满足性能要求且EAV解决方案能够并且值得的数据库部分。
答案 1 :(得分:3)
以下是此设计的一些问题。
您如何查询a的所有整数属性的当前值 给定实体?
您如何为属于NOT NULL
的属性建模?那是,
确保给定属性对于其实体是必需的,并且
如果没有该属性的值,则无法创建实体。
您如何为UNIQUE列建模?假设您可以更改 属性的值,然后将其更改回原始值。
如何支持引用实体的外键 除了整数主键之外的东西吗?
如何将给定属性限制为查找表中的值集?
解决大部分问题的唯一方法是使用应用程序代码。这就是EAV的问题:你最终重新发明了我们认为理所当然的SQL约束。这是Inner Platform Effect反模式的一个例子:
内部平台效应是软件架构师倾向于创建一个可自定义的系统,以便成为他们正在使用的软件开发平台的副本,通常是一个糟糕的副本。
第六种普通形式不是EAV。在第六种普通形式中,每个属性需要一个单独的表,而不是每种数据类型。您使用具有适当名称和数据类型的常规列。将此属性存储在不同的表中使您能够存储历史修订。
这意味着您仍然无法在6NF中对NOT NULL
进行建模,但至少您可以用相当传统的方式对UNIQUE
和FOREIGN KEY
进行建模。
答案 2 :(得分:2)
"我一直在阅读有关EAV数据库的信息,大多数缺点似乎都与真实,真实,糟糕的EAV设计或从数据生成报告有关。"
生成报告的困难本质上并且不可避免地来自EAV DB所代表的事实:"人XYZ的属性BIRTHDATE的值是......" "人XYZ的属性DECEASEDATE的值是......"等等。
这不是最终用户认为数据结构用于携带关于人XYZ(或任何其他)的信息的典型形式,最终用户和数据库之间的某个地方,另外的转换(非常类似于转动,尽管不完全是100%)是必要的。每个额外的转换都是错误和性能损失的潜在来源。
"通常当你看到有人抱怨EAV时,他们使用的表少于三个,试图在RDBMS中复制功能上单独的表+列。有时这意味着在单个TEXT值列中存储从小数到字符串的所有内容。"
这只是EAV的一个缺点。属性级类型约束变得更难或不可能定义。但除此之外还有其他人。
" EAV也对数据完整性的保护措施感到困惑,如果你不小心的话可能会非常糟糕。"
这个与报告生成的难度完全相关,这与表达有意义的查询的难度完全相同,这与表达构成违反某些给定规则的场景的难度完全相同
"但是,EAV确实提供了一种跟踪历史数据的简便方法,并允许我们在SQL和键值存储系统之间来回移动系统的各个部分。"
BS&胡扯。严格应用的EAV会将时间信息移动到与其他任何常规信息相同的地方。属性。如果你不这样做,那么你不再(严格地)申请EAV。见Bill Karwin的回答:EAV!= 6NF !!!!!!!!! 6NF仍然拥有所有"结构"任何其他"常规" DB也有,EAV就是这样(参见philip的答案和Bill'内部平台'评论)从DB中有效地删除了这个结构。