繁体   English   中英

UNION ALL SQL Server 2005中的性能

[英]UNION ALL Performance IN SQL Server 2005

我有一个以长链CTE结尾的查询

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets
UNION ALL
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryRegions

此查询的执行时间为1450毫秒。 当我分别执行这两个SELECT时,它花费的时间要少得多。 对于查询

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets

执行时间是106毫秒。 并为查询

SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryRegions

这是20毫秒。

为什么UNION ALL会将执行时间增加10倍以上? 我该怎么做才能减少它?

谢谢您的帮助。

更新整个查询(我缩短了,但问题仍然存在)是

WITH tFoundRegions AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 1) > 0
),
tFoundAreas AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 2) > 0
),
tFoundCities AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 4) > 0
),
tFoundSubCities AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 8) > 0
),
tFoundStreets AS
(
    SELECT KladrItemName FROM dbo.tBuiltKladrItemsWithQuants
    WHERE UserID = @UserID AND (indeces & 16) > 0
),
tDictionaryStreets AS
(
    SELECT DISTINCT
        CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE NULL END RegionName
      , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE NULL END AreaName
      , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE NULL END CityName
      , CASE WHEN SubCityName  IN (SELECT KladrItemName FROM tFoundSubCities) THEN SubCityName ELSE NULL END SubCityName
      , StreetName 
    FROM StreetNames
    WHERE StreetName IN (SELECT KladrItemName FROM tFoundStreets)
),
tMissingSubCities AS
(
    SELECT KladrItemName FROM tFoundSubCities
    WHERE KladrItemName NOT IN (SELECT SubCityName FROM tDictionaryStreets)
),
tDictionarySubCities AS
(
    SELECT DISTINCT 
        CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE NULL END RegionName
      , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE NULL END AreaName
      , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE NULL END CityName
      , SubCityName
      , NULL StreetName 
    FROM SubCityNames
    WHERE SubCityName IN (SELECT KladrItemName FROM tMissingSubCities)
)
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionaryStreets
UNION ALL
SELECT RegionName, AreaName, CityName, SubCityName, StreetName 
FROM tDictionarySubCities

确保清除每次测试运行之间的执行+数据缓存。

例如

DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS

如果先使用UNION ALL运行,然后再单独运行2个选择,则数据已经缓存在内存中,使性能更好(因此给人以错误的印象,即后续方法可能更快)。

如果您使用了UNION,那么它可能会变慢,因为它必须应用DISTINCT,但UNION ALL不必这样做,所以它应该没有什么不同。

更新:
看看执行计划并比较它们 - 看看是否有任何区别。 在运行查询之前,可以通过单击SSMS中的“包括实际执行计划”按钮来查看执行计划

更新2:
基于完整的CTE,我想我会考虑优化它们 - 我认为UNION ALL实际上并不是问题。

恕我直言,最好的尝试是逐个完成CTE,并尝试单独优化每个,以便当您在主查询中将它们全部组合时,它们表现更好。

例如对于tDictionaryStreets,尝试这个怎么样:

SELECT DISTINCT
    r.KladrItemName AS RegionName,
        a.KladrItemName AS AreaName,
        c.KladrItemName AS CityName,
        sc.KladrItemName AS SubCityName,
        s.StreetName      
FROM StreetNames s
    JOIN tFoundStreets fs ON s.StreetName = fs.KladrItemName
    LEFT JOIN tFoundRegions r ON s.RegionName = r.KladrItemName
    LEFT JOIN tFoundAreas a ON s.AreaName = a.KladrItemName
    LEFT JOIN tFoundCities c ON s.CityName = c.KladrItemName
    LEFT JOIN tFoundSubCities sc ON s.SubCityName = scc.KladrItemName

每个表上的KladrItemName至少应该有一个索引。 尝试以与连接相同的方式重新处理tDictionarySubCities。

你能比较执行计划吗? 有什么不同吗? “Union all”应该可以正常工作,因为没有重复删除(这需要排序,这对于大型数据集来说是昂贵的)。

可能是网络(不太可能)或内存。 取决于每个结果集带回的行数。 检查是网络还是服务器的一种方法是在SSMS中包含客户端统计信息(查询 - 包括客户端统计信息 - SHIFT-ALT-S)。 在底部,您可以区分大部分时间花在哪里。

你能比较执行计划吗? [...] lmsasu [...]当查询快速运行时,它使用'merge join',当慢 - '嵌套循环'。[...]

暂不公开,但你在执行计划中看到的是“加入”两个结果集(合并连接)和RBAR(发音为reebar - Row By Agonizing Row [Jeff Moden])操作之间的区别,通常称为循环。

合并连接:SQL使用公共链接查找两个结果集,并执行基于集合的操作以将两个集合在一起。 嵌套循环:SQL无法找到公共链接,并将第1组中的一行连接到第2组中的所有行,并丢弃那些不匹配的行。

直觉上,SQL偶然发现了NULL结果,这些结果都是未知结果。 尝试分配一个像“XYZ”(或任何已知的未出现的)的值,您可以在上一个查询中进行简单过滤。 这可能会避免某些结果集中的嵌套循环,因为值已确定且未知。 相近:

[...]
tDictionarySubCities AS 
( 
    SELECT DISTINCT  
        CASE WHEN RegionName IN (SELECT KladrItemName FROM tFoundRegions) THEN RegionName ELSE 'XYZXYZ' END RegionName 
      , CASE WHEN AreaName IN (SELECT KladrItemName FROM tFoundAreas) THEN AreaName ELSE 'XYZXYZ' END AreaName 
      , CASE WHEN CityName IN (SELECT KladrItemName FROM tFoundCities) THEN CityName ELSE 'XYZXYZ' END CityName 
      , SubCityName 
      , NULL StreetName  
    FROM SubCityNames 
    WHERE SubCityName IN (SELECT KladrItemName FROM tMissingSubCities) 
) 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName  
FROM tDictionaryStreets 
WHERE RegionName <> 'XYZ'
UNION ALL 
SELECT RegionName, AreaName, CityName, SubCityName, StreetName  
FROM tDictionarySubCities 
WHERE RegionName <> 'XYZ'

我偶然发现了类似的问题,经过仔细分析后的情况,在我看来,在UNION ALL查询中使用cte会关闭并行化(这很可能是一个错误)。

换句话说,UNION ALL将等于两个查询的总和,其中每个查询都设置为(maxdop 1)。

虽然必须进行更多测试,但实际上很难做出使用并行化的查询,以便能够测试或甚至作为错误提交给Microsoft Connect,仍然是您的问题以及为什么CTE中描述的问题( recurisve)不是平行的(MAXDOP = 8)? 也证明实际上存在这样的问题。

编辑:我已经进行了更广泛的测试,虽然UNION ALL多次并行化,但仍有一些情况,如果没有UNION ALL,它会并行化,但有一个UNION ALL将其关闭。

虽然这可能是一个错误,但也可能是因为查询优化器没有寻找最佳计划,而是寻找一个好的计划,并且因为与UNION连接的两个查询已经生成了复杂的计划,以及使用CTE的查询,它甚至可以在考虑并行化选项之前找到一个好的计划。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM