154

我的要求是:

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

其他信息:

  • 我主要在寻找性能
  • 有几百万条主记录可以附加 UDF 数据
  • 当我上次检查时,我们当前的数据库中有超过 5000 万条 UDF 记录
  • 大多数时候,UDF 只附加到几千条主记录,而不是全部
  • UDF 不连接或用作键。它们只是用于查询或报告的数据

选项:

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

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

  3. 创建一个包含 UDFName、UDFDataType 和 Value 的表。添加新的 UDF 时,生成一个视图,该视图仅提取该数据并将其解析为指定的任何类型。不符合解析条件的项目返回 NULL。

  4. 创建多个 UDF 表,每种数据类型一个。所以我们会有用于 UDFStrings、UDFDates 等的表。可能会做与 #2 相同的操作,并在添加新字段时自动生成视图

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

  6. 还有什么?

4

14 回答 14

52

如果性能是主要关注点,我会选择#6……每个 UDF 一个表(实际上,这是#2 的变体)。该答案专门针对这种情况以及所描述的数据分布和访问模式的描述而量身定制。

优点:

  1. 因为您指出某些 UDF 具有整个数据集的一小部分的值,所以单独的表将为您提供最佳性能,因为该表将仅与支持 UDF 所需的一样大。相关指数也是如此。

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

  3. 您可以使用反映数据实际内容的表/列名称。

  4. 您可以完全控制使用数据类型、检查约束、默认值等来定义数据域。不要低估动态数据类型转换对性能的影响。此类约束还有助于 RDBMS 查询优化器制定更有效的计划。

  5. 如果您需要使用外键,内置的声明性引用完整性很少会被基于触发器或应用程序级别的约束实施所超越。

缺点:

  1. 这可能会创建很多表。强制模式分离和/或命名约定将缓解这种情况。

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

其他注意事项:

  1. 如果数据的性质对 UDF 进行分组有意义,那么应该鼓励这样做。这样,这些数据元素可以组合到一个表中。例如,假设您有颜色、尺寸和成本的 UDF。数据的趋势是该数据的大多数实例看起来像

     'red', 'large', 45.03 
    

    而不是

     NULL, 'medium', NULL
    

    在这种情况下,通过组合 1 个表中的 3 列不会导致明显的速度损失,因为很少有值会是 NULL,并且您避免再创建 2 个表,即当您需要访问所有 3 个列时需要减少 2 个连接.

  2. 如果您从一个人口众多且经常使用的 UDF 中遇到性能障碍,那么应该考虑将其包含在主表中。

  3. 逻辑表设计可以将您带到某个点,但是当记录数变得非常庞大时,您还应该开始查看您选择的 RDBMS 提供了哪些表分区选项。

于 2011-03-01T06:46:57.570 回答
23

我已经了很多关于这个问题的文章。最常见的解决方案是实体-属性-值反模式,它类似于您在选项 #3 中描述的内容。 像瘟疫一样避免这种设计

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

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

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

于 2011-03-03T07:59:05.930 回答
12

这听起来像是一个可以通过非关系解决方案(如 MongoDB 或 CouchDB)更好地解决的问题。

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

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

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

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

使用面向文档的解决方案,如 MongoDB 或 CouchDB,您可以在单个元组中维护与用户关联的所有属性。由于联接不是问题,因此生活是幸福的,因为尽管大肆宣传,这两者都不能很好地处理联接。您的用户可以定义任意数量的属性(或者您将允许),其长度在达到大约 4MB 之前不会难以管理。

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

于 2011-03-06T08:24:13.283 回答
11

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

  • varchar 名称
  • varchar 类型
  • 十进制数值
  • varchar 字符串值
  • 日期日期值

课程的具体类型取决于您的需求(当然也取决于您使用的 dbms)。您还可以将 NumberValue(十进制)字段用于 int 和布尔值。您可能还需要其他类型。

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

您可能想要某种元数据信息。所以你最终得到以下结果:

表 UdfMetaData

  • 内部编号
  • varchar 名称
  • varchar 类型

表 MasterUdfValues

  • int Master_FK
  • 诠释元数据_FK
  • 十进制数值
  • varchar 字符串值
  • 日期日期值

无论您做什么,我都不会动态更改表结构。这是维护的噩梦。我也不会使用 XML 结构,它们太慢了。

