动态数据库架构

70

为支持动态逻辑数据库模式,推荐使用什么样的存储架构?

具体而言,如果系统需要为其用户在生产过程中可能扩展或更改的模型提供存储,则有哪些良好的技术、数据库模型或存储引擎可用?

以下是几个可能的方案:

  • 通过动态生成的DML创建/修改数据库对象
  • 创建具有大量稀疏物理列并仅使用覆盖逻辑模式所需列的表
  • 创建“长而窄”的表格,将动态列值作为行存储,然后需要对其进行旋转以创建包含特定实体所有值的“短而宽”的行集
  • 使用BigTable/SimpleDB PropertyBag类型系统

非常感谢基于真实世界经验的任何答案。

16个回答

40
你提出的并不是新的想法。很多人都尝试过...大多数人发现他们追求“无限”灵活性,最终得到的却比这要少得多。这就是数据库设计中的“蟑螂旅馆”——数据进去了,但几乎不可能取出来。试着构思任何约束条件的代码,你就会明白我的意思。
最终结果通常是一个更难调试、维护和充满数据一致性问题的系统。虽然这不总是这样,但更多时候是这样的。主要是因为程序员没有看到这场灾难的来临,也没有采取防御性编码。同时,通常情况下,“无限”的灵活性真的不是那么必要的;当开发团队得到一个规范,上面写着“天哪,我不知道他们要放什么数据在这里,所以让他们随便放吧”时,这是一个非常糟糕的“气味”,而最终用户只需要预定义的属性类型即可(编写一个通用的电话号码,让他们创建任意数量的电话号码——这在一个良好规范化的系统中非常简单,可以保持灵活性和完整性!)
如果你有一个非常优秀的开发团队,并且非常了解这种设计所需要克服的问题,那么你可以成功地编写出一个设计良好、不太容易出错的系统。大多数情况下是这样的。
但为什么要从一开始就让胜算如此之低呢?
不相信我?谷歌“单一真实查找表”或“单一表设计”。以下是一些很好的结果: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:10678084117056

http://thedailywtf.com/Comments/Tom_Kyte_on_The_Ultimate_Extensibility.aspx?pg=3

http://www.dbazine.com/ofinterest/oi-articles/celko22

http://thedailywtf.com/Comments/The_Inner-Platform_Effect.aspx?pg=2


20

MSSQL中的强类型XML字段已经为我们工作了。


3
这是一种强大的技术,你在使用之前应该要三思而后行。一定要优先考虑数据库规范化。尽管如此,它确实有效,你可以查询数据(虽然不太好看),而且性能也不算太差。 - aboy021

17

像其他人说的那样,除非没有其他选择,请不要这样做。必须允许用户记录自定义数据的情况之一是销售现成产品时。我的公司的产品属于此类。

如果确实需要允许客户这样做,则有几个提示:
- 创建一个强大的管理工具来执行模式更改,并且不要使用其他任何方法进行更改。
- 将其作为管理特性;不要允许普通用户访问它。
- 记录每个模式更改的每个细节。这将帮助您调试问题,还将为您提供CYA数据(如果客户做了某些愚蠢的事情)。

如果您能成功做到这些(尤其是第一个),则提到的任何架构都可行。我更喜欢动态更改数据库对象,因为这使您能够在访问存储在自定义字段中的数据时利用DBMS的查询功能。其他三个选项需要加载大块数据,然后在代码中进行大部分数据处理。


9

我有一个类似的需求,决定使用无模式MongoDB

MongoDB(来自“humongous”)是一种用C++编写的开源、可扩展、高性能、无模式、面向文档的数据库。(维基百科)

亮点:

  • 具有丰富的查询功能(可能是最接近SQL数据库的)
  • 已经准备就绪(foursquare、sourceforge使用它)

需要了解的低调事项(这样您才能正确使用mongo):


还有一个 ACID 兼容的 RavenDB。 - user1804599

7
拥有关系型数据库的主要目的在于保持数据的安全和一致性。一旦允许用户更改模式,你的数据完整性就会丧失...
如果您需要存储异构数据(例如 CMS 场景),我建议在行中存储通过 XSD 验证的 XML。当然,您会失去性能和易于搜索的功能,但这是一个很好的权衡。
由于现在已经是2016年了,忘掉XML吧!使用JSON存储非关系型数据包,并使用适当类型的列作为后端。您通常不需要在“包”内查询值,即使许多现代 SQL 数据库可以自然地理解 JSON,这也会很慢。

7

我曾在一个真实项目中这样做:

数据库由一个表格组成,其中一个字段是一个包含50个元素的数组。它有一个设置为“单词”的索引。所有数据都没有类型,所以“单词索引”按预期工作。数字字段被表示为字符,并且实际排序是在客户端完成的。(如果需要,仍然可以为每种数据类型设置多个数组字段)。

逻辑数据模式用不同的表格行“类型”(第一个数组元素)保存在同一数据库中。同时,它还支持使用相同的“类型”字段进行简单版本控制(copy-on-write风格)。

