查询优化(多个连接)

Query optimization (multiple joins)

我想找到一种改进查询的方法,但似乎我已经完成了所有工作。让我告诉你一些细节。

下面是我的查询:

SELECT 
    `u`.`id` AS `id`,
    `p`.`lastname` AS `lastname`,
    `p`.`firstname` AS `firstname`,
    COALESCE(`r`.`value`, 0) AS `rvalue`,
    SUM(`rat`.`category` = 'A') AS `count_a`,
    SUM(`rat`.`category` = 'B') AS `count_b`,
    SUM(`rat`.`category` = 'C') AS `count_c`
FROM
    `user` `u`
    JOIN `user_customer` `uc` ON (`u`.`id` = `uc`.`user_id`)
    JOIN `profile` `p` ON (`p`.`id` = `u`.`profile_id`)
    JOIN `ad` FORCE INDEX (fk_ad_customer_idx) ON (`uc`.`customer_id` = `ad`.`customer_id`)
    JOIN `ac` ON (`ac`.`id` = `ad`.`ac_id`)
    JOIN `a` ON (`a`.`id` = `ac`.`a_id`)
    JOIN `rat` ON (`rat`.`code` = `a`.`rat_code`)
    LEFT JOIN `r` ON (`r`.`id` = `u`.`r_id`)
GROUP BY `u`.`id`
;

注意:一些 table 和列名是自愿隐藏的。

现在让我给你一些体积数据:

user => 6534 rows
user_customer => 12 923 rows
profile => 6511 rows
ad => 320 868 rows
ac => 4505 rows
a => 536 rows
rat => 6 rows
r => 3400 rows

最后,我的执行计划:

我的查询目前 运行 在大约 1.3 到 1.7 秒内完成,这当然很慢,足以惹恼我的应用程序的用户......此外,结果集由 165 行组成。

有什么方法可以改进吗?

谢谢。

编辑 1(对下面 Rick James 的回答): 不使用FORCE INDEX时速度和EXPLAIN是多少?

令人惊讶的是,当我不使用 FORCE INDEX 时它会变得更快。老实说,我真的不记得为什么我做了那个改变。在我的各种尝试中,我可能发现它在性能方面有更好的结果,并且从那以后就没有删除它。

当我不使用 FORCE INDEX 时,它使用另一个索引 ad_customer_ac_id_blocked_idx(customer_id, ac_id, blocked) 并且时间约为 1.1 秒。 我不太明白,因为 fk_ad_customer_idx(customer_id) 在我们谈论 customer_id.

上的索引时是一样的

首先,您不需要在查询中使用 tick.everyTableAndColumn,也不需要结果列、别名等。tick 标记主要用于您在与保留工作冲突,因此解析器知道您指的是特定列...就像 table 和一个名为 "JOIN" 的列一样,但 JOIN 是 SQL 命令的一部分。 . 看到它会造成的混乱。也有助于清晰的可读性。

接下来,这只是个人喜好,可以帮助您和您后面的其他人在数据和他们的关系上。我将连接显示为从它的来源缩进。正如您在下面看到的,我看到了如何从 User(u 别名)到 rat 别名 table 的链...您只能深入 5 个级别才能到达那里,我把第一个 table 在连接的左侧(来自 table)然后 = table 连接到连接的右侧。

现在,我可以看到关系,我建议如下。在具有条件的 table 上创建 COVERING 索引,并在适当的地方创建 id/value。这样查询就可以得到它所需要的最好的结果,来自索引页面的数据与必须转到原始数据。所以这里有索引的建议。

table             index
user_customer     ( user_id, customer_id )   -- dont know what your fk_ad_customer_idx parts are)
ad                ( customer_id, ac_id )
ac                ( id, a_id )
a                 (id, rat_code )
rat               ( code, category )

重新格式化查询以提高可读性和查看 tables

之间的关系
SELECT 
        u.id,
        p.lastname,
        p.firstname,
        COALESCE(r.value, 0) AS rvalue,
        SUM(rat.category = 'A') AS count_a,
        SUM(rat.category = 'B') AS count_b,
        SUM(rat.category = 'C') AS count_c
    FROM
        user u
            JOIN user_customer uc
                ON u.id = uc.user_id
                JOIN ad FORCE INDEX (fk_ad_customer_idx) 
                    ON uc.customer_id = ad.customer_id
                    JOIN ac 
                        ON ad.ac_id = ac.id
                        JOIN a 
                            ON ac.a_id = a.id
                            JOIN rat 
                                ON a.rat_code = rat.code
            JOIN profile p
                ON u.profile_id = p.id
            LEFT JOIN r
                ON u.r_id = r.id
    GROUP BY 
        u.id

去掉FORCE INDEX。即使昨天有用;明天可能会痛。

其中一些索引可能是有益的。 (很难预测;所以只需将它们全部相加即可。)

a:  (rat_code, id)
rat:  (code, category)
ac:  (a_id, id)
ad:  (ac_id, customer_id)
ad:  (customer_id, ac_id)
uc:  (customer_id, user_id)
uc:  (user_id, customer_id)
u:  (profile_id, r_id, id)

(假设id是每个table的PRIMARY KEY。注意none先有id。)以上大部分是"covering".

另一种有时有用的方法:在加入任何不必要的 table 之前收集 SUMs。但是似乎 p 是唯一不参与从 uGROUP BY 的目标)到 rrat(用于聚合)。它看起来像:

SELECT ..., firstname, lastname
    FROM ( everything as above except for `p` ) AS most
    JOIN `profile` `p`  ON (`p`.`id` = most.`profile_id`)
    GROUP BY most.id

这避免了在进行大多数连接和 GROUP BY.

时绕过名字和姓氏

在执行 JOINsGROUP BY 时,请务必对聚合进行完整性检查。您的 COUNTsSUMs 可能 比应有的大。