【问题标题】:Storing JSON in database vs. having a new column for each key将 JSON 存储在数据库中与每个键都有一个新列
【发布时间】:2013-02-28 08:20:57
【问题描述】:

我正在实现以下模型,用于在我的表中存储用户相关数据 - 我有 2 列 - uid(主键)和一个 meta 列,它以 JSON 格式存储有关用户的其他数据。

 uid   | meta
--------------------------------------------------
 1     | {name:['foo'], 
       |  emailid:['foo@bar.com','bar@foo.com']}
--------------------------------------------------
 2     | {name:['sann'], 
       |  emailid:['sann@bar.com','sann@foo.com']}
--------------------------------------------------

这是否比每个属性一列模型更好的方法(性能方面,设计方面),其中表将包含许多列,例如uidnameemailid

我喜欢第一个模型的地方是,您可以添加尽可能多的字段,没有限制。

另外,我想知道,现在我已经实现了第一个模型。如何对其执行查询,例如,我想获取所有名称为 'foo' 的用户?

问题 - 使用 JSON 或每字段列在数据库中存储用户相关数据(请记住,字段数量不固定)的更好方法是什么?另外,如果实现了第一个模型,如何如上所述查询数据库?我是否应该使用这两种模型,通过将查询可能搜索的所有数据存储在单独的行中并将其他数据存储在 JSON 中(是不同的行)?


更新

由于我需要执行搜索的列不会太多,因此使用这两种模型是否明智?我需要搜索的数据的每列键和其他数据的 JSON(在同一个 MySQL 数据库中)?

