如何为用户定义的字段设计数据库?

时间:2011-02-24 14:58:53

标签: sql database database-design user-defined-fields

我的要求是:

  • 需要能够动态添加任何数据类型的用户定义字段
  • 需要能够快速查询UDF
  • 需要能够根据数据类型
  • 对UDF进行计算
  • 需要能够根据数据类型
  • 对UDF进行排序

其他信息:

  • 我主要是寻找表现
  • 有几百万条Master记录可以附加UDF数据
  • 上次检查时,我们当前的数据库中有超过50mil的UDF记录
  • 大多数情况下,UDF仅附加到几千条Master记录中,而不是全部记录
  • UDF未加入或用作键。它们只是用于查询或报告的数据

选项:

  1. 使用StringValue1,StringValue2创建一个大表... IntValue1,IntValue2,...等我讨厌这个想法,但如果有人能告诉我它比其他想法更好以及为什么会考虑它。 / p>

  2. 创建一个动态表,根据需要按需添加新列。我也不喜欢这个想法,因为除非你索引每一列,否则我觉得性能会很慢。

  3. 创建一个包含UDFName,UDFDataType和Value的表。添加新的UDF时,生成一个View,它只提取该数据并将其解析为指定的任何类型。不符合解析标准的项返回NULL。

  4. 创建多个UDF表,每种数据类型一个。所以我们有UDFStrings,UDFDates等的表。可能和#2一样,并且只要添加新字段就自动生成View

  5. XML数据类型?我之前没有使用过这些,但已经看过它们了。不确定他们是否会给我我想要的结果,尤其是性能。

  6. 还有别的吗?

14 个答案:

答案 0 :(得分:46)

如果性能是主要考虑因素,我会使用#6 ...每个UDF一个表(实际上,这是#2的变体)。这个答案专门针对这种情况以及所描述的数据分布和访问模式的描述而定制。

优点:

  1. 因为你指出了一些UDF 有一小部分的价值 整体数据集,单独的 桌子会给你最好的 性能,因为该表将 只有它需要的大 支持UDF。相关指数也是如此。

  2. 您还可以通过限制必须为聚合或其他转换处理的数据量来提高速度。将数据拆分为多个表允许您对UDF数据执行一些聚合和其他统计分析,然后通过外键将该结果连接到主表以获取非聚合属性。

  3. 您可以使用表名/列名 反映实际数据是什么。

  4. 您可以完全控制使用数据类型, 检查约束,默认值等 定义数据域。不要低估即时数据类型转换带来的性能损失。这样 约束也有助于RDBMS查询 优化者发展更有效 计划。

  5. 你是否需要使用外国人? 键,内置声明 参考     完整性很少超过     基于触发器或应用程序级别     约束执行。

  6. 缺点:

    1. 这可能会创建很多表。 实施模式分离和/或 命名惯例会减轻 此

    2. 还有更多应用程序代码 需要操作UDF定义 和管理。我希望这是 仍然需要的代码少于 原始选项1,3和& 4。

    3. 其他注意事项:

      1. 如果有什么关于 将产生的数据的性质 感觉UDF被分组, 应该鼓励。那样, 这些数据元素可以组合在一起 成一张桌子。例如, 假设你有颜色的UDF, 尺寸和成本。的倾向 数据就是大多数情况 数据看起来像

         'red', 'large', 45.03 
        

        而不是

         NULL, 'medium', NULL
        

        在这种情况下,你不会招致 明显的速度惩罚 将1列中的3列组合在一起 因为很少的值会是NULL和 你再避免再制作两张桌子, 这需要2个更少的连接 您需要访问所有3列。

      2. 如果你从一个表演墙上 人口密集的UDF 经常使用,那应该是 考虑列入 主表。

      3. 逻辑表设计可以带你去 某一点,但记录时 计数变得非常大,你也 应该开始看看什么表 分区选项由您选择的RDBMS提供。

答案 1 :(得分:22)

关于此问题我written a lot。最常见的解决方案是Entity-Attribute-Value反模式,它类似于您在选项#3中描述的内容。 Avoid this design like the plague

当我需要真正动态的自定义字段时,我使用此解决方案的方法是将它们存储在一个XML blob中,这样我就可以随时添加新字段。但为了使其快速,还需要为每个字段创建额外的表,您需要搜索或排序(每个字段不是一个表 - 每个可搜索的字段只有一个表)。这有时被称为倒排索引设计。

您可以在此处阅读2009年有关此解决方案的有趣文章:http://backchannel.org/blog/friendfeed-schemaless-mysql

或者您可以使用面向文档的数据库,预计每个文档都有自定义字段。我选择Solr

答案 2 :(得分:9)

我很可能会创建一个包含以下结构的表:

  • varchar名称
  • varchar类型
  • decimal NumberValue
  • varchar StringValue
  • date DateValue

确切的课程类型取决于您的需求(当然还有您正在使用的dbms)。您还可以将NumberValue(十进制)字段用于int和booleans。您可能还需要其他类型。

您需要一些指向拥有该值的主记录的链接。为每个主表创建一个用户字段表并添加一个简单的外键可能是最简单,最快速的。这样,您可以轻松快速地按用户字段过滤主记录。

您可能希望获得某种元数据信息。所以你最终会得到以下结论:

表UdfMetaData

  • int id
  • varchar名称
  • varchar类型

表MasterUdfValues

  • int Master_FK
  • int MetaData_FK
  • decimal NumberValue
  • varchar StringValue
  • date DateValue

无论你做什么,我都动态地改变表结构。这是一场维护噩梦。我也使用XML结构,它们太慢了。

答案 3 :(得分:8)

这听起来像一个非关系解决方案可能更好地解决的问题,如MongoDB或CouchDB。

它们都允许动态架构扩展,同时允许您维护所寻求的元组完整性。

我同意Bill Karwin的说法,EAV模型对你来说并不是一种高效的方法。在关系系统中使用名称 - 值对本质上并不坏,但只有在名称 - 值对完整的元组信息时才能正常工作。使用它时强制您在运行时动态重建表,各种事情开始变得困难。查询成为枢轴维护的练习或强制您将元组重建推送到对象层。

如果没有在对象层中嵌入架构规则,则无法确定null值或缺失值是有效条目还是缺少条目。

您失去了有效管理架构的能力。 100个字符的varchar是“value”字段的正确类型吗? 200字?它应该是nvarchar吗?这可能是一个艰难的权衡,最后你必须对你的集合的动态性质进行人为限制。像“你只能有x个用户定义的字段,每个字符只能是y字符长。”

使用面向文档的解决方案(如MongoDB或CouchDB),可以在单个元组中维护与用户关联的所有属性。因为连接不是问题,所以生活很幸福,因为尽管有炒作,但这两者都不能很好地连接。您的用户可以根据需要(或允许)定义尽可能多的属性,这些属性的长度在您达到大约4MB之前难以管理。

如果您的数据需要ACID级完整性,您可以考虑拆分解决方案,高完整性数据存储在关系数据库中,动态数据存储在非关系存储中。

答案 4 :(得分:6)

即使您提供了添加自定义列的用户,也不一定会查询这些列的效果。查询设计中有许多方面可以使它们表现良好,其中最重要的是对应该存储的内容的正确规范。因此,从根本上说,您是否希望允许用户在不考虑规范的情况下创建模式,并能够快速从该模式中获取信息?如果是这样,那么任何这样的解决方案都可以很好地扩展,特别是如果你想让用户对数据进行数值分析时,这是不可能的。

选项1

IMO这种方法为您提供了架构,而不了解架构意味着什么是灾难的处方和报表设计者的噩梦。即,您必须让元数据知道哪些列存储了哪些数据。如果元数据搞砸了,它就有可能阻塞你的数据。此外,它可以很容易地将错误的数据放入错误的列中。 (“什么?String1包含修道院的名称?我认为这是Chalie Sheen最喜欢的药物。”)

选项3,4,5

IMO,要求2,3和4消除了EAV的任何变化。如果你需要对这些数据进行查询,排序或计算,那么EAV就是Cthulhu的梦想,也是你的开发团队和DBA的噩梦。 EAV将在性能方面造成瓶颈,无法为您提供快速获取所需信息所需的数据完整性。查询将很快转向交叉表Gordian结。

选项2,6

这确实留下了一个选择:收集规范然后构建模式。

如果客户希望在他们希望存储的数据上获得最佳性能,那么他们需要经历与开发人员合作的过程,以了解他们的需求,以便尽可能高效地存储。它仍然可以存储在与其余表分开的表中,其代码可以根据表的模式动态构建表单。如果您有一个允许列上的扩展属性的数据库,您甚至可以使用它们来帮助表单构建器使用漂亮的标签,工具提示等。所有必要的是添加模式。无论哪种方式,要有效地构建和运行报告,都需要正确存储数据。如果有问题的数据有很多空值,则某些数据库可以存储该类型的信息。例如,SQL Server 2008有一个名为Sparse Columns的功能,专门用于包含大量空值的数据。

如果这只是一袋没有进行分析,过滤或排序的数据,我会说EAV的一些变化可能会成功。但是,根据您的要求,最有效的解决方案是获得正确的规范,即使您将这些新列存储在单独的表中并在这些表中动态构建表单。

Sparse Columns

答案 5 :(得分:4)

这是一个有问题的情况,并且所有解决方案都没有出现“正确”。然而,在简单性和性能方面,选项1可能是最好的。

这也是某些商业企业应用程序中使用的解决方案。

修改

另一个现在可用的选项,但当问题是原始问题时不存在(或者至少不成熟)是在数据库中使用json字段。

许多关系数据库现在支持基于json的字段(可以包含动态的子字段列表)并允许查询它们

postgress

mysql

答案 6 :(得分:4)

  
      
  1. 创建多个UDF表,每种数据类型一个。所以我们有UDFStrings,UDFDates等的表。可能和#2一样,并且只要添加新字段就自动生成View
  2.   

根据我的研究,基于数据类型的多个表格无法帮助您提高性能。特别是如果您有批量数据,例如20K或25K记录,包含50+ UDF。表现最差。

