这是一个关于权衡的问题。
想象一下社交网络。每个用户都有一个状态消息,他可以随时更改。每当他确实改变它时,他的所有朋友都会通过隔离墙通知(例如在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
那你对此有何看法?
答案 0 :(得分:3)
首先,与写入相比,读取始终是压倒性的,因为每个“墙”将被看到的次数比将要更新的次数多。所以你最好快速读取。
其次,这类大型社交网站固有的问题之一是数据的分发(分片,分区,没有单个数据库能够存储所有帐户,所有朋友,所有通知),这意味着新的通知放在墙上,必须在其他服务器上通知朋友。这意味着无论如何更新都是异步和消息传递。
所以我肯定会采用优化阅读的结构。
我建议你浏览参与Facebook和MySpace等网站架构的各种人员所做的公开演示,例如this Christa Stelzmuller's one。他们解释了他们设计中的许多思考和推理。
答案 1 :(得分:1)
与SELECT相比,更新速度非常慢......几个数量级。另外,随着您的网站扩展,您将在内存中缓存所有内容,因此选择的速度将是微不足道的。
答案 2 :(得分:1)
在这种情况下,在(toUser,identity)上创建聚簇索引似乎是一个坏主意,因为聚簇索引确实应该按升序插入。当然SQL会负责保持表的排序,但这会带来很高的性能成本(这是你的问题的重点。)但是一般来说,不建议将提前知道的插件以不特定的顺序排列。聚集索引。以下是关于聚集索引建议的非常好的three part article。
话虽如此,我会坚持使用identity列作为您的聚簇索引,并在toUserId上创建一个非聚簇索引,也许是一个datetime列。通过包含日期时间列,您可以更有效地查询最近的数据。
关于缓慢更新,社交网站上的状态更新是消息队列的完美情况。这样,您可以根据需要调整数据库以快速读取,如果它对写入性能有影响,则用户不必受到影响。从他们的角度来看,更新是即时的,即使可能需要一些时间来“坚持”。
对于非常大的数据库,我会推荐可以讨论分区策略的SQL专家(更新的数据更小的可管理表,旧数据更大/重度索引的表)和复制解决方案。