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

时间:2019-06-02 00:30:30

标签: mysql sql database-design relational-database

我相信这个问题并不能专门针对MySQL-我正在使用的数据库-,而这与最佳实践有关。

到目前为止,我的问题可以通过创建表并查询它们来解决(有时在这里和那里联接)。但是我正在做的事情感觉不对劲,每当我需要“ strong”查询旁边的非规范化数据时,就会触发我。

示例用例

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

  • user可以购买product,从而生成purchase(让我们忽略purchase只能有一个product的事实); < / li>
  • 我们需要用product d的总次数查询purchase

要解决用例,我们可以定义一个简单结构

  • product表:

    • product_id [INT PK ]
  • user表:

    • user_id [INT PK ]
  • purchase表:

    • purchase_id [INT PK ]
    • product_id [INT FK NOT NULL]
    • user_id [INT FK NOT NULL]

这是感觉不对的地方:当我们需要检索product列表及其购买总次数时,我将创建查询:< / p>

# 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_id.product_id)
FROM
    product

我担心的原因是,我已经了解到COUNT会进行全表扫描,并且当我缩放到要购买的数千种产品时,即使在{{ 1}}在product_id上按FK键(默认情况下,MySQL会这样做)。


可能的解决方案

我对关系数据库的了解还很浅,所以在比较这些问题的替代方案(合理的替代方案)时,我有点迷失了。不用说我还没有做完作业(在询问之前先搜索),我发现可以这样做:

创建交易:

插入新的purchase时,它必须始终位于事务中,该事务还必须用purchase更新product表。

可能的问题:人为错误。有人可能会手动插入purchase.product_id,而无需执行交易和BAM-我们存在不一致之处。

创建触发器:

每当我插入,删除或更新某些特定表中的某行时,我都会使用新值(purchase)更新我的product s表。因此表将变为:

  • bought_amount表:
    • product [INT PK]
    • product_id [INT NOT NULL];

可能的问题:触发器是否昂贵?有没有一种方法可以成功插入但触发器不会成功-从而使我前后不一致?


问题

更新某些表以存储不断变化的数据是RDBMS的一种可行方法吗?长期加入并计数/求和其他事件是否更安全,并且从长远来看是否更有益?

我找到了关于此问题的两个有用的问题/答案,但是没有一个能从广泛的角度解决这个问题。 请考虑我对RDBMS的无知,因为我可能会建议废话可能的解决方案

3 个答案:

答案 0 :(得分:1)

此查询:

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

必须同时扫描productpurchase。我不确定为什么您会为一个表扫描而不是另一个表感到激动。

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

除非成为问题,否则您不必担心此类查询的性能。如果您需要提高这种查询的性能,首先我会问:为什么?这将返回很多信息-一直以来关于所有产品的信息。通常,我希望有人关心一种产品或一段时间,或两者兼而有之。而且,这些担忧将暗示数据集市的发展。

如果性能是一个问题,则有许多替代方法,例如:

  • 定义数据集市以定期将数据汇总为此类查询的更有效结构。
  • 如果需要实时结果,则将触发器添加到数据库以汇总数据。
  • 开发一种用于维护数据的方法,该方法还可以在应用程序级别或使用存储过程来维护摘要。

实际上,关系数据库(具有合理的数据模型)的巨大优势对您而言并不“合适”。您可以使其保持最新状态。而且,您可以使用满足业务需求的简洁语言进行查询。

答案 1 :(得分:0)

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

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

您不需要提及product表,因为它包含的只是键列。现在,尽管它使用COUNT(*),但是它不需要为每个product_id进行全表扫描,因为SQL引擎足够聪明,可以看到GROUP BY

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

然后,在您开始担心实施和效率之前,您想回答什么问题?如果要查看是否所有product都已购买,则必须扫描整个product表并从中查找到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在这种连接和聚合方面效率很低,并且架构经常在多个表中使用重复的列进行非规范化。 SQL引擎现在更加智能,因此没有必要。您可能会在较旧的教科书中看到这种过时的做法。我会忽略它,并设计您的架构尽可能标准化。

答案 2 :(得分:0)

  

可能的问题:人为错误。有人可能会在不进行交易和BAM的情况下手动插入购买商品-我们存在不一致之处。

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

  

可能的问题:触发器是否昂贵?有没有一种方法可以成功插入但触发器不会成功-从而使我前后不一致?

触发器不是太糟糕。但是,再次,我建议强迫用户执行执行所有所需步骤的存储过程。

注意:您可以拥有一个执行必要步骤的应用程序,而不是存储过程。然后强迫用户浏览该应用,并赋予他们 no 直接访问数据库的权限。

数据库是数据的“真理之源”。它是此类的“持久”存储库。不应将其视为构建应用程序的整个引擎。

关于性能:

  • 对一百万行进行汇总可能需要花费大量时间。
  • 您可以轻松地每秒进行一百个单行查询(选择/插入/更新)。
  • 请仔细考虑这样的数字。