使用 cte ROW_NUMBER() 提高性能

Posted

技术标签:

【中文标题】使用 cte ROW_NUMBER() 提高性能【英文标题】:Improve the performance with cte ROW_NUMBER() 【发布时间】:2021-04-14 02:56:32 【问题描述】:

我在以下脚本中遇到了性能问题。

一开始,脚本运行大约几秒钟。

现在,它需要运行大约 3 分钟。

我认为最主要的原因是 TransactionSendQueue 表,此时它有超过 300 万行。在“ctetran”中,我需要找出最新的记录并与临时表进行比较。

我尝试添加不同的索引,但它无法改进它甚至更慢。有关如何提高性能的任何建议。

WITH ctetran AS --the lastest transaction
(
    SELECT 
        Tran_ID, 
        Field2, 
        Field3, 
        Field4, 
        Field5, 
        Field6, 
        Field7, 
        Field8, 
        Field9, 
        ROW_NUMBER() OVER (PARTITION BY Tran_ID
                           ORDER BY LastUpdate DESC) AS rn
    FROM 
        TransactionSendQueue
    WHERE 
        STATUS = '1'
)  --where 1 mean complete
UPDATE temp
SET STATUS = CASE
                WHEN temp.f2 = cte.Field2
                     AND temp.f3 = cte.Field3
                     AND temp.f4 = cte.Field4
                     AND temp.f5 = cte.Field5
                     AND temp.f6 = cte.Field6
                     AND temp.f7 = cte.Field7
                     AND temp.f8 = cte.Field8
                     AND temp.f9 = cte.Field9
                   THEN '2' -- where 2 mean skip
                ELSE '3' --where 3 mean ready to execute
             END
FROM #TempTran temp
INNER JOIN ctetran cte ON temp.Tran_ID = cte.Tran_ID
                       AND cte.rn = 1;

餐桌设计:

CREATE TABLE [dbo].[TransactionSendQueue]
(
    [Batch_ID]   [CHAR](20) NOT NULL, 
    [Tran_ID]    [VARCHAR](20) NOT NULL,  
    [Field2]     [VARBINARY](100) NULL, 
    [Field3]     [VARBINARY](100) NULL, 
    [Field4]     [VARBINARY](100) NULL, 
    [Field5]     [VARBINARY](100) NULL, 
    [Field6]     [VARBINARY](100) NULL, 
    [Field7]     [VARBINARY](100) NULL, 
    [Field8]     [VARBINARY](100) NULL, 
    [Field9]     [VARBINARY](100) NULL, 
    [LastUpdate] [DATETIME] NOT NULL, 
    [STATUS]     [INTEGER] NOT NULL,

    CONSTRAINT [PK_TransactionSendQueue] 
        PRIMARY KEY CLUSTERED([Batch_ID], [Tran_ID])
);

【问题讨论】:

对于与性能相关的问题,我们需要使用“Paste The Plan”查看执行计划 尝试使用 CTE 的简单方法是将其具体化为临时表。因此,将您的 cte 查询结果放入临时表中,然后执行您通常对 cte 结果执行的任何操作。这经常解决 cte 性能问题。但是基本的cte查询还是很慢,那么我们需要执行计划。 @DaleK 谢谢。很难分享执行计划。我正在尝试将其转换为临时表并再次测试。谢谢 正如我所说,使用“粘贴计划” - 正是为了这个目的。 请添加索引的定义,以及临时表的定义,同时分享查询计划brentozar.com/pastetheplan 【参考方案1】:

使用多个步骤进行 SQL 查询时的一个核心原则是:尽早消除尽可能多的数据。当您只需要每个 Tran_ID 的最新事务时,您的 CTE 会从 TransactionSendQueue 加载所有行。处理的数据越多,加载的数据写入磁盘的风险就越高,这对性能极为不利。写入磁盘的数据越多,影响就越严重。您可以查看您的执行计划以检查是否是这种情况,但我会说它可能会考虑执行时间。

CTE 应该只返回每行可能在您的#TempTran 表中更新的一行。您可以先使用额外的 CTE 检索最新更新,然后在您的 ctetran 中使用该信息来减少在更新语句中搜索的数据量(行)。

WITH LatestTran AS --the lastest transaction
(
    SELECT 
        Tran_ID, 
        MAX(LastUpdate) AS LastUpdate
    FROM 
        TransactionSendQueue
    WHERE 
        STATUS = '1' --where 1 mean complete
    GROUP BY
        Tran_ID
), ctetran AS
(
    SELECT 
        Tran_ID, 
        Field2, 
        Field3, 
        Field4, 
        Field5, 
        Field6, 
        Field7, 
        Field8, 
        Field9
    FROM 
        TransactionSendQueue TSQ
    INNER JOIN LatestTran LT ON 
        TSQ.Tran_ID = LT.Tran_ID AND 
        TSQ.LastUpdate = LT.LastUpdate
)


UPDATE temp
SET STATUS = CASE
                WHEN temp.f2 = cte.Field2
                     AND temp.f3 = cte.Field3
                     AND temp.f4 = cte.Field4
                     AND temp.f5 = cte.Field5
                     AND temp.f6 = cte.Field6
                     AND temp.f7 = cte.Field7
                     AND temp.f8 = cte.Field8
                     AND temp.f9 = cte.Field9
                   THEN '2' -- where 2 mean skip
                ELSE '3' --where 3 mean ready to execute
             END
FROM #TempTran temp
INNER JOIN ctetran cte ON temp.Tran_ID = cte.Tran_ID

这将带来多大的性能提升取决于每个 Tran_ID 有多少 Batch_ID,性能提升越大。

如果查询仍然运行缓慢,您还可以考虑使用 TransactionSendQueue 表中 LastUpdate 列的索引,因为查询现在在连接语句中使用该索引。

请告诉我查询时间减少了多少,知道会很有趣。

【讨论】:

以上是关于使用 cte ROW_NUMBER() 提高性能的主要内容,如果未能解决你的问题,请参考以下文章

row_number 和 cte 使用实例:考场监考安排

CTE、ROW_NUMBER 和 ROWCOUNT

分区上的递归 CTE 或 ROW_NUMBER?

SQL中使用WITH AS提高性能-使用公用表表达式(CTE)简化嵌套SQL

递归 CTE - row_number() 聚合

INDEX 用于提高包含 ROW_NUMBER OVER PARTITION 的视图的性能