【问题标题】:Database design with dynamic fields: single table vs many tables - many indexes具有动态字段的数据库设计:单表与多表 - 多索引
【发布时间】:2012-01-10 12:53:00
【问题描述】:

我必须选择将存储内容类型(例如博客文章、页面、文档、发票、估算等)的数据库结构与动态字段:例如,Estimate 内容类型应该具有titledatetotal price 字段。

但是,这些字段可以添加或删除,因此 1 年后 Estimate 常量类型可以具有 notes 字段。

这是著名的 CMS(例如 drupal)提供的一项常见任务,但我想知道获得最佳性能和灵活性的最佳方法是什么:例如 Drupal 使用一个带有 basic 字段的表(例如 @987654329 @),所有辅助字段都存储在动态创建的子表中,并通过外键链接到主表:

table node
| id | title         | ...
|  1 | First example |
table fields_node_total_price
| id | node_id | value  |
|  1 | 1       | 123.45 |
table fields_node_date
| id | node_id | value    |
|  1 | 1       | 12345677 |

等等。

我的观点是这种方法非常灵活但容易陷入性能问题:为了获取文档的所有字段,您必须多次连接表,并且代码本身必须迭代多次才能构建查询(但这应该不是问题)。

顺便说一句,多表是最常用的方法。所以肯定有很多缺点。

我正在考虑使用单个表会有哪些缺点:

| id | title | total_price | date | ec...

我用 5 个和 50 个附加字段做了一些测试;单表方法和多表方法之间的性能是巨大的:单表大约快 50 倍。

每增加一个字段,就会在表中增加一列..这种做法会引发什么样的问题?

编辑

让我提供一些细节:

  1. 该应用程序仍处于设计阶段,是对字段编号为静态的旧应用程序的完全重新设计
  2. 我们做了一些测试来模拟要存储的对象,包括单表方法和多表方法(使用 50 个字段),结果是:

以秒为单位的时间:

Test                                                            1°          2°          3°          4°          5°          avg
1000 insert single_table                                        8,5687      8,6832      8,7143      8,7977      8,6906      8,69090137389466
1000 select single table LIKE '%key%' on char(250) field        1,5539      1,5540      1,5591      1,5602      1,5564      1,556705142
1000 select single table LIKE '%key%' on char(25) field         0,8848      0,8923      0,8894      0,8919      0,8888      0,889427996
1000 select single table id = $n                                0,2645      0,2620      0,2645      0,2632      0,2636      0,263564462
1000 select single table integer field < $j                     0,8627      0,8759      0,8673      0,8713      0,8767      0,870787334
1000 insert multi_table                                         446,3830    445,2843    440,8151    436,6051    446,0302    443,023531816
1000 select multi table LIKE '%key%' on char(250) field         1,7048      1,6822      1,6817      1,7041      1,6840      1,691367196
1000 select multi table LIKE '%key%' on char(25) field          0,9391      0,9365      0,9382      0,9431      0,9408      0,939536426
1000 select multi table id = $n                                 0,9336      0,9287      0,9349      0,9331      0,9428      0,93460784
1000 select multi table integer field < $j                      2,3366      2,3260      2,3134      2,3342      2,3228      2,326600456

【问题讨论】:

  • “你必须多次查询数据库”——嗯,不。您将构建一个连接到要从中检索数据的每个表的查询。 “代码本身必须迭代很多次”——再说一次,不,除非你在做一些奇怪的事情。
  • @Damien_The_Unbeliever 你是对的;对于query the db many times,我的意思是即使构造带有许多连接的查询,这通常会导致性能下降
  • 也许这个问题更适合dba.stackexchange.com

标签: design-patterns database-design database-schema


【解决方案1】:

可能值得研究一下 NoSQL 数据库的可能性。我自己并没有太多使用它们,但是鉴于您说您需要“......使用动态字段存储内容类型(例如,博客文章、页面、文档、发票、估计等)”,看起来好像可能是一个合理的方法。

