是否有任何指导方针或经验法则来确定何时存储聚合值以及何时动态计算它们?
例如,假设我有用户可以评价的小部件(参见下面的架构)。每次我显示一个小部件时,我都可以从Ratings
表格中计算出平均用户评分。或者,我可以将平均评分存储在Widget
表格中。这将使我不必在每次显示小部件时都计算评分,但是每次用户对小部件进行评分时,我都必须重新计算平均评分。
Ratings Widgets
--------- -------
widget_id widget_id
user_id name
rating avg_rating <--- The column in question
这取决于。预先计算聚合值会给写入带来更大的负载,派生它们会使读取更加困难
如果您经常访问派生值,则预计算是一个有效的反规范化步骤。但是,在这种情况下,我建议使用物化视图(一个视图,写入磁盘,通过触发器链接到父表)。物化视图旨在存储经常询问但难以派生的数据,并且对于大量写入和少量读取很有用。
在高写入、高读取的场景中,考虑在后台有一个模拟物化视图效果的任务,但不是实时的。这将呈现“足够好”的平均值,同时保持写入和读取性能。
在任何情况下,您都不应将派生列视为“普通”列:确保小部件“视图”中显示的数据存在于表中的其他位置,以便整个元组可以通过您放置的任何过程派生。这个问题也非常依赖于数据库(和数据库版本),因此我建议针对正常大小的数据集和物化视图对聚合(使用适当的索引)进行性能测试。
您需要多久计算/显示相对于基础数字更改/更新频率的值。
因此,如果您有一个每天点击量为 10k 的网站显示的值每小时只会更改一次,那么我会在基础值更改时计算它(可能是数据库触发器,等等)。
如果您有一个工具可以查看统计数据,其中统计数据会按秒变化,但您只有三个人可以访问,而且他们每天只看几次,我更有可能计算它在飞行中。(除非,首先需要几分钟才能计算出过时的数据并不是什么大问题......而且我的老板告诉我每小时从 cron 生成东西,所以他没有等他想看的时候。)
使用 StaleWidgets 表作为“无效”(要重新计算)小部件的队列。使用可以重新计算这些值的其他线程(异步)任务。重新计算的周期或时刻取决于系统要求:
如果计算不是太麻烦,我建议进行即时计算. 例如,在应该重新计算但不重新计算时将此列设置为true,并且当您重新计算时将此列设置为false(这将表示计算值是最新的而不是陈旧的)。
这样您就不必每次都重新计算,只有当您必须读取并且重新计算列值为 true 时才会计算。这样,您将节省大量的重新计算。
特别是对于这种情况,有一个不同的解决方案,您不必添加所有评级并将其除以总数来找到平均值。相反,您可以拥有一个包含评论总数的其他字段,因此每次添加评分时,您都会使用 (avg_rating×total+new_rating)/total 计算新的平均值,这比聚合要快得多,并且会减少磁盘读数,因为您不必访问所有评级值。类似的解决方案可能适用于其他情况。
这样做的缺点是它不是酸性交易,因此您可能会以过时的评级结束。但是您仍然可以通过在数据库中使用触发器来解决这个问题。另一个问题是数据库不再规范化,但不要害怕对数据进行非规范化以换取性能。