值得对我们的数据进行去规范化吗?

Worth de-normalizing our data?

关于规范化数据有很多帖子和讨论。大多数时候,我看到人们非常坚持规范化,但并非总是如此,而且这似乎是个案,所以我将描述我们的案例。它看起来并不复杂,但我觉得也许我只是缺少一些优雅的东西。如果有人可以,我会很高兴:

主要是我们要做的是近乎实时的搜索,当用户在搜索字段中输入文本时逐字符过滤结果,因此需要非常灵敏。但是非常低功耗的硬件——想想物联网。搜索需要 return 单个项目名称、捆绑包名称以及找到的捆绑包中的单个项目列表。项目和捆绑包具有多对多关系,尽管任何捆绑包中的项目数量都是有限的,因此存在界限,即价值。

Ex DB: 
[ items ]
    int: item_id
    string: name
    ….
[ bundles ]
    int: bundle_id
    string: bundle_name
    ….
[ items_x_bundles ]
    int: item_id
    int: bundle_id

想象一下礼品篮中的不同食物束,给定的篮子组合中通常不超过 10 件物品,但没有绝对固定的限制。很少创建新的捆绑包,而且从不更改。

假设有各种单独的项目,例如:

apple, orange, pear, banana, saltines, cheez-its, ritz, 
potato chips, carrots, peas, beans, oreos, gummies, 
hershey bars, coke, gatorade, milk, etc.

和捆绑包,例如:

special : [ apple, saltines, peas, gummies, coke ], 
deluxe: [ pear, carrots, potato chips, oreos ],
fancy: [ orange, ritz, beans, gummies, milk ],
mondo: [ banana, pear, saltines, carrots, peas, oreos, coke, milk ]

搜索 "delu" 会 return:

[ deluxe: [ pear, carrots, potato chips, oreos ]

搜索 "appl" 会 return:

[ apple ] 
[ special : [ apple, saltines, peas, gummies, coke ] ]

搜索 "milk" 会 return:

[ milk ]
[ fancy: [ orange, ritz, beans, gummies, milk ]
[ mondo: [banana, pear, saltines, carrots, peas, oreos, coke, milk ]

如果我们保持数据完全规范化,则很容易找到单个项目名称,但 return 包含搜索字符串的每个购物篮中的单个项目列表要复杂得多。效率很重要,因为在低功耗物联网硬件上这将是 运行。如果重要的话,使用 sqlite3。

一个可能的解决方案是在创建包时向包 table 添加一个字段。类似于:

    string: bundle_items

[special] 可能看起来像:

    "apple / saltines / peas / gummies / coke".

这使得一切都变得 faster/easier 以冗余为代价进行搜索。对我来说感觉像是 "hack",但我没有看到明显优雅、高效的解决方案。

更新

我正在将 5 个 updates/iterations 压缩成这个。

也许我上面说的不够清楚,但是性能问题是固有的。低功耗物联网级硬件,以及面向用户的实时过滤器,需要使用输入的每个字符搜索数据。我们预计,无论我们如何构建它,它都不会像我们希望的那样快,因为任何延迟都会直接被用户注意到,即使是几分之一秒。我没有确切的数字,因为虽然在开发机器上执行基准测试模拟相当容易,但在真实硬件上还不是那么容易。这是否意味着我们需要 de-normalize/optimize No Matter What?也许吧,但我还不知道这是事实,因此这里的问题。另外,我想知道我们正在考虑的特定去规范化方法是否存在任何明显的问题(上文)。

我知道如何查询非规范化数据,但我不知道如何对规范化数据构建智能、合理优化的查询。这可能有助于指导我们做出决定。所以:

问题 #1) 为了获得上面列出的结果,对规范化数据的智能(快速)查询是什么样的?

问题 #2) 有没有人看到我描述的反规范化方法有任何明显的问题。在所描述的上下文中,是否有意义 and/or 是否有更好的不同解决方案?

几次通过后,Bill Karwin 的以下查询有效,因此回答了第一部分,谢谢。第 2 部分最终可能会出现在另一个问题中。

如果有人跟进,不同类型查询的实际百分比差异如此之大(取决于记录的数量),坦率地说,我们需要更深入地挖掘。它有所不同并不奇怪,但数量惊人。从大约 15 倍到超过 35,000 倍不等,记录数量并非不合理。即使是 15 倍,这可能更接近真实世界,我会说我们倾向于去规范化,但这提供了一个工作规范化查询来测试。

评论太长了。

规范化是一种工具,可以在为关系数据库设计数据模型时使用。这是相当强大的。但是,它的初衷是支持数据完整性。任何数据项都存储在一个地方,恰好一次。更新很容易,因为更新只发生一次。当您更新数据时,规范化尤为重要,因此底层数据模型保持一致性。

通常,关系数据库用于其他目的,例如分析和报告。事实上,我经常使用创建一次然后查询多次的表。必要时会重新创建它们。在这种情况下,规范化不一定有帮助。

是否规范化数据以及如何规范化数据在很大程度上取决于应用程序。我倾向于在规范化的大小上出错;但是,如果您有充分的理由对数据进行反规范化,那很好,特别是对于主要只读的应用程序。

如果您将数据保存在标准化表中,您可以执行如下查询:

经过几次编辑和测试此查询 (SQLFiddle):

SELECT CONCAT(b1.bundle_name, ' : ', GROUP_CONCAT(i1.name))
FROM bundles b1 
JOIN items_x_bundles bi1 USING (bundle_id)
JOIN items i1 USING (item_id)
WHERE b1.bundle_name LIKE CONCAT('milk', '%')
GROUP BY b1.bundle_id
UNION ALL
SELECT CONCAT(b2.bundle_name, ' : ', GROUP_CONCAT(i2b.name))
FROM bundles b2
JOIN items_x_bundles bi2 ON (b2.bundle_id=bi2.bundle_id)
JOIN items i2 ON (bi2.item_id=i2.item_id)
JOIN items_x_bundles bi2b ON (b2.bundle_id=bi2b.bundle_id)
JOIN items i2b ON (bi2b.item_id=i2b.item_id)
WHERE i2.name LIKE CONCAT('milk', '%')
GROUP BY b2.bundle_id
UNION ALL
SELECT i3.name
FROM items i3
WHERE i3.name LIKE CONCAT('milk', '%')

? 占位符是您绑定搜索词的地方。是的,你得绑定三遍。

items(name)bundles(bundle_name)items_x_bundles(item_id,bundle_id)items_x_bundles(bundle_id,item_id) 上建立索引。

然后使用EXPLAIN确认查询有效地使用了索引。