1

我有一个客户端-服务器应用程序,它从几个表中获取所有数据,重新计算并存储它。

例子:

每个项目都有一个“材料清单”= 其他项目的清单和数量。因此,一个项目的成本是其 BOM 中项目的成本 * 它们的数量之和。最终,一些“基础”项目没有 BOM,只是独立设置成本。(即:原材料)

即:A 的 BOM 说它由 2xB 和 3xC 制成。

我现在所做的,我不记得我为什么这样做,是我从数据库中获取所有项目和所有 BOM,并一次针对每个项目递归计算其成本。一旦我计算了一个项目,我就会标记它,这样我就不会再重做成本了。(也防止无限递归)

事情是,这有点愚蠢:首先,它很慢并且会重新计算没有改变的东西,更糟糕的是,给它一个足够大的数据库,它会耗尽内存。

相反,我可以按需重新计算项目:当一个项目的 BOM 发生变化时,我重新计算该 BOM,然后选择包含此更新项目的所有 BOM,并重新计算它们;冲洗并递归重复,直到到达顶部,数据库中没有 BOM 依赖于任何更改的项目。

这在实践中意味着什么:假设一些项目是原材料,其成本可能会经常更新,而一些项目是“最终用户”的东西,它们的 BOM 很少会改变。当用户更改其中一种材料的成本时,可能意味着要处理数千个项目,重新计算它们。假设 1 个项目/BOM 的 SELECT 需要 15 毫秒(我在 Postgresql 上),然后仅 SELECTing 1000 个项目/BOM 将需要 15 秒,然后您必须将重新计算的成本更新回数据库中的项目......哦亲爱的,延迟现在可以变成几分钟。

我工作的公司使用的 ERP 软件采用第一种方法:一次批量重新计算整个数据库。从字面上看,这需要几个小时,而且在 10 多年的使用中,这种方法似乎已经出现了问题。批量重新计算每周进行一次。

既然我实际上已经“大声写出来”,我认为几分钟的时间并不重要。问题是我不太了解数据库,而且我担心并发性:由于在项目 A 上更新需要很长时间,因此很可能有人会在项目 A 正在更新期间更新第二个项目 B更新。

假设 D 项是由上面的 A 和 B 组成的。用户 1 更新 A,因此服务器软件开始与数据库自慰几分钟,最终更新 D。但与此同时,用户 2 更新 B,因此服务器最终将再次更新 D。

使用 Postgresql 的事务会解决问题吗?事务从数据库的当前状态开始,因此事务 1 看到 D 由 A1 和 B1 组成,并将 A 从 A1 更新到 A2,但在它完成并提交之前,事务 2 将开始,也看到 A1和 B1。T1 重新计算并提交,D = A2 + B1。但是T2已经开始了,并没有看到新的A,A2。因此,它最终将 D = A1 + B2 提交给 DB,这是不正确的。它应该是 D = A2 + B2。

此外,一些处理会重叠,浪费服务器时间。

如果我按顺序而不是并行执行 T1 和 T2,那么万岁,答案是正确的,但用户 2 将不得不等待更长时间。此外,如果一组事务彼此没有关系(完全独立的......依赖树;即:A=X+Y 和 B=N+M),那么并行计算将给出正确的答案并且对于用户。

重要提示:即使按顺序处理,我仍然会使用事务,因此软件的其余部分仍然可以并行处理该数据,除了重新计算成本的功能。

现在,如果……数据库延迟不会那么“糟糕”,那么整个“按顺序处理”的事情就不会那么糟糕了。比如说,如果整个数据都保存在 RAM 中,那么处理 1000 个对象将是轻而易举的事。啊,但是即使我构建了一个系统来快速将大块数据移入/移出磁盘/RAM并进行一些缓存 - 以替换 DB - ,那也行不通,因为我仍然需要事务,以便服务器的其余功能可以并行工作。(上面的“重要说明”)所以我最终会建立另一个数据库。可能会快一点,但它愚蠢/浪费时间。

我“缓存”每个项目的成本的全部原因是我每次使用它时都不会重新计算它,因为它不仅浪费了有限的资源,而且数据库延迟太大而且并发问题的规模更大。

现在我不需要奇怪为什么“他们”会大批量这样做……这让我很头疼。

Q1:你们如何以“最佳”方式解决这个问题?

根据我目前的理解(即在遇到之前我默默忽略的并发问题之后),我会让那个函数按顺序使用事务,而应用程序的其余部分仍然可以并行使用数据,我相信最适合用户。这就是目标:对用户最好,但保证系统的正确性。

也许稍后我可以向它扔硬件并使用软件黑魔法来减少延迟,但我现在开始对自己撒谎。

另外,在过去的几个月里,我对一些显而易见的事情完全视而不见(有些与编程无关),所以我期待有人会指出一些我设法错过的可耻的明显事情......: |

4

2 回答 2

4

我不记得为什么我会这样做......

这让我大吃一惊,作为你需要解决的第一件事!

