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

想象一个社交网络。每个用户都有一个状态消息,他可以随时更改。每当他改变它时,他的所有朋友都会通过墙得到通知(就像在 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

那么您对此有何看法?

有帮助吗?

解决方案

首先,读取总是会被压倒与写入相比,因为每个“墙”将要看到的许多更多的时间比它将被更新。所以你最好使读取织补快速。

其次,这些好心的社交网站中固有的问题之一是数据的分布(分片,分区,没有一个单一的数据库将永远是能够储存所有帐户,所有的朋友,所有的通知),这意味着当新的通知被放在墙壁上,朋友对的其他的服务器通知。这意味着更新是异步的,基于消息呢。

所以,我肯定会用阅读优化的结构去了。

我建议你去通过参与像Facebook和MySpace网站的架构不同的人做了公开展示,像的这个克里斯塔Stelzmuller的一个。他们解释了很多该进入他们的设计思想和推理的。

其他提示

相比的SELECT ...幅度的几个订单

更新速度非常慢。此外,为您的网站扩展你会被缓存在内存中所有的读取,所以选择的速度将是微不足道的。

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top