【问题标题】:How to design a database for User Defined Fields?如何为用户定义字段设计数据库?
【发布时间】:2011-07-03 15:25:16
【问题描述】:

我的要求是:

  • 需要能够动态添加任何数据类型的用户定义字段
  • 需要能够快速查询 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. 还有别的吗?

【问题讨论】:

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


【解决方案1】:

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

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

您可以进一步拆分 #1 以获得像 this answer 建议的“每个数据类型的表”,这样您甚至可以索引您的 UDF。

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

【讨论】:

  • 嗨@Alex,我遇到了类似的问题。如果我理解你有:1)一个custom_fields表存储值,例如1 => last_concert_year,2 => band,3 => music,然后是一个custom_fields_values表,值为001, 1, 1976 002, 1, 1977 003, 2, Iron Maiden 003, 3, Metal 希望这个例子对你有意义,对格式表示抱歉!
  • @thitami 不完全是。按照您的示例:我有一个bands 表,其中一行1,'Iron Maiden' 然后custom_fields1,'concert_year' | 2,'music' 然后custom_fields_values1,1,'1977'|1,2,'metal'
  • @AlexfromJitbit 一个简单的问题,如果您有超过一百万的用户,在创建新的自定义字段时,必须使用插入触发器为每个用户插入 custom_field_value 行,对吗?
  • 另外,您如何确保json 字段和custom_field_value 行之间的一致性?
  • @ShyamalParikh 我们没有使用触发器,一致性是在应用程序端管理的。当我们添加一个新的自定义字段时 - 没有任何反应,除非您为特定用户设置该字段。当我们删除一个字段时——也没有任何反应,我们只是在应用端显示 JSON 时验证该字段是否存在。
【解决方案2】:

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

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

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

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

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

