mysql左连接需要太长时间

Posted

技术标签:

【中文标题】mysql左连接需要太长时间【英文标题】:mysql left join takes too long 【发布时间】:2012-03-17 22:59:13 【问题描述】:

我有以下 SQL 查询:

SELECT 
    upd.*,
    usr.username AS `username`,
    usr.profile_picture AS `profile_picture`
FROM 
    updates AS upd
LEFT JOIN 
    subscribers AS sub ON upd.uid=sub.suid
LEFT JOIN 
    users AS usr ON upd.uid=usr.uid
WHERE 
    upd.deleted='0' && (upd.uid='118697835834' || sub.uid='118697835834')
GROUP BY upd.id
ORDER BY upd.date DESC
LIMIT 0, 15

我在哪里获得所有用户 (118697835834) 更新、他使用左连接从另一个表中获取的个人资料图片以及他所有订阅用户的更新,所以我可以在他的新闻源中显示它们。

但是随着更新越来越多,因此查询需要更多时间来加载...现在使用 Codeigniter 的 Profiler 我可以看到查询需要 1.3793...

现在我已经创建了大约 18k 虚拟帐户并订阅了我,反之亦然,所以我可以测试执行时间......考虑到我在 localhost,我得到的时间是悲惨的......

我还有一些索引,我想在用户表中需要更多(用户名和 uid 作为唯一),更新表(update_id 作为唯一,uid 作为索引)

我想我做错了什么得到如此糟糕的结果......

编辑: 运行 EXPLAIN EXTENDED 结果:

Array
(
    [0] => stdClass Object
        (
            [id] => 1
            [select_type] => SIMPLE
            [table] => upd
            [type] => ALL
            [possible_keys] => i2
            [key] => 
            [key_len] => 
            [ref] => 
            [rows] => 22
            [filtered] => 100.00
            [Extra] => Using where; Using temporary; Using filesort
        )

    [1] => stdClass Object
        (
            [id] => 1
            [select_type] => SIMPLE
            [table] => sub
            [type] => ALL
            [possible_keys] => 
            [key] => 
            [key_len] => 
            [ref] => 
            [rows] => 18244
            [filtered] => 100.00
            [Extra] => Using where
        )

    [2] => stdClass Object
        (
            [id] => 1
            [select_type] => SIMPLE
            [table] => usr
            [type] => eq_ref
            [possible_keys] => uid
            [key] => uid
            [key_len] => 8
            [ref] => site.upd.uid
            [rows] => 1
            [filtered] => 100.00
            [Extra] => 
        )

)

EDIT2:显示表的创建 用户表:

CREATE TABLE `users` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `uid` bigint(20) NOT NULL,
 `username` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
 `email` text CHARACTER SET latin1 NOT NULL,
 `password` text CHARACTER SET latin1 NOT NULL,
 `profile_picture_full` text COLLATE utf8_unicode_ci NOT NULL,
 `profile_picture` text COLLATE utf8_unicode_ci NOT NULL,
 `date_registered` datetime NOT NULL,
 `activated` tinyint(1) NOT NULL,
 `closed` tinyint(1) NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `uid` (`uid`),
 UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM AUTO_INCREMENT=23521 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

订阅者表:

CREATE TABLE `subscribers` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `sid` bigint(20) NOT NULL,
 `uid` bigint(20) NOT NULL,
 `suid` bigint(20) NOT NULL,
 `date` datetime NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=18255 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

更新表格:

CREATE TABLE `updates` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `update_id` bigint(19) NOT NULL,
 `uid` bigint(20) NOT NULL,
 `type` text COLLATE utf8_unicode_ci NOT NULL,
 `update` text COLLATE utf8_unicode_ci NOT NULL,
 `date` datetime NOT NULL,
 `total_likes` int(11) NOT NULL,
 `total_comments` int(11) NOT NULL,
 `total_favorites` int(11) NOT NULL,
 `category` bigint(20) NOT NULL,
 `deleted` tinyint(1) NOT NULL,
 `deleted_date` datetime NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `i1` (`update_id`),
 KEY `i2` (`uid`),
 KEY `deleted_index` (`deleted`)
) ENGINE=MyISAM AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

【问题讨论】:

使用 EXPLAIN 以便您可以看到查询是如何执行的 您完成了吗EXPLAIN EXTENDED(将其添加到您的查询中) 您是否为搜索和加入的所有内容编制索引? 我还有一些索引,我认为用户表中需要更多索引(用户名和 uid 作为唯一),更新表(update_id 作为唯一,uid 作为索引) 在担心其他事情之前先索引那只小狗。今天我从索引得到了一个 120 秒的查询,下降到了 0.05 秒。此外,您似乎将 uids 视为 SQL 中的字符串? 【参考方案1】:

