我有一个存储Feeds及其文章的数据库。用户可以订阅它们,我允许它们按文件夹对它们进行分类,我通过使用与用户和供稿表相关的标签来解决这些问题。
表格如下:
订阅:
CREATE TABLE [dbo].[Feed](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Url] [nvarchar](250) NOT NULL,
...
FeedItems:
CREATE TABLE [dbo].[FeedItem](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[FeedId] [int] NOT NULL, -- FK to Feed
..
FeedTags:
CREATE TABLE [dbo].[FeedTag](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[UserId] [int] NOT NULL, -- FK to Users
[FeedId] [int] NOT NULL, -- FK to Feed
...
我将User和UserSubscriptions表排除在外,因为它们非常明显而且不是问题的一部分(恕我直言)。
在FeedItem上,我有[FeedId] ASC聚集索引,[Id] DESC
我在Id上选择DESC,因为我排序的Feeditems总是以他们的Id而下降,我大部分时间都对最近的项目感兴趣。我也选择这个索引,因为通常情况下,我想知道特定Feed的FeedItems。
但是当我需要查询一系列Feeds时,例如当我想要用户的所有订阅的前50篇文章,或者特定文件夹的前50篇文章(=具有特定FeedTag的Feeds)时变得复杂。主要是如果有很多Feed和/或Feeds有很多文章。
在大多数情况下,SQL Server选择在FeedItems上执行Clustered Index Seek,并且从Counts行可以看出,它从磁盘中检索受影响的Feeds中的所有文章。这可能导致大量的物理读取。
以下是一个示例查询:
select f.Id, f.Name, fi.Id, fi.Title, fi.Inserted
from Feed f
inner join FeedItem fi on f.id = fi.feedid
where (exists (select 1 from UserSubscription us where us.UserId = @userId and us.FeedId = f.Id))
and (exists(select 1 from FeedTag ft where ft.FeedId = f.id and ft.UserId = @userId and ft.TagId = @tagid))
order by fi.id desc
生成的执行计划如下所示:
显然,SQL Server需要所有行进行排序才能在这些排序结果中应用前50个,因为当我通过FeedId限制结果时,聚簇索引并没有真正具有完美排序顺序的项目。
有没有办法帮助SQL Server不需要获取每个Feed的每个FeedItem?我想了很多,但我不能提出一个有用的附加索引。
是否有更好的数据模型可以帮助实现这种情况?或者可以改进查询?
任何想法&非常感谢帮助:)
答案 0 :(得分:2)
创建索引视图。
这个用于每位用户的热门商品:
CREATE VIEW
UserFeedItem
WITH SCHEMABINDING
AS
SELECT us.userId, fi.feedId, fi.id AS feedItemId
FROM dbo.UserSubscription us
JOIN dbo.FeedItem fi
ON fi.userId = us.userId
AND fi.feedId = us.feedId
GO
CREATE UNIQUE CLUSTERED INDEX
UX_UserFeedItem_User_FeedItem
ON UserFeedItem (userId, feedItemId)
GO
,这个用于给定标记的每个用户的热门项目:
CREATE VIEW
UserTagFeedItem
WITH SCHEMABINDING
AS
SELECT us.userId, ft.tagId, fi.feedId, fi.id AS feedItemId
FROM dbo.FeedItem fi
JOIN dbo.UserSubscription us
ON us.feedId = fi.feedId
JOIN dbo.FeedTag ft
ON ft.feedId = fi.feedId
AND ft.userId = us.userId
GO
CREATE UNIQUE CLUSTERED INDEX
UX_UserTagFeedItem_User_Tag_FeedItem
ON UserFeedItem (userId, tagId, feedItemId)
GO
前50个用户的项目:
SELECT TOP 50
f.*, fi.*
FROM UserFeedItem fi WITH (NOEXPAND)
JOIN FeedItem fi
ON fi.feedId = ufi.feedId
AND fi.id = ufi.feedItemId
JOIN Feed f
ON f.id = fi.feedId
WHERE ufi.userId = @userId
ORDER BY
ufi.feedItemId DESC
标记中前50个用户的项目:
SELECT TOP 50
f.*, fi.*
FROM UserTagFeedItem fi WITH (NOEXPAND)
JOIN FeedItem fi
ON fi.feedId = ufi.feedId
AND fi.id = ufi.feedItemId
JOIN Feed f
ON f.id = fi.feedId
WHERE ufi.userId = @userId
AND tagId = @tagId
ORDER BY
ufi.feedItemId DESC
<强>更新强>
如果您因任何原因不愿意创建索引视图,可以使用此查询:
SELECT TOP 50
f.*, fi.*
FROM UserSubscription us
JOIN Feed f
ON f.id = us.feedId
CROSS APPLY
(
SELECT TOP 50
*
FROM FeedItem fi
WHERE fi.feedId = f.id
ORDER BY
fi.id DESC
) fi
WHERE us.userId = @userId
ORDER BY
fi.id DESC
或者这个(对于标签):
SELECT TOP 50
f.*, fi.*
FROM UserSubscription us
JOIN FeedTag ft
ON ft.userId = us.userId
JOIN Feed f
ON f.id = us.feedId
CROSS APPLY
(
SELECT TOP 50
*
FROM FeedItem fi
WHERE fi.feedId = f.id
ORDER BY
fi.id DESC
) fi
WHERE us.userId = @userId
AND ft.tagId = @tagId
ORDER BY
fi.id DESC
您需要创建以下索引:
UserSubscription (userId, feedId) -- unique
FeedTag (userId, tagId, feedId) -- unique
这些查询不如索引视图那样有效,但是它们将从FeedItem获取最多N * 50
个记录,其中N
是订阅(或订阅和标记)的订阅源数量给予用户。