【问题标题】:Most efficient way to join "most recent row"加入“最近一行”的最有效方式
【发布时间】:2017-10-06 17:29:40
【问题描述】:

我知道这个问题已经被问了 100 次,这不是一个“我该怎么做”,而是一个效率问题——一个我不太了解的话题。

从我的互联网阅读中,我已经确定了一种解决最新问题的方法,听起来它非常有效 - 左连接一个“最大”表(按匹配条件分组),然后左连接匹配分组的行状况。像这样的:

 Select employee.*, evaluation.* form employee 
 LEFT JOIN (select max(report_date) report_date, employee_id 
      from evaluation group by employee_id) most_recent_eval 
    on most_recent_eval.employee_id = employee.id
 LEFT JOIN evaluation 
    on evaluation.employee_id = employee.id and evaluation.report_date = most_recent_eval.report_date

是否存在我不知道的问题?这是在进行 2 次表扫描(一次查找最大值,一次查找行)?是否必须对每位员工进行 2 次全面扫描?

我问的原因是,我现在正在考虑加入 3 个需要最新行(评估、安全许可和项目)的表,并且似乎任何低效率都会成倍增加。

谁能给我一些建议?

【问题讨论】:

  • 您可以通过运行explain select ... 查询来了解查询在做什么。 MySQL 的网站上有详细的指导如何解释结果。如果没有解释输出,我们无法判断您的查询效率如何。
  • 这是一个简化的示例,我试图不使用包含许多不相关列的实际查询的输出来掩盖问题。我真的在寻找一些关于解决“最近”连接的各种方法的效率的更一般的指导。我现在将打开关于解释输出的书籍,谢谢你的指点。
  • 从查看解释结果来看,我的子查询似乎正在为派生表进行笛卡尔连接(在连接类型下显示 ALL)。我在 start_date 上有一个索引 - 不应该使用它吗?
  • 我更新了我的答案以解决有关您的索引的评论。
  • 删除LEFT,除非你需要它。它可能会妨碍性能。

标签: mysql sql join query-optimization


【解决方案1】:

您应该对您提出的查询模式保持良好状态。

一个可能的建议,如果您的evaluation 表有自己的自动递增id 列,这将有所帮助。您也许可以使用此子查询找到每个员工的最新评估:

            SELECT MAX(id) id
              FROM evaluation
             GROUP BY employee_id

那么你的加入可以是这样的:

        FROM employee
   LEFT JOIN (
               SELECT MAX(id) id
                 FROM evaluation
                GROUP BY employee_id
             ) most_recent_eval ON most_recent_eval.employee_id=employee.id
   LEFT JOIN evaluation ON most_recent_eval.id = evaluation.id

如果id 值和evaluation 表中的report_date 值顺序相同,这将起作用。只有您知道您的应用程序是否属于这种情况。但如果是,这是一个非常有用的优化。

除此之外,您可能需要向某些表添加一些复合索引以加快查询速度。首先让它们正常工作。阅读http://use-the-index-luke.com/。请记住,除非选择它们来加速特定查询,否则大量单列索引通常会损害 MySQL 查询性能。

如果您在(employee_id, report_date) 上创建复合索引,则此子查询

 select max(report_date) report_date, employee_id 
   from evaluation
  group by employee_id

可以对效率惊人的loose index scan 感到满意。同样,如果您使用 InnoDB,查询

            SELECT MAX(id) id
              FROM evaluation
             GROUP BY employee_id

可以通过对employee_id 上的单列索引进行松散索引扫描来满足。 (如果您使用的是 MyISAM,则需要在 (employee_id, id) 上创建复合索引,因为 InnoDB 将主键列隐式地放入每个索引中。)

【讨论】:

  • 当你得到这个工作时,如果你仍然需要帮助,请考虑问另一个关于查询性能的问题。请先阅读本文,尤其是关于查询性能的部分。 meta.stackoverflow.com/a/271056
  • 我不知道评估是按顺序输入的,所以我不能使用 ID,但我使用时间戳(在 int(11) 列中)进行数字排序。我现在正在 use-the-index-luke.com 上阅读.. 谢谢。按照您提供的链接中的建议,我将在未来的性能问题中添加更多细节。我想我正在寻找一个更通用的“正确方向”答案,因为我有很多新的查询要使用我正在使用的新的基于日期的表结构来编写。
  • 您应该知道DATETIMETIMESTAMP 数据项在索引方面与整数一样好,无需对时间/日期值执行不自然的操作即可获得良好的索引和排序性能.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-07
  • 2011-04-06
  • 1970-01-01
  • 2021-11-17
  • 2016-05-10
  • 1970-01-01
相关资源
最近更新 更多