【问题标题】:SQL ROW_NUMBER() over performance problemSQL ROW_NUMBER() 超过性能问题
【发布时间】:2010-08-15 00:16:40
【问题描述】:

我有这个运行良好的 SQL。

希望我的过滤器返回具有最高 UserSessionSequenceID 的最新唯一 SessionGuid。

问题是性能很差——即使我有很好的索引。 我该如何重写 - 省略 ROW_NUMBER 行?

SELECT TOP(@resultCount) * FROM 
(
    SELECT
        [UserSessionSequenceID]
        ,[SessionGuid]
        ,[IP]
        ,[Url]
        ,[UrlTitle]
        ,[SiteID]
        ,[BrowserWidth]
        ,[BrowserHeight]
        ,[Browser]
        ,[BrowserVersion]
        ,[Referer]
        ,[Timestamp]
        ,ROW_NUMBER() over (PARTITION BY [SessionGuid] 
                                    ORDER BY UserSessionSequenceID DESC) AS sort 
   FROM [tblSequence]
) AS t     
WHERE ([Timestamp] > DATEADD(mi, -@minutes, GETDATE())) 
  AND (SiteID = @siteID) 
  AND sort = 1
ORDER BY [UserSessionSequenceID] DESC

非常感谢:-)

【问题讨论】:

  • 提及您的表中有多少行以及您有多少个分区(SessionGuids)会很有用。
  • 您能否提供针对该表的索引的详细信息(索引是否良好!)?
  • 超过一百万行。据我所知,只有一个分区。索引:除了我拥有的主键(siteID、timestamp、sessionGuid)
  • @seo:你有多少个独特的 sessionguid?
  • 我每 1,000,000 个有大约 400,000 个 uniqueSessionGuids

标签: sql sql-server tsql query-optimization


【解决方案1】:

即使我有很好的索引

没有冒犯,但让我们来判断。在询问 SQL Server 性能问题时,请始终发布表的确切架构,包括所有索引和基数。

例如,让我们考虑下面的表结构:

create table tblSequence (
 [UserSessionSequenceID] int not null
        ,[SessionGuid] uniqueidentifier not null
        ,[SiteID] int not null
        ,[Timestamp] datetime not null
        , filler varchar(512));
go

create clustered index cdxSequence on tblSequence (SiteID, [Timestamp]);
go

这与您的相同,但与性能问题无关的所有字段都汇总到通用填充符中。让我们看看,在大约 50k 个会话中,例如 1M 行的性能有多差?让我们用随机数据填充表格,但我们将模拟“用户活动”的数量:

set nocount on;
declare @i int = 0, @sc int = 1;
declare @SessionGuid uniqueidentifier = newid()
    , @siteID int = 1
    , @Timestamp datetime = dateadd(day, rand()*1000, '20070101')
    , @UserSessionSequenceID int = 0;
begin tran;
while @i<1000000
begin
    insert into tblSequence (
        [UserSessionSequenceID]
        ,[SessionGuid]
        ,[SiteID]
        ,[Timestamp]
        , filler)
    values (
        @UserSessionSequenceID
        , @SessionGuid
        , @siteID
        , @timestamp
        , replicate('X', rand()*512));

    if rand()*100 < 5
    begin
        set @SessionGuid = newid();
        set @siteID = rand() * 10;
        set @Timestamp = dateadd(day, rand()*1000, '20070101');
        set @UserSessionSequenceID = 0;
        set @sc += 1;
    end
    else
    begin
        set @timestamp = dateadd(second, rand()*300, @timestamp);
        set @UserSessionSequenceID += 1;
    end

    set @i += 1;
    if (@i % 1000) = 0
    begin
        raiserror(N'Inserted %i rows, %i sessions', 0, 1, @i, @sc);
        commit;
        begin tran;
    end
end
commit;

填满大约需要 1 分钟。现在让我们查询您提出的相同查询:在最后 Y 分钟内,站点 X 上的任何用户会话的最后一个操作是什么?我将不得不为 @now 使用特定日期而不是 GETDATE() 因为 emy dtaa 是模拟的,而不是真实的,所以我使用的是 SiteId 1 随机填充的任何最大时间戳:

set statistics time on;
set statistics io on;

declare @resultCount int = 30;
declare @minutes int = 60*24;
declare @siteID int = 1;
declare @now datetime = '2009-09-26 02:08:27.000';

SELECT TOP(@resultCount) * FROM  
( 
    SELECT 
        [UserSessionSequenceID] 
        ,[SessionGuid] 
        , SiteID
        , Filler
        ,[Timestamp] 
        ,ROW_NUMBER() over (PARTITION BY [SessionGuid]  
                                    ORDER BY UserSessionSequenceID DESC) AS sort  
   FROM [tblSequence] 
   where SiteID = @siteID
   and [Timestamp] > DATEADD(mi, -@minutes, @now)
) AS t      
WHERE sort = 1 
ORDER BY [UserSessionSequenceID] DESC ;

这与您的查询相同,但限制性过滤器被移动 ROW_NUMBER() 部分子查询中。结果返回:

Table 'tblSequence'. Scan count 1, logical reads 12, physical reads 0.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 31 ms.

暖缓存的响应时间为 31 毫秒,从表的近 60k 页中读取了 12 页。

更新

再次阅读原始查询后,我意识到我修改后的查询有所不同。您只需要 new 个会话。我仍然相信通过 SiteID 和 Timestmap 过滤是获得必要性能的唯一方法,因此解决方案是使用 NOT EXISTS 条件验证候选结果:

SELECT TOP(@resultCount) * FROM  
( 
    SELECT 
        [UserSessionSequenceID] 
        ,[SessionGuid] 
        , SiteID
        , Filler
        ,[Timestamp] 
        ,ROW_NUMBER() over (
            PARTITION BY [SessionGuid]  
            ORDER BY UserSessionSequenceID DESC) 
         AS sort  
   FROM [tblSequence] 
   where SiteID = @siteID
   and [Timestamp] > DATEADD(mi, -@minutes, @now)
) AS new
WHERE sort = 1 
and not exists (
    select SessionGuid 
    from tblSequence
    where SiteID = @siteID
    and SessionGuid = new.SessionGuid
    and [TimeStamp] < DATEADD(mi, -@minutes, @now)
)
ORDER BY [UserSessionSequenceID] DESC 

这会在我的笔记本电脑上返回,对于 40 毫秒内超过 40 万个会话的 100 万行来自热缓存:

Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0
Table 'tblSequence'. Scan count 2, logical reads 709, physical reads 0

 SQL Server Execution Times:
   CPU time = 16 ms,  elapsed time = 40 ms.

【讨论】:

  • 将 where 移入子查询几乎肯定会提高性能,但您确定它会给出相同的结果吗?选择不同的行将更改 ROW_NUMBER。
  • @OMG:我的第一个查询确实是错误的。第二个通过“验证”子查询返回的每一行确实是正确的(是“新”会话)来完成我理解的 OP 需要。对于预期的基数/使用(即合理的@minutes 和子查询中的积极过滤),“验证”的额外工作应该可以忽略不计,因为它应该由“候选”结果的一小部分驱动。随着子查询返回更多行,“验证”成本将增加,例如。如果@minutes 进入数周/数月。
【解决方案2】:

试试这些 - 应该是等效的查询,但您必须比较查询计划:

使用连接

  SELECT DISTINCT  TOP(@resultCount)
         s.usersessionsequenceid,
         s.sessionguid,
         s.ip,
         s.url,
         s.urltitle,
         s.siteid,
         s.browserwidth,
         s.browserheight,
         s.browser,
         s.browserversion,
         s.referer,
         s.timestamp
    FROM tblsequence s
    JOIN (SELECT t.sessionquid,
                 MAX(t.timestamp) AS max_ts
            FROM tblsequence t
        GROUP BY t.sessionguid) x ON x.sessionguid = s.sessionguid
                                 AND x.max_ts = s.timestamp
   WHERE s.siteid = @SiteID
     AND s.timestamp > DATEADD(mi, -@minutes, GETDATE())
ORDER BY s.usersessionsequenceid DESC

使用 EXISTS

  SELECT TOP(@resultCount)
         s.usersessionsequenceid,
         s.sessionguid,
         s.ip,
         s.url,
         s.urltitle,
         s.siteid,
         s.browserwidth,
         s.browserheight,
         s.browser,
         s.browserversion,
         s.referer,
         s.timestamp
    FROM tblsequence s
   WHERE s.siteid = @SiteID
     AND s.timestamp > DATEADD(mi, -@minutes, GETDATE())
     AND EXISTS(SELECT NULL
                  FROM tblsequence t
                 WHERE t.sessionguid = s.sessionguid
              GROUP BY t.sessionguid
                HAVING MAX(t.timestamp) = s.timestamp
ORDER BY s.usersessionsequenceid DESC

但是,如果您想获得值为 2 或更大的位置,则必须坚持使用您的 ROW_NUMBER 查询。

【讨论】:

  • 在 WHERE NOT EXISTS(...) 方法中可能会有一些里程 - '直到我们获得更多信息,我才会开火。 :)
  • @Will A:添加了使用EXISTS的版本
  • +1 正在按照NOT EXISTS(some record younger than this one) 的思路来替换 ROW_NUMBER() - 似乎暂时安静了......
  • 我已经对我的测试数据运行了您的查询,我得到了 Q1 约 200 毫秒和 Q2 约 700 毫秒。
猜你喜欢
  • 1970-01-01
  • 2011-03-13
  • 2011-05-21
  • 2012-06-14
  • 2012-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多