【问题讨论】:

    标签: mysql sql sql-server database optimization


    【解决方案1】:

    2017 年 6 月 4 日更新

    鉴于这个问题/答案已经获得了一定的人气,我认为值得更新。

    最初发布此问题时,MySQL 不支持 JSON 数据类型,而 PostgreSQL 中的支持还处于起步阶段。从 5.7 开始,MySQL now supports a JSON data type(二进制存储格式)和 PostgreSQL JSONB 已经显着成熟。这两种产品都提供了可以存储任意文档的高性能 JSON 类型,包括支持对 JSON 对象的特定键进行索引。

    但是,我仍然坚持我最初的说法,即在使用关系数据库时,您的默认首选项仍应为 column-per-value。关系数据库仍然建立在假设它们中的数据将被很好地标准化的假设之上。查询计划器在查看列时比查看 JSON 文档中的键时具有更好的优化信息。可以在列之间创建外键(但不能在 JSON 文档中的键之间)。重要的是:如果您的大部分架构都足够不稳定,足以证明使用 JSON 是合理的,那么您可能至少需要考虑关系数据库是否是正确的选择。

    也就是说,很少有应用程序是完全关系型或面向文档的。大多数应用程序都有两者的混合。以下是我个人发现 JSON 在关系数据库中有用的一些示例:

    • 在存储联系人的电子邮件地址和电话号码时,将它们作为值存储在 JSON 数组中比多个单独的表更容易管理

    • 保存任意键/值用户首选项(其中值可以是布尔值、文本或数字,并且您不希望为不同的数据类型设置单独的列)

    • 存储没有定义架构的配置数据(如果您正在构建 Zapier 或 IFTTT 并且需要为每个集成存储配置数据)

    我相信还有其他的,但这些只是几个简单的例子。

    原答案

    如果您真的希望能够不受限制地添加任意数量的字段(任意文档大小限制除外),请考虑使用 NoSQL 解决方案,例如 MongoDB。

    对于关系数据库:每个值使用一列。将 JSON blob 放在列中几乎不可能进行查询(当您真正找到有效的查询时会非常缓慢)。

    关系数据库在编制索引时利用数据类型,并且旨在通过规范化结构实现。

    附带说明:这并不是说您永远不应该将 JSON 存储在关系数据库中。如果您要添加真正的元数据,或者如果您的 JSON 描述的信息不需要查询并且仅用于显示,那么为所有数据创建单独的列可能会过大点。

    【讨论】:

    • 由于我需要执行搜索的列不会太多,因此使用这两种模型是否明智?我需要搜索的数据的每列键和其他数据的 JSON(在同一个 MySQL 数据库中)?
    • @Sann 对于要经常读取查询的数据,您应该为每个值使用一列。将某人的姓名放入 JSON 中没有意义,因为即使您不太可能基于它进行查询,您也可能非常经常需要它。这在您的应用程序端有很多浪费的解码。除非您真的觉得您的数据更好地表示为 JSON(相信我,它可能不是),否则您不应该诉诸于此。
    • "virtually impossible to query" - 今天psql允许你搜索和索引它的jsonb
    • @ted true。但是,在撰写此答案时,该答案并不真正可用。此外,这个问题引用了 MySQL,其中不存在功能。
    • @ColinM,是的,我意识到我的评论比你的帖子年轻 3 岁。我离开它的原因是因为它可能对其他人有所帮助和改变决定。至于对 MySQL 的引用:可能是真的,但你的答案中有"For relational databases" =P
    【解决方案2】:

    像大多数事情一样“视情况而定”。将数据存储在列或 JSON 中本身没有对错/好坏之分。这取决于您以后需要做什么。您预计访问这些数据的方式是什么?您是否需要交叉引用其他数据?

    其他人已经很好地回答了技术权衡是什么。

    没有多少人讨论过您的应用和功能会随着时间的推移而发展,以及此数据存储决策如何影响您的团队。

    因为使用 JSON 的诱惑之一是避免迁移模式,因此如果团队没有纪律,很容易将另一个键/值对粘贴到 JSON 字段中。它没有迁移,没有人记得它的用途。没有验证。

    我的团队在 postgres 的传统列旁边使用 JSON,起初这是自切片面包以来最好的东西。 JSON 具有吸引力且功能强大,直到有一天我们意识到灵活性是有代价的,它突然成为一个真正的痛点。有时,这一点很快就会出现,然后就变得很难改变,因为我们在这个设计决策的基础上构建了很多其他的东西。

    加班、添加新功能、使用 JSON 格式的数据会导致看起来比我们坚持使用传统列时可能添加的查询更复杂。因此,我们开始将某些键值重新放入列中,以便我们可以进行连接并在值之间进行比较。馊主意。现在我们有了重复。一个新的开发人员会加入并感到困惑?我应该存回哪个值? JSON 之一还是列?

    JSON 字段变成了这个和那个的小碎片的垃圾抽屉。没有数据库级别的数据验证,文档之间没有一致性或完整性。这将所有责任推到了应用程序中,而不是从传统列中进行硬类型和约束检查。

    回顾过去,JSON 让我们能够非常快速地进行迭代并取得成果。太棒了。然而,当我们达到一定的团队规模后,它的灵活性也让我们背上了一长串的技术债务,从而减缓了后续的功能发展进程。谨慎使用。

    认真思考数据的性质。它是您的应用程序的基础。随着时间的推移,这些数据将如何被使用。以及它可能会发生怎样的变化?

    【讨论】:

      【解决方案3】:

      只是把它扔在那里,但 WordPress 有这种东西的结构(至少 WordPress 是我观察到它的第一个地方,它可能起源于其他地方)。

      它允许无限键,并且比使用 JSON blob 搜索更快,但不如某些 NoSQL 解决方案快。

      uid   |   meta_key    |   meta_val
      ----------------------------------
      1         name            Frank
      1         age             12
      2         name            Jeremiah
      3         fav_food        pizza
      .................
      

      编辑

      用于存储历史记录/多个键

      uid   | meta_id    |   meta_key    |   meta_val
      ----------------------------------------------------
      1        1             name            Frank
      1        2             name            John
      1        3             age             12
      2        4             name            Jeremiah
      3        5             fav_food        pizza
      .................
      

      并通过以下方式查询:

      select meta_val from `table` where meta_key = 'name' and uid = 1 order by meta_id desc
      

      【讨论】:

      • 我很想知道 NoSQL 解决方案是否真的比对正确索引键的关系查询执行得更好。我怀疑在这样的 1 级示例中应该或多或少相同。
      • +1。我也注意到了!但它为您提供了一个巨大的表格(就行而言)。您也可以存储多个值,例如,如果用户更改了他/她的姓名,但我也想保留旧名称,在这种情况下,我需要 JSON 类型的数据模型。
      • @Sann,如果您想在 JSON 中保留旧值,您还必须重命名密钥:您可以使用 EAV(本示例就是这样)或 JSON。并没有特别的不同。
      • 它确实给了你一个巨大的表,但是对于重复的值,你会遇到与 JSON 相同的问题 - 你不能在同一级别有重复的键(例如两个“名称”键)并期待可预测的行为。
      • 这是''EAV模型(实体-属性-值)模型。
      【解决方案4】:

      这种方法的缺点正是你提到的:

      它使查找内容变得非常缓慢,因为每次您都需要对其执行文本搜索。

      每列的值匹配整个字符串。

      您的方法(基于 JSON 的数据)适用于您不需要搜索的数据,只需与普通数据一起显示即可。

      编辑:澄清一下,以上内容适用于经典的关系数据库。 NoSQL 在内部使用 JSON,如果这是所需的行为,它可能是更好的选择。

      【讨论】:

      • 所以你的意思是,我应该两者都用。我需要搜索的数据的每列键和其他人的 JSON,对吗?
      • 是的。这样,您可以通过搜索每列数据字段获得所需的性能,并在需要时获取 JSON blob 以在代码中使用。
      【解决方案5】:

      基本上,您使用的第一个模型称为基于文档的存储。您应该看看流行的 NoSQL 基于文档的数据库,例如 MongoDB 和 CouchDB。基本上,在基于文档的数据库中,您将数据存储在 json 文件中,然后您可以查询这些 json 文件。

      第二种模型是流行的关系数据库结构。

      如果你想使用像 MySql 这样的关系数据库,那么我建议你只使用第二个模型。 没有必要像第一个模型那样使用 MySql 和存储数据

      要回答您的第二个问题,如果您使用第一个模型,则无法查询像 'foo' 这样的名称

      【讨论】:

      • 同时使用这两种模型是否明智?我需要搜索的数据的每列键和其他人的 JSON(在同一数据库中)?
      • @Sann - 哈哈。那就是数据重复。您必须确保两条数据始终相同。即使其中一个数据在任何时候都不同,那么您的数据也不干净,可能会导致严重的问题。所以,我的答案是NO
      • 但是当冗余数据较小时,冗余并不昂贵,比如说,我只需要在两个字段上执行搜索,所以我为它们创建了两个新列,[也许]删除它们来自我的 JSON 数据 [/也许]。这不会是昂贵的重复,对吧?
      • 如果您关注性能,那么 MongoDB 和 CouchDB 提供比 MySql 更快的读写操作,因为它们在关系数据库中没有提供很多在大多数用例中不需要的特性。
      • 存储来自 API 的 JSON 对象/回调难道不是好处吗?例如,您可以只在本地数据库(mysql、lite 等)中查询 JSON 对象,而不是为 URL、拇指等调用 youtube 的 API?我不知道,这对我来说很有意义,尤其是当您尝试缓存或使应用程序运行得更快时。但我不是专业的:/
      【解决方案6】:

      看来您主要是在犹豫是否使用关系模型。

      就目前而言,您的示例非常适合关系模型,但是当您需要使该模型发展时,问题当然可能会出现。

      如果您的主要实体(用户)只有一个(或几个预先确定的)属性级别,您仍然可以在关系数据库中使用实体属性值 (EAV) 模型。 (这也有利有弊。)

      如果您预计使用应用程序搜索的结构化值较少,那么 MySQL 可能不是这里的最佳选择。

      如果您使用的是 PostgreSQL,您可能会获得两全其美的效果。 (这真的取决于这里数据的实际结构...... MySQL 也不一定是错误的选择,NoSQL 选项可能很有趣,我只是建议替代方案。)

      事实上,PostgreSQL 可以在(不可变的)函数上构建索引(据我所知 MySQL 不能),在最近的版本中,您可以 use PLV8 on the JSON data directly 为感兴趣的特定 JSON 元素构建索引,这将改善搜索该数据时查询的速度。

      编辑:

      因为我需要执行的列不会太多 搜索一下,这两个模型都用是明智的吗?数据的每列键 我需要为其他人搜索和 JSON(在同一个 MySQL 数据库中)?

      混合这两个模型不一定是错误的(假设额外的空间可以忽略不计),但如果您不确保两个数据集保持同步,它可能会导致问题:您的应用程序绝不能更改其中之一更新另一个。

      实现此目的的一个好方法是让触发器执行自动更新,方法是在进行更新或插入时在数据库服务器中运行一个存储过程。据我所知,MySQL 存储过程语言可能缺乏对任何类型的 JSON 处理的支持。同样,支持 PLV8 的 PostgreSQL(以及可能具有更灵活存储过程语言的其他 RDBMS)应该更有用(使用触发器自动更新关系列与以相同方式更新索引非常相似)。

      【讨论】:

      • 除了我上面所说的之外,还值得看看 PostgreSQL 9.4 及更高版本中 JSONB 数据类型的运算符。
      【解决方案7】:

      有时在表上连接会产生开销。让我们说OLAP。如果我有两张表,一张是 ORDERS 表,另一张是 ORDER_DETAILS。为了获得所有订单详细信息,我们必须连接两个表,当表中的行数没有增加时,查询会变慢,比如说数百万左右。左/右连接比内连接慢。 我认为如果我们在相应的 ORDERS 条目中添加 JSON 字符串/对象,将避免加入。添加报告生成会更快...

      【讨论】:

        【解决方案8】:

        简短的回答 你必须在它们之间混合, 将 json 用于您不会与它们建立关系的数据,例如联系数据、地址、产品变量

        【讨论】:

          【解决方案9】:

          您正在尝试将非关系模型拟合到关系数据库中,我认为使用 NoSQL 数据库(例如 MongoDB)会更好。没有预定义的模式可以满足您对字段数量没有限制的要求(请参阅典型的 MongoDB 集合示例)。查看 MongoDB documentation 了解如何查询文档,例如

          db.mycollection.find(
              {
                name: 'sann'
              }
          )
          

          【讨论】:

          • 出于好奇,是什么让您认为他的模型是非关系模型。他在上面提供的信息似乎与我非常相关。
          【解决方案10】:

          正如其他人指出的那样,查询会变慢。我建议至少添加一个“_ID”列来查询。

          【讨论】:

            猜你喜欢
            • 2012-03-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-11-17
            • 1970-01-01
            • 2017-10-10
            • 2018-05-03
            • 1970-01-01
            相关资源
            最近更新 更多