查询优化(多连接)
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
是唯一不涉及从u
(GROUP BY
的目标)到r
和rat
(用于聚合)的表。它看起来像:
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
时拖拉名字和姓氏。
在执行JOINs
和GROUP BY
时,请务必检查聚合。你的COUNTs
和SUMs
可能比他们应该的大。
【讨论】:
在“(除p
之外的所有内容)”中,这是一个子查询“select sum(...), sum(...)”吗?
@Gosfly - 我们的想法是尽可能快速轻松地完成所有聚合,然后访问 (JOIN
) 所需的其他列。
您可以(可能)GROUP BY uc.user_id
而不是 u.id
。然后user
、profile
和r
可以在聚合子查询之外加入。虽然我怀疑它是否会改进很多(如果有的话),因为这些连接不会增加行数。以上是关于查询优化(多连接)的主要内容,如果未能解决你的问题,请参考以下文章