2

介绍

我一直在阅读有关EAV 数据库的信息,大多数缺点似乎与非常非常糟糕的 EAV 设计或难以从数据生成报告有关

通常,当您看到人们抱怨 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]

RDBMS 模式设计

众所周知,用户资料和产品是世界上最多样化的项目之一。每个公司对它们的处理方式不同,并且针对它们的需要有不同的“列”或“属性”。

以下是如何处理多个(嵌套和/或关系)实体的视图。

这个想法是,每个实体都有这个主属性表,然后指定如何查找和解释这些值。这使我们能够处理特殊情况,例如其他实体的外键以及“选项”或十进制数之类的东西。

entity_type { id, type, // 即“博客”、“用户”、“产品”等。 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 = ?

接下来,这个实体的属性是什么?(表属性)

SELECT * FROM attr WHERE entity_id = ?

接下来,该实体的属性中存在哪些值?(attr_### 表)

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”实体,并且我们想要它的“comments”(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;

除了多个查询(无论如何都需要键值存储),这种方法会存在什么问题?

4

3 回答 3

6

EAV '数据库' [原文如此]从字面上讲,从 数学 上讲,它是数据库及其元数据的三元组中的未记录描述,没有将关系制表,或查询关系,或查询元数据,或类型检查,或保持完整性,或优化的功能,或以原子方式处理,或控制并发。

软件工程原则规定,健全的 EAV 数据库 [原文如此] 的使用完全包括定义适当的抽象(类型、操作符、进程、解释器、模块)来重构 DBMS 的功能。

从一个人的 EAV 三元组及其含义到(碎片)数据库描述的映射的机械性质使得这很容易显示。

套用Greenspun的话说,任何足够复杂的 EAV 项目都包含一个临时的、非正式指定的、充满错误的、缓慢的半个 DBMS 实现。

我再说一遍:EAV 是数据库及其元数据的三元组中的未记录描述,没有 DBMS。仅将 EAV 用于已证明 DDL 解决方案无法满足性能要求并且 EAV 解决方案可以而且值得的部分数据库。

于 2014-05-30T09:06:57.067 回答
3

这是这种设计的一些问题。

  • 您将如何查询给定实体的所有整数属性的当前值?

  • 您将如何建模应该是的属性NOT NULL?也就是说,确保给定属性对其实体是强制性的,并且如果没有该属性的值,就无法创建实体。

  • 您将如何为 UNIQUE 列建模?假设您可以更改属性的值,然后将其更改回原始值。

  • 您如何支持引用具有整数主键以外的实体的外键?

  • 如何将给定属性限制为查找表中的一组值?

解决其中大部分问题的唯一方法是使用应用程序代码。这就是 EAV 的问题:您最终会重新发明许多我们认为 SQL 理所当然的约束。这是内部平台效应反模式的一个示例:

内部平台效应是软件架构师倾向于创建一个可定制的系统,从而成为他们正在使用的软件开发平台的复制品,而且通常是糟糕的复制品。

第六范式不是 EAV。在第六范式中,每个属性需要一个单独的表,而不是每个数据类型。您使用具有适当名称和数据类型的常规列。将此属性存储在不同的表中可以让您存储历史修订。

这意味着您仍然无法在 6NF 中建模,NOT NULL但至少您可以以非常传统的方式进行建模。UNIQUEFOREIGN KEY

于 2014-02-11T23:23:07.150 回答
2

“我一直在阅读有关 EAV 数据库的信息,大多数缺点似乎与非常非常糟糕的 EAV 设计或难以从数据生成报告有关。”

生成报告的困难本质上和不可避免地源于 EAV DB 所代表的事实:“人 XYZ 的属性 BIRTHDATE 的值是……” “人 XYZ 的属性 DECEASEDATE 的值是……”等等。 .

这不是最终用户考虑用于携带有关人 XYZ(或任何其他人)信息的数据结构的典型形式,因此在最终用户和数据库之间的某个地方,额外的转换(非常类似于旋转,虽然不完全是 100 %) 有必要的。每个额外的转换都是错误和性能损失的潜在来源。

“通常当您看到人们抱怨 EAV 时,他们使用少于三个表来尝试复制 RDBMS 中单独表 + 列的功能。有时这意味着将所有从小数到字符串的所有内容存储在单个 TEXT 值列中。”

这只是 EAV 的缺点之一。属性级别的类型约束变得更难或无法定义。但除此之外还有其他的。

“EAV 还扰乱了数据完整性的保护措施,如果你不小心,这可能会非常糟糕。”

这与报告生成的难度完全相关,这与表达有意义的查询的难度完全相同,这与表达构成违反某些给定规则的场景的难度完全相同。

“然而,EAV 确实提供了一种简单的方法来跟踪历史数据,并允许我们在 SQL 和键值存储系统之间来回移动部分​​系统。”

BS &胡说八道。严格应用 EAV 将使时间信息远离它所应用的任何其他“常规”属性。如果您不这样做,那么您将不再(严格地)应用 EAV。请参阅比尔卡尔文的回答:EAV != 6NF !!!!!!!!! 6NF 仍然具有任何其他“常规”数据库也具有的所有“结构”,EAV 就是关于(参见菲利普的回答和比尔的“内部平台”评论)有效地从数据库中删除该结构。

于 2014-06-02T13:41:34.427 回答