在左连接表上使用 WHERE OR 时查询很慢?

Posted

技术标签:

【中文标题】在左连接表上使用 WHERE OR 时查询很慢?【英文标题】:Query is slow when use WHERE OR on tables left join? 【发布时间】:2020-10-24 16:50:16 【问题描述】:

我有表 user 与表 A 和表 B 的关系 OneToMany。我需要在表 A 和表 B 上找到其合作伙伴 = 275 的用户

我的查询是:

SELECT     u.id 
 FROM      user u 
 LEFT JOIN A a 
 ON        a.user_id = u.id
 LEFT JOIN B b 
 ON        b.user_id = u.id
 WHERE     a.partner_id = 275 
 OR        b.partner_id = 275
 GROUP BY  u.id 
 ORDER BY  u.id DESC 
 LIMIT     20

虽然我在表上创建索引,但查询速度很慢。 这里解释一下:

id : 1
select_type: SIMPLE
table: u
type: index
possible_keys: NULL
key: PRIMARY
key_len: 4
ref: NULL
rows: 20
Extra: Using index

我试图删除“或”侧,例如:

SELECT u.id FROM user u 
LEFT JOIN A a ON a.user_id = u.id
LEFT JOIN B b ON b.user_id = u.id
WHERE a.partner_id = 275
GROUP BY u.id ORDER BY u.id DESC LIMIT 20

SELECT u.id FROM user u 
LEFT JOIN A a ON a.user_id = u.id
LEFT JOIN B b ON b.user_id = u.id
WHERE b.partner_id = 275
GROUP BY u.id ORDER BY u.id DESC LIMIT 20

两个查询都很快。不知道为什么?

【问题讨论】:

请提供样本数据和期望的结果。 术语问题——你说“both”,但查询却说“OR”。是哪一个;它在查询制定和优化方面有很大的不同。 【参考方案1】:

如果您想要 ab 的合作伙伴为 275 的用户 ID,您可以使用:

SELECT a.user_id
FROM A a
WHERE a.partner_id = 275
UNION 
SELECT b.user_id
FROM B b
WHERE b.partner_id = 275;
ORDER BY user_id DESC
LIMIT 20;

那么对于这个查询,您需要A(partner_id, user_id)B(partner_id, user_id) 上的索引。

【讨论】:

【参考方案2】:

大概,OR 条件正在扼杀性能;这是 SQL 中经常遇到的问题。

您可以尝试使用两个exists 条件来表达这一点;这避免了外部聚合,并使意图更清晰:

select u.id
from user u
where exists (select 1 from a where a.user_id = u.id and a.partner_id = 275)
   or exists (select 1 from b where b.user_id = u.id and b.partner_id = 275)
order by u.id desc limit 20

这会带来在ab 中与给定partner_id 匹配的用户。

为了提高性能,您需要以下索引:

a(partner_id, user_id)
b(partner_id, user_id)

您可以尝试更改每个索引中列的顺序,看看是否有区别。

【讨论】:

【参考方案3】:

如果您在两个表上有索引(例如,在 partner_id 上),使用它们的简单方法是将 OR 更改为 UNIONUNION ALL。例如

SELECT     u.id 
  FROM     user u 
  WHERE    u.id IN
           (SELECT a.user_id
             FROM A a
             WHERE a.partner_id = 275
           UNION
           SELECT b.user_id
             FROM B b
             WHERE b.partner_id = 275
           )
 ORDER BY  u.id DESC 
 LIMIT     20;

为了便于阅读,我在这里使用了IN,但您可以使用LEFT JOIN,如果您愿意,甚至可以使用EXISTS

上述的优点是每个 SELECT 都可以专门命中索引 - 并且不会将查询计划生成与“或”混淆。

请注意,从技术上讲,您在这里也不需要 user 表(例如,您可以只使用 UNIONed 语句并从那里对 user_ids 进行排序 - 但我猜您可能还需要用户表中的其他信息.

【讨论】:

【参考方案4】:

如果WHERE 条件从 JOIN 的 LEFT 侧引用列,则连接将转换为 INNER JOIN。所以不需要LEFT JOIN。因为您需要找到具有“partners = 275 on both table...”的行,所以不需要OR 条件。像这样。

select distinct u.id 
from [user] u 
     join a a on a.user_id = u.id
     join b b on b.user_id = u.id
where a.partner_id = 275 
      and b.partner_id = 275
order by u.id desc 
limit 20;

据推测,user.id 上有一个索引。 a 和 b 表将受益于覆盖 (user_id, partner_id) 的索引

【讨论】:

我相信 OP 希望在 至少一个 表 a 或 b 中找到存在的 user_ids(与合作伙伴 275)。 user_ids 不需要同时存在于两者中,因此这些内部连接过于严格。 好吧,把另一只虾扔到芭比娃娃上,叫我鳄鱼邓迪!你看看那个。

以上是关于在左连接表上使用 WHERE OR 时查询很慢?的主要内容,如果未能解决你的问题,请参考以下文章

实体框架在左连接时强制内连接使用 DefaultIfEmpty() 语法

在左连接中使用 Oracle rank()

如何将在左连接中具有连接的 sql 转换为查询构建器?

SQL Server:在左连接查询的执行计划中插入隐藏的“排序”

SQL 查询条件放在LEFT OUTER JOIN 的ON语句后与放在WHERE中的区别

日期字段上的左连接 + Where 子句