mysql 优化慢复杂sql (多个left join 数量过大 order by 巨慢)

Posted 废物重利用

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql 优化慢复杂sql (多个left join 数量过大 order by 巨慢)相关的知识,希望对你有一定的参考价值。

前沿

懒得看过程的话这里直接总结一下最后的解决方法:
如果不能直接减少主表的数据(小表驱动大表),就想办法把多个left join合成一个子查询,速度是否变快,没有的话再在子查询底下加一个having条件(having什么不重要,结果不会错就行)

项目场景:

项目场景:因为一些迫不得已的原因(产品一定要)导致一个分页查询数据的sql非常复杂,查询效率巨巨巨慢(从来没查到过结果,最长等了2分钟)


问题描述

涉及项目,就不贴真实代码了,大概结构是

select p.id,p.name,ps2.sort
from table1 p
left join table2 ps
on p.name = ps.name
and ps.region = 1
left join table2 ps2
on ps.name = ps2.name
and ps2.region = 1
and ...
where ...
order by ps2.sort asc,p.sale desc,p.time desc
limit 0,10

table1表有1w+的数据量,table2表有八百万数据,每个region大概有1w+数据。


原因分析:

left join数据量太大,笛卡尔积相当于1w✖️1w✖️1w,也就是做了1w✖️1w✖️1w的关联。最后的order by由于数据量过大,要反复回表查做排序,导致查询速度及其慢。
(不加order by时10秒能查出来,虽然也还是慢但是至少能查出来,但是order by 不能不要)


解决方案:

经过一系列百度,作出以下几种解决尝试。

方案一:按需给涉及的两个表加了索引,explain走了索引,但仍然因为order by 无法查出结果。(删除order by仍需要10s+)

方案二:根据对mysql join的理解,按理说减少笛卡尔积应该能大幅度提升速度,于是猜想把sql的两次left join 变成一次,是不是就能解决问题?对sql进行分析,能把后两次left join抽成一个子查询,即如下结构

select p.id,p.name,pss.sort
from table1 p
left join (
select name,sort
from table2 ps
left join table2 ps2
on ps.name = ps2.name
and ...
where ...
) pss
on p.name = pss.name
where ...
order by pss.sort asc,p.sale desc,p.time desc
limit 0,10

但是仍然查询很慢也仍然无法查出结果,explain的结果与未作修改的explain结果没有差别,也就是说笛卡尔积仍然是1w✖️1w✖️1w,而我预想的结果应当是1w✖️1w。

方案三:百思不得其解时,我随手在方案二升级的子查询里加了一个having sort = 1,速度陡然变快,与方案二的sql仅一个having之差,速度却提升了数十倍,原本2min都查不出来的sql,现在3秒内能查出,explain结果也有所不同,多了一行table为 < dervied2>的结果,看样子是实现了1w✖️1w的效果。


猜想:

方案二的子查询应该是被mysql自动优化成直接的left join关联,所以explain结果才会没有差别,而在子查询中加了having之后mysql无法自动优化成直接的left join,就沿用了sql的调用顺序,所以explain才会多一个子查询的行,也就实现了我想要的效果,即从1w✖️1w✖️1w ->1w✖️1w,速度也就得到了很大的提升。
没有谷歌出来实锤,但是根据explain的结果应该是这样没错。

讲道理应该从更根本的表结构的源头上解决问题,或者考虑是不是应该用es,但是有些东西身不由己,代码和人总要有一个能跑,哎。

MySQL 对多个 OR 使用索引,但对 IN 没有索引,而且速度慢得多

【中文标题】MySQL 对多个 OR 使用索引,但对 IN 没有索引,而且速度慢得多【英文标题】:MySQL using Indexes for multiple ORs, but no index for IN and so much slower 【发布时间】:2019-01-03 13:44:55 【问题描述】:

我一直忙于更改一些 SQL 查询,以使它们在人眼看来更具可读性,我还被告知它们可能会快 5-10%。

之前的 SQL 语句如下所示。

SELECT * FROM teams WHERE Team1='Joe Bloggs' OR Team2='Joe Bloggs' OR Team3='Joe Bloggs'

我改成

SELECT * FROM team WHERE 'Joe Bloggs' IN (Team1,Team2,Team3)

新查询大约慢了 10 倍,在检查了可能是什么原因后,我发现它没有使用任何索引,即使我尝试强制使用索引,它仍然不会使用它。

该表大约有 120,000 行,我无法更改表格式,因为我无权访问的其他应用程序使用它。 Team1,Team2,Team3 列都是 VARCHAR(45)

谁能解释为什么索引用于原始查询而不是新查询?我已经阅读了大量页面,但找不到答案,我已经读到 mysql 可能确定不使用索引的速度更快,但是这里不应该是这种情况,因为 IN 查询几乎慢了 10 倍。

多个 ORs SELECT(运行 1000 次,没有缓存)- 12.863906860352 已过 IN SELECT(在没有缓存的情况下运行 1000 次)- 122.73787903786 已过

感谢您的宝贵时间。

【问题讨论】:

为两个版本提供EXPLAIN SELECT ...。特别是,我正在寻找“索引合并联合”。 【参考方案1】:

在查询中:

SELECT * FROM teams WHERE 'Joe Bloggs' IN (Team1,Team2,Team3)

您正在将一堆列与字符串文字进行比较(查找)。优化器通常会使用搜索目标上的索引(在本例中为 Joe Bloggs)来查找 IN 子句中的值。但是,它不能在字符串文字上放置索引。所以,这里的一切都颠倒过来了,这就是索引不起作用的原因。

另一方面,在您的第一个查询中:

SELECT * FROM teams WHERE Team1='Joe Bloggs' OR Team2='Joe Bloggs' OR Team3='Joe Bloggs'

