【问题标题】:Key value pairs in relational database关系数据库中的键值对
【发布时间】:2010-09-12 16:19:50
【问题描述】:

有人有在数据库中存储键值对的经验吗?

我一直在用这种类型的桌子:

CREATE TABLE key_value_pairs ( 
    itemid           varchar(32) NOT NULL,
    itemkey         varchar(32) NOT NULL,
    itemvalue       varchar(32) NOT NULL,
    CONSTRAINT ct_primarykey PRIMARY KEY(itemid,itemkey)
)

那么例如可能存在以下行:

 itemid            itemkey        itemvalue    
 ----------------  -------------  ------------ 
 123               Colour         Red            
 123               Size           Medium             
 123               Fabric         Cotton

这种方案的问题在于提取数据所需的 SQL 语法相当复杂。 只创建一系列键/值列会更好吗?

CREATE TABLE key_value_pairs ( 
    itemid            varchar(32) NOT NULL,
    itemkey1        varchar(32) NOT NULL,
    itemvalue1      varchar(32) NOT NULL,
    itemkey2        varchar(32) NOT NULL,
    itemvalue2      varchar(32) NOT NULL,
 . . .etc . . .
)

这将更容易和更快地查询,但缺乏第一种方法的可扩展性。 有什么建议吗?

【问题讨论】:

  • 这个例子不是最优的,因为每件商品可能只有一种颜色、尺寸和面料,在这种情况下,您可以使用列作为属性。

标签: sql database


【解决方案1】:

第一种方法还可以。您可以创建一个提取所需数据的 UDF,然后调用它。

