3

我目前运行一个网站,该网站在列表中跟踪最新的分数和评级。该列表有数千个经常更新的条目,并且该列表应该可以按这些分数和评级列进行排序。

我获取此数据的 SQL 目前看起来像(大致):

SELECT e.*, SUM(sa.amount) AS score, AVG(ra.rating) AS rating
FROM entries e 
LEFT JOIN score_adjustments sa ON sa.entry_id = e.id
    HAVING sa.created BETWEEN ... AND ... 
LEFT JOIN rating_adjustments ra ON ra.entry_id = e.id
    HAVING ra.rating > 0 
ORDER BY score 
LIMIT 0, 10

表格在哪里(简化):

entries:
    id: INT(11) PRIMARY
    ...other data...

score_adjustments:
    id: INT(11), PRIMARY
    entry_id: INT(11), INDEX, FOREIGN KEY (entries.id)
    created: DATETIME
    amount: INT(4)

rating_adjustments:
    id: INT(11), PRIMARY
    entry_id: INT(11), INDEX, FOREIGN KEY (entries.id)
    rating: DOUBLE

大约有 300,000score_adjustments个条目,并且它们以每天大约 5,000 个的速度增长。大约是那个的rating_adjustments1/4。

现在,我不是 DBA 专家,但我猜打电话SUM()总是AVG()不是一件好事——尤其是当sa包含ra数十万条记录时——对吧?

我已经对查询进行了缓存,但我希望查询本身快速 - 但仍尽可能保持最新。我想知道是否有人可以分享任何解决方案来优化像这样的繁重的连接/聚合查询?如有必要,我愿意进行结构性改变。

编辑 1

添加了有关查询的更多信息。

4

2 回答 2

2

如果您担心性能,您可以将 score 和 rating 列添加到相应的表中,并在插入时更新它们或使用触发器更新到引用的表。这将在每次更新时缓存新结果,并且您不必每次都重新计算它们,从而显着减少获得结果所需的连接量......只是猜测,但在大多数情况下,您的查询结果可能是获取的次数多于更新的次数。

查看这个 sql fiddle http://sqlfiddle.com/#!2/b7101/1以了解如何制作触发器及其效果,我只在插入时添加了触发器,如果​​您有的话,您可以很容易地添加更新触发器删除数据也添加删除触发器。

没有添加 datetime 字段,如果between ... and ...参数经常更改,您可能仍必须每次手动执行此操作,否则您只需将 between 子句添加到 score_update 触发器即可。

于 2012-10-07T13:50:35.883 回答
2

您的数据聚集严重。

InnoDB 将使用物理上靠近的“关闭”PK 存储行。由于您的子表使用代理 PK,因此它们的行将随机存储。当需要对“主”表中的给定行进行计算时,DBMS 必须到处跳转以从子表中收集相关行。

尝试使用更多“自然”键,而不是代理键,父键位于前缘,类似于:

score_adjustments:
    entry_id: INT(11), FOREIGN KEY (entries.id)
    created: DATETIME
    amount: INT(4)
    PRIMARY KEY (entry_id, created)

rating_adjustments:
    entry_id: INT(11), FOREIGN KEY (entries.id)
    rating_no: INT(11)
    rating: DOUBLE
    PRIMARY KEY (entry_id, rating_no)

注意:这假设created' 的分辨率足够好,并且rating_no添加了 以允许每个entry_id. 这只是一个示例 - 您可以根据需要更改 PK。

这将“强制”属于同一行的行entry_id物理上靠得很近存储,因此只需对 PK/集群键进行范围扫描并使用很少的 I/O,就可以计算出 SUM 或 AVG。

或者(例如,如果您使用不支持集群的 MyISAM),请使用索引覆盖查询,以便在查询期间根本不会触及子表。


最重要的是,您可以对设计进行非规范化,并将当前结果缓存在父表中:

  • 将 SUM(score_adjustments.amount) 存储为物理字段,并在每次插入、更新或删除行时通过触发器对其进行调整score_adjustments
  • 将 SUM(rating_adjustments.rating) 存储为“S” 将 COUNT(rating_adjustments.rating) 存储为“C”。将一行添加到 时rating_adjustments,将其添加到 S 并递增 C。在运行时计算 S/C 以获得平均值。类似地处理更新和删除。
于 2012-10-07T14:42:44.740 回答