慢速更新与慢速选择

时间:2010-01-13 05:05:10

标签: sql-server indexing tradeoff

这是一个关于权衡的问题。

想象一下社交网络。每个用户都有一个状态消息,他可以随时更改。每当他确实改变它时,他的所有朋友都会通过隔离墙通知(例如在Facebook中)。

使这项工作。我们有3个表用户(id,name),FriendLists(userId,friendUserId),Notifications(?)。

现在让我们假设每个用户在他的朋友列表中有大约50个朋友。我面临着两难 - 如何实施通知表。


第一个选项

CREATE TABLE Notifications
(
toUserId bigint NOT NULL,
[identity] bigint IDENTITY(1,1) NOT NULL,
fromUserId bigint NOT NULL,
data varchar(256) NOT NULL,
CONSTRAINT [PK_Notifications] PRIMARY KEY CLUSTERED (toUserId, [identity])
)

发送通知:

-- Get all friends of @fromUserId.
WITH Friends AS
   (SELECT FriendLists.friendUserId
 FROM FriendLists
 WHERE userId = @fromUserId)
-- Send updates to all friends.
SELECT
 friendUserId as toUserId,
 @fromUserId as fromUserId,
 @data as data
INTO Notifications
FROM Friends

在这种情况下,对于每个状态更改,我们创建50条记录(假设有50位朋友)。这是不好的。然而,好处是要检索特定用户的通知,它真的很快,因为我们在toUserId上有一个聚集索引。

第二个选项

CREATE TABLE Notifications
(
toUserId bigint NOT NULL,
[identity] bigint IDENTITY(1,1) NOT NULL,
fromUserId bigint NOT NULL,
data varchar(256) NOT NULL,
CONSTRAINT [PK_Notifications] PRIMARY KEY CLUSTERED ([identity])
)
CREATE NONCLUSTERED INDEX [IX_toUserId] ON Notifications (toUserId ASC)

发送通知:

-- Get all friends of @fromUserId.
WITH Friends AS
   (SELECT FriendLists.friendUserId
 FROM FriendLists
 WHERE userId = @fromUserId)
-- Send updates to all friends.
INSERT INTO Notifications(toUserId, fromUserId, data)
    VALUES(friendUserId, @fromUserId, @data)

这里我们只为每个状态更新插入一条记录。这很好。不好的一点是,通知的检索速度会变慢,因为记录不会被toUserId聚集。


获取通知对于这两种方法都是相同的:

SELECT TOP(50) fromUserId, [identity], data
FROM Notifications
WHERE toUserId  = @toUserId

那你对此有何看法?

3 个答案:

答案 0 :(得分:3)

首先,与写入相比,读取始终是压倒性的,因为每个“墙”将被看到的次数比将要更新的次数多。所以你最好快速读取。

其次,这类大型社交网站固有的问题之一是数据的分发(分片,分区,没有单个数据库能够存储所有帐户,所有朋友,所有通知),这意味着新的通知放在墙上,必须在其他服务器上通知朋友。这意味着无论如何更新都是异步和消息传递。

所以我肯定会采用优化阅读的结构。

我建议你浏览参与Facebook和MySpace等网站架构的各种人员所做的公开演示,例如this Christa Stelzmuller's one。他们解释了他们设计中的许多思考和推理。

答案 1 :(得分:1)

与SELECT相比,更新速度非常慢......几个数量级。另外,随着您的网站扩展,您将在内存中缓存所有内容,因此选择的速度将是微不足道的。

答案 2 :(得分:1)

在这种情况下,在(toUser,identity)上创建聚簇索引似乎是一个坏主意,因为聚簇索引确实应该按升序插入。当然SQL会负责保持表的排序,但这会带来很高的性能成本(这是你的问题的重点。)但是一般来说,不建议将提前知道的插件以不特定的顺序排列。聚集索引。以下是关于聚集索引建议的非常好的three part article

话虽如此,我会坚持使用identity列作为您的聚簇索引,并在toUserId上创建一个非聚簇索引,也许是一个datetime列。通过包含日期时间列,您可以更有效地查询最近的数据。

关于缓慢更新,社交网站上的状态更新是消息队列的完美情况。这样,您可以根据需要调整数据库以快速读取,如果它对写入性能有影响,则用户不必受到影响。从他们的角度来看,更新是即时的,即使可能需要一些时间来“坚持”。

对于非常大的数据库,我会推荐可以讨论分区策略的SQL专家(更新的数据更小的可管理表,旧数据更大/重度索引的表)和复制解决方案。