没有任何理由需要将数据取回应用程序来计算每个 BOM 的总成本。有许多技术可以处理 SQL 中的“部件爆炸”或分层数据集。

我在我的演讲“ SQL Antipatterns Strike Back ”中介绍了几种解决方案,或者您可以阅读诸如“ Joe Celko 的 SQL 中的树和层次结构”之类的书。

有些解决方案是特定于供应商的,有些可以使用任何普通的 SQL DBMS 完成。我没有注意到什么品牌的数据库,但乔纳森正确地让我意识到你正在使用 PostgreSQL。

在这种情况下,您应该阅读WITHPostgreSQL 8.4 中新增的“”查询,并允许您执行一些复杂的递归查询效果。

http://www.postgresql.org/docs/current/static/queries-with.html

我已经实现了一个系统,其中 BOM 由单个资源的层次结构组成,并且我不必执行您描述的任何批处理(诚然,当我工作时,数据库中只有几千个资源它)。

你应该学习如何在 SQL 中使用聚合函数,比如SUM()GROUP BY(任何关于 SQL 的书都应该包括这个),以及存储实体层次关系的技术。

既然您说您不太了解数据库,我建议您在对真实系统进行任何更改之前尝试实现一个“玩具”系统。我只是从个人经验出发,但我发现我无法学习新的技术技能,同时我试图在实际项目中使用该技能。

于 2009-09-24T07:42:10.813 回答
2

在我看来,这听起来像是一种可以从数据库中的存储过程中受益的计算,或多或少与您使用哪种实现方法无关。这减少了客户端和服务器之间的流量,几乎总是提高了这样一组复杂计算的性能。

你说:

我现在所做的,我不记得我为什么这样做,是我从数据库中获取所有项目和所有 BOM,并一次针对每个项目递归计算其成本。一旦我计算了一个项目,我就会标记它,这样我就不会再重做成本了。(也防止无限递归)。

我对这个解释中的“标记它”部分感到困惑——不知道为什么你会以你的方式做某事是个坏消息。你真的需要了解你在做什么。

有很多方法可以进行 BOM 处理 - Bill Karwin 为您指出了一些有趣的信息(SQL Antipatterns 链接大约有 250 张幻灯片!)。SQL 反模式部分讨论了“朴素树”(例如下面概述的那些)。但是,这些解决方案不包括下面概述的情况,其中同一子树可以由多个父级使用(因为一个子组件可以是多个产品的组件)。

  • 路径枚举不起作用:您不能使用相同的子组件信息,因为您将包含产品信息构建到路径中。
  • 在一个产品中使用子组件时,嵌套集可以正常工作;在许多产品中使用子组件时不是这样。
  • “封闭表”解决方案可以适应这一点——它或多或少是下面的第二种选择。

您需要考虑对受影响的部分进行自下而上的扫描是否有意义,或者进行某种广度优先或深度优先扫描是否会更好。此决策的一个驱动因素将是 BOM 数据的性质。如果您有一个结构,其中某个子组件用作多个产品的组件,您是为每个产品单独记录子组件中使用的零件,还是记录产品使用子组件?

澄清:

  • 子组件 A (P001) 包含 24 x 8mm 螺母 (P002)、24 x 8mm x 50 mm 螺栓 (P003)、1 x 底板 (P004)、1 x 盖板 (P005)。
  • 产品 B (P006) 包含 1 个子组件 A 和许多其他零件。
  • 产品 B (P007) 包含 1 个子组件 B 和许多其他零件。

您的 BOM 记录可能如下所示(朴素树):

Part      Component     Quantity
P001      P002          24
P001      P003          24
P001      P004          1
P001      P005          1
P006      P001          1
P007      P001          1

或者它们可能看起来像这样(闭包表):

Part      Component     Quantity
P001      P002          24
P001      P003          24
P001      P004          1
P001      P005          1
P006      P002          24
P006      P003          24
P006      P004          1
P006      P005          1
P007      P002          24
P007      P003          24
P007      P004          1
P007      P005          1

第二种情况不太理想 - 获得正确的值要困难得多,如果像螺母或螺栓这样的零件,多个子组件可以使用相同的零件,那么在一个主要可交付产品(P006、P007)将非常困难。但是,在第二种情况下,重新计算任何零件的成本要简单得多——您只需计算组成零件的每个组件的“成本乘以数量”的总和。如果您保留幼稚树以记录零件结构分解并(重新)计算某些产品或子组件的结构(而不​​是价格)发生变化时的闭合表,那么您可能会尽可能接近必杀技要得到。

在某个地方(但在另一台计算机上),我有一些旧代码可以使用虚构的程序集来处理这些东西。编码完成了......咕哝,咕哝......很久以前,并且为特定的 DBMS 使用临时表(并且没有提到嵌套集或路径枚举;它确实计算了闭包表) - 它必须是适应其他 DBMS。问,我会挖出来的。

于 2009-09-24T08:27:26.867 回答