2

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

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

使这项工作。我们有 3 个表 Users(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

那么您对此有何看法?

4

3 回答 3

3

首先,与写入相比,读取总是压倒性的,因为每面“墙”的出现次数都会比更新次数多得多。所以你最好让阅读速度更快。

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

所以我肯定会选择一个为阅读而优化的结构。

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

于 2010-01-13T06:20:02.360 回答
1

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

于 2010-01-13T05:10:40.603 回答
1

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

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

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

对于非常大的数据库,我会听从 SQL 专家的意见,他们可以谈论分区策略(用于新数据的更小更易于管理的表,用于旧数据的更大/重索引表)和复制解决方案。

于 2010-01-13T05:23:25.790 回答