使用动态字段的数据库设计:单个表与多个表 - 许多索引

时间:2012-01-10 12:53:01

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

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

然而,在这些字段可以被删除的时候,所以在1年之后,Estimate种类可以有notes字段。

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

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 |

等。

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

Btw多表是最常用的方法..因此必须有很多缺点。

我在思考使用单个表格会有什么样的不利条件:

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

我做了一些测试,增加了5个和50个字段;单表方法和多表方法之间的性能非常高:单表的速度提高了约50倍。

每次添加一个字段时,都会在表格中添加一个列。这种方法会出现什么样的问题?

修改

让我提供一些细节:

  1. 该应用程序仍处于设计阶段,是一个完整的旧应用程序重新设计,其中字段编号是静态的
  2. 我们做了很少的测试来模拟要存储的对象,使用单表方法和多表方法(使用50个字段),结果如下:
  3. 以秒为单位的时间:

    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
    

4 个答案:

答案 0 :(得分:5)

调查NoSQL数据库的可能性可能是值得的。我自己并没有太多地使用它们,但是如果你说你需要“...存储内容类型(例如博客文章,页面,文档,发票,估算等等)和动态字段”,它似乎就像它一样可能是一种合理的方法。

来自Wikipedia article;

  

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

  

通常,NoSQL数据库根据其存储方式进行分类   数据和它属于Key-Value商店等类别,   BigTable实施,文档存储数据库和图形   数据库中。

我不是说这是你所有问题的答案,但我肯定会说这值得一看。

关于其他方法,我过去曾使用实体 - 属性 - 值(EAV),虽然性能可能落后于固定架构,但我觉得这是的妥协可以提供架构的灵活性。

我的情况可能与您的情况有所不同,但如果有任何帮助,我会为您解决。我们将表结构打破成了符合我们情况的逻辑。有一个自然的层次结构,因为有一个父表,其他表的大部分都与之相关。

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

此示例(特定于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上有很多例子。只需搜索数据透视动态列。

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

祝你好运。

答案 1 :(得分:3)

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

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

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

您是否考虑过观点以抵消多表方法的缺点?

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

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

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

摘要:

  1. 就个人而言,如果我想创建一个能够持久的系统,我会选择 具有虚拟化视图的多表方法。那我会的 “物化”只是那些我认为表现的观点 不足。这是更加努力的目标 单桌速度,但它将保持非常灵活。
  2. 如果我想快速又脏又快,我会选择单桌。 - 但它可能偶尔会带来痛苦,但要加入一些变化。我没有看到因为有大量列而引起的问题。任何关系DBMS都应该没问题。
  3. 如果我想要快速,肮脏但又灵活的东西,我会选择多桌而不用担心定义视图和触发但只定义一些索引以加速连接操作。
  4. 最后一点: 您应该尝试在DBMS中尽可能多地进行数据处理。 (即使用查询)您已经意识到“代码本身必须多次迭代才能构建查询”这不是真的(请参阅视图等)。但是,它表明您倾向于在应用程序中执行过多的数据处理。 SQL具有令人难以置信的表现力,您的数据库很可能会使用更有效的算法来评估您的数据处理,而不是您自己可能实现的任何事情。注意:看起来非常复杂的SQL查询实际上可能运行得非常快!

    因此,如果您正在执行除查询结果循环以显示网页之外的任何内容,您仍可以将更多逻辑放入查询中。

答案 2 :(得分:0)

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

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

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

答案 3 :(得分:0)

在大型系统(50+列使用5+主机的复制)中,添加其他列时,与更新表中单行相关的负载会增加(bc。必须复制整行)。通过将大表分成多个部分可以减少这种影响。使用适当的索引时,分析工作负载几乎不需要任何成本。虽然它会影响刀片的性能。