【问题标题】:MySQL join across 3 tables running very slowlyMySQL 跨 3 个运行非常缓慢的表连接
【发布时间】:2015-09-09 11:14:53
【问题描述】:

我正在显示一页日记事件以及客户详细信息和他们案例的最新记录。

我正在使用 Laravel 的查询生成器,我现在的查询是:

$data['events'] = DB::table('events')
    ->where('cancelled', 0)
    ->where('complete', 0)
    ->join('clients', 'events.clientid', '=', 'clients.id')
    ->join('notes', function ($join) {
    $join->on('events.clientid', '=', 'notes.clientid')
    ->on('notes.created_at', '=', DB::raw('(select created_at from notes where clientid = clients.id ORDER BY created_at DESC LIMIT 1)'));
        })
    ->select('events.*', 'clients.firstname', 'clients.surname', 'notes.note')
    ->orderBy('eventtime', 'asc')->paginate(75); //Load events sorted by time

由此产生的 SQL 输出为:

select `events`.*, `clients`.`firstname`, `clients`.`surname`, `notes`.`note` from `events`
inner join `clients` on `events`.`clientid` = `clients`.`id`
inner join `notes` on `events`.`clientid` = `notes`.`clientid`
and `notes`.`created_at` = (
select created_at from notes where clientid = clients.id ORDER BY created_at DESC LIMIT 1)
where `cancelled` = ? and `complete` = ? order by `eventtime` asc

查询运行良好,但随着表的增大而变得非常缓慢。大约需要 30 秒才能完成,并且是唯一运行如此缓慢的查询。

有没有更好的方法来组织连接?在单独的查询中获取最新的注释是否有意义?

非常感谢,

山姆

【问题讨论】:

  • 数据库查询优化问题可能更适合Database Administrators 站点。
  • 执行EXPLAIN select events.*,... 并向我们展示输出
  • 为join中使用的所有字段和where条件添加索引
  • 向我们展示索引和基数以及解释计划
  • 现阶段不知道如何从 Laravel 生成解释。任何想法,并很乐意发布任何内容。

标签: php mysql laravel join inner-join


【解决方案1】:

这些索引会很有帮助

  1. events(clientid)
  2. clients(id)
  3. notes(clientid,created_at)
  4. notes(created_at,clientid)

注意,4 可能是 notes(created_at,clientid,cacelled,complete,eventtime) 我不会给它添加注释,让它成为一个覆盖索引,因为注释太宽了。

【讨论】:

  • 哇,仅添加索引 1、2 和 3 即可将查询时间从 30 秒缩短到 3.8 毫秒。现在完美运行,谢谢。
  • 在重新查看您的查询时,4 不会有帮助,但将 cancelled, complete, eventtime 添加到 3 可能会有所帮助。抱歉,还在这里醒来
【解决方案2】:

不确定,但您可以尝试以下查询,因为如果它是一个庞大的表,您可以避免子查询中注释表的反向排序-

SELECT `events`.*, `clients`.`firstname`, `clients`.`surname`, `notes`.`note` 
FROM `events` 
INNER JOIN `clients` ON `events`.`clientid` = `clients`.`id` 
INNER JOIN `notes` ON `events`.`clientid` = `notes`.`clientid` 
AND `notes`.`created_at` = 
(
SELECT MAX(created_at) 
FROM notes 
WHERE clientid = clients.id
) 
WHERE `cancelled` = ? AND `complete` = ? 
ORDER BY `eventtime` ASC

假设所有连接的字段以及 created_at 字段都有索引。

此外,如果 events 表包含一些 text/blob 或 long varchar 类型的列,那么如果在使用事件时不需要它们,则应排除它们。*

【讨论】:

  • 为什么不喜欢:notes.id = ( SELECT MAX(id) FROM notes WHERE clientid = clients.id ) 如果您不手动编辑 created_at 字段,则 id 和 created_at 两者将显示最新记录。主键总是很快,因为 id 是整数类型,所以它需要更少的 CPU 时间来操作
  • 使用@num8er 和原来的建议将查询时间减半。很有帮助,谢谢。
【解决方案3】:

解决步骤:

  1. 更改您的查询

这就是你的问题:

select `events`.*, `clients`.`firstname`, `clients`.`surname`, `notes`.`note` from `events`
inner join `clients` on `events`.`clientid` = `clients`.`id`
inner join `notes` on `events`.`clientid` = `notes`.`clientid`
and `notes`.`created_at` = (
select created_at from notes where clientid = clients.id ORDER BY created_at DESC LIMIT 1)
where `cancelled` = ? and `complete` = ? order by `eventtime` asc

让我们像这样改变它:

select `events`.*, `clients`.`firstname`, `clients`.`surname`, `notes`.`note` from `events`
inner join `clients` on `events`.`clientid` = `clients`.`id`
inner join `notes` on `events`.`clientid` = `notes`.`clientid`
and `notes`.`id` = (SELECT MAX(id) FROM notes WHERE clientid = clients.id)
where `cancelled` = ? and `complete` = ? order by `eventtime` asc

如果您想从笔记中获取最新记录,则无需按 created_at 排序并获取最新记录。如果您不手动编辑表格,则 MAX(id) 和 latest created_at 都将指示最新记录。

  1. 添加索引

索引总是有助于数据库避免在搜索数据时获取整个表。
因此,当您加入或进行查询时,它会在内存中收集所有具有相关字段条件的记录,然后加入数据数组。
添加索引将帮助数据库首先快速找到需要的数据。
索引有助于数据库引擎转到存在针数据的二进制文件中的偏移量

所以让我们添加索引:

ALTER TABLE `events` ADD INDEX `clientid` (`clientid`);
ALTER TABLE `notes` ADD INDEX `clientid` (`clientid`);
ALTER TABLE `events` ADD INDEX `cancelled` (`cancelled`);
ALTER TABLE `events` ADD INDEX `complete` (`complete`);
ALTER TABLE `events` ADD INDEX `cancelled_complete` (`cancelled`, `cancelled`);
ALTER TABLE `events` ADD INDEX `eventtime` (`eventtime`);

就是这样(:

附言大多数 laravel 开发人员的主要问题是他们在编写迁移模式时忘记设置索引。 http://laravel.com/docs/5.1/migrations#creating-indexes

【讨论】:

  • 这些索引中有几个永远不会在有或没有 Laravel 的情况下使用。事实上,最多可能只有前两个。所以它只会让插入/更新陷入困境
  • 我知道,但由于这个应用程序和作者将来可能会发生复杂的查询 - 最好的方法是在之前设置这些索引(:
  • 面对甚至没有看到其他查询,这不是一个好习惯
  • 好的,clientid 用于连接 - 对吧?那么当使用 where cancelled=0 和 complete = 0 时,2个字段的索引不会有帮助吗?还结合索引cancelled_complete (cancelled, cancelled) 将帮助数据库不单独检查 2 个索引,而是检查 1 个索引以搜索“0-0”。将索引添加到事件时间也有助于进行排序,因为它将从事件时间索引中获取最顶层的元素。在大型系统中,会发生分析查询。所以总是需要额外的索引。
  • 我的 cmets 总有一天会对你有所帮助。你可能会注意到,我回答的只是mysql 标签的东西。有点知道,但我的文字不是福音
猜你喜欢
  • 1970-01-01
  • 2010-10-24
  • 2023-03-03
  • 2018-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-25
  • 1970-01-01
相关资源
最近更新 更多