关系数据库 (RDBMS) 非规范化数据

Relational database (RDBMS) denormalized data

我认为这个问题没有具体解决 MySQL - 这是我正在使用的数据库 - 并且是关于最佳实践的问题。

到目前为止,我的问题可以通过创建 tables 并查询它们来解决(有时在这里和那里加入)。但是我正在做的事情感觉不对,每当我需要 非规范化数据 以及 "common" 查询时,它都会触发我。

示例用例

为了更好地表达自己,让我们创建一个肤浅的场景:

为了解决我们的用例,我们可以定义一个简单结构,由:

这里感觉不对:当我们需要检索product的列表及其购买的总次数时,我会创建查询:

# There are probably faster queries than this to reach the same output
SELECT
    product.product_id,
    (SELECT COUNT(*) FROM purchase
      WHERE purchase.product_id = product.product_id)
FROM
    product

我的担心来源是我读到 COUNT 进行了完整的 table 扫描,当扩展到数千种正在购买的产品时,我害怕执行上面的查询 - 即使我已经创建在 purchase 上带有 product_id FK 的索引(MySQL 默认情况下这样做)。


可能的解决方案

我对关系数据库的了解很浅,所以在比较这些问题的替代方案(可能的方案)时,我有点不知所措。不是说我没有做功课(问之前搜索),我发现合理的:

创建交易:

INSERT 插入一个新的 purchase 时,它必须始终在一个事务中,该事务还更新 product table purchase.product_id.

可能的问题:人为错误。有人可能会在不执行事务和 BAM 的情况下手动插入 purchase - 我们存在不一致。

创建触发器:

每当我在某个特定 table 中插入、删除或更新某些行时,我都会用新值 (bought_amount) 更新我的 products table。所以 table 会变成:

可能出现的问题:触发器贵吗?有没有一种方法可以使插入成功但触发器不会 - 从而让我感到不一致?


问题

更新某些 table 以存储不断变化的数据是 RDBMS 的合理方法吗?继续加入和 counting/summing 其他事件是否更安全并且 - 从长远来看 - 更有益?

关于这个问题,我发现了一些有用的 questions/answers,但其中 none 从广泛的角度讨论了这个主题。 请考虑到我对 RDBMS 的无知,因为我可能会提出一些废话 可能的解决方案

这个查询:

SELECT p.product_id,
      (SELECT COUNT(*)
       FROM purchase pu
       WHERE pu.product_id = p.product_id
      )
FROM product p;

必须同时扫描 productpurchase。我不确定为什么你对一个 table 扫描而不是另一个扫描感到情绪化。

至于性能,这可以利用 purchase(product_id) 上的索引。在 MySQL 中,这可能比等效的(左)连接版本更快。

在这成为问题之前,您不应该担心此类查询的性能。如果您需要提高此类查询的性能,首先我会问:为什么?这是返回的大量信息——关于所有产品的所有时间。更典型的是,我希望有人关心一种产品或一段时间或两者。而且,这些担忧会建议开发数据集市。

如果性能是个问题,您有很多选择,例如:

  • 定义数据集市以定期将数据汇总为更有效的结构以供此类查询使用。
  • 如果real-time中需要结果,则向数据库添加触发器以汇总数据。
  • 开发一种用于维护数据的方法,该方法还可以在 application-level 或使用存储过程中维护摘要。

对您来说 "feel right" 实际上没有的是关系数据库(具有合理的数据模型)的巨大力量。你可以保留它 up-to-date。您可以使用符合业务需求的非常简洁的语言对其进行查询。

获取每个键的计数的常用方法是

SELECT product_id, COUNT(*)
FROM purchase
GROUP BY product_id

producttable就不用提了,因为它只包含键列。现在虽然它使用 COUNT(*),但它不需要对每个 product_id 进行完整的 table 扫描,因为 SQL 引擎足够智能,可以看到 GROUP BY

但这会产生与您的查询不同的结果:对于从未购买过的 product,我的查询根本不会显示它们;您的查询将显示计数为零的 product_id

那么在您开始担心实施和效率之前,您想要回答什么问题?如果您想查看所有 product 是否已购买,则必须扫描整个 product table 并从中查找 purchase。我会去

SELECT product_id, count
FROM product
OUTER JOIN (SELECT product_id, COUNT(*) AS count
            FROM purchase
            GROUP BY product_id) AS purch
ON product.product_id = purch.product_id

关于你更广泛的问题(不确定我是否完全理解它们),在早期 SQL 在这种连接和聚合方面效率很低,并且模式经常被多个 [= 中的重复列非规范化=35=]秒。 SQL 引擎现在更加智能,因此没有必要。您可能会在较旧的教科书中看到 old-fashioned 的做法。我会忽略它并尽可能规范地设计您的架构。

Possible Problems: human error. Someone might manually insert a purchase without doing the transaction and BAM - we have an inconsistency.

--> 构建一个在事务中执行两个步骤的存储过程,然后强制用户完成。

Possible problems: are triggers expensive? is there a way that the insertion succeeds but the trigger won't - thus leaving me with an inconsistency?

触发器还不错。但是,我再次建议强制用户通过执行所有所需步骤的存储过程。

注意:您可以使用执行必要步骤的应用程序代替存储过程;然后强制用户通过应用程序并让他们直接访问数据库。

数据库就是数据上的"source of truth"。它是此类的 "persistent" 存储库。它不应被视为构建应用程序的整个引擎。

至于表现:

  • 汇总超过一百万行可能需要很长时间。
  • 您每秒可以轻松执行一百 single-row 个查询 (select/insert/update)。
  • 请像这样思考数字。