在多列上有效使用row_number

时间:2014-05-24 11:38:50

标签: sql sql-server sql-server-2008 tsql sql-order-by

我正在尝试在SQL Server 2008 R2中实现有效的用户评级,其中记录不断变化,并且每次写入用户数据都会导致后续读取评级,这对于多列只有ROW_NUMBER

CREATE TABLE [dbo].[Scores]
(
    [Id] int NOT NULL IDENTITY (1, 1),
    [UserId] int NOT NULL,
    [MaxLevel] int NOT NULL,
    [BestDiff] int NOT NULL,
    [BestDiffGames] int NOT NULL,
    [BestDiffLastDate] datetime NOT NULL,
    [MaxLevelLastWinDate] datetime,

    -- other statistics

CONSTRAINT [PK_Scores] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Scores_REF_Users] FOREIGN KEY([UserId]) REFERENCES [dbo].[Users] ([Id])
)
GO
CREATE UNIQUE NONCLUSTERED INDEX IX_Scores_User ON dbo.Scores
(
    UserId
)
GO
CREATE NONCLUSTERED INDEX IX_Scores_Rating ON dbo.Scores
(
    MaxLevel desc, BestDiff desc, BestDiffGames desc, 
    BestDiffLastDate desc, MaxLevelLastWinDate desc
)
GO

每次写入Scores表都会导致后续读取:

with Ratings (Rating, UserId) as
(
    select (ROW_NUMBER() over 
        (order by MaxLevel desc, BestDiff desc, BestDiffGames desc, 
    BestDiffLastDate desc, MaxLevelLastWinDate desc)) as Rating, 
        UserId
    from Scores with (nolock)
) 
select @Rating = Rating
from Ratings 
where UserId = @UserId

此外,还有使用相同ROW_NUMBER评分页面的查询。 目前表Scores包含大约30K行,当我运行后一个查询时,执行计划看起来不错,但它的执行持续时间大约为100-200ms!在峰值工作负载期间,每秒进行多次用户评级更新是不可接受的。

我想知道是否有更有效的方法来组织用户评分?

更新1:感谢 Gordon Linoff 我做了进一步的实验,获得用户评分的最终优化方法是使用上面的查询和以下修改后的索引(< !强>非唯一):

CREATE NONCLUSTERED INDEX IX_Scores_Rating ON dbo.Scores
(
    MaxLevel desc, BestDiff desc, BestDiffGames desc, 
    BestDiffLastDate desc, MaxLevelLastWinDate desc,
    UserId
)
GO

更新2 :感谢 Mikael Eriksson top 1的以下查询提高了查询速度 2x ,即使对于中等评分用户!评分最高的用户获得了 8x 更快的查询。 优化1(索引更改)之后实现了这些速度提升数,因此当前执行时间从最初的100-200ms降至2-16ms,比最初快6-100倍!

with Ratings (Rating, UserId) as
(
    select (ROW_NUMBER() over 
        (order by MaxLevel desc, BestDiff desc, BestDiffGames desc, 
    BestDiffLastDate desc, MaxLevelLastWinDate desc)) as Rating, 
        UserId
    from Scores with (nolock)
) 
select top 1 @Rating = Rating
from Ratings 
where UserId = @UserId

1 个答案:

答案 0 :(得分:1)

100-200毫秒似乎并不那么糟糕。

如果您只有一列评分,那么您可以这样做:

select @Rating = 1 + count(*)
from scores s cross join
     (select * from scores s where userId = @UserId) su
where s.score > su.score;

如果你有联系,那么完全完全相同;它等同于rank()而不是row_number(),因此它以不同方式处理关系。如果你可以将列放到带有索引的单个列中,这应该很快。

您可以使用多列执行相同的操作,但逻辑变得复杂,并且我不能100%确定索引将始终正确使用。类似的东西:

where s.score > su.score or
      (s.score = su.score and s.bestdiff > su.bestdif) or
      (s.score = su.score and s.bestdiff =  su.bestdif and s.BestDiffGames > su.BestDiffGames) or
      (s.score = su.score and s.bestdiff =  su.bestdif and s.BestDiffGames = su.BestDiffGames and s.MaxLevelLastWinDate > su.MaxLevelLastWinDate)