SQL Server UNION ALL 合并连接(连接)太慢

Posted

技术标签:

【中文标题】SQL Server UNION ALL 合并连接(连接)太慢【英文标题】:SQL Server UNION ALL Merge Join (Concatenation) too slow 【发布时间】:2017-08-17 15:56:58 【问题描述】:

我有一个选择查询,它在两个具有相同结构的表(列和主键,它们具有不同的非聚集索引)上使用 UNION ALL 关键字。这两个表包含 3900 万行,其中一个为 100 万行,另一个为 3800 万行。仅在具有一百万行的 table1 上运行查询时,大约需要 0.2 秒,在 table2 上,我们有不同的情况,最多需要 0.5 到 1.2 秒,具体取决于 DB 的压力。

实际上,为了显示,我需要联合这两个表,但问题是联合查询需要 8 秒才能运行。查看执行计划时,最繁重的操作是 Merge Join (Concatenation),成本为 91%,我有点担心,因为我正在运行的 WHERE 子句从 table1 中选择 51 个条目,从table2(更大的表格)。

我无法理解它,过去两天我一直在努力寻找解决问题的方法,但我发现要么使用 UNION 而不是 UNION ALL,要么使用不必要的子句就像GROUP BYLEFT/INNER JOINS 一样。 Query 也执行分页,使用此命令 ORDER BY [Id] DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY;,所有测试(在单个表上和使用 UNION ALL)都是使用分页(OFFSETFETCH NEXT)关键字执行的。

如果需要,我可以提供表格详细信息和查询详细信息。这是一个简单的选择查询,包含 2 个INNER JOINS 和 2 个LEFT JOINS,所有连接的表都包含非常少量的数据(从 50 个条目到 20K 个条目)。


这是查询

SELECT   *
FROM     (SELECT    tr.Id, 
        tr.Amount, 
        tr.TypeId, 
        t.Name AS [Type], 
        tr.Date, 
        tr.ExternalKey, 
        tr.ExternalDescription, 
        tr.GameId, 
        tr.GameProviderId, 
        gp.Name AS GameProvider, 
        u.Username, 
        u.Pincode, 
        gp.Name, 
        g.GameName, 
        u.OperatorId, 
        tr.BalanceBefore,
        tr.BalanceAfter,
        tr.UserId
FROM ( 
        SELECT    *
              FROM      dbo.ActiveTransactions at
              WHERE    ( 1 = 1 )
         AND ( [Date] >= '2017-07-17 20:00:00' )
         AND ( [TypeId] != 10 )
         AND ( [UserId] = 29041 )
              UNION ALL
              SELECT    *
              FROM      dbo.TransactionHistory th --WITH(INDEX(IX_TransactionHistory_DateType_UserId))
              WHERE    ( 1 = 1 )
         AND ( [Date] >= '2017-07-17 20:00:00' )
         AND ( [TypeId] != 10 )
         AND ( [UserId] = 29041 )
            ) AS tr 

INNER JOIN dbo.Users u ON tr.UserId = u.Id
LEFT JOIN dbo.GameProviders gp ON tr.GameProviderId = gp.Id
LEFT JOIN dbo.Games g ON tr.GameId = g.GameId AND tr.GameProviderId = g.ProviderId 
INNER JOIN dbo.Types t ON tr.TypeId = t.Id     ) AS t
ORDER BY [Id] DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY;

【问题讨论】:

什么是段落?我将包括查询。 考虑在联合之前分别重复连接和过滤 activetransactions 和 transactionhistory,然后联合结果。这应该需要 ..2 + .5 到 1.2 并在 2 秒内完成。它会保存联合操作,直到数据被过滤。 【参考方案1】:

这是一个有根据的猜测:不了解索引或执行计划,引擎实际正在追赶什么。

考虑:在连接/过滤器之后而不是之前进行联合:您必须重复连接,但您可能会在联合集处理中获得一些索引效率损失。

在这种情况下,我创建了两个 CTE,然后将它们联合起来。

因为我无法真正测试这个,我可能有一些语法错误。

WITH cte1 AS 
(SELECT    tr.Id, 
        tr.Amount, 
        tr.TypeId, 
        t.Name AS [Type], 
        tr.Date, 
        tr.ExternalKey, 
        tr.ExternalDescription, 
        tr.GameId, 
        tr.GameProviderId, 
        gp.Name AS GameProvider, 
        u.Username, 
        u.Pincode, 
        gp.Name, 
        g.GameName, 
        u.OperatorId, 
        tr.BalanceBefore,
        tr.BalanceAfter,
        tr.UserId
FROM ( 
        SELECT    *
              FROM      dbo.ActiveTransactions at
              WHERE    ( 1 = 1 )
         AND ( [Date] >= '2017-07-17 20:00:00' )
         AND ( [TypeId] != 10 )
         AND ( [UserId] = 29041 )) AS tr 
INNER JOIN dbo.Users u ON tr.UserId = u.Id
LEFT JOIN dbo.GameProviders gp ON tr.GameProviderId = gp.Id
LEFT JOIN dbo.Games g ON tr.GameId = g.GameId AND tr.GameProviderId = g.ProviderId 
INNER JOIN dbo.Types t ON tr.TypeId = t.Id     ) AS t),
CTE2 as (
SELECT  tr.Id, 
        tr.Amount, 
        tr.TypeId, 
        t.Name AS [Type], 
        tr.Date, 
        tr.ExternalKey, 
        tr.ExternalDescription, 
        tr.GameId, 
        tr.GameProviderId, 
        gp.Name AS GameProvider, 
        u.Username, 
        u.Pincode, 
        gp.Name, 
        g.GameName, 
        u.OperatorId, 
        tr.BalanceBefore,
        tr.BalanceAfter,
        tr.UserId

FROM (SELECT    *
      FROM dbo.TransactionHistory th --WITH(INDEX(IX_TransactionHistory_DateType_UserId))
      WHERE    ( 1 = 1 )
         AND ( [Date] >= '2017-07-17 20:00:00' )
         AND ( [TypeId] != 10 )
         AND ( [UserId] = 29041 ) as tr
INNER JOIN dbo.Users u ON tr.UserId = u.Id
LEFT JOIN dbo.GameProviders gp ON tr.GameProviderId = gp.Id
LEFT JOIN dbo.Games g ON tr.GameId = g.GameId AND tr.GameProviderId = g.ProviderId 
INNER JOIN dbo.Types t ON tr.TypeId = t.Id) AS t
)
SELECT * from CTE1
UNION ALL 
SELECT * from CTE2
ORDER BY [Id] DESC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY;

【讨论】:

以上是关于SQL Server UNION ALL 合并连接(连接)太慢的主要内容,如果未能解决你的问题,请参考以下文章

sql中用union all最多可以合并几个表

sql 查询结果合并union all用法_数据库技巧

sql server的exists,union,union all

sql server 中union的用法

SQL 占位符 和 union all

SQL 占位符 和 union all