【问题标题】:"horizontal" vs. "vertical" table design, SQL“水平”与“垂直”表设计,SQL
【发布时间】:2012-03-04 15:17:21
【问题描述】:

抱歉,如果过去已对此进行了全面介绍 - 我看过一些相关的帖子,但没有找到任何让我对此特定情况感到满意的内容。

我最近一直在研究一个相对简单的游戏,大约有 10,000 名玩家。在游戏中,您可以捕捉和繁殖具有特定属性(即翅膀、角、鬃毛)的宠物。数据库中当前有一个如下所示的表:

-------------------------------------------------------------------------------
| pet_id | wings1 | wings1_hex | wings2 | wings2_hex | horns1 | horns1_hex | ...
-------------------------------------------------------------------------------
|      1 |      1 |     ffffff |   NULL |       NULL |      2 |     000000 | ...
|      2 |   NULL |       NULL |   NULL |       NULL |   NULL |       NULL | ...
|      3 |      2 |     ff0000 |      1 |     ffffff |      3 |     00ff00 | ...
|      4 |   NULL |       NULL |   NULL |       NULL |      1 |     0000ff | ...
etc...

表格就这样继续下去,目前有 100 多列,但一般来说,一只宠物只有大约 1-8 个这些属性。每 1-2 个月添加一个新属性,这需要添加表列。该表很少更新和经常阅读。

我一直建议我们转向更垂直的设计方案以获得更好的灵活性,因为我们希望在未来开始添加更多的属性,即:

----------------------------------------------------------------
| pet_id | attribute_id | attribute_color | attribute_position |
----------------------------------------------------------------
|      1 |            1 |          ffffff |                  1 |  
|      1 |            3 |          000000 |                  2 |  
|      3 |            2 |          ffffff |                  1 |  
|      3 |            1 |          ff0000 |                  2 |  
|      3 |            3 |          00ff00 |                  3 |  
|      4 |            3 |          0000ff |                  1 | 
etc...

老开发者担心这会造成性能问题,因为用户会非常频繁地搜索具有特定属性的宠物(即必须具有这些属性,必须至少具有该颜色或位置的一个,必须具有 > 30 个属性)。目前搜索速度很快,因为不需要 JOINS,但引入垂直表可能意味着对每个搜索的属性进行额外的连接,并且行数也会增加三倍左右。

我的问题的第一部分是,是否有人对此有任何建议?我对数据库设计或优化不是特别有经验。

我已经针对各种案例运行了测试,但它们基本上没有定论 - 我运行的所有查询的时间差异很大(即在半秒到 20 多秒之间),所以我认为我的问题的第二部分是是否有比在 PHP 中使用 microtime(true) 更可靠的分析查询时间的方法。

谢谢。

【问题讨论】:

  • 您是否在用户搜索的列上创建了适当的索引? 20 多秒对于 10k 玩家来说听起来很长,即使他们每个人都有一百只宠物。
  • 在提议的新模式中,不可能定义适当的索引。

标签: php mysql database


【解决方案1】:

这个问题是一个非常主观的问题。如果您有资源来更新中间件以反映已添加的列,那么无论如何,使用横向没有什么比固定结构更安全、更容易学习的了。要记住的一件事是,每当您更新表结构时,您都必须更新其每个依赖项,除非有一些像 * 这样的包罗万象的东西,我建议您注意这一点,除非您只是将数据转储到屏幕和列的顺序无关紧要。

话虽如此,如果您没有满足所有要求或不想在 n 个领域更新代码,那么 Verticle 是您的最佳选择。大多数时候,您只需要存储容器来存储数据。我会将数字、日期、二进制和文本等内容分隔在单独的列中,以保持一些数据完整性,但垂直存储没有任何问题,只要您知道如何制定和构造查询以将数据带回适当的格式。

仅供参考,Wordpress 使用垂直数据存储来存储它必须为数百万次使用而存储的大部分动态内容。