来自Wikipedia article

...这些数据存储可能不需要固定的表模式,通常 避免连接操作,通常水平缩放。

通常,NoSQL 数据库根据其存储方式进行分类 数据,它属于键值存储等类别, BigTable 实现、Document-Store 数据库和图表 数据库。

我并不是说它可以解决你所有的问题,但我肯定会说它值得一看。

关于其他方法,我过去使用过 Entity-Attribute-Value (EAV),虽然性能可能落后于固定模式,但我觉得这是 有的折衷

strong> 以提供模式的灵活性。

我的情况可能与您的情况不同,但我会为您列出以防万一。我们将表结构分解为符合我们情况的逻辑。有一点自然的层次结构,因为有一个父表,大多数其他表都与之相关。

尽管由于我们处理的数据种类繁多,我们需要动态结构,但也有一些固定结构。因此,对于每个需要动态结构的表,我们创建了一个“主”表和一个“属性”表。

下面是一个示例(特定于 SQL Server);

CREATE TABLE [dbo].[ParentTbl](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [KnownCol1] [real] NOT NULL,
        -- Lots of other columns ommitted
    [KnownColn] [real] NULL
)        

CREATE TABLE [dbo].[MainTbl](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [ParentId] [int] NOT NULL, -- FK to ParentTbl.Id
    [KnownCol1] [real] NOT NULL,
        -- Lots of other columns ommitted
    [KnownColn] [real] NULL
) 

CREATE TABLE [dbo].[MainTblAttr](
    [Id] [bigint] IDENTITY(1,1) NOT NULL, -- Note big int to cater for LOTS of records
    [MainId] [int] NOT NULL, --FK to MainTbl.Id
    [AttributeColumn] [nvarchar](255) NOT NULL,
    [AttributeValue] [nvarchar](max) NOT NULL
)

然后,您可以执行 PIVOT 查询来帮助获取数据。鉴于您将拥有不同的属性,您需要确定哪些列要包含在数据透视表中。我在开发解决方案时发现了this example to be invaluable。但是,有很多关于 SO 的示例。只需搜索数据透视动态列。

在我的例子中,拥有一个父表对于限制我需要浏览的数据量有很大帮助,因为它限制了我需要查看的子记录。在你的情况下可能不是这样,但希望这会给你一些想法。

祝你好运。

【讨论】:

  • 除非您知道自己需要 NoSQL,否则我会远离 NoSQL。除非你是谷歌、亚马逊等。我认为不是。 NoSQL 的最大优势在于它们可以处理比传统关系数据库大得多的数据量。然而,这是以使用关系数据库的便利性为代价的:例如没有描述性的查询语言,对数据一致性的保证较少等 - 简而言之,如果处理大量数据,它们可以非常高效,但相比之下使用起来很尴尬。要利用效率,您需要一个大小合适的集群来运行它们。
【解决方案2】:

这个问题没有单一的“正确”答案。正如您已经提到的,它归结为灵活性和速度之间的权衡。

这取决于您的应用程序的瓶颈是什么。您是否对您的应用程序进行了一些分析?数据库查询时间是否与典型的最终用户 ping 时间、传输速度等相关?在您确定确实存在性能问题并且知道瓶颈在哪里之前,担心优化性能真的没有意义!

我喜欢在 Firefox 上使用 firebug 来计算我的页面需要多长时间才能显示给最终用户,并将其与在查询之前启动并在查询之后停止的秒表计时器的结果进行比较。为了便于使用,我在分析过程中将其打印在每一页的底部。

您是否考虑过用视图来弥补多表方法的缺点?

关于复杂的查询问题:使用“虚拟”视图,您可以避免在日常查询中使用复杂的联接。您将连接放在视图定义中,并在更改动态字段时只需要调整视图。 (注意:对于虚拟视图,您的“简单”查询会使用视图定义中的连接动态重写。)

