关系数据库 (RDBMS) 非规范化数据
Relational database (RDBMS) denormalized data
我认为这个问题没有具体解决 MySQL - 这是我正在使用的数据库 - 并且是关于最佳实践的问题。
到目前为止,我的问题可以通过创建 tables 并查询它们来解决(有时在这里和那里加入)。但是我正在做的事情感觉不对,每当我需要 非规范化数据 以及 "common" 查询时,它都会触发我。
示例用例
为了更好地表达自己,让我们创建一个肤浅的场景:
- 一个
user
可以买一个product
,生成一个purchase
(忽略purchase
只能有一个product
) ;
- 我们需要查询
product
s 的总次数 purchase
d;
为了解决我们的用例,我们可以定义一个简单结构,由:
product
table:
product_id
[INT PK]
user
table:
user_id
[INT PK]
purchase
table:
purchase_id
[INT PK]
product_id
[INT FK NOT NULL]
user_id
[INT FK NOT NULL]
这里感觉不对:当我们需要检索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
) 更新我的 product
s table。所以 table 会变成:
product
table:
product_id
[智力PK]
bought_amount
[INT NOT NULL];
可能出现的问题:触发器贵吗?有没有一种方法可以使插入成功但触发器不会 - 从而让我感到不一致?
问题
更新某些 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;
必须同时扫描 product
和 purchase
。我不确定为什么你对一个 table 扫描而不是另一个扫描感到情绪化。
至于性能,这可以利用 purchase(product_id)
上的索引。在 MySQL 中,这可能比等效的(左)连接版本更快。
在这成为问题之前,您不应该担心此类查询的性能。如果您需要提高此类查询的性能,首先我会问:为什么?这是返回的大量信息——关于所有产品的所有时间。更典型的是,我希望有人关心一种产品或一段时间或两者。而且,这些担忧会建议开发数据集市。
如果性能是个问题,您有很多选择,例如:
- 定义数据集市以定期将数据汇总为更有效的结构以供此类查询使用。
- 如果real-time中需要结果,则向数据库添加触发器以汇总数据。
- 开发一种用于维护数据的方法,该方法还可以在 application-level 或使用存储过程中维护摘要。
对您来说 "feel right" 实际上没有的是关系数据库(具有合理的数据模型)的巨大力量。你可以保留它 up-to-date。您可以使用符合业务需求的非常简洁的语言对其进行查询。
获取每个键的计数的常用方法是
SELECT product_id, COUNT(*)
FROM purchase
GROUP BY product_id
product
table就不用提了,因为它只包含键列。现在虽然它使用 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)。
- 请像这样思考数字。
我认为这个问题没有具体解决 MySQL - 这是我正在使用的数据库 - 并且是关于最佳实践的问题。
到目前为止,我的问题可以通过创建 tables 并查询它们来解决(有时在这里和那里加入)。但是我正在做的事情感觉不对,每当我需要 非规范化数据 以及 "common" 查询时,它都会触发我。
示例用例
为了更好地表达自己,让我们创建一个肤浅的场景:
- 一个
user
可以买一个product
,生成一个purchase
(忽略purchase
只能有一个product
) ; - 我们需要查询
product
s 的总次数purchase
d;
为了解决我们的用例,我们可以定义一个简单结构,由:
product
table:product_id
[INT PK]
user
table:user_id
[INT PK]
purchase
table:purchase_id
[INT PK]product_id
[INT FK NOT NULL]user_id
[INT FK NOT NULL]
这里感觉不对:当我们需要检索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
) 更新我的 product
s table。所以 table 会变成:
product
table:product_id
[智力PK]bought_amount
[INT NOT NULL];
可能出现的问题:触发器贵吗?有没有一种方法可以使插入成功但触发器不会 - 从而让我感到不一致?
问题
更新某些 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;
必须同时扫描 product
和 purchase
。我不确定为什么你对一个 table 扫描而不是另一个扫描感到情绪化。
至于性能,这可以利用 purchase(product_id)
上的索引。在 MySQL 中,这可能比等效的(左)连接版本更快。
在这成为问题之前,您不应该担心此类查询的性能。如果您需要提高此类查询的性能,首先我会问:为什么?这是返回的大量信息——关于所有产品的所有时间。更典型的是,我希望有人关心一种产品或一段时间或两者。而且,这些担忧会建议开发数据集市。
如果性能是个问题,您有很多选择,例如:
- 定义数据集市以定期将数据汇总为更有效的结构以供此类查询使用。
- 如果real-time中需要结果,则向数据库添加触发器以汇总数据。
- 开发一种用于维护数据的方法,该方法还可以在 application-level 或使用存储过程中维护摘要。
对您来说 "feel right" 实际上没有的是关系数据库(具有合理的数据模型)的巨大力量。你可以保留它 up-to-date。您可以使用符合业务需求的非常简洁的语言对其进行查询。
获取每个键的计数的常用方法是
SELECT product_id, COUNT(*)
FROM purchase
GROUP BY product_id
product
table就不用提了,因为它只包含键列。现在虽然它使用 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)。
- 请像这样思考数字。