查询优化(多连接)

Posted

技术标签:

【中文标题】查询优化(多连接)【英文标题】:Query optimization (multiple joins) 【发布时间】:2020-04-30 22:48:33 【问题描述】:

我想找到一种改进查询的方法,但似乎我已经完成了这一切。让我给你一些细节。

以下是我的查询:

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`
;

注意:有些表名和列名是自愿隐藏的。

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

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 秒。 我真的不明白,因为当我们谈论 customer_id 上的索引时,fk_ad_customer_idx(customer_id) 是一样的。

【问题讨论】:

您可以检查您的数据库设计以确保数据不会低效混乱。如果没有,别担心,我认为现在是数据检索的好时机。 1.3 秒内返回多少行? 不使用FORCE INDEX时的速度和EXPLAIN是多少? Mmmh 乍一看似乎是一个相当简单的查询...您是否尝试调整引擎? (加入)缓冲区、内存分配等? 你已经证明了为什么FORCE INDEX通常是个坏主意。 【参考方案1】:

首先,您不需要在查询中使用tick.everyTableAndColumn,也不需要结果列、别名等。tick 标记主要在您与保留的工作冲突时使用,因此解析器知道你指的是一个特定的列......就像有一个名为“JOIN”的 COLUMN 的表,但 JOIN 是 SQL 命令的一部分......看看它会引起的混乱。也有助于提高可读性。

接下来,这只是个人喜好,可以帮助您和其他人在数据及其关系方面跟随您。我将连接显示为从它的来源缩进。正如你在下面看到的,我看到了如何从用户(u 别名)到老鼠别名表的链......你只有通过 5 层深才能到达那里,我把第一个表放在左边-连接的一侧(来自表)然后 = 连接到连接右侧的表。

现在,我可以看到这些关系,我建议如下。在具有条件的表上创建 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 )

重新格式化查询以提高可读性和查看表之间的关系

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

【讨论】:

【参考方案2】:

摆脱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 是每个表的PRIMARY KEY。注意没有id 在前。)以上大部分都是“覆盖”。

另一种有时会有所帮助的方法:在加入任何不必要的表之前收集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 可能比他们应该的大。

【讨论】:

在“(除p之外的所有内容)”中,这是一个子查询“select sum(...), sum(...)”吗? @Gosfly - 我们的想法是尽可能快速轻松地完成所有聚合,然后访问 (JOIN) 所需的其他列。 您可以(可能)GROUP BY uc.user_id 而不是 u.id。然后userprofiler 可以在聚合子查询之外加入。虽然我怀疑它是否会改进很多(如果有的话),因为这些连接不会增加行数。

以上是关于查询优化(多连接)的主要内容,如果未能解决你的问题,请参考以下文章

多连接数百万条记录的优化查询需要建议

优化多表连接的配置单元查询

针对标签上的多对多连接优化 MySQL 查询

mysql多条件查询的优化

MySQL调优--05---多表查询优化子查询优化 ORDER BY优化GROUP BY优化分页查询优化

MySql:查询优化(多个左连接)