关于速度问题:您可以使用“物化”视图定义和多表方法来获得单表性能。对于物化视图,DBMS 通过使用视图定义中的连接来使用视图定义创建一个物理表。结果是您真正查询的是“单表” - 但是它会自动与您的多表定义保持同步。以牺牲数据库存储空间为代价获得两全其美。

根据您的 DBMS,您还可以直接更新视图(而不是多表)。我相信 MySQL 就是这种情况。使用 Postgres,您需要使用触发器来告诉系统如何修改底层的多表。

总结:

  1. 就个人而言,如果我想创建一个持久的系统,我会选择 具有虚拟化视图的多表方法。那时我会 仅“实现”那些我认为表现不重要的观点 不足。这是为了起步而付出的更多努力 单表速度,但仍将保持令人难以置信的灵活性。
  2. 如果我想要一些又快又脏又快的东西,我会选择单桌。 - 但它可能偶尔会很痛苦,但要合并一些更改。我没有看到有大量列引起的问题。任何关系型 DBMS 都应该没问题。
  3. 如果我想要一些快速、肮脏但灵活的东西,我会使用多表而不用担心定义视图和触发器,而只定义一些索引来加速连接操作。

最后一点: 您真的应该尝试在 DBMS 中进行尽可能多的数据处理。 (即使用查询)您已经意识到“代码本身必须迭代多次才能构建查询”它不是真的(参见视图等)。但是,这表明您倾向于在应用程序中进行过多的数据处理。 SQL 具有令人难以置信的表现力,您的数据库很可能会使用比您可能自己实现的任何东西更有效的算法来评估您的数据处理。注意:看起来非常复杂的 SQL 查询实际上可能运行得非常快!

因此,如果您做的不仅仅是循环查询结果以显示网页,您可能仍然可以将更多逻辑放入查询中。

【讨论】:

  • Have you done some profiling on your application? mmh 不,整个应用程序(数据库、服务器、代码)仍处于设计阶段。我们用虚假数据模拟了单表和多表结构,结果表明单表在插入时比多表快 4997%,select ... like '%key%' on a big field, 5% faster with like 'select ... %key% 快 8% ' 在一个小字段上,使用 select ... id = X' and 167% faster with select ... integer_field
  • 很可能查询时间在宏观计划中是微不足道的:例如那么,如果您向用户显示结果的网页平均需要 500 毫秒的 PC 渲染时间,那么如果您的查询只需要 0.01 毫秒而不是 1 毫秒来执行,该怎么办?你的时间最好花在清理你的 html 模板上。 - 我会选择最方便使用的实现。如果结果真的太慢,那么稍后更改 DB 主干结构应该不会太难,只要您在编码中使用一些抽象层(例如视图)。
  • 1+ for Note: a SQL query that looks incredibly complex may in fact run very fast! 也,总的来说我喜欢你的回答.. 特别是materialized
【解决方案3】:

第一个解决方案是“值属性”数据库:Entity Attribute Value Database vs. strict Relational Model Ecommerce

我会选择后面的解决方案:数据库是用来存储数据而不是结构的!我们遇到了重大问题,因为我们有一个实体值属性数据库,我们可以插入任何类型的数据,但是如果没有魔术字符串,就无法查询它们或定位特定数据。

或者您可以采取其他解决方案:将您的附加字段存储在您的 AdditionnalFields 对象的序列化版本中。

【讨论】:

    【解决方案4】:

    在大型系统中(50 多列使用复制和 5 多台主机),当添加额外的列时,与更新表中的单行相关的负载会增加(bc. 必须复制整行)。可以通过将大表拆分为多个部分来减少这种影响。当使用适当的索引时,这对于分析工作负载几乎没有任何成本。虽然它会损害插入的性能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-19
      • 1970-01-01
      • 2017-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-13
      • 2015-12-19
      相关资源
      最近更新 更多