【讨论】:

    【解决方案2】:

    从数据库的角度来看,第一件事是您的数据应该垂直而不是水平增长。因此,添加新列根本不是一个好的设计。第二件事,这是数据库设计中非常常见的场景。解决这个问题的方法是创建三个表。第一个是宠物,第二个是属性,第三个是两者之间的映射表。示例如下:

    表 1(宠物)
    宠物_ID |宠物名称
    1      |狗
    2      |猫

    表 2(属性)
    属性_ID |属性名称
    1            |翅膀
    2            |眼睛

    表 3(宠物属性)
    宠物_ID |属性_ID |属性值
    1      | 1          | 0
    1      | 2         | 2

    关于性能:
    Pet_ID 和 Attribute_ID 是索引的主键(http://developer.mimer.com/documentation/html_92/Mimer_SQL_Engine_DocSet/Basic_concepts4.html),因此搜索非常快。这是解决问题的正确方法。希望,现在你会明白的。

    【讨论】:

    • 这正是实体-属性-值模型。看看你的表 3。
    【解决方案3】:

    这叫做Entity-Attribute-Value-Model,关系型数据库系统实在是根本不适合它。

    引用一个认为它是五个errors not to make之一的人:

    那么,EAV 被吹捧的好处是什么?好吧,没有。由于 EAV 表将包含任何类型的数据,因此我们必须将数据透视为带有适当列的表格表示,以使其有用。在许多情况下,有中间件或客户端软件在幕后执行此操作,从而向用户提供他们正在处理精心设计的数据的错觉。

    EAV 模型存在许多问题。

    首先,海量数据本身基本上是无法管理的。

    其次,没有可能的方法来定义必要的约束——任何潜在的检查约束都必须包括适当的属性名称的大量硬编码。由于单个列包含所有可能的值,因此数据类型通常是 VARCHAR(n)。

    第三,不要考虑有任何有用的外键。

    最后,查询的复杂性和尴尬。有些人认为在必要时能够将各种数据塞进一个表中是有好处的——他们称之为“可扩展的”。实际上,由于 EAV 将数据与元数据混为一谈,因此即使对于简单的需求,也很难操作数据。

    EAV 噩梦的解决方案很简单:分析和研究用户的需求并预先确定数据需求。关系数据库维护数据的完整性和一致性。如果没有明确定义的要求,几乎不可能设计这样一个数据库。期间。


    表格就这样继续下去,目前有 100 多列,但一般来说,一只宠物只有大约 1-8 个这些属性。

    这看起来像是一个规范化的案例:将表格分成多个,例如一个用于角,一个用于翅膀,所有这些都通过外键连接到主实体表。但请确保每个属性仍映射到一个或多个列,以便您可以定义约束、数据类型、索引等。

    【讨论】:

    • +1 拆分成不同的表确实看起来合乎逻辑。应执行适当的分析以确定要创建哪些表。
    • 嗨蒂洛!我看到您在我的回答中提到数据库并非旨在使用联接,但在您的回答中,您特别建议 ​​OP 这样做。也许我误解了你,也许你误解了我,但我想从你的经验中学习(我不关心 SE 代表,但我想学习)。你觉得我的回答有什么不足之处?通过说“进行联接”,我的意思是 OP 应该将表分成多个表,然后在查询他的数据时对不同的表执行 JOIN。
    • @dotancohen:有连接,然后有连接。您是否尝试在问题中提出的架构中(或按照 Muhammad Ahsan 的回答)编写对 select pet_id, wings1, wings1_hex, wings2, wings2_hex where wings1_hex != wings2_hex and wings1 =1 and wings2 = 2 order by horns1,horns2 的等效查询?
    • 不,编写良好的查询只会搜索实际正在搜索的那些属性。实际上,您提到“有连接,然后有连接”,嘲笑我的连接建议,然后建议连接。您引用的“专家报价”仅表示不应使用 EAV,这也是我所说的:而是使用联接。我非常努力地理解你,假设我可以向你学习,但你的矛盾很难理解。你的答案和我的有什么不同?
    • @dotancohan:您的回答是“进行联接。数据库专门设计用于支持您的用例的联接”。在问题的上下文中阅读(以及如何阅读它?),其中两个选择是“大表”和“具有许多连接的 EAV”,这只能意味着“继续,使用 EAV”(这是一个不好的建议)和“RDBMS 旨在处理 EAV 类型的连接”(这是错误的)。如果您正在考虑其他类型的连接,则需要更具体地说明它们,您的答案中肯定没有更多解释。
    【解决方案4】:

    您提出的建议称为“normalization”。这正是关系数据库的用途 - 如果您处理好索引,则连接的运行速度几乎与数据在一个表中一样快。

    实际上,它们甚至可能会更快:您可以只加载所需的列,而不是加载 1 个包含 100 列的表行。如果宠物只有 8 个属性,则只加载这 8 个。

    【讨论】:

    • 在这里进行一些规范化可能会很好,但他提出的是别的东西:一个完全不透明的动态模式,将很难查询,并且 DB 无法弄清楚如何有效地访问。
    • 嗯,你有什么建议?一个有 100 列的表,其中每行的大多数为 NULL,对我来说也像是一种反模式......
    • 是的,通过将表格分成几个(一个用于翅膀,一个用于角)进行标准化似乎是个好主意。但是属性必须映射到列,否则不能指定数据类型、索引、约束等。
    • 几张桌子看起来像是“所有在一张大桌子上,每只宠物有 100 列”和“都在一张大桌子上,每只宠物有 100 行”之间的良好折衷。
    • 特别是因为没有角或爪的宠物不会在这些额外的表格中输入。
    【解决方案5】:

    加入。该数据库专门设计用于支持您的用例的连接。如果有任何疑问,请进行基准测试。

    编辑:分析查询的更好方法是直接在 CLI 上的 MySQL 解释器中运行查询。它将为您提供运行查询所需的确切时间。 PHP microtime() 函数还会引入其他延迟(Apache、PHP、服务器资源分配、连接到远程 MySQL 实例的网络等)。

    【讨论】:

    • 不!关系数据库不是为这种“动态模式”设计的。性能会很糟糕,正确的索引将变得不可能。
    猜你喜欢
    • 1970-01-01
    • 2016-05-31
    • 1970-01-01
    • 2013-12-22
    • 1970-01-01
    • 2021-05-16
    • 2023-03-23
    • 2010-09-30
    相关资源
    最近更新 更多