当数据按聚集索引的顺序时,覆盖索引是否会得到回报?

Sef*_*efe 7 sql sql-server indexing

我的情景,我有帖子,按类别分组.对于类别的概述列表,我想显示具有类别的前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
Run Code Online (Sandbox Code Playgroud)

我现在可以使用视图来获取每个类别的前十个帖子:

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
Run Code Online (Sandbox Code Playgroud)

我理解CROSS APPLY将使用覆盖索引IX_Scores_CategoryId_Value_PostId,因为它包含类别ID(用于WHERE)值(用于ORDER BYSELECT)和帖子ID(用于SELECT),因此将相当快.

现在的问题是:那怎么样INNER JOIN?连接谓词使用post ID,它是Post表的聚簇索引(主键)的键.当我创建包含所有字段的覆盖索引SELECT(见下文)时,我是否可以显着提高查询性能(具有更好的执行计划,减少I/O,索引缓存等),即使访问聚簇索引已经一个非常快的操作?

覆盖指数如下所示:

CREATE INDEX [IX_Posts_Covering]
    ON [dbo].[Posts] ([Id], [Key], [SummaryText])
GO
Run Code Online (Sandbox Code Playgroud)

更新:

由于我的问题的方向似乎并不完全清楚,让我更详细地记下我的想法.我想知道覆盖索引(或包含列的索引)是否可以更快,原因如下(并且性能增益值得):

  1. 硬盘访问.第二个索引将比聚集索引小得多,SQL Server必须在HD上浏览较少的页面,这将产生更好的读取性能.这是正确的,你会看到差异吗?
  2. 内存消耗.要将数据加载到内存中,我假设SQL Server必须将整行加载到内存中,然后选择所需的列.那不会增加内存消耗吗?
  3. 中央处理器.我的假设是你不会看到CPU使用量的可测量差异,因为从列中提取行本身不是CPU操作.正确?
  4. 缓存.我的理解是你不会在缓存方面看到太多差异,因为SQL Server只会缓存它返回的数据,而不是整行.还是我错了?

这些基本上(或多或少受过教育)的假设.如果有人可以告诉我这个公认非常具体的问题,我会非常感激.

Jer*_*ert 5

这是一个有趣的问题,因为你提出的所有四个子问题都可以用"它取决于"来回答,这通常是主题有趣的一个好兆头.

首先,如果你对SQL Server的工作原理有不健康的迷恋(就像我一样),那么源代码是Delaney等人的"Microsoft SQL Server Internals".您不需要阅读所有~1000页,存储引擎上的章节本身就足够有趣.

我不会触及这个特定覆盖索引在这种特殊情况下是否有用的问题,因为我认为其他答案已经很好地涵盖(没有双关语意),包括建议INCLUDE用于不需要的列索引自己.

第二个索引将比聚集索引小得多,SQL Server必须在HD上浏览较少的页面,这将产生更好的读取性能.这是正确的,你会看到差异吗?

如果假设的选择是要么聚集索引的阅读页面之间覆盖索引的网页,覆盖索引小1,这意味着I/O,更好的性能,更少的,所有美好的事物.但查询不是在真空中执行 - 如果这不是表上的唯一查询,则缓冲池可能已包含大部分或全部聚簇索引,在这种情况下,磁盘读取性能可能会因读取而受到负面影响较少使用的覆盖指数.数据页面的总增长也可能会降低整体性能.优化器仅考虑单个查询; 它不会根据所有组合的查询仔细调整缓冲池使用情况(通过简单的LRU策略删除页面).因此,如果您过度创建索引,尤其是不经常使用的索引,则整体性能将受到影响.而这甚至没有考虑插入或更新数据时索引的内在开销.

即使我们假设覆盖指数是一个净收益,"你会看到差异"的问题(如果,性能可测量地增加)只能通过经验有效地回答.SET STATISTICS IO ON是你的朋友(以及DBCC DROPCLEANBUFFERS在测试环境中).您可以根据假设进行尝试和猜测,但由于结果取决于执行计划,索引的大小,SQL Server总计的内存量,I/O特性,所有数据库的负载以及查询模式应用程序,我不会这样做,除了猜测索引是否可能有用.一般来说,当然,如果你有一个非常宽的桌子和一个小的覆盖指数,不难看出这是如何得到回报的.一般来说,你会很快看到索引不够,而不是来自太多的索引.但真正的数据库并不是在概括上运行.

要将数据加载到内存中,我假设SQL Server必须将整行加载到内存中,然后选择所需的列.那不会增加内存消耗吗?

往上看.聚集索引占用的页数多于覆盖索引,但是内存使用是否受到正面或负面影响取决于每个索引的使用方式.在最糟糕的情况下,聚集索引被其他不会从覆盖索引中获利的查询密集使用,而覆盖索引只对稀有查询有帮助,因此所有覆盖索引都会导致缓冲池流失减慢大部分工作量.这可能是不寻常的,并且是服务器可以通过内存升级进行的一个标志,但它肯定是可能的.

我的假设是你不会看到CPU使用量的可测量差异,因为从列中提取行本身不是CPU操作.正确?

CPU 使用率通常不受行大小的显着影响.执行时间(反过来,会影响使用,具体取决于您希望并行运行的查询数).一旦通过为服务器提供足够的内存来弥补I/O瓶颈,仍然需要在内存中扫描数据.

我的理解是你不会在缓存方面看到太多差异,因为SQL Server只会缓存它返回的数据,而不是整行.还是我错了?

行存储在页面上,SQL Server将其读取的页面缓存在缓冲池中.它不会缓存结果集,也不会缓存作为查询执行的一部分生成的任何中间数据或单个行.如果在最初的空缓冲池上执行两次查询,则第二个通常更快,因为它需要的页面已经在内存中,但这是加速的唯一来源.

考虑到这一点,请参阅第一个问题的答案 - 是的,缓存会受到影响,因为覆盖索引的页面(如果使用)将与聚集索引的页面(如果使用)分开缓存.


1如果由于页面拆分而导致覆盖索引严重碎片,则覆盖索引实际上可能不会更小.但这是一个学术观点,因为它并不是关于什么索引在物理上更大但实际访问了多少页面.