我目前运行的网站会在列表中跟踪最新的分数和评分。该列表包含数千个经常更新的条目,该列表应按这些分数和评级列进行排序。
我获取此数据的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,000 score_adjustments
个条目,每天大约增加5,000个。 rating_adjustments
约为1/4。
现在,我没有DBA专家,但我一直在猜SUM()
和AVG()
并不是一件好事 - 尤其是sa
并且ra
包含数十万条记录 - 对吧?
我已经对查询进行了缓存,但我希望查询本身很快 - 但仍然尽可能最新。我想知道是否有人可以分享任何解决方案来优化这样的重型连接/聚合查询?如有必要,我愿意进行结构性改变。
编辑1
添加了有关查询的更多信息。
答案 0 :(得分:2)
如果您担心性能,可以将得分和评级列添加到相应的表中,并在插入时更新它们或使用触发器更新引用的表。这会在每次更新时缓存新结果,并且您不必每次都重新计算它们,大大减少了获得结果所需的连接数量......只是猜测但在大多数情况下查询的结果可能是比更新更经常获取。
查看这个sql fiddle http://sqlfiddle.com/#!2/b7101/1以了解如何制作触发器及其效果,我只在插入时添加了触发器,您可以轻松添加更新触发器,如果您曾删除数据添加触发器以进行删除好。
没有添加datetime字段,如果between ... and ...
参数经常更改,您可能每次都必须手动执行此操作,否则您只需将between子句添加到score_update触发器。
答案 1 :(得分:2)
您的数据非常clustered。
InnoDB将存储物理上靠近“PK”的行。由于您的子表使用代理PK,因此它们的行将随机存储。当需要计算“master”表中给定行的时间时,DBMS必须遍布整个地方以从子表中收集相关行。
代替代理键,尝试使用更多“自然”键,父级PK位于前沿,类似于:
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 /群集键上的范围扫描来计算SUM或AVG,并且只需很少的I / OS。
或者(例如,如果您使用的MyISAM不支持群集),cover带索引的查询,以便在查询期间不会触及子表。
最重要的是,您可以对设计进行非规范化,并将当前结果缓存在父表中:
score_adjustments
插入,更新或删除行时通过触发器对其进行调整。rating_adjustments
时,将其添加到S并增加C.在运行时计算S / C以获得平均值。同样处理更新和删除。