[更新]
主键不好
您有一个独特的实体,即 [GameID] + [PlayerName]。并且复合聚集索引 > 120 字节与 nvarchar。在相关主题SQL Server - Clustered index design for dictionary中寻找@marc_s 的答案
您的表架构与时间段的要求不匹配
例如:我在星期三获得了 300 分,这个分数存储在排行榜上。第二天我获得了 250 分,但它不会记录在排行榜上,如果我对周二排行榜进行查询,你也不会得到结果
有关完整信息,您可以从历史桌上游戏的得分中获得,但它可能非常昂贵
CREATE TABLE GameLog (
[id] int NOT NULL IDENTITY
CONSTRAINT [PK_GameLog] PRIMARY KEY CLUSTERED,
[gameId] smallint NOT NULL,
[playerId] int NOT NULL,
[score] int NOT NULL,
[createdDateTime] datetime2(3) NOT NULL)
以下是与聚合相关的加速它的解决方案:
- 历史表的索引视图(参见@Twinkles 的帖子)。
您需要 3 个索引视图的 3 个时间段。可能巨大的历史表和 3 个索引视图。无法删除表格的“旧”时段。保存分数的性能问题。
分数保存在历史表中。SQL 作业/“工人”(或多个)根据计划(每分钟 1 个?)对历史表进行排序并使用预先计算的用户排名填充排行榜表(3 个时间段的 3 个表或一个带时间段键的表)。该表也可以非规范化(具有分数、日期时间、玩家名称和...)。优点:快速阅读(无需排序),快速保存分数,任何时间段,灵活的逻辑和灵活的时间表。缺点:用户已完成游戏但没有立即在排行榜上找到自己
在记录比赛结果的过程中做预处理。在您的情况下,类似于UPDATE [Leaderboard] SET score = @CurrentScore WHERE @CurrentScore > MAX (score) AND ...
玩家/游戏 ID,但您只为“所有时间”排行榜这样做。该方案可能如下所示:
CREATE TABLE [Leaderboard] (
[id] int NOT NULL IDENTITY
CONSTRAINT [PK_Leaderboard] PRIMARY KEY CLUSTERED,
[gameId] smallint NOT NULL,
[playerId] int NOT NULL,
[timePeriod] tinyint NOT NULL, -- 0 -all time, 1-monthly, 2 -weekly, 3 -daily
[timePeriodFrom] date NOT NULL, -- '1900-01-01' for all time, '2013-11-01' for monthly, etc.
[score] int NOT NULL,
[createdDateTime] datetime2(3) NOT NULL
)
playerId timePeriod timePeriodFrom 分数
----------------------------------------------
1 0 1900-01-01 300
...
1 1 2013-10-01 150
1 1 2013-11-01 300
...
1 2 2013-10-07 150
1 2 2013-11-18 300
...
1 3 2013-11-19 300
1 3 2013-11-20 250
...
因此,您必须更新所有时间段的所有 3 个分数。此外,您可以看到排行榜将包含“旧”时期,例如十月的每月。如果您不需要此统计信息,也许您必须将其删除。优点:不需要历史表。缺点:存储结果的过程复杂。需要维护排行榜。查询需要排序和JOIN
CREATE TABLE [Player] (
[id] int NOT NULL IDENTITY CONSTRAINT [PK_Player] PRIMARY KEY CLUSTERED,
[playerName] nvarchar(50) NOT NULL CONSTRAINT [UQ_Player_playerName] UNIQUE NONCLUSTERED)
CREATE TABLE [Leaderboard] (
[id] int NOT NULL IDENTITY CONSTRAINT [PK_Leaderboard] PRIMARY KEY CLUSTERED,
[gameId] smallint NOT NULL,
[playerId] int NOT NULL,
[timePeriod] tinyint NOT NULL, -- 0 -all time, 1-monthly, 2 -weekly, 3 -daily
[timePeriodFrom] date NOT NULL, -- '1900-01-01' for all time, '2013-11-01' for monthly, etc.
[score] int NOT NULL,
[createdDateTime] datetime2(3)
)
CREATE UNIQUE NONCLUSTERED INDEX [UQ_Leaderboard_gameId_playerId_timePeriod_timePeriodFrom] ON [Leaderboard] ([gameId] ASC, [playerId] ASC, [timePeriod] ASC, [timePeriodFrom] ASC)
CREATE NONCLUSTERED INDEX [IX_Leaderboard_gameId_timePeriod_timePeriodFrom_Score] ON [Leaderboard] ([gameId] ASC, [timePeriod] ASC, [timePeriodFrom] ASC, [score] ASC)
GO
-- Generate test data
-- Generate 500K unique players
;WITH digits (d) AS (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION
SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 0)
INSERT INTO Player (playerName)
SELECT TOP (500000) LEFT(CAST(NEWID() as nvarchar(50)), 20 + (ABS(CHECKSUM(NEWID())) & 15)) as Name
FROM digits CROSS JOIN digits ii CROSS JOIN digits iii CROSS JOIN digits iv CROSS JOIN digits v CROSS JOIN digits vi
-- Random score 500K players * 4 games = 2M rows
INSERT INTO [Leaderboard] (
[gameId],[playerId],[timePeriod],[timePeriodFrom],[score],[createdDateTime])
SELECT GameID, Player.id,ABS(CHECKSUM(NEWID())) & 3 as [timePeriod], DATEADD(MILLISECOND, CHECKSUM(NEWID()),GETDATE()) as Updated, ABS(CHECKSUM(NEWID())) & 65535 as score
, DATEADD(MILLISECOND, CHECKSUM(NEWID()),GETDATE()) as Created
FROM ( SELECT 1 as GameID UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4) as Game
CROSS JOIN Player
ORDER BY NEWID()
UPDATE [Leaderboard] SET [timePeriodFrom]='19000101' WHERE [timePeriod] = 0
GO
DECLARE @From date = '19000101'--'20131108'
,@GameID int = 3
,@timePeriod tinyint = 0
-- Get paginated ranking
;With Lb as (
SELECT
DENSE_RANK() OVER (ORDER BY Score DESC) as Rnk
,Score, createdDateTime, playerId
FROM [Leaderboard]
WHERE GameId = @GameId
AND [timePeriod] = @timePeriod
AND [timePeriodFrom] = @From)
SELECT lb.rnk,lb.Score, lb.createdDateTime, lb.playerId, Player.playerName
FROM Lb INNER JOIN Player ON lb.playerId = Player.id
ORDER BY rnk OFFSET 75 ROWS FETCH NEXT 25 ROWS ONLY;
-- Get rank of a player for a given game
SELECT (SELECT COUNT(DISTINCT rnk.score)
FROM [Leaderboard] as rnk
WHERE rnk.GameId = @GameId
AND rnk.[timePeriod] = @timePeriod
AND rnk.[timePeriodFrom] = @From
AND rnk.score >= [Leaderboard].score) as rnk
,[Leaderboard].Score, [Leaderboard].createdDateTime, [Leaderboard].playerId, Player.playerName
FROM [Leaderboard] INNER JOIN Player ON [Leaderboard].playerId = Player.id
where [Leaderboard].GameId = @GameId
AND [Leaderboard].[timePeriod] = @timePeriod
AND [Leaderboard].[timePeriodFrom] = @From
and Player.playerName = N'785DDBBB-3000-4730-B'
GO
这只是表达想法的一个例子。它可以被优化。例如,通过字典表将 GameID、TimePeriod、TimePeriodDate 列合并为一列。该指标的有效性会更高。
PS对不起我的英语。随意修正语法或拼写错误