SQL Performance UNION与OR


70

我刚刚阅读了优化文章的一部分,并对以下语句进行了细分

当使用SQL代替using语句ORUNION

select username from users where company = ‘bbc’ or company = ‘itv’;

至:

select username from users where company = ‘bbc’ union
select username from users where company = ‘itv’;

从快速EXPLAIN

使用OR

在此处输入图片说明

使用UNION

在此处输入图片说明

这是否意味着工作量UNION增加一倍

尽管我赞赏UNION某些RDBMS和某些表模式的性能可能更高,但这在作者看来并不完全正确

我错了吗?


1
我认为UNION这需要做更多的工作,因为它需要删除重复项,其中过滤器将提取指定的条件。不建议使用,我感到很惊讶IN
柯米特(Kermit)2012年

3
如果mysql在有ORinWHERE子句时不能使用索引,那是可以的。
伊戈尔·罗曼琴科

@Igor Romanchenko,请回答。
杰森·麦克雷里

我猜想查询分析器会将or语句重写为IN语句,因为它们是等效的
Darren Kopp 2012年

@Darren Kopp,我敢肯定这是另一回事。无论如何,至少在MySQL中。
杰森·麦克莱里

Answers:


114

您阅读的文章使用了一个错误的例子,或者您误解了他们的观点。

select username from users where company = 'bbc' or company = 'itv';

这等效于:

select username from users where company IN ('bbc', 'itv');

MySQL可以在上使用索引 company为此查询。无需执行任何UNION。

更棘手的情况是您的OR条件涉及两个不同的列。

select username from users where company = 'bbc' or city = 'London';

假设在上有一个索引,在上company有一个单独的索引city。鉴于MySQL通常在给定查询中每个表只使用一个索引,应该使用哪个索引?如果它使用on上的索引company,则仍必须进行表扫描以查找city伦敦所在的行。如果使用on上的索引city,则必须进行表扫描以查找其中的行companybbc。

UNION解决方案是对于这种类型的箱子。

select username from users where company = 'bbc' 
union
select username from users where city = 'London';

现在,每个子查询都可以使用索引进行搜索,并且子查询的结果由组合UNION


匿名用户对我的回答提出了修改建议,但主持人拒绝了该修改。它应该是评论,而不是编辑。提议的编辑声称,UNION必须对结果集进行排序以消除重复的行。这会使查询运行速度变慢,因此索引优化是一项艰巨的任务。

我的回答是,索引有助于在UNION发生之前将结果集减少为少量的行。实际上,UNION确实消除了重复项,但这样做只需要对小的结果集进行排序。在某些情况下,WHERE子句与表的大部分匹配,并且在UNION期间进行排序与​​进行表扫描一样昂贵。但是,通过索引搜索减少结果集的情况更为常见,因此排序的成本比表扫描的成本低得多。

差异取决于表中的数据以及要搜索的术语。确定给定查询的最佳解决方案的唯一方法是尝试在MySQL查询探查器中尝试这两种方法并比较它们的性能。


1
我提供的报价是本文中的确切示例。因此,没有什么可曲解的。我知道使用UNIONOR断然不会如此。但是我将其标记为正确的,因为它提供了作者可能含义的用例,但它将原始示例视为错误
杰森·麦克雷里

las,作者可能一直在写有关解决方案的文章,但却不了解解决方案有用或不需要的情况。或者,他可能已经将自己的知识建立在没有优化IN()谓词的MySQL的较早版本上。
Bill Karwin 2012年

@BillKarwin如果为两个不同的列建立索引,那么MySQL不会执行“索引合并优化”来基于两个索引获得单个扫描的合并结果吗?
sactiw

@sactiw,有时。在实践中,我发现优化器不会像人们期望的那样使用索引合并,因此我不依赖于此。
比尔·卡温

1
我终于知道需要UNION。谢谢!我正在从亚马逊订购您的书。
isapir '16

