繁体   English   中英

在数据库中保留小计字段是一个坏主意

[英]Is it a bad idea to keep a subtotal field in database

我有一个MySQL表,代表一个订单列表和一个相关的子表,代表与每个订单关联的货件(一些订单有多个货件,但大多数只有一个)。

每批货物都有许多费用,例如:

  • ItemCost
  • 运输费
  • HandlingCost
  • TaxCost

应用程序中有许多地方需要获取订单的综合信息,例如:

  • TotalItemCost
  • TotalShippingCost
  • TotalHandlingCost
  • TotalTaxCost
  • 总消耗
  • 总支付
  • TotalProfit

所有这些字段都取决于相关货件表中的汇总值。 此信息用于其他查询,报告,屏幕等,其中一些必须快速为用户返回数万条记录的结果。

在我看来,有几个基本的方法可以解决这个问题:

  1. 使用子查询可在需要时从货件表中计算这些项目。 对于需要全部或部分此类信息的所有查询,这会使事情变得复杂。 它也很慢。

  2. 创建一个将子查询公开为简单字段的视图。 这使得需要它们的报告变得简单。

  3. 在订单表中添加这些字段。 这些将为我提供我正在寻找的性能,代价是在我对货件记录进行任何更改时必须复制数据并进行计算。

另一件事,我正在使用一个业务层来公开函数来获取这些数据(例如GetOrders(过滤器))并且我不需要每次都使用小计(或者某些时候只需要其中的一些),所以生成一个每次(即使从一个视图)子查询可能是一个坏主意。

是否有任何人可以指出我帮助我决定最佳设计的最佳实践?

顺便说一下,我最终做了#3主要是出于性能和查询简单性的原因。

更新:

很快就得到了很多很棒的反馈,谢谢大家。 为了给出更多背景信息,显示信息的地方之一是在管理控制台上,我有一个可能很长的订单列表,需要为每个订单显示TotalCost,TotalPaid和TotalProfit。

将统计数据汇总并存储以提高应用程序性能绝对没有错。 请记住,您可能需要创建一组触发器或作业,以使汇总与源数据保持同步。

我可能会通过在数据库中缓存小计来获得最快的查询性能,如果大多数时候你正在读取而不是写入。 创建更新触发器以在行更改时重新计算小计。

我只会使用一个视图来计算它们在SELECT的行数,如果行数通常非常小并且访问有点不常见。 如果缓存它们,性能会好很多。

选项3是最快的
如果您遇到性能问题并且无法以其他任何方式解决这些问题,那么选项#3就是您的选择。

使用触发器进行更新
您应该在插入,更新和删除后使用触发器,以使订单表中的小计与基础数据保持同步。
在追溯性地改变价格和东西时要特别小心,因为这需要完全重新计算所有小计。 所以你需要很多触发器,这些触发器通常在大多数情况下都不会做太多。
如果税率发生变化,那么对于您尚未拥有的订单,它将来会发生变化

如果触发器需要花费大量时间,请确保在非高峰时段进行这些更新。

定期运行自动检查以确保缓存的值正确
您可能还需要保持一个golden到位子查询计算所有值,并checkes并将其与顺序表存储的值。
每晚运行此查询并让它报告任何异常,以便您可以看到非规范化值何时不同步。

对未经验证查询处理的订单不进行任何开票
在名为timeoflastsuccesfullvalidationorder添加一个额外的日期字段,如果验证不成功,则将其设置为null
dateoflastsuccesfullvalidation少于24小时前的物品。
当然,您不需要检查完全处理的订单,只检查待处理的订单。

选项1可能足够快
关于#1

它也很慢。

这在很大程度上取决于您如何查询数据库。
你提到子选择,在下面的大多数完整的骨架查询中,我没有看到需要很多子选择,所以你让我困惑了一下。

SELECT field1,field2,field3
       , oifield1,oifield2,oifield3
       , NettItemCost * (1+taxrate) as TotalItemCost
       , TotalShippingCost
       , TotalHandlingCost
       , NettItemCost * taxRate as TotalTaxCost
       , (NettItemCost * (1+taxrate)) + TotalShippingCost + TotalHandlingCost as TotalCost
       , TotalPaid
       , somethingorother as TotalProfit
FROM (

  SELECT o.field1,o.field2, o.field3
         , oi.field1 as oifield1, i.field2 as oifield2 ,oi.field3 as oifield3
         , SUM(c.productprice * oi.qty) as NettItemCost
         , SUM(IFNULL(sc.shippingperkg,0) * oi.qty * p.WeightInKg) as TotalShippingCost
         , SUM(IFNULL(hc.handlingperwhatever,0) * oi.qty) as TotalHandlingCost
         , t.taxrate as TaxRate
         , IFNULL(pay.amountpaid,0) as TotalPaid
  FROM orders o
  INNER JOIN orderitem oi ON (oi.order_id = o.id)
  INNER JOIN products p ON (p.id = oi.product_id)
  INNER JOIN prices c ON (c.product_id = p.id 
                       AND o.orderdate BETWEEN c.validfrom AND c.validuntil)
  INNER JOIN taxes t ON (p.tax_id = t.tax_id 
                       AND o.orderdate BETWEEN t.validfrom AND t.validuntil) 
  LEFT JOIN shippingcosts sc ON (o.country = sc.country
                       AND o.orderdate BETWEEN sc.validfrom AND sc.validuntil)
  LEFT JOIN handlingcost hc ON (hc.id = oi.handlingcost_id
                       AND o.orderdate BETWEEN hc.validfrom AND hc.validuntil)
  LEFT JOIN (SELECT SUM(pay.payment) as amountpaid FROM payment pay 
             WHERE pay.order_id = o.id) paid ON (1=1)
  WHERE o.id BETWEEN '1245' AND '1299'
  GROUP BY o.id DESC, oi.id DESC ) AS sub  

想一想,你需要将这个查询拆分为每个订单和每个order_item相关的东西,但我现在懒得这么做。

速度提示
确保您在join-criteria中涉及的所有字段都有索引。
对于较小的表使用MEMORY表,例如taxshippingcost ,并对内存表中的id使用hash索引。

我会尽可能地避免#3。 我更喜欢这个原因:

  1. 没有测量就很难讨论性能。 成像用户正在四处购物,将订单商品添加到订单中; 每次添加项目时,您都需要更新订单记录,这可能不是必需的(某些网站仅在您单击购物车并准备结帐时显示订单总数)。

  2. 有一个重复的列是要求错误 - 你不能指望每个未来的开发人员/维护者都知道这个额外的列。 触发器可以提供帮助,但我认为触发器应该仅用作解决数据库设计错误的最后手段。

  3. 可以使用不同的数据库模式进行报告。 报告数据库可以高度去标准化以用于性能目的,而不会使主应用程序复杂化。

  4. 我倾向于在应用层将实际逻辑用于计算小计,因为小计实际上是与不同上下文相关的重载事件 - 有时你想要“原始小计”,有时你想要应用折扣后的小计。 您无法继续为订单表添加列以用于不同的方案。

这不是一个坏主意,不幸的是MySQL没有一些功能可以使这个非常简单 - 计算列和索引(物化视图)。 你可以用触发器模拟它。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM