我有一个MySQL表,代表一个订单列表和一个相关的子表,代表与每个订单关联的货件(某些订单有多个货件,但大多数只有一个)。
每批货都有许多费用,例如:
应用程序中有许多地方需要获取订单的综合信息,例如:
所有这些字段都取决于相关货件表中的汇总值。此信息用于其他查询,报告,屏幕等,其中一些必须快速为用户返回数万条记录的结果。
正如我所看到的,有几种基本方法可以解决这个问题:
使用子查询可在需要时从货件表中计算这些项目。对于需要全部或部分此类信息的所有查询,这会使事情变得复杂。它也很慢。
创建一个将子查询公开为简单字段的视图。这使得需要它们的报告变得简单。
在订单表中添加这些字段。这些将为我提供我正在寻找的性能,代价是在我对货件记录进行任何更改时必须复制数据并进行计算。
另一件事,我正在使用业务层来公开函数来获取这些数据(例如GetOrders(filter)),我不需要每次都使用小计(或者某些时候只需要其中的一些),所以每次(甚至从视图)生成子查询可能都是个坏主意。
是否有任何人可以指出我帮助我确定最佳设计的最佳做法是什么?
顺便说一下,我最终做了#3主要是出于性能和查询简单的原因。
更新
很快得到了很多很棒的反馈,谢谢大家。为了给出更多背景信息,显示信息的地方之一是在管理控制台上,我有一个可能很长的订单列表,需要显示每个订单的TotalCost,TotalPaid和TotalProfit。
答案 0 :(得分:4)
对统计数据进行汇总并将其存储以提高应用程序性能绝对没有错。请记住,您可能需要创建一组触发器或作业,以使汇总与源数据保持同步。
答案 1 :(得分:3)
如果大多数时候你正在读取而不是写入,我可能会通过缓存数据库中的小计来获得最快的查询性能。创建更新触发器以在行更改时重新计算小计。
如果行数通常非常小并且访问不常见,我只会使用视图在SELECT
上计算它们。如果缓存它们,性能会好很多。
答案 2 :(得分:3)
选项3是最快的
如果您遇到性能问题并且无法以其他任何方式解决这些问题,那么选项#3就是您的选择。
使用触发器进行更新
您应该在插入,更新和删除后使用触发器,以使订单表中的小计与基础数据保持同步
在追溯性地改变价格和东西时要特别小心,因为这需要完全重新计算所有小计。
因此,您需要大量的触发器,这些触发器通常在大多数情况下都不会起作用
如果税率发生变化,对于您尚未拥有的订单,它将来会发生变化
如果触发器需要花费大量时间,请确保在非高峰时段进行这些更新。
定期运行自动检查以确保缓存的值正确
您可能还希望保留一个golden
子查询来计算所有值,并根据订单表中的存储值检查它们。
每晚运行此查询并让它报告任何异常,以便您可以看到非规范化值何时不同步。
请勿对未经验证查询处理的订单执行任何发票
在名为order
的表timeoflastsuccesfullvalidation
中添加一个额外的日期字段,如果验证不成功,则将其设置为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相关的东西,但我现在懒得这么做。
速度提示
确保您在连接标准中涉及的所有字段都有索引
对{(1}}和MEMORY
等较小的表使用tax
表,并在内存表中使用shippingcost
索引作为hash
。
答案 3 :(得分:2)
尽可能避免#3。我更喜欢这个原因:
没有测量就很难讨论性能。成像用户正在四处购物,将订单商品添加到订单中;每次添加商品时,您都需要更新订单记录,这可能不是必需的(某些网站仅在您点击购物车并准备结帐时显示订单总数)。
有一个重复的列要求提供错误 - 您不能指望每个未来的开发人员/维护人员都知道这个额外的列。触发器可以提供帮助,但我认为触发器只应作为解决数据库设计错误的最后手段。
可以使用不同的数据库架构进行报告。报告数据库可以高度去标准化以达到性能目的,而不会使主应用程序复杂化。
我倾向于在应用层设置计算小计的实际逻辑,因为小计实际上是与不同上下文相关的重载事件 - 有时你想要“原始小计”,有时你想要应用折扣后的小计。您无法继续为订单表添加列以用于不同的方案。
答案 4 :(得分:1)
这不是一个坏主意,不幸的是MySQL没有一些功能可以使这个非常简单 - 计算列和索引(物化视图)。你可以用触发器来模拟它。