您应该使用包含多个列的单个表格,如:

varchar Name
varchar Type
decimal NumberValue
varchar StringValue
date DateValue

答案 7 :(得分:2)

我有过经验或1,3和4,它们都会变得混乱,因为不清楚数据是什么或者真的很复杂,通过某种软分类将数据分解为动态记录类型

我很想尝试使用XML,你应该能够针对xml的内容强制执行模式来检查数据输入等,这将有助于保存不同的UDF数据集。在较新版本的SQL Server中,您可以对XML字段建立索引,这应该有助于提高性能。 (例如,参见http://blogs.technet.com/b/josebda/archive/2009/03/23/sql-server-2008-xml-indexing.aspx

答案 8 :(得分:2)

如果您使用的是SQL Server,请不要忽略sqlvariant类型。这很快,应该做你的工作。其他数据库可能有类似的东西。

由于性能原因,XML数据类型不太好。如果你在服务器上进行计算,那么你经常需要反序列化这些。

选项1听起来很糟糕,看起来很邋,,但性能方面可能是你最好的选择。我之前创建了名为Field00-Field99的列的表,因为你无法击败性能。您可能也需要考虑您的INSERT性能,在这种情况下,这也是您需要的。如果您希望它看起来整洁,您可以随时在此表上创建视图!

答案 9 :(得分:1)

SharePoint使用选项1并具有合理的性能。

答案 10 :(得分:1)

我过去使用这些选项都没有成功(选项6?:))。

我为用户创建一个模型(存储为xml并通过自定义建模工具公开),并从模型生成的表和视图创建模型,以使用用户定义的数据表连接基表。因此,每种类型都有一个包含核心数据的基表和一个包含用户定义字段的用户表。

以文档为例:典型的字段是名称,类型,日期,作者等。这将在核心表中。然后用户可以使用自己的字段定义自己的特殊文档类型,例如contract_end_date,renewal_clause,blah blah blah。对于该用户定义的文档,将在公共主键上连接核心文档表xcontract表(因此xcontracts主键在核心表的主键上也是外来的)。然后我会生成一个视图来包装这两个表。查询时的性能很快。其他业务规则也可以嵌入到视图中。这对我来说非常好。

答案 11 :(得分:0)

在评论中,我看到你说UDF字段是要转储未被用户正确映射的导入数据。

也许另一种选择是跟踪每个用户所做的UDF的数量,并强制他们重复使用字段,说他们可以使用6个(或其他一些同等随机限制)的自定义字段顶部。

当您遇到像这样的数据库结构问题时,通常最好回到应用程序的基本设计(在您的情况下导入系统)并对其进行一些限制。

现在,我要做的是选项4(编辑),并添加了一个指向用户的链接:

general_data_table
id
...


udfs_linked_table
id
general_data_id
udf_id


udfs_table
id
name
type
owner_id --> Use this to filter for the current user and limit their UDFs
string_link_id --> link table for string fields
int_link_id
type_link_id

现在确保制作视图以优化性能并使索引正确。这种标准化水平使DB占用空间更小,但您的应用程序更复杂。

答案 12 :(得分:0)

我们的数据库为SaaS应用程序(帮助台软件)提供支持,用户拥有超过7k个“自定义字段”。我们使用组合方法:

  1. (EntityID, FieldID, Value)搜索数据
  2. entities表中的JSON字段,其中包含用于显示数据的所有实体值。 (这样你就不需要一百万个JOIN来获取值了。)
  3. 您可以进一步拆分#1以获得“每个数据类型的表”,如this answer所示,这样您甚至可以索引UDF。

    P.S。几个词来捍卫“实体 - 属性 - 价值”的方法,每个人都在抨击。几十年来,我们已经使用了没有#2的#1,它工作得很好。有时这是一个商业决策。你有时间重写你的应用程序并重新设计数据库,或者你可以在云服务器上花几块钱,这些天真的很便宜吗?顺便说一句,当我们使用#1方法时,我们的数据库拥有数百万个实体,由数以千计的用户访问,而16GB双核数据库服务器做得很好(在AWS上真的是“r3”vm)

答案 13 :(得分:0)

我建议使用#4 ,因为 Magento 使用了这种类型的系统,该系统是高度认可的电子商务CMS平台。使用单个表格使用 fieldId label 列定义您的自定义字段。然后,为每种数据类型提供单独的表,并且在每个表中都有一个索引,该索引通过 fieldId 和数据类型 value 列进行索引。然后,在您的查询中,使用类似以下内容:

SELECT *
FROM FieldValues_Text
WHERE fieldId IN (
    SELECT fieldId FROM Fields WHERE userId=@userId
)
AND value LIKE '%' + @search + '%'

我认为这将确保用户定义类型的最佳性能。

根据我的经验,我曾在多个Magento网站上工作,这些网站每月为数百万用户提供服务,托管具有自定义产品属性的数千种产品,并且数据库可以轻松处理工作负载,甚至用于报告。

对于报告,您可以使用PIVOT将您的 Fields label 值转换为列名,然后将每个数据类型表的查询结果转换为那些枢轴的列。