提高大表有序顶级查询的性能

时间:2014-07-01 16:10:45

标签: sql-server tsql

我有一个存储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

生成的执行计划如下所示:

Execution plan

显然,SQL Server需要所有行进行排序才能在这些排序结果中应用前50个,因为当我通过FeedId限制结果时,聚簇索引并没有真正具有完美排序顺序的项目。

有没有办法帮助SQL Server不需要获取每个Feed的每个FeedItem?我想了很多,但我不能提出一个有用的附加索引。

是否有更好的数据模型可以帮助实现这种情况?或者可以改进查询?

任何想法&非常感谢帮助:)

1 个答案:

答案 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是订阅(或订阅和标记)的订阅源数量给予用户。