试试这个(没有GROUP BY):

SELECT 
    upd.*,
    usr.username AS `username`,
    usr.profile_picture AS `profile_picture`
FROM 
        updates AS upd
    LEFT JOIN 
        users AS usr 
            ON  upd.uid = usr.uid
WHERE 
    upd.deleted='0' 
  AND 
    ( upd.uid='118697835834'
   OR EXISTS
      ( SELECT *
        FROM   subscribers AS sub 
        WHERE  upd.uid = sub.suid
          AND  sub.uid = '118697835834'
      )
    )
ORDER BY upd.date DESC
LIMIT 0, 15

至少应该对 Joins 中使用的列进行索引:updates.uidusers.uidsubscribers.suid

我还会在subscribers.uid 上添加一个索引。

【讨论】:

这确实更快...我现在需要正确索引以希望获得更好的性能 @fxuser:编辑您的问题并添加表的描述(列数据类型、索引和 PK、FK)。 users.uid 被索引为唯一,updates.uid 被索引为索引,subscribers.uid 和 suid 也被索引为索引 索引(uid,suid)还是索引(suid,uid)?您可能需要另一个,具体取决于此查询的 EXPLAIN 告诉您的内容。如果没有必要,我仍然会删除引号。 @Naltharial 如果您在谈论订阅者表,我有 2 个单独的索引,分别带有 uid 和 suid...我是否必须添加另一列与它们(id)? - 关于我看到的报价,它提供了性能,我将在必要时删除它们【参考方案2】:

试试:

SELECT 
    upd.*,
    usr.username AS `username`,
    usr.profile_picture AS `profile_picture`
FROM 
    updates AS upd
LEFT JOIN 
    subscribers AS sub ON upd.uid=sub.suid
LEFT JOIN 
    users AS usr ON upd.uid=usr.uid
WHERE 
    upd.deleted=0 and upd.uid in (118697835834,118697835834)
GROUP BY upd.id
ORDER BY upd.date DESC
LIMIT 0, 15

请注意,' 已从数值中删除,位运算符已更改为常规运算符。

【讨论】:

【参考方案3】:

不要使用连接,试试这个:

select  *, 
        (select username from users where uid = upd.uid) as username,
        (select profile_picture from users where uid = upd.uid) as profile_picture,
from    updates as upd
WHERE 
    upd.deleted='0' && upd.uid='118697835834'

(未测试!)

也许您必须使用另一个子选择检查 where 子句中是否存在订阅者。

另一种方法是在子选择上而不是整个表上进行连接。这也可能会提高您的表现。

【讨论】:

避免JOINs 的理由是?你能从你的查询中发布一个比JOINed 更好的EXPLAIN 计划吗? @Naltharial:我没有设置数据库,所以我没有解释。但我经常通过在我的项目中避免“加入”来提高性能。 @fxuser:订阅者必须通过 EXISTS 进行检查(正如我在评论中提到的以及您在示例中所做的那样) 这是因为 mysql 会在可能的情况下将您的子选择隐式转换为适当的JOINs。 JOINs 的性能几乎总是更好(除非您可以提供与标准不同的使用情况的硬数据),因为引擎可以更有效地优化它们。【参考方案4】:

运行时间不宜过长;你有关于“已删除”的索引吗? “GROUP BY id”在做什么?它应该是UID吗?如果 ID 实际上只是一个自动递增的唯一 ID,它会出来吗? (这既昂贵又毫无意义)

【讨论】:

在删除时添加了一个索引,没有任何改变。查询需要 GROUP BY id 才能显示我获得的所有更新,因为通过删除它,它将一次又一次地显示 1..【参考方案5】:

我认为您最好将此查询分离为用户表上的选择,然后将这些结果与订阅者表上的选择合并。

【讨论】:

也从用户表开始,然后将更新合并到该表上。您的更新表可能会比您的用户大得多。

以上是关于mysql左连接需要太长时间的主要内容,如果未能解决你的问题,请参考以下文章

mysql左连接和右连接的区别

mysql - 子查询和连接

mysql长时间不操作会不会连接超时?怎么改?

MySql - 在查询中连接左外连接

使用“不为空”的左连接查询的优化

MySQL - 向左连接添加子句