关于mysql:在数据库中存储JSON与为每个键创建一个新列

Storing JSON in database vs. having a new column for each key

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

1
2
3
4
5
6
7
8
 uid   | meta
--------------------------------------------------
 1     | {name:['foo'],
       |  emailid:['[email protected]','[email protected]']}
--------------------------------------------------
 2     | {name:['sann'],
       |  emailid:['[email protected]','[email protected]']}
--------------------------------------------------

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

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

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

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

更新

因为我不需要在其中执行搜索的列太多,所以使用这两个模型是否明智?我需要搜索的数据的每列键和其他数据的json(在同一个mysql数据库中)?


更新日期:2017年6月4日

考虑到这个问题/答案得到了一些支持,我认为它值得更新。

当这个问题最初发布时,MySQL不支持JSON数据类型,PostgreSQL中的支持还处于起步阶段。从5.7开始,MySQL现在支持JSON数据类型(二进制存储格式),PostgreSQL JSONB已经非常成熟。这两种产品都提供可存储任意文档的性能JSON类型,包括支持为JSON对象的特定键建立索引。

但是,我仍然支持我的原始语句,即在使用关系数据库时,您的默认首选项应该仍然是每个值的列。关系数据库仍然建立在这样的假设之上:它们内部的数据将相当好地规范化。查询规划器在查看列时比在JSON文档中查看键时具有更好的优化信息。可以在列之间创建外键(但不能在JSON文档中的键之间创建)。重要的是:如果您的模式大部分是易失的,足以证明使用JSON是正确的,那么您可能至少要考虑关系数据库是否是正确的选择。

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

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

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

  • 存储没有定义模式的配置数据(如果您正在构建zapier或ifttt,并且需要为每个集成存储配置数据)

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

原始答案

如果您真的想在不受限制的情况下添加尽可能多的字段(除了任意文档大小限制),可以考虑使用NoSQL解决方案,如MongoDB。

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

关系数据库在索引时利用数据类型,并打算使用规范化结构实现。

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


就像大多数事情一样,"这取决于"。在列或JSON中存储数据本身并不是对的或错的/好的或坏的。这取决于你以后需要做什么。您预测的访问此数据的方式是什么?是否需要交叉引用其他数据?

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

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

因为使用JSON的一个诱惑是避免迁移模式,所以如果团队没有遵守规则,那么很容易将另一个键/值对插入到JSON字段中。它没有迁移,没有人记得它的用途。没有对其进行验证。

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

加班、添加新特性、将数据保存在JSON中会导致比我们坚持使用传统列可能添加的查询更复杂的查询。因此,我们开始将某些键值重新捕获到列中,这样我们就可以进行连接并在值之间进行比较。坏主意。现在我们有了重复。一个新的开发人员会上船并被搞糊涂?我应该存回哪个值?JSON 1还是列?

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

回首过去,JSON允许我们非常快速地迭代,并从门中得到一些东西。太棒了。然而,当我们达到一定的团队规模后,它的灵活性还允许我们将自己挂在一条长的技术债务绳子上,这条绳子会减慢随后的特性进化进程。小心使用。

仔细考虑数据的性质。这是你的应用程序的基础。随着时间的推移,数据将如何使用。它可能会发生什么变化?


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

它允许无限的键,搜索速度比使用JSONBLOB快,但不如某些NoSQL解决方案快。

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

编辑

用于存储历史/多个键

1
2
3
4
5
6
7
8
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
.................

并通过如下方式进行查询:

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


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

因为每次需要对其执行文本搜索,所以查找东西的速度非常慢。

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

对于不需要搜索的数据(基于JSON的数据),您的方法(基于JSON的数据)是很好的,只需要与正常数据一起显示即可。

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


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

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

如果您想使用像mysql这样的关系数据库,那么我建议您只使用第二个模型。使用MySQL和存储第一个模型中的数据没有意义。

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


似乎您主要是在犹豫是否使用关系模型。

事实上,您的示例很适合一个关系模型,但是当您需要使这个模型发展时,问题可能会出现。

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

如果您预计将得到一些不那么结构化的值,而您希望使用您的应用程序进行搜索,那么MySQL可能不是这里的最佳选择。

如果您使用的是PostgreSQL,那么您可能会得到两个世界中最好的。(这真的取决于这里数据的实际结构…MySQL也不一定是错误的选择,NoSQL选项可能会引起兴趣,我只是建议其他选项。)

事实上,PostgreSQL可以在(不可变的)函数上建立索引(据我所知,MySQL无法建立索引),在最近的版本中,您可以直接在JSON数据上使用PLV8在感兴趣的特定JSON元素上建立索引,这将提高您在搜索该数据时的查询速度。

编辑:

Since there won't be too many columns on which I need to perform
search, is it wise to use both the models? Key-per-column for the data
I need to search and JSON for others (in the same MySQL database)?

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

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


简短回答你得把它们混在一起,使用JSON来处理您不想与之建立关系的数据,如联系人数据、地址、产品变量等。


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


正如其他人指出的那样,查询速度会变慢。我建议至少添加一个"i d"列来进行查询。


您试图将非关系模型放入关系数据库中,我认为您最好使用NoSQL数据库(如MongoDB)。没有符合您对字段数量没有限制要求的预定义模式(请参见典型的MongoDB集合示例)。查看MongoDB文档,了解如何查询文档,例如

1
2
3
4
5
db.mycollection.find(
    {
      name: 'sann'
    }
)