5

这些不是同一查询。

我对MySQL没有太多的经验,所以我不确定查询优化器会做什么或不做什么,但是这是我一般背景下的想法(主要是ms sql server)。

通常,查询分析器可以采用上述两个查询,并根据它们制定完全相同的计划(如果它们相同),所以没关系。我怀疑这些查询之间没有性能差异(等效)

select distinct username from users where company = ‘bbc’ or company = ‘itv’;

select username from users where company = ‘bbc’ 
union
select username from users where company = ‘itv’;

现在的问题是,以下查询之间是否会有区别,而我实际上并不知道这些区别,但是我怀疑优化程序会使它更像第一个查询

select username from users where company = ‘bbc’ or company = ‘itv’;

select username from users where company = ‘bbc’ 
union all
select username from users where company = ‘itv’;

1
+关于查询不同。但是,UNION ALL仍然产生与相同EXPLAIN的结果UNION
杰森·麦克莱里

UNION ALL通常比快UNION。后者意味着UNION DISTINCT,因此需要在临时表上进行重复数据删除。较新的版本在某些情况下避免使用临时表,从而提供了更多帮助。or您拥有的示例始终会更快,因为它可以使用INDEX(company)
Rick James

2

这取决于优化器根据数据,索引,软件版本等的大小来完成的工作。

我猜想使用OR会给优化器提供更高的效率,因为所有内容都在一个逻辑语句中。

同样,UNION也有一些开销,因为它创建了一个重置(没有重复项)。如果将公司编入索引,则UNION中的每个语句都应该很快执行...不确定它是否真的会做两倍的工作。

底线

除非您真的有迫切需要从查询中挤出每一点速度,否则最好采用能最好地传达您意图的表格。

更新资料

我也想提到IN。我相信以下查询将比OR提供更好的性能(这也是我更喜欢的形式):

select username from users where company in ('bbc', 'itv');


0

在几乎所有情况下,unionorunion all版本都会对users表进行两次全表扫描。

or版本在实践中要好得多,因为它只会扫描表一次。如果可用,它也只会使用一次索引。

对于任何数据库和任何情况,原始语句似乎都是错误的。


为了清楚起见,UNION还将使用索引(如果有)。但是它将扫描两个。只是一个较小的数据集,然后将它们合并在一起。
杰森·麦克莱里

不,不,如果您使用or/in您将使用“索引范围扫描”,并且在union/的情况下,union all您将使用non-unique甚至是primary key lookup加号index merge
Yevgeniy Afanasyev

@YevgeniyAfanasyev。。。解释结果非常清楚,表上没有索引。
Gordon Linoff '19

感谢您的评论。问题并没有回答“表上没有索引”的条件。如果您将其放在答案的开头,它将帮助像我这样的人寻找他们的案例。
Yevgeniy Afanasyev

INOR一样。您可以通过EXPLAIN看到优化器将其转换为另一种来查看。
瑞克·詹姆斯

-1

Bill Karwin的答案是正确的。当OR语句的两个部分都有自己的索引时,最好进行并集,因为一旦结果的子集很小,就可以对它们进行排序并消除重复项。总成本几乎比仅使用一个索引(用于一个列)和对另一列进行表扫描(因为mysql仅对一个列使用一个索引)少。

它通常取决于表的结构和需求,但在大表中,联合会给我带来更好的结果。


你说mysql only uses one index for one column-这不是真的。您可以在许多索引中使用列。
Yevgeniy Afanasyev

在执行查询期间,mysql对单个列仅使用一个索引。这与在一个列上定义多个索引的能力无关。
ÇağatayGürtürk

事实并非如此。MySQL对查询使用一个索引,而不对一列使用。
Yevgeniy Afanasyev

让我们更精确地表达一下:“每个索引一个SELECT”。(这避免了aUNION是一个还是多个“查询”的歧义。)
Rick James
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.