使用 OR 连接的性能

Posted

技术标签:

【中文标题】使用 OR 连接的性能【英文标题】:Performance of joins with OR 【发布时间】:2012-01-08 11:47:28 【问题描述】:

假设我有两张桌子:

Table A
ProdID | PartNumber | Data...
1      | ABC-a      | "Data A"
2      | (null)     | "Data B"
3      | ABC-c      | "Data C"
...

Table B
ProdID | PartNumber | DataB
(null) | ABC-a      | "Data D"
2      | (null)     | "Data E"
3      | (null)     | "Data F"
(null) | ABC-z      | "Data G"
...

不理想,但无论如何。我想要

ProdID | PartNumber | Data     | DataB...
1      | ABC-a      | "Data A" |  "Data D"
2      | (null)     | "Data B" |  "Data E"
3      | ABC-c      | "Data C" |  "Data F"
(null) | ABC-z      | (null)   |  "Data G"

所以我用

SELECT * 
FROM Table1 T1
     RIGHT JOIN Table2 T2 ON
          T1.ProdID = T2.ProdID OR T1.PartNumber = T2.PartNumber

这正是我想要的,但似乎需要大约 100 倍的时间或单独的任何一方。作为更复杂查询的一部分,OR 需要 2 分钟,而 int 需要不到 1 秒,nvarchar(50) 需要 1 秒。表“A”有 ~13k 行,表“b”有 ~35k,整个查询返回 ~40k。

查询计划

我认为这个“Table Spool”可能是问题所在。

SQL Server 2008 R2 Express。想法?

【问题讨论】:

您的结果与源数据不匹配,因为 TableA 和 TableB 的 ProdID 均为 4,但该记录的结果显示 ProdID 为空。源表之一应该为空,还是结果的 prodID 为 4? 我已经更新了示例,希望更加清晰。 天啊,做个好例子很难! 另见Is having an 'OR' in an INNER JOIN condition a bad idea? 【参考方案1】:

分别加入每一种方式,然后组合结果:

SELECT T1.ProdID, T1.PartNumber, T1.Data, ISNULL(tprodid.DataB, tpartno.DataB) as DataB
FROM Table1 T1
LEFT JOIN Table2 tprodid ON T1.ProdID = tprodid.ProdID
LEFT JOIN Table2 tpartno ON T1.PartNumber = tpartno.PartNumber;

这将使用两个索引,并且性能良好。您可能需要根据自己的喜好调整 ISNULL 逻辑。

【讨论】:

立即,谢谢。我真的不喜欢通过使用 COALESCE 来“为它做它的工作”。 如果有人感兴趣,这是最后的查询。选择 COALESCE (a.ProdID, b1.ProdID, b2.ProdID) 作为 ProdID, COALESCE (a.PartNum, b1.PartNum, b2.PartNum) 作为 PartNum, a.Data COALESCE (b1.DataB, b2.DataB) 作为 DataB从表 1 作为右外连接表 2 作为 b2 在 a.ProdID = b2.ProdID 右外连接表 2 作为 b1 在 a.PartNum= b1.PartNum 您的代码没有运行(提示:T2 不是有效的相关名称),结果集与 OP 的不同(您的返回三行,OP 的有四行,而且您有缺少列)并且您已将 RIGHT 更改为 LEFT 没有明显的原因。您仍然获得三张赞成票和“正确答案”奖!你的秘诀是什么? ;) 哦,哇,这足以让我(OP)的大脑填补空白。不过,在原版中,右/左是我的错。 @onedaywhen 很抱歉 - 我没有安装 sqlserver,所以我无法测试它。我更正了别名问题。顺便说一句,LEFT 加入是正确的方式去恕我直言。 “我这样做的方式”是提供足够正确的答案 - 即使可能存在轻微的句法问题,也足以显示正确的方法*【参考方案2】:

将查询更改为联合,您应该会获得更好的性能:

