想象一个电影应用程序,它根据这个非常简单的算法向用户推荐下一部电影:
这是 SQL Server 数据库的简单设计:
Movies:
Id bigint
Name nvarchar(100)
SeenMovies:
Id bigint
UserId bigint
MovieId bigint
NotInterestedFlags:
Id bigint
UserId bigint
MovieId bigint
为了获取下一部电影,我们运行以下查询:
select top 1 *
from Movies
where Id not in
(
select MovieId
from SeenMovies
where UserId = 89283
)
and Id not in
(
select MovieId
from NotInterestedFlags
where UserId = 89283
)
随着应用程序的使用和数据的增加,这种设计变得越来越慢。
那么对于一个拥有 10 万部电影和超过 1000 万客户的虚构数据库,如何改变这种设计使其水平扩展?
最佳答案
以下是我推荐的代码。
我假设 SeenMovies 和 NotInterestedFlags 在 UserId 上聚集,或者至少索引。并且 Movies 聚集在 MovieId 上。如果没有,首先添加此类索引。
我当然看不出有任何理由为什么每个查询的性能会很差,因为一旦我们将查询限制在特定用户,SeenMovies 和 NotInterestedFlags 最多应该只有一个该用户各有几千行。
SELECT TOP 1
Movies.*
FROM
Users
CROSS JOIN
Movies
WHERE
NOT EXISTS
(
SELECT NULL
FROM SeenMovies
WHERE
SeenMovies.UserId = Users.Id
AND
SeenMovies.MovieId = Movies.Id
)
AND
NOT EXISTS
(
SELECT NULL
FROM NotInterestedFlags
WHERE
NotInterestedFlags.UserId = Users.Id
AND
NotInterestedFlags.MovieId = Movies.Id
)
AND
Users.Id = 89283
如果即使使用适当的索引这仍然表现不佳,我只能想象第一个可能是第一个
UNION
在该 UserId 的 SeenMovies 和 NotInterestedFlags 中添加 MovieId 条目,然后 EXCEPT
将这些用于电影,可能会提供更好的性能。另一方面,如果问题是系统的整体性能在许多用户的负载下下降,您可能需要考虑为每个用户预先准备一个列表,用于未看过和未列入黑名单的电影,从您查询的
TOP 1
.然后,当用户观看电影或将其列入黑名单(或添加新电影)时,此新表与单独的 SeenMovies 和 NotInterestedFlags 表同时被修改。
同样,如果这对性能没有足够的帮助,那么您必须考虑实现每日批处理作业,这可能会预先准备一份列表,其中包含每个用户的 10 部未看过且未列入黑名单的电影,然后该表是一次查询并提供给用户。
不过我坦率地认为,如果您有可能拥有 1000 万用户,那么您可能会聘请一位专家来编写代码或评估现有系统。
关于sql - 在需要集合操作的 SQL Server 数据库中,如何设计更高的可扩展性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44543923/