于 2011-03-01T18:05:45.683 回答
6

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

选项1

IMO 这种方法为您提供了架构,但不知道架构意味着什么,这是灾难的根源,也是报表设计者的噩梦。即,您必须拥有元数据才能知道哪个列存储了哪些数据。如果该元数据搞砸了,它就有可能破坏您的数据。另外,它可以很容易地将错误的数据放在错误的列中。(“什么?String1 包含修道院的名称?我以为这是 Chalie Sheen 最喜欢的药物。”)

选项 3、4、5

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

选项 2,6

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

如果客户想要他们希望存储的数据的最佳性能,那么他们需要通过与开发人员合作的过程来了解他们的需求,以便尽可能高效地存储数据。它仍然可以存储在与其他表分开的表中,代码可以根据表的模式动态构建表单。如果您有一个允许在列上扩展属性的数据库,您甚至可以使用它们来帮助表单构建器使用漂亮的标签、工具提示等,这样只需添加模式即可。无论哪种方式,为了有效地构建和运行报告,都需要正确存储数据。如果有问题的数据有很多空值,一些数据库有能力存储这种类型的信息。例如,

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

稀疏列

于 2011-03-04T23:50:39.770 回答
5
  1. 创建多个 UDF 表,每种数据类型一个。所以我们会有用于 UDFStrings、UDFDates 等的表。可能会做与 #2 相同的操作,并在添加新字段时自动生成视图

根据我的研究,基于数据类型的多个表不会帮助您提高性能。尤其是如果您有大量数据,例如 20K 或 25K 记录和 50 多个 UDF。性能是最差的。

您应该使用具有多个列的单个表,例如:

varchar Name
varchar Type
decimal NumberValue
varchar StringValue
date DateValue
于 2015-09-11T11:32:49.940 回答
4

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

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

编辑

现在可用但不存在(或至少不成熟)的另一个选项是在最初提出问题时使用数据库中的 json 字段。

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

后退

mysql

于 2011-02-24T15:08:43.123 回答
3

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

  1. (EntityID, FieldID, Value)用于搜索数据的表
  2. 表中的 JSON 字段entities,包含所有实体值,用于显示数据。(这样你不需要一百万个 JOIN 来获取值)。

您可以进一步拆分 #1 以获得“每个数据类型的表”,就像这个答案所暗示的那样,这样您甚至可以索引您的 UDF。

PS 几句话来捍卫每个人都在抨击的“实体-属性-值”方法。几十年来,我们一直使用 #1 而没有 #2,它工作得很好。有时这是一个商业决策。您是否有时间重写您的应用程序并重新设计数据库,或者您可以在云服务器上投入几块钱,这些天真的很便宜?顺便说一句,当我们使用 #1 方法时,我们的数据库拥有数百万个实体,被成千上万的用户访问,而 16GB 双核数据库服务器运行良好

于 2017-12-22T21:09:26.740 回答
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)例如

于 2011-02-24T15:09:36.293 回答
2

如果您使用的是 SQL Server,请不要忽略 sqlvariant 类型。它非常快,应该可以完成您的工作。其他数据库可能有类似的东西。

出于性能原因,XML 数据类型并不是那么好。如果您在服务器上进行计算,那么您必须不断地反序列化这些。

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

于 2011-02-24T15:13:06.257 回答
1

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

于 2011-02-24T15:10:37.973 回答
1

过去,我没有使用这些选项(选项 6?:))成功地管理了这一点。

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

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

于 2011-03-04T09:53:49.150 回答
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

现在确保创建视图以优化性能并正确设置索引。这种标准化水平使数据库占用空间更小,但您的应用程序更复杂。

于 2011-03-07T09:55:16.557 回答
0

我会推荐#4,因为这种类型的系统用于Magento,这是一个高度认可的电子商务 CMS 平台。使用单个表使用fieldId标签列定义您的自定义字段。然后,为每种数据类型创建单独的表,并且在每个表中都有一个按fieldId和数据类型列进行索引的索引。然后,在您的查询中,使用以下内容:

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

在我看来,这将确保用户定义类型的最佳性能。

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

对于报告,您可以使用PIVOT字段标签值转换为列名,然后将每个数据类型表中的查询结果转置到这些转置列中。

于 2019-03-22T21:11:22.917 回答