【讨论】:

    【解决方案3】:

    我有written 关于这个问题a lot。最常见的解决方案是实体-属性-值反模式,类似于您在选项 #3 中描述的内容。 Avoid this design like the plague.

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

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

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

    【讨论】:

    • 你能解释一下为什么我应该避免使用选项#3吗?我查看了您的一些示例,但它们确实与我正在尝试做的不同。我只是想要一个存储额外数据的地方,而不是一个存储所有属性的地方。
    • 对于初学者,你会让谁的属性不为空?在不使所有属性都唯一的情况下,如何使属性唯一?它从那里继续。您最终会编写应用程序代码来提供 RDBMS 已经为您提供的功能,甚至不得不编写某种映射类来简单地插入逻辑实体记录并将其取回。
    • 简短的回答是“不要混合数据和元数据”。为fieldnametablename 创建varchar 列会将元数据标识符存储为数据字符串,这就是许多问题的开始。另见en.wikipedia.org/wiki/Inner-platform_effect
    • @Thomas:在倒排索引设计中,您可以对数据类型使用标准模式解决方案,以及 UNIQUE 和 FOREIGN KEY 等约束。当您使用 EAV 时,这些根本不起作用。我同意倒排索引与 EAV 共享非关系性的特点,仅仅是因为它支持每行不同的属性,但这是一个妥协点。
    • @thitami,多年来我了解到,any 解决方案可能适合您的应用。对于某些特定的应用程序,即使 EAV 也可能是最不坏的解决方案。在不了解您的查询的情况下,您无法选择优化策略。每种优化都会以牺牲其他查询为代价来改进某些查询。
    【解决方案4】:

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

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

    编辑

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

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

    postgress

    mysql

    【讨论】:

    • 我讨厌创建可能数百个未使用的列的想法。这与我所学和阅读的有关 SQL 数据库设计的内容背道而驰。目前,我们有超过 1300 个不同的用户定义值,尽管其中很多只是现有项目的复制品,但名称不同。
    • 单个表有 1300 个不同的 UDF?每个用户都可以选择添加 UDF,还是只有某种高级用户?
    • 它是导入过程的一部分...它将任何非映射数据添加到用户定义的字段。由于没有人花时间将未映射的数据映射到现有的 UDF 字段,它只会创建新的字段,并且多年来已经添加了很多。
    【解决方案5】:
    1. 创建多个 UDF 表,每种数据类型一个。所以我们会有用于 UDFStrings、UDFDates 等的表。可能会做与 #2 相同的操作,并在添加新字段时自动生成视图

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

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

    varchar Name
    varchar Type
    decimal NumberValue
    varchar StringValue
    date DateValue
    

    【讨论】:

    • 这应该是正确的和赞成的。菲尔先前在 2011 年的回答在 2016 年今天不再是一个好建议。
    • 我能得到一个简单的例子来说明如何在sql中进行这样的过程吗?
    • 抱歉回复晚了,但您希望数据库结构相同。我没有得到你@Niroj。你能详细解释一下你想要什么。
    【解决方案6】:

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

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

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

    【讨论】:

      【解决方案7】:

      在 cmets 中我看到您说 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
      

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

      【讨论】:

        【解决方案8】:

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

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

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

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

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

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

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

        【讨论】:

          【解决方案9】:

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

          选项 1

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

          选项 3、4、5

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

          选项 2,6

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

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

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

          Sparse Columns

          【讨论】:

            【解决方案10】:

            如果性能是主要问题,我会选择#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. 如果您从 人口稠密的UDF和 经常使用,那么应该是 考虑列入 主表。

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

            【讨论】:

            • 检查清单!我和菲尔之间的内部玩笑,我希望这不违反规则。
            • 谢谢,我想我会做一些变化。我们的大部分 UDF 数据来自未映射的导入字段,这些字段只需要保留以供参考,因此我想将它们放在一个表中。其他 UDF 是根据需要定义的(我无法提前识别它们。它们通常是在我们更改某些流程或决定在几个月内跟踪某些特殊情况时创建的)并且通常用于查询。我想我会为这些值的每个逻辑单元制作一个单独的表。
            • 我正在使用带有日期/版本的 UDF 的表,我使用这种方法 stackoverflow.com/a/123481/328968 来获取最新值。
            【解决方案11】:

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

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

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

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

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

            表 UdfMetaData

            • int id
            • varchar 名称
            • varchar 类型

            表 MasterUdfValues

            • int Master_FK
            • int MetaData_FK
            • 十进制数字值
            • varchar 字符串值
            • 日期日期值

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

            【讨论】:

            • 我喜欢你的策略,也许会选择它,但在 2017 年,你会选择不同的策略吗?像json
            • 在我们的项目中,我们实现了自己的数据结构,序列化为类似于 json 的东西。它具有类型保存接口,无需转换即可读取和写入数据,并且具有出色的编程语言集成。这真的很棒。它与数据库中所有此类“文档”具有相同的问题。很难查询特定值,也不能轻易引用“文档”之外的数据。根据使用情况,两者都不是问题。
            • 除此之外,我在 2011 年提出的 IMHO 仍然是一个有效的解决方案。
            【解决方案12】:

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

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

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

            【讨论】:

            • 谢谢,我再看看 SQL 变体。我最担心的是性能,我不确定它会如何处理,尤其是当我们谈论超过 5000 万行时
            • 刚刚发现 sql_varients 不能与 LIKE 子句一起使用......这对我来说是一个巨大的缺点。当然,如果我确实为每个 UDF 创建了一个视图,那么我可以根据 SQL_VARIANT_PROPERTY(value, 'BaseType') 将其转换为适当的数据类型......不过,这似乎对性能不利
            • 你可以使用LIKE,但你必须先转换值。 LIKE 仅适用于 varchars,因此您必须将 sql_variant 转换为 varchar。只要您知道您的 UDF 是否为 varchar(例如,因为类型存储在其他地方),您就可以将所有行过滤为 varchars,然后强制转换并运行您的 LIKE 查询:例如。 select * FROM MyTable where variant_type = 'v' Cast(variant_value as varchar(max)) LIKE 'Blah%' 这样一来,您就不会将整数等转换为会减慢您速度的字符串。
            • 我需要运行一些测试来看看性能如何,尤其是数百万行。知道任何关于使用 sql_varients 的性能的在线文章吗?尤其是演员阵容和大量记录?
            【解决方案13】:

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

            【讨论】:

              【解决方案14】:

              我有过 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)例如

              【讨论】:

              • 老实说,我根本没有研究过 XML。这样做的主要缺点是我必须了解它是如何工作的以及如何查询它,而且我听说性能可能比其他选项更差
              • 我会避免使用 xml:它可以完成这项工作,而且我过去在 xml 中实现过类似的东西,但是随着数据结构的增长,性能变得非常糟糕,并且代码复杂度很高。
              猜你喜欢
              • 2012-03-13
              • 1970-01-01
              • 2014-06-24
              • 1970-01-01
              • 1970-01-01
              • 2013-07-20
              • 1970-01-01
              • 1970-01-01
              • 2016-04-30
              相关资源
              最近更新 更多