优点:

  1. 您可以动态重新排列和添加/删除列,无需转储/重新加载数据库。任何新列数据都可以在零时间内设置为初始值(虚拟)。
  2. 碎片化很小,因为所有记录和表格大小相同,有时会提供更好的性能。
  3. 所有表结构都是虚拟的。任何逻辑模式结构都是可能的(甚至是递归或面向对象的)。
  4. 对于“只写一次,大多数情况下只读,不删除/标记为删除”的数据非常适用(实际上大多数Web应用程序都是这样的)。

缺点:

  1. 仅通过完整单词进行索引,无法使用缩写,
  2. 复杂查询可能会导致轻微的性能下降。
  3. 取决于您选择的数据库系统是否支持数组和单词索引(在PROGRESS RDBMS中实现)。
  4. 关系模型仅存在于程序员的头脑中(即仅在运行时存在)。

现在我正在考虑下一步——在文件系统级别上实现这样的数据库。那可能相对容易。


3
听起来你真正需要的是某种“元模式”,即一个能够描述灵活的存储实际数据模式的数据库模式。动态模式更改很敏感,如果允许用户进行更改,那么这不是你想要涉及的事情。
你不会找到比其他数据库更适合这项任务的数据库,所以你最好根据其他标准选择一个数据库。例如,你用什么平台来托管数据库?应用程序用哪种语言编写?等等。
为了澄清我所说的“元模式”的含义:
CREATE TABLE data (
    id INTEGER NOT NULL AUTO_INCREMENT,
    key VARCHAR(255),
    data TEXT,

    PRIMARY KEY (id)
);

这只是一个非常简单的例子,你可能需要更具体、更易于操作的内容,但它确实可以说明我的观点。你应该将数据库模式本身视为应用程序级别上不可变的;任何结构性的更改都应该反映在数据中(也就是说,该模式的实例化)。


3
我知道问题中提到的模型在生产系统中被广泛使用。我所在的一所大学/教育机构正在使用一个相当大的模型,他们专门使用长而窄的表格方法来映射由许多不同数据采集系统收集的数据。
此外,谷歌最近通过他们的代码网站将他们的内部数据共享协议——协议缓冲区开源。基于这种方法建立的数据库系统将会非常有趣。
请查看以下内容: 实体-属性-值模型 Google Protocol Buffer

3

创建两个数据库

  • DB1 包含静态表格,代表数据的“真实”状态。
  • DB2 可供用户自由使用 - 他们(或您)需要编写代码从 DB1 中填充其奇形怪状的表格。

2
我知道这是一个老话题,但我认为它永远不会过时。我现在正在开发类似的东西。以下是我的方法。我使用了一个服务器设置,其中包括MySQL、Apache、PHP和Zend Framework 2作为应用程序框架,但任何其他设置也应该能够正常工作。
下面是一个简单的实现指南,你可以从中进一步发展。
你需要实现自己的查询语言解释器,因为有效的SQL语句会太复杂。
例如:
select id, password from user where email_address = "xyz@xyz.com"

物理数据库布局:

表'specs':(应该缓存在您的数据访问层中)

  • id:整数
  • parent_id:整数
  • name:varchar(255)

表'items':

  • id:整数
  • parent_id:整数
  • spec_id:整数
  • data:varchar(20000)

表'specs'的内容:

  • 1,0,'user'
  • 2,1,'email_address'
  • 3,1,'password'

表'items'的内容:

  • 1,0,1,''
  • 2,1,2,'xyz@xyz.com'
  • 3,1,3,'my password'

我们自己查询语言示例的翻译:

select id, password from user where email_address = "xyz@xyz.com"

转换为标准SQL的代码如下:

select 
    parent_id, -- user id
    data -- password
from 
    items 
where 
    spec_id = 3 -- make sure this is a 'password' item
    and 
    parent_id in 
    ( -- get the 'user' item to which this 'password' item belongs
        select 
            id 
        from 
            items 
        where 
            spec_id = 1 -- make sure this is a 'user' item
            and 
            id in 
            ( -- fetch all item id's with the desired 'email_address' child item
                select 
                    parent_id -- id of the parent item of the 'email_address' item
                from 
                    items 
                where 
                    spec_id = 2 -- make sure this is a 'email_address' item
                    and
                    data = "xyz@xyz.com" -- with the desired data value
            )
    )

你需要将规格表缓存在关联数组或哈希表中,以便从规格名称中获取spec_id。否则,你需要插入更多的SQL开销来从名称中获取spec_id,例如在此代码片段中:
不好的例子,请勿使用,避免使用,应该缓存规格表!
select 
    parent_id, 
    data 
from 
    items 
where 
    spec_id = (select id from specs where name = "password") 
    and 
    parent_id in (
        select 
            id 
        from 
            items 
        where 
            spec_id = (select id from specs where name = "user") 
            and 
            id in (
                select 
                    parent_id 
                from 
                    items 
                where 
                    spec_id = (select id from specs where name = "email_address") 
                    and 
                    data = "xyz@xyz.com"
            )
    )

我希望你能理解这个想法,并自行判断这种方法是否适合你。祝愉快!:-)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接