使用大量连接优化MySQL查询

时间:2012-10-07 11:03:37

标签: mysql database-design database-optimization

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

我获取此数据的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

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

2 个答案:

答案 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带索引的查询,以便在查询期间不会触及子表。


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

  • 将SUM(score_adjustments.amount)存储为物理字段,并在每次从score_adjustments插入,更新或删除行时通过触发器对其进行调整。
  • 将SUM(rating_adjustments.rating)存储为“S” COUNT(rating_adjustments.rating)为“C”。将行添加到rating_adjustments时,将其添加到S并增加C.在运行时计算S / C以获得平均值。同样处理更新和删除。