【讨论】:

    【解决方案2】:

    根据经验,我发现某些键将被更广泛地使用或更频繁地查询。然后,我们通常会稍微去规范化设计,以在主“项目”表中包含特定字段。

    例如。如果每个项目都有一个颜色,您可以将颜色列添加到您的项目表中。 Fabric 和 Size 的使用频率可能较低,并且可以在键值对表中单独保存。您甚至可以保留键值对表中的颜色,但复制项目表中的数据以获得性能优势。

    显然,这取决于数据以及您需要键值对的灵活性。它还可能导致您的属性数据的位置不一致。但是,反规范化确实极大地简化了查询并提高了它们的性能。

    我通常只会在性能出现问题时才考虑去规范化,而不仅仅是为了简化查询。

    【讨论】:

      【解决方案3】:

      第二张表严重反规范化。我会坚持第一种方法。

      【讨论】:

        【解决方案4】:

        如果您只有很少的可能键,那么我会将它们存储为列。但是,如果可能的密钥集很大,那么您的第一种方法很好(第二种方法是不可能的)。

        或者是不是每个项目只能有有限数量的键,但键可能来自一个大集合?

        您还可以考虑使用对象关系映射器来简化查询。

        【讨论】:

        • ORM 使查询更容易,但不会提高性能。手动编码的 SQL 查询可能会提供更好的性能。
        • 可以的。但可能不是,而且速度不是他要问的问题。
        【解决方案5】:

        以您提到的成本,第一种方法要灵活得多。

        如您所展示的,第二种方法永远不可行。相反,您会这样做(根据您的第一个示例)

        create table item_config (item_id int, colour varchar, size varchar, fabric varchar)
        

        当然,这只有在数据量已知且变化不大的情况下才有效。

        作为一般规则,任何需要更改表的 DDL 以进行正常工作的应用程序都应该三思而后行。

        【讨论】:

          【解决方案6】:

          我认为您的做法是正确的,只要给定类型项目的键/值经常更改即可。
          如果它们是相当静态的,那么简单地使项目表更宽更有意义。

          我们使用类似(但更复杂)的方法,围绕键/值有很多逻辑,以及每个键允许的值类型的表。
          这允许我们将项目定义为键的另一个实例,并且我们的中心表将任意键类型映射到其他任意键类型。它可以迅速将你的大脑打结,但一旦你编写并封装了处理这一切的逻辑,你就有很大的灵活性。

          如果需要,我可以写下我们所做的更多细节。

          【讨论】:

            【解决方案7】:

            我不明白为什么提取数据的 SQL 对于您的第一个设计来说应该很复杂。当然要获得一个项目的所有值,你只需这样做:

            SELECT itemkey,itemvalue FROM key_value_pairs WHERE itemid='123';
            

            或者如果您只想要该项目的一个特定键:

            SELECT itemvalue FROM key_value_pairs WHERE itemid='123' AND itemkey='Fabric';
            

            第一个设计还使您可以灵活地随时轻松添加新键。

            【讨论】:

            • 如果其中一个值是日期并且您想在日期之间搜索某些键,则会变得复杂。
            • 反向思考查询 - 找到一组键/值对的 itemid,这需要一组级联的连接。由于需要避免选择超集,情况变得更加复杂;例如find (Colour=Red,Size=Medium) 不能返回 itemid 123,因为该集合包含另一行 (Fabric=Cotton)
            【解决方案8】:

            如果键是动态的,或者有很多键,则使用您拥有的映射表作为第一个示例。此外,这是最通用的解决方案,随着您添加更多键,它在未来的扩展性最好,很容易编写 SQL 来获取数据,并且数据库将能够比您想象的更好地优化查询(也就是说,我不会过早地优化这个案例,除非它被证明是以后测试的瓶颈,在这种情况下你可以考虑下面的两个选项)。

            如果键是已知的集合,并且数量不多(

            如果有中等数量的已知固定键(10 - 30),那么可能有另一个表来保存 item_details。

            但是我认为不需要使用您的第二个示例结构,它看起来很麻烦。

            【讨论】:

              【解决方案9】:

              还有另一种解决方案介于两者之间。您可以为键和值使用 xml 类型的列。所以你保留 itemid 字段,然后有一个 xml 字段,其中包含为某些键值对定义的 xml,例如 <items> <item key="colour" value="red"/><item key="xxx" value="blah"/></items> 然后,当您从数据库中提取数据时,您可以通过多种不同的方式处理 xml。根据你的使用情况。这是一个可扩展的解决方案。

              【讨论】:

              • 这是一种可能的情况,我也更喜欢简单的 KV 概念。将数据(键、值)与其元数据(例如,在单独的“属性”列中的 XML 配置中)分开。灵活、可扩展且易于处理(例如 JAXB)。当您扩展业务逻辑时,您不必一直更改数据库架构。持久性逻辑(加载/保存)和域接口可以使用“约定优于配置”开发一次,并且不需要更改/扩展。
              • 我使用与 JSON 类似的方法取得了很好的成功。最大的缺点是 value 字段中的数据在 SQL 层中没有用处。换句话说,告别对值字段中的数据进行索引/排序/加入/搜索/过滤。
              【解决方案10】:

              只要业务需求仍然可以满足,违反规范化规则就可以了。拥有key_1, value_1, key_2, value_2, ... key_n, value_n 可以,直到您需要key_n+1, value_n+1

              我的解决方案是共享属性的数据表和唯一属性的 XML。这意味着我同时使用两者。如果所有东西(或大多数东西)都有大小,那么大小就是表格中的一列。如果只有对象 A 具有属性 Z,则 Z 存储为 XML,类似于 Peter Marshall 已经给出的答案。

              【讨论】:

              • 只要业务需求仍然可以满足,违反规范化规则是不行的。只要规范化的数据是非性能的,违反规范化规则就可以了,即使那样,您确实需要数据的规范化版本和规范化的物化视图。
              • key_n, value_n 解决方案使得 SQl 真的很难。你如何为“fabric = 'cotton' and color = 'Red' 编码 Sal?你最终会得到: where ( key_1 = "Fabric" and value_1 = "Cotton" or key_2 = "Fabric" and value_1 = "Cotton " .... 和 ( ...
              【解决方案11】:

              在大多数情况下,您会使用第一种方法,这是因为您还没有真正坐下来思考您的模型。 “嗯,我们还不知道钥匙会是什么”。一般来说,这是非常糟糕的设计。它会比实际将键作为列要慢,它们应该是。

              我还想问为什么你的 id 是 varchar。

              在您确实必须实现键/值表的极少数情况下,第一个解决方案很好,不过,我通常希望将键放在单独的表中,这样您就不会将 varchars 作为键存储在您的键/值表。

              例如,

              CREATE TABLE valid_keys ( 
                  id            NUMBER(10) NOT NULL,
                  description   varchar(32) NOT NULL,
                  CONSTRAINT pk_valid_keys PRIMARY KEY(id)
              );
              
              CREATE TABLE item_values ( 
                  item_id NUMBER(10) NOT NULL,
                  key_id  NUMBER(10) NOT NULL,
                  item_value VARCHAR2(32) NOT NULL,
                  CONSTRAINT pk_item_values PRIMARY KEY(item_id),
                  CONSTRAINT fk_item_values_iv FOREIGN KEY (key_id) REFERENCES valid_keys (id)
              );
              

              然后您甚至可以发疯并为键添加“TYPE”,从而允许进行一些类型检查。

              【讨论】:

                【解决方案12】:

                在继续你的方法之前,我谦虚地建议你退后一步,考虑一下你是否真的想将这些数据存储在“键值对”表中。我不了解您的应用程序,但我的经验表明,每次我完成您正在做的事情后,我希望我创建了一个颜色表、一个织物表和一个尺码表。

                考虑参照完整性约束,如果您采用键值对方法,数据库无法告诉您何时尝试将颜色 id 存储在大小字段中

                考虑一下加入具有 10 个值的表与可能具有跨多个域的数千个值的通用值的性能优势。 Key Value 的索引到底有多大用处?

                通常,您所做的事情背后的原因是因为域需要是“用户可定义的”。如果是这种情况,那么即使我也不会推动您即时创建表格(尽管这是一种可行的方法)。

                但是,如果您的理由是因为您认为它比多个表更易于管理,或者因为您正在设想一个适用于所有域的维护用户界面,那么在继续之前停下来认真思考一下。

                【讨论】:

                • A++ ,这就是我想说的,但你表达得更好。我处理的数据库中有几个键/值对表,我每天都后悔。每次他们完成都是因为“我们现在需要一个解决方案”,而且每次我都知道这样做是错误的。
                • 除了 SQL 之外,还有哪些数据存储系统可以更好地处理 KVP? SQL做不好,没人做,还是做不好?
                • @quillbreaker NoSql 解决方案经常围绕 kv 对的有效存储展开。
                • 我可以看到这个答案有很多赞成票。 @Daniel 您如何看待这篇关于 reddit 的帖子的“第 3 课:开放架构”部分?似乎 reddit 正在使用键/值方法,这是他们在 reddit 成长过程中学到的经验之一。谢谢。
                • @Guido 对于某些系统,关系模型并不是最好的方法。我的回答只有在您选择了关系模型来存储数据时才适用。
                【解决方案13】:

                如果你走 KVP 表的路线,我不得不说我自己一点也不喜欢这种技术,因为它确实很难查询,那么你应该考虑将单个项目 id 的值聚集在一起使用适合您使用的任何平台的技术。

                RDBMS 倾向于分散行以避免插入时的块争用,如果您有 8 行要检索,您很容易发现自己访问了表的 8 个块来读取它们。在 Oracle 上,您最好考虑使用散列集群来存储这些内容,这将大大提高访问给定项目 ID 值的性能。

                【讨论】:

                  【解决方案14】:

                  您的示例不是使用键值对的一个很好的示例。一个更好的例子是在计费应用程序中使用诸如费用表、客户表和 Customer_Fee 表之类的东西。费用表将包含以下字段: fee_id、fee_name、fee_description Customer_Fee 表将包含以下字段: customer_id、fee_id、fee_value

                  【讨论】:

                    【解决方案15】:

                    我认为设计此类表格的最佳方法如下:

                    • 将常用字段设为数据库中的列。
                    • 提供一个 Misc 列,其中包含一个字典(在 JSON/XML/其他字符串格式中),它将包含字段作为键值对。

                    要点:

                    • 在大多数情况下,您可以编写普通 SQL 查询来查询 SQL。
                    • 您可以对键值对执行全文搜索。 MySQL 有一个全文搜索引擎,否则您可以使用速度较慢的“like”查询。虽然全文搜索不好,但我们假设此类查询较少,因此不会导致太多问题。
                    • 如果您的键值对是简单的布尔标志,则此技术与为键设置单独的列具有相同的功能。任何更复杂的键值对操作都应该在数据库之外进行。
                    • 查看一段时间内的查询频率将告诉您哪些键值对需要在列中转换。
                    • 此技术还可以轻松地对数据库强制执行完整性约束。
                    • 它为开发人员重构架构和代码提供了更自然的途径。

                    【讨论】:

                      【解决方案16】:

                      我曾经在数据库中使用键值对来创建电子表格(用于数据输入),出纳员可以在其中总结他在现金抽屉中的活动。每个 k/v 对代表一个命名单元格,用户在其中输入货币金额。这种方法的主要原因是电子表格很容易发生变化。定期添加新产品和服务(因此出现了新细胞)。此外,某些单元格在某些情况下是不需要的,可能会被丢弃。

                      我编写的应用程序是对一个应用程序的重写,该应用程序确实将柜员表分成不同的部分,每个部分表示在不同的表格中。这里的问题是,随着产品和服务的添加,需要对模式进行修改。与所有设计选择一样,与另一个方向相比,采用某个方向也有利有弊。我的重新设计确实执行得更慢并且更快地消耗了磁盘空间。但是,它非常灵活,可以在几分钟内添加新产品和服务。然而,唯一值得注意的问题是磁盘消耗。我想不起其他的头痛了。

                      如前所述,我通常考虑键值对方法的原因是当用户(可能是企业所有者)想要创建自己的类型时,该类型具有特定于用户的属性集。在这种情况下,我做出了以下决定。

                      如果不需要通过这些属性检索数据,或者一旦检索到一大块数据就可以将搜索推迟到应用程序,我建议将所有属性存储在单个文本字段中(使用 JSON、YAML、XML , 等等。)。如果强烈需要通过这些属性检索数据,就会变得混乱。

                      您可以创建单个“属性”表(id、item_id、key、value、data_type、sort_value),其中排序列将实际值转换为字符串可排序的表示形式。 (例如日期:“2010-12-25 12:00:00”,数字:“0000000001”)或者您可以按数据类型(例如 string_attributes、date_attributes、number_attributes)创建单独的属性表。在这两种方法的众多优点和缺点中:第一种更简单,第二种更快。两者都会导致您编写丑陋、复杂的查询。

                      【讨论】:

                        【解决方案17】:

                        时代变了。现在,除了关系数据库之外,您还可以使用其他数据库类型。 NOSQL 选项现在包括列存储、文档存储、图形和多模型(请参阅:http://en.wikipedia.org/wiki/NoSQL)。

                        对于键值数据库,您的选择包括(但不限于)CouchDb、Redis 和 MongoDB。

                        【讨论】:

                          【解决方案18】:

                          PostgreSQL 8.4 支持 hstore 数据类型,用于在单个 PostgreSQL 数据字段中存储(键、值)对集。 请参考http://www.postgresql.org/docs/8.4/static/hstore.html 了解其使用信息。虽然这是一个非常古老的问题,但考虑传递此信息认为它可能对某人有所帮助。

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 2019-10-06
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多