MySQL 会抓取字符串文字,然后使用 B-tree 索引在各个列中查找它们。这与您预期和看到的一样。

【讨论】:

。 .我不认为 MySQL 使用OR 的索引,尽管更新的版本可能已经实现了这种优化。 @GordonLinoff 但是如何解释 OP 的观察结果? OR 查询肯定是在使用索引,它要快得多,并且使用 EXPLAIN 表明它只查询少量行和它正在使用的索引,而 IN 语句正在查询所有表中的行并且不使用索引。 蒂姆,根据您的经验,我应该坚持使用多个 OR,因为到目前为止我的基准标记似乎表明在我的用例中这是最快的,或者它们仍然是一种更简单的编写方式查询,同时仍在使用索引? 您对原始查询的反感是什么,它对您有用,除了它的视觉部分?【参考方案2】:

你有一个“倒置的 IN”;优化器只会使用column in (value1, value2, value3) 的索引。

但是,如果您在 3 列中的每一列上都有单独的索引,那么还有另一种方法可以产生比您的任何尝试都好得多的性能:

SELECT * FROM teams WHERE Team1='Joe Bloggs'
UNION
SELECT * FROM teams WHERE Team2='Joe Bloggs'
UNION
SELECT * FROM teams WHERE Team3='Joe Bloggs'

该表将被查询 3 次,但每次都会使用一个索引。

如果您确定不会有任何欺骗,或者您不介意欺骗,请将 UNION 更改为 UNION ALL 以进一步加快速度(UNION 有额外的开销或重复数据删除)。

【讨论】:

感谢您的回答,我从来不知道“inverted IN”语句是什么,我将查询更改为与您在上面写的完全一样,但不幸的是,在运行基准测试大约 10 次以获得良好的平均值后,它的速度几乎是原始 OR 语句的两倍。 1000 次迭代需要 28.898875951767 秒。它也打败了我所针对的主要目标,即可读性,它对我和其他人来说更容易理解多个 OR,但也许这只是因为我们习惯于长时间查看这些语句。谢谢你的回答:) @twingo 我只是编造了“inverted IN”这个名字来帮助描述这种模式(不过我会从现在开始继续使用它,因为它听起来很酷)。很遗憾听到重写对您不利。您确定定义了 3 个索引 - 每列一个索引吗?如果是这样,请尝试运行analyze teams。但是,可能是 MySQL 的优化器做得很好,而您原来的 OR 查询就是要走的路。 @Twingo - 计时时,运行两次并进行第二次计时。第一个可能涉及 I/O;由于缓存数据,后续运行可能彼此相同。 @Twingo - 通过将OR 写入多行并对齐文本,OR 将与IN 一样清晰易读。【参考方案3】:

计划 A:使用 FULLTEXT (team1, team2, team3)MATCH(team1, team2, team3) AGAINST ('+Joe +Briggs' IN BOOLEAN MODE)。使用这种方法有很多注意事项,但是,如果它适用于您的情况,它会非常快。

B 计划:尽管“无法更改表格格式”,但您可以使用 VIEW 玩一些游戏,以避免跨列展开数组(团队)。

【讨论】:

【参考方案4】:

我不知道为什么性能会有所不同——在这两种情况下似乎都不会使用索引。

你可以这样写查询:

SELECT t.*
FROM teams t
WHERE Team1 = 'Joe Bloggs'
UNION ALL
SELECT t.*
FROM teams t
WHERE Team2 = 'Joe Bloggs' AND Team1 <> 'Joe Bloggs' 
UNION ALL
SELECT t.*
FROM teams t
WHERE Team3 =  'Joe Bloggs'
  AND Team2 <> 'Joe Bloggs'
  AND Team1 <> 'Joe Bloggs';

这可以利用(Team1)(Team2, Team1)(Team3, Team2, Team1)上的索引。

【讨论】:

正如我在上面的评论中提到的,索引最肯定与多个 OR 查询一起使用,在语句显示它使用索引之前使用 EXPLAIN 并且它显示它只查询少量 ROWS,vs执行全表扫描且不使用索引的 IN 语句。我真的很感谢你抽出时间来写你的答案,不幸的是它比使用多个 OR 运行得慢,而且它使人类阅读查询比只使用 OR 几次更难。虽然我完全欣赏你的方式,但也许是正确的方式。 是的,MySQL 可以使用索引合并优化来为or 等条件使用索引,请参阅dev.mysql.com/doc/refman/5.5/en/index-merge-optimization.html 正如您所看到的,该功能至少从 v5.5 开始可用。 @Shadow 。 . .令人惊讶的是,合并扫描不会用于 in 列列表。 @GordonLinoff - MySQL 的设计和开发历史充斥着仅仅足以说实现了一个特性。 ALTER 总是重建表时非常简单(实现)。现在 5.6、5.7 和 8.0 进行了多次尝试,以挤出所有可能的优化。我一直在关注EXPLAINs,因为索引合并被实施(在 4.1 中?)它几乎从未被使用过,即使对于看起来很可能的查询也是如此。它可能只针对IN,而不是JOIN,当然也不是IN。我会说“倒置 IN”非常罕见。 @GordonLinoff - 功能存在但未优化的另一种情况:WHERE (a, b) &gt; (123, 345) -- INDEX(a,b) 没有帮助。解决方法很丑陋(尽管已优化)。超过 3 列更难看。

以上是关于mysql 优化慢复杂sql (多个left join 数量过大 order by 巨慢)的主要内容,如果未能解决你的问题,请参考以下文章

MySQL删除千万级数据量导致的慢查询优化

Mysql left join with nested select慢,如何优化

MYSQL优化

mysql慢查询

MySQL优化:慢SQL分析

Mysql优化之慢查询优化