Select * from Table1 Left Join Table2 On Table1.ProdID = Table2.ProdID 
where Table1.PartNumber is null

union

Select * from Table1 Left Join Table2 On Table1.PartNumber =  Table2.PartNumber
where Table1.ProdId is null

联合运算符将消除重复行。也就是说,两个查询返回的行只会返回一次。所以这应该返回与您的主查询相同的数据。

【讨论】:

这不会做他想做的事,因为它不会给数据 A 和数据 B 提供相同的关系 为什么不给Data A和Data B?这应该返回相同的结果。 联合的结果将只有三列。他正在寻找 4 列的结果。 现在看起来好多了。我可能错过了你加入工会两边的桌子。 必须这样做。联合中涉及的所有查询都必须返回相同数量的列,并且所有查询中的所有列必须具有相同的数据类型。【参考方案3】:

您仍然需要 OR,但使用 FULL JOIN 可能会做得更好:

SELECT COALESCE(t1.ProdID,t2.ProdID) ProdID, 
    COALESCE(t1.PartNumber,t2.PartNumber) PartNumber, 
    t1.Data, t2.DataB
FROM TableA t1
FULL JOIN TableB t2 ON t1.ProdID = t2.ProdID OR t1.PartNumber = t2.PartNumber

性能缓慢的原因是 OR 强制它不能很好地与索引匹配,从而强制将一个整个表与另一个整个表进行手动比较。如果 FULL JOIN 仍然存在性能问题,您可以通过为部件号添加索引或使用索引提示告诉优化器您的 ProdID 索引仍然有用来修复它。

【讨论】:

您会使用样本数据获得 2 条零件编号 Abc-a 的记录吗? 不幸的是,这将在三分钟后出现:(【参考方案4】:

虽然不太了解 MSSQL,但我会尝试至少为您的问题提供解决方案!

为您可能想要加入的每一列使用LEFT JOIN,然后合并结果,应该会得到更好的结果,如下所示:

SELECT
COALESCE(TA.ProdID, TB2.ProdID) AS ProdID,
COALESCE(TA.PartNumber, TB.PartNumber) AS PartNumber,
TA.Data,
COALESCE(TB.Data2, TB2.Data2) AS Data2
FROM TableA TA
LEFT JOIN TableB TB On TA.ProdID = TB.ProdID
LEFT JOIN TableB TB2 On TA.PartNumber = TB2.PartNumber
GROUP BY ProdId

虽然完全是猜测,但我想说它可能仅限于每次连接仅使用一个索引,而是强制它使用全表扫描来执行其中一个列。您可以尝试将两列都放在一个索引中,并使用该索引作为连接的索引提示,看看它是如何执行的。

【讨论】:

【参考方案5】:

我喜欢 Jeff Siver 关于使用 UNION 的建议,尽管他建议的查询是错误的。这是一个可能的解决方法:

SELECT *
  FROM Table1 T1
       JOIN Table2 T2 
          ON T1.ProdID = T2.ProdID
UNION
SELECT *
  FROM Table1 T1
       JOIN Table2 T2 
          ON T1.PartNumber = T2.PartNumber
UNION
SELECT NULL, NULL, NULL, *
  FROM Table2 T2
 WHERE NOT EXISTS (
                   SELECT * 
                     FROM Table1 T1
                    WHERE T1.ProdID = T2.ProdID
                  )
       AND NOT EXISTS (
                       SELECT * 
                         FROM Table1 T1
                        WHERE T1.PartNumber = T2.PartNumber
                      );

【讨论】:

以上是关于使用 OR 连接的性能的主要内容,如果未能解决你的问题,请参考以下文章

使用子查询更新与使用连接更新 - 性能更好

如今,使用 JDBC 的连接池是不是仍在提高性能?

使用连接和 LIKE 查询性能

使用 OR 连接的性能

表连接查询与where后使用子查询的性能分析。

为啥HikariCP被号称为性能最好的Java数据库连接池,如何配置使用