【发布时间】:2016-11-14 13:15:37
【问题描述】:
在我的场景中,我有帖子,这些帖子按类别分组。对于类别的概述列表,我想显示前 10 个帖子的摘要以及类别(与显示完整数据的类别的详细视图相反)。排名前 10 的帖子由分数决定,分数来自另一个表(实际上是索引视图 - 但在这里无关紧要)。
表结构如下:
CREATE TABLE [dbo].[Categories]
(
[Id] INT NOT NULL IDENTITY CONSTRAINT [PK_Categories] PRIMARY KEY,
[Key] CHAR(10) CONSTRAINT [UK_Categories_Key] UNIQUE,
[Caption] NVARCHAR(500) NOT NULL,
[Description] NVARCHAR(4000) NULL
)
GO
CREATE TABLE [dbo].[Posts]
(
[Id] INT NOT NULL IDENTITY CONSTRAINT [PK_Posts] PRIMARY KEY,
[CategoryId] INT NOT NULL CONSTRAINT [FK_Posts_Category] FOREIGN KEY REFERENCES [dbo].[Categories] ([Id]),
[Key] CHAR(10) CONSTRAINT [UK_Post_Key] UNIQUE,
[Text] NVARCHAR(4000) NULL,
[SummaryText] AS
CASE WHEN LEN([Text]) <= 400
THEN CAST([Text] AS NVARCHAR(400))
ELSE CAST(SUBSTRING([Text], 0, 399) + NCHAR(8230) AS NVARCHAR(400)) --First 399 characters and ellipsis
END
PERSISTED
)
GO
CREATE TABLE [dbo].[Scores] (
[Id] INT NOT NULL IDENTITY CONSTRAINT [PK_Scores] PRIMARY KEY,
[CategoryId] INT NOT NULL CONSTRAINT [FK_Scores_Category] FOREIGN KEY REFERENCES [dbo].[Categories] ([Id]),
[PostId] INT NOT NULL CONSTRAINT [FK_Scores_Post] FOREIGN KEY REFERENCES [dbo].[Posts] ([Id]),
[Value] INT NOT NULL
)
GO
CREATE INDEX [IX_Scores_CategoryId_Value_PostId]
ON [dbo].[Scores] ([CategoryId], [Value] DESC, [PostId])
GO
我现在可以使用视图来获取每个类别的前十个帖子:
CREATE VIEW [dbo].[TopPosts]
AS
SELECT c.Id AS [CategoryId], cp.PostId, p.[Key], p.SummaryText, cp.Value AS [Score]
FROM [dbo].[Categories] c
CROSS APPLY (
SELECT TOP 10 s.PostId, s.Value
FROM [dbo].[Scores] s
WHERE s.CategoryId = c.Id
ORDER BY s.Value DESC
) AS cp
INNER JOIN [dbo].[Posts] p ON cp.PostId = p.Id
我知道CROSS APPLY 将使用覆盖索引IX_Scores_CategoryId_Value_PostId,因为它包含类别ID(对于WHERE)、值(对于ORDER BY 和SELECT)和帖子ID (对于SELECT),因此会相当快。
现在的问题是:INNER JOIN 呢?连接谓词使用post ID,它是Post 表的聚集索引的键(主键)。当我创建一个包含SELECT 的所有字段的覆盖索引时(见下文),我是否可以显着提高查询性能(使用更好的执行计划、减少 I/O、索引缓存等),即使访问集群index 已经是一个相当快的操作了?
覆盖索引如下所示:
CREATE INDEX [IX_Posts_Covering]
ON [dbo].[Posts] ([Id], [Key], [SummaryText])
GO
更新:
由于我的问题的方向似乎并不完全清楚,让我更详细地写下我的想法。我想知道覆盖索引(或包含列的索引)是否会因为以下原因而更快(并且性能提升是值得的):
- 硬盘访问。第二个索引将比聚集索引小得多,SQL Server 将不得不在 HD 上通过更少的页面,这将产生更好的读取性能。这是正确的吗?您能看出其中的不同吗?
- 内存消耗。 要将数据加载到内存中,我假设 SQL Server 必须将整行加载到内存中,然后选择它需要的列。这不会增加内存消耗吗?
- CPU。 我的假设是您不会看到 CPU 使用率的可测量差异,因为从列中提取行本身并不是 CPU 操作。正确吗?
- 缓存。我的理解是,您不会看到缓存有太大的不同,因为 SQL Server 只会缓存它返回的数据,而不是整行。还是我错了?
这些基本上是(或多或少受过教育的)假设。如果有人能就这个公认的非常具体的问题告诉我,我将不胜感激。
【问题讨论】:
-
让 SSMS 向您显示两个选项的实际执行计划(有和没有附加索引),您将立即看到是否 a) 优化器将选择 JOIN 的索引和 b) 那里是您的 SQL Server 版本的显着性能提升。我的猜测是肯定的,因为索引优化向导的自动建议通常在像你这样的场景中包含那种索引。
-
执行计划会告诉我 if 它使用索引。是否值得使用索引将取决于表中的数据量。我想避免在生产数据库中遇到性能问题。因此,如果您(或其他人)有类似情况的经验,那将对我有很大帮助。
-
参见上面的@dlatikay 评论。这就是你得到你所寻求的答案的方式,先生。
-
@Sefe 这就是为什么索引并不总是,也不一定是数据库模式的静态部分。添加它会有一个折衷:查询速度/插入速度/更新速度和存储大小,尤其是当您包含有效负载时 (
SummaryText)。有疑问,不要现在创建它,而是从生产数据库的 DBA 那里获得反馈,如果经验获得的执行计划建议,让他们创建索引。 -
@dlatikay:是的,通常的做法是根据当前的表现和经验数据设置索引。我们也在这样做。我想更进一步,并尝试了解什么是更好的解决方案。我对 DBMS 了解得越多,我就越能设计 DB。我很乐意采用经验方法,但我想知道为什么 SQL Server 会以某种方式运行。覆盖索引会减少表上的 I/O 吗?它会在数据库服务器上使用更少的内存吗?它会更好地缓存吗?等等。
标签: sql sql-server indexing