增强 MS SQL 服务器在 UNION 操作上的性能

Enhance MS SQL Server performance on UNION operations

我有一个 MS SQL 服务器 table T1,其中包含三个代码列:CodeACodeB1CodeB2。我有一个具有相同列的 table 类型变量。

我必须将 table 变量与 T1 table 连接起来,以便在 table 变量中获取与 CodeB1 [=] 匹配的行52=] CodeB2,但不匹配 CodeA,或匹配 CodeA,但既不匹配 CodeB1,也不匹配 CodeB2

我最初做了一个 SELECT 这样的语句:

SELECT *
(SELECT 
 CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
 CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
 CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
FROM @tableVariable v
INNER JOIN [T1] t
ON t.CodeA = v.CodeA or
   t.CodeB1 = v.CodeB1 or
   t.CodeB2 = v.CodeB2
)
WHERE NOT(EqualCodeA = 1 AND (EqualCodeB1 = 1 OR EqualCodeB2 = 1)

但是那个查询的性能很差。所以我将谓词中的 OR 切换为 UNION,像这样:

SELECT *
(SELECT 
 CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
 CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
 CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
 FROM @tableVariable v
 INNER JOIN [T1] t
 ON t.CodeA = v.CodeA

 UNION
 SELECT 
 CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
 CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
 CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
 FROM @tableVariable v
 INNER JOIN [T1] t
 ON t.CodeB1 = v.CodeB1

 UNION
 SELECT 
 CASE WHEN t.CodeA = v.CodeA then 1 else 0 end as [EqualCodeA],
 CASE WHEN t.CodeB1 = v.CodeB1 then 1 else 0 end as [EqualCodeB1],
 CASE WHEN t.CodeB2 = v.CodeB2 then 1 else 0 end as [EqualCodeB2]
 FROM @tableVariable v
 INNER JOIN [T1] t
 ON t.CodeB2 = v.CodeB2)
)
WHERE NOT(EqualCodeA = 1 AND (EqualCodeB1 = 1 OR EqualCodeB2 = 1)

性能现在大约提高了十倍,但仍然无法接受table。例如。对于 table 变量中的 10K 行和 T1 中的 50K 行,查询需要两分钟。

查看实际执行计划,我看到两个Hash Match (Union)操作,每个占46%的成本。

我怎样才能提高这个性能?

注1:有一个包括所有三列的非聚集索引,以及三个单独的非聚集索引,每个列一个。

注2:我使用OPTION(RECOMPILE);是为了让优化器至少知道table变量的实际行数。

对于这种情况,您需要在 T1 上使用三个独立的非聚集索引。另外,如果这三个部分相互排斥,则更改为 "union all" 而不是 "union"。

老实说,我不太明白示例代码如何与您的要求描述相匹配,但假设后者是正确的,这就是我想出的:

-- in order to get the lines in table variable that match for CodeB1 and/or CodeB2, but not CodeA, 
-- or match CodeA, but neither CodeB1 nor CodeB2.
SELECT v.*
  FROM @tableVariable v
  JOIN T1 t
    ON (v.CodeA <> t.CodeA AND (v.CodeB1 = t.CodeB1 OR v.CodeB2 = t.CodeB2))
    OR (v.codeA = t.CodeA  AND v.CodeB1 <> t.CodeB1 AND v.codeB2 <> t.codeB2)


GO

-- convert OR into UNION
SELECT v.*
  FROM @tableVariable v
  JOIN T1 t
    ON (v.CodeA <> t.CodeA AND (v.CodeB1 = t.CodeB1 OR v.CodeB2 = t.CodeB2))

UNION

SELECT *
  FROM @tableVariable v
  JOIN T1 t
    ON (v.codeA = t.CodeA  AND v.CodeB1 <> t.CodeB1 AND v.codeB2 <> t.codeB2)


GO

-- further convert OR into UNION
SELECT v.*
  FROM @tableVariable v
  JOIN T1 t
    ON v.CodeA <> t.CodeA 
   AND v.CodeB2 = t.CodeB2

UNION

SELECT v.*
  FROM @tableVariable v
  JOIN T1 t
    ON v.CodeA <> t.CodeA 
   AND v.CodeB1 = t.CodeB1

UNION

SELECT v.*
  FROM @tableVariable v
  JOIN T1 t
    ON v.codeA = t.CodeA  
   AND v.CodeB1 <> t.CodeB1 
   AND v.codeB2 <> t.codeB2

-- potentially helpfull indexes
CREATE INDEX idx1 ON T1 (CodeA) INCLUDE (CodeB1, CodeB2)
CREATE INDEX idx2 ON T1 (CodeB1) INCLUDE (CodeA)
CREATE INDEX idx3 ON T1 (CodeB2) INCLUDE (CodeA)

这很可能与您的解决方案具有完全相同的成本,优化器可能能够(在内部)将它们转换为完全相同的 action。看到查询计划 and/or 更好地了解手头的数据会很有趣。

PS:正如其他地方已经提到的,尽量避免@tableVariables,#tempTables 在处理多条记录时要好得多。 (您可以根据需要放置索引、统计信息等...)