【问题标题】:Rewrite query to use joins instead of a subquery重写查询以使用联接而不是子查询
【发布时间】:2011-11-15 08:13:09
【问题描述】:

我有以下作为 CRON 作业的一部分运行的查询,它的性能非常糟糕。我想重写它,也许将子查询转换为连接,但我不确定如何。

查询归结为如下内容:

SELECT 
    u.id, gu.level
FROM game_users gu
INNER JOIN users u ON u.id = gu.id
WHERE
    gu.points >= 5 AND
    (
        SELECT COUNT(g.id)
        FROM groups g
        INNER JOIN group_members gm ON gm.group_id = g.id
        WHERE g.active = 1
        AND gm.user_id = gu.id
    ) = 0 AND
    gu.level BETWEEN 5 AND 6 AND
    gu.ai = 0 AND
    u.last_visit > '2011-08-12 14:56:00'
ORDER BY gu.points DESC
LIMIT 1

解释输出:

+----+--------------------+-------+--------+----------------------------------------+-----------------------------+---------+--------------------------+------+-------------------------------------------+
| id | select_type        | table | type   | possible_keys                          | key                         | key_len | ref                      | rows | Extra                                     |
+----+--------------------+-------+--------+----------------------------------------+-----------------------------+---------+--------------------------+------+-------------------------------------------+
|  1 | PRIMARY            | gu    | range  | PRIMARY,level_points_ai                | level_points_ai             | 10      | NULL                     | 2016 | Using where; Using index; Using temporary |
|  1 | PRIMARY            | u     | eq_ref | PRIMARY,last_visit                     | PRIMARY                     | 4       | joomla.gu.id             |    1 | Using where                               |
|  2 | DEPENDENT SUBQUERY | g     | ref    | PRIMARY,active                         | active                      | 5       | const                    |  549 | Using where; Using index                  |
|  2 | DEPENDENT SUBQUERY | gm    | eq_ref | group_id_user_id,party_member_to_party | group_id_user_id            | 8       | joomla.g.id,joomla.gu.id |    1 | Using index                               |
+----+--------------------+-------+--------+----------------------------------------+-----------------------------+---------+--------------------------+------+-------------------------------------------+

【问题讨论】:

    标签: mysql optimization join count subquery


    【解决方案1】:

    试试这样的:

    SELECT 
        u.id, gu.level
    FROM game_users gu
    INNER JOIN users u ON u.id = gu.id
    inner join (
        SELECT gm.user_id, COUNT(g.id) as count
        FROM groups g
        INNER JOIN group_members gm ON gm.group_id = g.id
        WHERE g.active = 1
        group by gm.user_id
        having count(g.id) = 0
    ) a on gu.id = a.user_id
    WHERE gu.level BETWEEN 5 AND 6 
        AND gu.ai = 0 
        AND u.last_visit > '2011-08-12 14:56:00'
    ORDER BY gu.points DESC
    LIMIT 1
    

    【讨论】:

    • 当我解释这一点时,它说“不可能在阅读 const 表后注意到 WHERE”,这似乎不对,是吗?
    • 这意味着你得到一个空的结果集。删除所有 WHERE 子句,然后慢慢将它们添加回来,直到您发现哪个在过滤您的数据。
    • 我的原始查询确实返回结果,您的版本没有。
    【解决方案2】:

    LEFT OUTER JOIN 替换子选择应该返回等效的结果。

    SQL 语句

    SELECT  u.id
            , gu.level
    FROM    game_users gu
            INNER JOIN users u ON u.id = gu.id
            LEFT OUTER JOIN group_members gm ON gm.user_id = gu.id
            LEFT OUTER JOIN groups g ON g.id = gm.group_id AND g.active = 1
    WHERE   gu.points >= 5 
            AND g.id IS NULL
            AND gu.level BETWEEN 5 AND 6 
            AND gu.ai = 0 
            AND u.last_visit > '2011-08-12 14:56:00'
    ORDER BY 
            gu.points DESC
    LIMIT 1
    

    【讨论】:

    • 我对左外连接不太熟悉,但第一个到group_members 的左外连接不应该是常规的左连接吗?考虑到存在group_members 记录但针对非活动组的情况?
    • @Aistina - LEFT JOINLEFT OUTER JOIN 相同。 OUTER 子句可以安全地省略,但我个人的偏好是始终完整地写出来。
    • 哦,对了,我没注意到g.id IS NULL。我得看看这个表现如何。
    • 这个查询产生了大约 80% 的性能提升。谢谢!
    猜你喜欢
    • 2021-10-23
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 2011-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-27
    相关资源
    最近更新 更多